From b9e52f9688f83a0e0e4208d6f9ad415e79e332a2 Mon Sep 17 00:00:00 2001 From: kota-yata Date: Tue, 24 Sep 2024 07:34:58 -0700 Subject: [PATCH] separate the core moqt logics and byteUtils --- package.json | 7 +- src/lib/ivs.ts | 21 --- src/lib/moq/constants.ts | 102 +--------- src/lib/moq/loc.ts | 4 +- src/lib/moq/moqt.ts | 282 ---------------------------- src/lib/moq/publish/publisher.ts | 25 ++- src/lib/moq/subscribe/subscriber.ts | 14 +- src/lib/moq/track.ts | 32 ---- src/lib/moq/utils/bytes.ts | 232 ----------------------- src/lib/moq/utils/mogger.ts | 22 --- yarn.lock | 110 ++--------- 11 files changed, 43 insertions(+), 808 deletions(-) delete mode 100644 src/lib/ivs.ts delete mode 100644 src/lib/moq/moqt.ts delete mode 100644 src/lib/moq/track.ts delete mode 100644 src/lib/moq/utils/bytes.ts delete mode 100644 src/lib/moq/utils/mogger.ts diff --git a/package.json b/package.json index 08367c9..b3be307 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,10 @@ }, "type": "module", "dependencies": { - "hls.js": "^1.5.8" + "hls.js": "^1.5.8", + "moqtail": "^1.2.1" }, "engines": { - "node": "18" - } + "node": "18" + } } diff --git a/src/lib/ivs.ts b/src/lib/ivs.ts deleted file mode 100644 index a4a5ab5..0000000 --- a/src/lib/ivs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import IVSBroadcastClient from 'amazon-ivs-web-broadcast'; - -export const broadcast = async (mediaStream: MediaStream, streamKey: string) => { - const client = IVSBroadcastClient.create({ - streamConfig: { - maxResolution: { width: 640, height: 480 }, - maxFramerate: 30, - maxBitrate: 3500 - }, - ingestEndpoint: 'rtmps://516172ba1ccf.global-contribute.live-video.net:443/app/?keyframeInterval=1' - }); - client.addVideoInputDevice(mediaStream, 'camera', { index: 0 }); - client.addAudioInputDevice(mediaStream, 'microphone'); - - try { - await client.startBroadcast(streamKey); - console.log('Broadcast started'); - } catch (error) { - console.error('Failed to start broadcast:', error); - } -}; diff --git a/src/lib/moq/constants.ts b/src/lib/moq/constants.ts index b458cc9..9826cb4 100644 --- a/src/lib/moq/constants.ts +++ b/src/lib/moq/constants.ts @@ -1,8 +1,11 @@ export const VIDEO_ENCODER_CONFIGS: { [key: string]: VideoEncoderConfig } = { 'high': { - codec: 'hvc1.1.6.L120.00', - width: 3840, - height: 2160, + // codec: 'hvc1.1.6.L120.00', + // width: 3840, + // height: 2160, + codec: 'avc1.64002A', + width: 1920, + height: 1080, framerate: 30, latencyMode: 'realtime', hardwareAcceleration: 'no-preference' @@ -11,7 +14,6 @@ export const VIDEO_ENCODER_CONFIGS: { [key: string]: VideoEncoderConfig } = { codec: 'avc1.64002A', width: 1920, height: 1080, - bitrate: 2_000_000, framerate: 60, latencyMode: 'realtime', hardwareAcceleration: 'no-preference' @@ -53,95 +55,3 @@ export const AUDIO_DECODER_DEFAULT_CONFIG: AudioDecoderConfig = { frameDuration: 10000 // In ns. Lower latency than default = 20000 } }; - -// MOQ Parameters -export const MOQ_DRAFT01_VERSION = 0xff000001; -export const MOQ_DRAFT04_VERSION = 0xff000004; -export const MOQ_DRAFT05_VERSION = 0xff000005; -export const MOQ_SUPPORTED_VERSIONS = [MOQ_DRAFT04_VERSION]; - -export const MOQ_PARAMETER_ROLE = { - KEY: 0x00, - PUBLISHER: 0x01, - SUBSCRIBER: 0x02, - PUBSUB: 0x03, -}; -export const MOQ_PARAMETER_PATH = { KEY: 0x01 }; -export const MOQ_PARAMETER_AUTHORIZATION_INFO = 0x2; - -export const MOQ_MAX_PARAMS = 256; -export const MOQ_MAX_ARRAY_LENGTH = 1024; - -export const MOQ_LOCATION_MODE_NONE = 0x0; -export const MOQ_LOCATION_MODE_ABSOLUTE = 0x1; -export const MOQ_LOCATION_MODE_RELATIVE_PREVIOUS = 0x2; -export const MOQ_LOCATION_MODE_RELATIVE_NEXT = 0x3; - -export const MOQ_MESSAGE = { - OBJECT_STREAM: 0x0, - OBJECT_DATAGRAM: 0x1, - CLIENT_SETUP: 0x40, - SERVER_SETUP: 0x41, - // SUBSCRIBE_UPDATE: 0x2, - SUBSCRIBE: 0x3, - SUBSCRIBE_OK: 0x4, - SUBSCRIBE_ERROR: 0x5, - SUBSCRIBE_DONE: 0xB, - UNSUBSCRIBE: 0xA, - ANNOUNCE: 0x6, - ANNOUNCE_OK: 0x7, - ANNOUNCE_ERROR: 0x8, - ANNOUNCE_CANCEL: 0xC, - UNANNOUNCE: 0x9, - GOAWAY: 0x10, - TRACK_STATUS_REQUEST: 0xD, - TRACK_STATUS: 0xE, - STREAM_HEADER_TRACK: 0x50, - STREAM_HEADER_GROUP: 0x51, -}; - -export const MOQ_SESSION_CLOSE_ERROR = { - NO_ERROR: 0x0, - INTERNAL_ERROR: 0x1, - UNAUTHORIZED: 0x2, - PROTOCOL_VIOLATION: 0x3, - DUPLICATE_TRACK_ALIAS: 0x4, - PARAMETER_LENGTH_MISMATCH: 0x5, - GOAWAY_TIMEOUT: 0x10, -}; - -export const SUBSCRIBE_ERROR = { - INTERNAL_ERROR: 0x0, - INVALID_RANGE: 0x1, - RETRY_TRACK_ALIAS: 0x2 -}; - -export const SUBSCRIBE_DONE = { - UNSUBSCRIBED: 0x0, - INTERNAL_ERROR: 0x1, - UNAUTHORIZED: 0x2, - TRACK_ENDED: 0x3, - SUBSCRIPTION_ENDED: 0x4, - GOING_AWAY: 0x5, - EXPIRED: 0x6 -}; - -export const SUBSCRIBE_FILTER = { - LATEST_GROUP: 0x1, - LATEST_OBEJCT: 0x2, - ABSOLUTE_START: 0x3, - ABSOLUTE_RANGE: 0x4 -}; - -export const SUBSCRIBE_GROUP_ORDER = { - ASCENDING: 0x1, - DESCENDING: 0x2 -}; - -export const OBJECT_STATUS = { - NORMAL: 0x0, - NON_EXISTENT_OBJECT: 0x1, - NON_EXISTENT_GROUP: 0x2, - END_OF_GROUP: 0x3, - END_OF_TRACK_AND_GROUP: 0x4, -}; diff --git a/src/lib/moq/loc.ts b/src/lib/moq/loc.ts index d11d1a7..6f4ea86 100644 --- a/src/lib/moq/loc.ts +++ b/src/lib/moq/loc.ts @@ -1,4 +1,6 @@ -import { buffRead, concatBuffer, deSerializeMetadata, numberToVarInt, readUntilEof, varIntToNumber } from './utils/bytes'; +import { moqtBytes } from 'moqtail'; + +const { buffRead, concatBuffer, deSerializeMetadata, numberToVarInt, readUntilEof, varIntToNumber } = moqtBytes; type LOCMediaTypeUnion = 'data' | 'video' | 'audio'; type LOCChunkTypeUnion = EncodedVideoChunkType | EncodedAudioChunkType; diff --git a/src/lib/moq/moqt.ts b/src/lib/moq/moqt.ts deleted file mode 100644 index 69db648..0000000 --- a/src/lib/moq/moqt.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { MOQ_DRAFT04_VERSION, MOQ_MAX_PARAMS, MOQ_MESSAGE, MOQ_PARAMETER_AUTHORIZATION_INFO, MOQ_PARAMETER_ROLE, OBJECT_STATUS, SUBSCRIBE_FILTER, SUBSCRIBE_GROUP_ORDER } from './constants'; -import { TrackManager } from './track'; -import { numberToVarInt, concatBuffer, varIntToNumber, buffRead, stringToBytes, toString } from './utils/bytes'; -import { moqVideoEncodeLatencyStore, moqVideoFrameOnEncode, moqVideoTransmissionLatencyStore } from './utils/store'; - -interface SenderState { - [key: string]: { - currentGroupSeq: number, - currentObjectSeq: number, - } -} - -export class MOQT { - private MAX_INFLIGHT_REQUESTS = 50; - private wt: WebTransport; - private controlStream: WebTransportBidirectionalStream; - private controlWriter: WritableStream; - private controlReader: ReadableStream; - private senderState: SenderState = {}; - private inflightRequests: string[] = []; - public trackManager: TrackManager; - constructor(props: { url: string, maxInflightRequests?: number }) { - this.wt = new WebTransport(props.url, { congestionControl: 'throughput' }); // 'throughput' or 'low-latency' although only Firefox supports 'low-latency' - this.trackManager = new TrackManager(); - if (props.maxInflightRequests) this.MAX_INFLIGHT_REQUESTS = props.maxInflightRequests; - } - public async initControlStream() { - await this.wt.ready; - this.controlStream = await this.wt.createBidirectionalStream(); - this.controlWriter = this.controlStream.writable; - this.controlReader = this.controlStream.readable; - } - - public getIncomingStream(): ReadableStream { return this.wt.incomingUnidirectionalStreams; } - private async send(props: { writerStream: WritableStream, dataBytes: Uint8Array }) { - const writer = props.writerStream.getWriter(); - await writer.write(props.dataBytes); - writer.releaseLock(); - } - // read message type - public async readControlMessageType(): Promise { - return await varIntToNumber(this.controlReader); - } - // SETUP - private generateSetupMessage(props: { role: number }) { - const messageType = numberToVarInt(MOQ_MESSAGE.CLIENT_SETUP); - const versionLength = numberToVarInt(1); - const version = numberToVarInt(MOQ_DRAFT04_VERSION); - const numberOfParams = numberToVarInt(1); - const roleParamId = numberToVarInt(MOQ_PARAMETER_ROLE.KEY); - const roleParamData = numberToVarInt(props.role); - const roleParamRoleLength = numberToVarInt(roleParamData.byteLength); - return concatBuffer([messageType, versionLength, version, numberOfParams, roleParamId, roleParamRoleLength, roleParamData]); - } - public async setup(props: { role: number }) { - const setup = this.generateSetupMessage(props); - await this.send({writerStream: this.controlWriter, dataBytes: setup}); - } - public async readSetup() { - const ret = { version: 0, parameters: null }; - const type = await varIntToNumber(this.controlReader); - if (type !== MOQ_MESSAGE.SERVER_SETUP) { - throw new Error(`SETUP answer with type ${type} is not supported`); - } - ret.version = await varIntToNumber(this.controlReader); - ret.parameters = await this.readParams(); - return ret; - } - // ANNOUNCE - private generateAnnounceMessage(props: { namespace: string, authInfo: string }) { - const messageType = numberToVarInt(MOQ_MESSAGE.ANNOUNCE); - const namespace = stringToBytes(props.namespace); - const numberOfParams = numberToVarInt(1); - const authInfoIdBytes = numberToVarInt(MOQ_PARAMETER_AUTHORIZATION_INFO); - const authInfoBytes = stringToBytes(props.authInfo); - return concatBuffer([messageType, namespace, numberOfParams, authInfoIdBytes, authInfoBytes]); - } - public async announce(props: { namespace: string, authInfo: string }) { - const announce = this.generateAnnounceMessage(props); - await this.send({writerStream: this.controlWriter, dataBytes: announce}); - } - public async readAnnounce() { - const type = await varIntToNumber(this.controlReader); - if (type !== MOQ_MESSAGE.ANNOUNCE_OK) { - throw new Error(`ANNOUNCE answer type must be ${MOQ_MESSAGE.ANNOUNCE_OK}, got ${type}`); - } - const namespace = await toString(this.controlReader); - return { namespace }; - } - public generateUnannounceMessage(ns: string) { - const messageType = numberToVarInt(MOQ_MESSAGE.UNANNOUNCE); - const namespace = stringToBytes(ns); - return concatBuffer([messageType, namespace]); - } - public async unannounce() { - const unannounce = this.generateUnannounceMessage('kota'); - await this.send({writerStream: this.controlWriter, dataBytes: unannounce}); - } - // TODO: announce ok, announce error, announce cancel and unannounce - // TODO: track status request, track status - // SUBSCRIBE - private generateSubscribeMessage(props: {subscribeId: number, namespace: string, trackName: string, authInfo: string}) { - const messageTypeBytes = numberToVarInt(MOQ_MESSAGE.SUBSCRIBE); - const subscribeIdBytes = numberToVarInt(props.subscribeId); - const trackAliasBytes = numberToVarInt(props.subscribeId); // temporary value - const namespaceBytes = stringToBytes(props.namespace); - const trackNameBytes = stringToBytes(props.trackName); - // const subscriberPriorityBytes = numberToVarInt(1); // temporary constant - const filterTypeBytes = numberToVarInt(SUBSCRIBE_FILTER.LATEST_OBEJCT); // temporary constant - // const groupOrderBytes = numberToVarInt(SUBSCRIBE_GROUP_ORDER.ASCENDING); // temporary constant prob v5 - // const startGroupBytesValue = numberToVarInt(0); - // const startObjectBytesValue = numberToVarInt(0); - // const endGroupBytesValue - // const endObjectBytesValue - const numberOfParamsBytes = numberToVarInt(1); - const authInfoParamIdBytes = numberToVarInt(MOQ_PARAMETER_AUTHORIZATION_INFO); - const authInfoBytes = stringToBytes(props.authInfo); - return concatBuffer([messageTypeBytes, subscribeIdBytes, trackAliasBytes, namespaceBytes, trackNameBytes, filterTypeBytes, numberOfParamsBytes, authInfoParamIdBytes, authInfoBytes]); - } - public async subscribe(props: {subscribeId: number, namespace: string, trackName: string, authInfo: string }) { - const subscribe = this.generateSubscribeMessage(props); - await this.send({ writerStream: this.controlWriter, dataBytes: subscribe }); - } - public async readSubscribe() { - const ret = { subscribeId: -1, trackAlias: -1, namespace: '', trackName: '', filterType: -1, startGroup: -1, startObject: -1, endGroup: -1, endObject: -1, parameters: null }; - ret.subscribeId = await varIntToNumber(this.controlReader); - ret.trackAlias = await varIntToNumber(this.controlReader); - ret.namespace = await toString(this.controlReader); - ret.trackName = await toString(this.controlReader); - ret.filterType = await varIntToNumber(this.controlReader); - // ret.startGroup = await varIntToNumber(this.controlReader); - // if (ret.startGroup !== MOQ_LOCATION_MODE_NONE) await varIntToNumber(this.controlReader); - // ret.startObject = await varIntToNumber(this.controlReader); - // if (ret.startObject !== MOQ_LOCATION_MODE_NONE) await varIntToNumber(this.controlReader); - // ret.endGroup = await varIntToNumber(this.controlReader); - // if (ret.endGroup !== MOQ_LOCATION_MODE_NONE) await varIntToNumber(this.controlReader); - // ret.endObject = await varIntToNumber(this.controlReader); - // if (ret.endObject !== MOQ_LOCATION_MODE_NONE) await varIntToNumber(this.controlReader); - ret.parameters = await this.readParams(); - - return ret; - } - private generateSubscribeResponseMessage(props: {subscribeId: number, expiresMs: number }) { - const messageTypeBytes = numberToVarInt(MOQ_MESSAGE.SUBSCRIBE_OK); - const subscriptionIdBytes = numberToVarInt(props.subscribeId); - const expiresBytes = numberToVarInt(props.expiresMs); - const contentExistsBytes = numberToVarInt(1); // temporary constant - const largestGroupIdBytes = numberToVarInt(0); // temporary constant - const largestObjectIdBytes = numberToVarInt(0); // temporary constant - return concatBuffer([messageTypeBytes, subscriptionIdBytes, expiresBytes, contentExistsBytes, largestGroupIdBytes, largestObjectIdBytes]); - } - public async sendSubscribeResponse(props: {subscribeId: number, expiresMs: number }) { - const subscribeResponse = this.generateSubscribeResponseMessage(props); - await this.send({ writerStream: this.controlWriter, dataBytes: subscribeResponse }); - } - public async readSubscribeResponse() { - const ret = { subscribeId: -1, expires: -1, contentExists: -1 }; - ret.subscribeId = await varIntToNumber(this.controlReader); - ret.expires = await varIntToNumber(this.controlReader); - ret.contentExists = await varIntToNumber(this.controlReader); - return ret; - } - private generateSubscribeUpdateMessage() {} - public async readSubscribeError() { - const subscribeId = await varIntToNumber(this.controlReader); - const errorCode = await varIntToNumber(this.controlReader); - const reasonPhrase = await toString(this.controlReader); - const trackAlias = await varIntToNumber(this.controlReader); - return { subscribeId, errorCode, reasonPhrase, trackAlias }; - } - public readSubscribeDone() {} - private generateUnsubscribeMessage(subscribeId: number) { - const messageTypeBytes = numberToVarInt(MOQ_MESSAGE.UNSUBSCRIBE); - const subscribeIdBytes = numberToVarInt(subscribeId); - return concatBuffer([messageTypeBytes, subscribeIdBytes]); - } - public async unsubscribe(subscribeId: number) { - const unsubscribeMessage = this.generateUnsubscribeMessage(subscribeId); - await this.send({ writerStream: this.controlWriter, dataBytes: unsubscribeMessage }); - } - public async readUnsubscribe () { - const subscribeId = await varIntToNumber(this.controlReader); - return { subscribeId }; - } - // OBJECT - private generateObjectMessage(props: {subscribeId: number, groupSeq: number, objectSeq: number, sendOrder: number, data: Uint8Array}) { - const messageTypeBytes = numberToVarInt(MOQ_MESSAGE.OBJECT_STREAM); - const subscribeIdBytes = numberToVarInt(props.subscribeId); - const trackAliasBytes = numberToVarInt(props.subscribeId); // temporary value - const groupIdBytes = numberToVarInt(props.groupSeq); - const objectIdBytes = numberToVarInt(props.objectSeq); - const sendOrderBytes = numberToVarInt(props.sendOrder); - const objectStatusBytes = numberToVarInt(OBJECT_STATUS.NORMAL); - const performanceBytes = numberToVarInt(Math.floor(performance.timeOrigin + performance.now())); - return { - getId: () => `${props.subscribeId}-${props.groupSeq}-${props.objectSeq}`, - toBytes: () => concatBuffer([messageTypeBytes, subscribeIdBytes, trackAliasBytes, groupIdBytes, objectIdBytes, sendOrderBytes, objectStatusBytes, performanceBytes, props.data]) - }; - } - public async sendObject(props: { trackName: string, data: Uint8Array, newGroup: boolean }) { - const targetTrack = this.trackManager.getTrack(props.trackName); - if (!this.senderState[props.trackName]) { - this.senderState[props.trackName] = { - currentGroupSeq: 0, - currentObjectSeq: 0, - }; - } else { - this.senderState[props.trackName].currentObjectSeq++; - } - if (props.newGroup) { - this.senderState[props.trackName].currentGroupSeq++; - this.senderState[props.trackName].currentObjectSeq = 0; - } - const sendOrder = (this.senderState[props.trackName].currentObjectSeq + 1) * targetTrack.priority; // Really temporary - const uniStream = await this.wt.createUnidirectionalStream({ sendOrder }); - for (const subscribeId of targetTrack.subscribeIds) { - const moqtObject = this.generateObjectMessage({ - subscribeId, - groupSeq: this.senderState[props.trackName].currentGroupSeq, - objectSeq: this.senderState[props.trackName].currentObjectSeq, - sendOrder, - data: props.data}); - const success = this.addInflightRequest(moqtObject.getId()); - if (success.success) { - const latency = moqVideoFrameOnEncode.calcLatency(performance.now()); - moqVideoEncodeLatencyStore.set(latency); - await this.send({ writerStream: uniStream, dataBytes: moqtObject.toBytes() }); - uniStream.close().finally(() => { - this.removeInflightRequest(moqtObject.getId()); - }); - } - } - } - public async readObject(props: { readableStream: ReadableStream }) { - const type = await varIntToNumber(props.readableStream); - if (type !== MOQ_MESSAGE.OBJECT_STREAM && type !== MOQ_MESSAGE.OBJECT_DATAGRAM) { - throw new Error(`OBJECT answer type must be ${MOQ_MESSAGE.OBJECT_STREAM} or ${MOQ_MESSAGE.OBJECT_DATAGRAM}, got ${type}`); - } - const subscribeId = await varIntToNumber(props.readableStream); - const trackAlias = await varIntToNumber(props.readableStream); - const groupId = await varIntToNumber(props.readableStream); - const objId = await varIntToNumber(props.readableStream); - const sendOrder = await varIntToNumber(props.readableStream); - const objectStatus = await varIntToNumber(props.readableStream); - const sourcePerformance = await varIntToNumber(props.readableStream); - moqVideoTransmissionLatencyStore.set(Math.floor(performance.timeOrigin + performance.now()) - sourcePerformance); - return { subscribeId, trackAlias, groupId, objId, sendOrder, objectStatus }; - } - // TODO: OBJECT DATAGRAM, Multi-Object Streams, track status request, track status - private async readParams() { - const ret = { authInfo: '', role: -1 }; - const numParams = await varIntToNumber(this.controlReader); - if (numParams > MOQ_MAX_PARAMS) { - throw new Error(`exceeded the max number of supported params ${MOQ_MAX_PARAMS}, got ${numParams}`); - } - for (let i = 0; i < numParams; i++) { - const paramId = await varIntToNumber(this.controlReader); - if (paramId === MOQ_PARAMETER_AUTHORIZATION_INFO) { - ret.authInfo = await toString(this.controlReader); - break; - } else if (paramId === MOQ_PARAMETER_ROLE) { - await varIntToNumber(this.controlReader); - ret.role = await varIntToNumber(this.controlReader); - } else { - const paramLength = await varIntToNumber(this.controlReader); - const skip = await buffRead(this.controlReader, paramLength); - ret[`unknown-${i}-${paramId}-${paramLength}`] = JSON.stringify(skip); - } - } - return ret; - } - private addInflightRequest(requestId: string): { success: boolean } { - if (this.inflightRequests.length > this.MAX_INFLIGHT_REQUESTS) { - return { success: false }; - } - this.inflightRequests.push(requestId); - return { success: true }; - } - private removeInflightRequest(requestId: string) { - this.inflightRequests = this.inflightRequests.filter((id) => id !== requestId); - } -} diff --git a/src/lib/moq/publish/publisher.ts b/src/lib/moq/publish/publisher.ts index 2a82d81..1ef848e 100644 --- a/src/lib/moq/publish/publisher.ts +++ b/src/lib/moq/publish/publisher.ts @@ -1,8 +1,6 @@ -import { AUDIO_ENCODER_DEFAULT_CONFIG, MOQ_MESSAGE, MOQ_PARAMETER_ROLE, VIDEO_ENCODER_CONFIGS } from '../constants'; +import { AUDIO_ENCODER_DEFAULT_CONFIG, VIDEO_ENCODER_CONFIGS } from '../constants'; import { LOC } from '../loc'; -import { MOQT } from '../moqt'; -import { serializeMetadata } from '../utils/bytes'; -import { Mogger } from '../utils/mogger'; +import { MOQT, MOQ_MESSAGE, MOQ_PARAMETER_ROLE, moqtBytes } from 'moqtail'; import { moqVideoFrameOnEncode } from '../utils/store'; export interface InitProps { namespace: string, videoTrackName: string, audioTrackName: string, keyFrameDuration: number, authInfo: string }; @@ -26,7 +24,6 @@ export class Publisher { private videoReader: ReadableStreamDefaultReader; private audioReader: ReadableStreamDefaultReader; private keyframeDuration = 60; - private mogger = new Mogger('Publisher'); state: 'created' | 'running' | 'stopped'; videoChunkCount: number; audioChunkCount: number; @@ -60,13 +57,13 @@ export class Publisher { await this.moqt.readAnnounce(); } this.state = 'running'; - this.mogger.info(`Announced tracks ${props.videoTrackName} (low, medium and high quality) and ${this.audioTrackName}`); + console.info(`Announced tracks ${props.videoTrackName} (low, medium and high quality) and ${this.audioTrackName}`); this.startLoopSubscriptionsLoop(); } public async stop() { await this.moqt.unannounce(); this.state = 'stopped'; - this.mogger.info('unannounced'); + console.info('unannounced'); } public async replaceTrack(mediaStream: MediaStream) { this.resetStream(mediaStream); @@ -87,13 +84,13 @@ export class Publisher { const track = this[name]; track.encoder = new VideoEncoder({ output: (chunk, metadata) => this.handleEncodedVideoChunk(chunk, track.trackName, metadata), - error: (error: DOMException) => this.mogger.error(error.message) + error: (error: DOMException) => console.error(error) }); track.encoder.configure(VIDEO_ENCODER_CONFIGS[name]); } const audioEncoder = new AudioEncoder({ output: (chunk, metadata) => this.handleEncodedAudioChunk(chunk, metadata), - error: (error: DOMException) => this.mogger.error(error.message) + error: (error: DOMException) => console.error(error) }); audioEncoder.configure(this.audioEncoderConfig); @@ -125,19 +122,19 @@ export class Publisher { const chunkData = new Uint8Array(chunk.byteLength); chunk.copyTo(chunkData); const locPacket = new LOC(); - locPacket.setData('video', chunk.type, this.videoChunkCount, chunk.timestamp, chunkData, serializeMetadata(metadata)); + locPacket.setData('video', chunk.type, this.videoChunkCount, chunk.timestamp, chunkData, moqtBytes.serializeMetadata(metadata)); this.videoChunkCount++; try { await this.moqt.sendObject({ trackName: trackName, data: locPacket.toBytes(), newGroup: locPacket.chunkType === 'key' }); } catch (e) { - this.mogger.error(e); + console.error(e); } } private async handleEncodedAudioChunk(chunk: EncodedAudioChunk, metadata) { const chunkData = new Uint8Array(chunk.byteLength); chunk.copyTo(chunkData); const locPacket = new LOC(); - locPacket.setData('audio', chunk.type, this.audioChunkCount++, chunk.timestamp, chunkData, serializeMetadata(metadata)); + locPacket.setData('audio', chunk.type, this.audioChunkCount++, chunk.timestamp, chunkData, moqtBytes.serializeMetadata(metadata)); await this.moqt.sendObject({ trackName: this.audioTrackName, data: locPacket.toBytes(), newGroup: locPacket.chunkType === 'key' }); } public async startLoopSubscriptionsLoop() { @@ -146,12 +143,12 @@ export class Publisher { if (messageType === MOQ_MESSAGE.SUBSCRIBE) { const subscribe = await this.moqt.readSubscribe(); this.moqt.trackManager.addSubscribeId(subscribe.trackName, subscribe.subscribeId); - this.mogger.info(`Received subscription to track ${subscribe.trackName}`); + console.info(`Received subscription to track ${subscribe.trackName}`); await this.moqt.sendSubscribeResponse({ subscribeId: subscribe.subscribeId, expiresMs: 0 }); } else if (messageType === MOQ_MESSAGE.UNSUBSCRIBE) { const unsubscribe = await this.moqt.readUnsubscribe(); this.moqt.trackManager.removeSubscribeId(unsubscribe.subscribeId); - this.mogger.info(`Received unsubscrition from id ${unsubscribe.subscribeId}`); + console.info(`Received unsubscrition from id ${unsubscribe.subscribeId}`); } else { throw new Error('Unexpected message type received'); } diff --git a/src/lib/moq/subscribe/subscriber.ts b/src/lib/moq/subscribe/subscriber.ts index e1978b6..8d700ba 100644 --- a/src/lib/moq/subscribe/subscriber.ts +++ b/src/lib/moq/subscribe/subscriber.ts @@ -1,8 +1,7 @@ -import { AUDIO_DECODER_DEFAULT_CONFIG, MOQ_MESSAGE, MOQ_PARAMETER_ROLE, VIDEO_DECODER_DEFAULT_CONFIG } from '../constants'; +import { AUDIO_DECODER_DEFAULT_CONFIG, VIDEO_DECODER_DEFAULT_CONFIG } from '../constants'; import { LOC } from '../loc'; import { MitterMuffer } from '../mitter-muffer'; -import { MOQT } from '../moqt'; -import { Mogger } from '../utils/mogger'; +import { MOQT, MOQ_MESSAGE, MOQ_PARAMETER_ROLE } from 'moqtail'; import { moqVideoDecodeLatencyStore, moqVideoFrameOnDecode } from '../utils/store'; export class Subscriber { @@ -14,18 +13,17 @@ export class Subscriber { private waitForKeyFrame = true; private videoDecoderConfig: VideoDecoderConfig = VIDEO_DECODER_DEFAULT_CONFIG; private audioEncoderConfig: AudioDecoderConfig = AUDIO_DECODER_DEFAULT_CONFIG; - private mogger = new Mogger('Subscriber'); private videoJitterBuffer: MitterMuffer; private audioJitterBuffer: MitterMuffer; constructor(url: string) { this.moqt = new MOQT({ url }); this.vDecoder = new VideoDecoder({ output: (frame) => this.handleVideoFrame(frame), - error: (error: DOMException) => this.mogger.error(error.message) + error: (error: DOMException) => console.error(error.message) }); this.aDecoder = new AudioDecoder({ output: (frame) => this.handleAudioFrame(frame), - error: (error: DOMException) => this.mogger.error(error.message) + error: (error: DOMException) => console.error(error.message) }); this.vDecoder.configure(this.videoDecoderConfig); this.setWaitForKeyFrame(true); @@ -91,7 +89,7 @@ export class Subscriber { try { await loc.fromBytes(readerStream); } catch (e) { - this.mogger.error(e); + console.error(e); } const currentLocObject = loc.toObject(); if (loc.chunkType === 'delta' && this.waitForKeyFrame) return; @@ -103,7 +101,7 @@ export class Subscriber { const locObject = jitterRet.frame; if (locObject.metadata) { const config: VideoDecoderConfig = locObject.metadata; - this.mogger.info(`received config: ${JSON.stringify(config)}`); + console.info(`received config: ${JSON.stringify(config)}`); config.optimizeForLatency = true; this.vDecoder.configure(config); } diff --git a/src/lib/moq/track.ts b/src/lib/moq/track.ts deleted file mode 100644 index f890a5d..0000000 --- a/src/lib/moq/track.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Track } from 'src/app'; - -export class TrackManager { - private tracks: Track[] = []; - public addTrack(track: Track) { - this.tracks.push(track); - } - public getTrack(name: string) { - return this.tracks.find(track => track.name === name); - } - public getTrackBySubscribeId(id: number) { - return this.tracks.find(track => track.subscribeIds.includes(id)); - } - public getAllTracks() { - return this.tracks; - } - public addSubscribeId(name: string, id: number) { - this.tracks.map(track => { - if (track.name === name) { - track.subscribeIds.push(id); - } - }); - } - public removeSubscribeId(id: number) { - this.tracks.map(track => { - const index = track.subscribeIds.indexOf(id); - if (index > -1) { - track.subscribeIds.splice(index, 1); - } - }); - } -} diff --git a/src/lib/moq/utils/bytes.ts b/src/lib/moq/utils/bytes.ts deleted file mode 100644 index e71d7e6..0000000 --- a/src/lib/moq/utils/bytes.ts +++ /dev/null @@ -1,232 +0,0 @@ -/* -Copyright (c) Meta Platforms, Inc. and affiliates. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -const MAX_U6 = Math.pow(2, 6) - 1; -const MAX_U14 = Math.pow(2, 14) - 1; -const MAX_U30 = Math.pow(2, 30) - 1; -const MAX_U53 = Number.MAX_SAFE_INTEGER; -// const MAX_U62 = 2n ** 62n - 1n - -export const buffReadFrombyobReader = async (reader, buffer, offset, size) => { - const ret = null; - if (size <= 0) { - return ret; - } - let remainingSize = size; - while (remainingSize > 0) { - const { value, done } = await reader.read(new Uint8Array(buffer, offset, remainingSize)); - if (value !== undefined) { - buffer = value.buffer; - offset += value.byteLength; - remainingSize = remainingSize - value.byteLength; - } - if (done && remainingSize > 0) { - throw new Error('short buffer'); - } - } - return buffer; -}; - -export const numberToVarInt = (v) => { - if (v <= MAX_U6) { - return setUint8(v); - } else if (v <= MAX_U14) { - return setUint16(v | 0x4000); - } else if (v <= MAX_U30) { - return setUint32(v | 0x80000000); - } else if (v <= MAX_U53) { - return setUint64(BigInt(v) | 0xc000000000000000n); - } else { - throw new Error(`overflow, value larger than 53-bits: ${v}`); - } -}; - -export const varIntToNumber = async (readableStream) => { - let ret; - const reader = readableStream.getReader({ mode: 'byob' }); - try { - let buff = new ArrayBuffer(8); - - buff = await buffReadFrombyobReader(reader, buff, 0, 1); - const size = (new DataView(buff, 0, 1).getUint8() & 0xc0) >> 6; - if (size === 0) { - ret = new DataView(buff, 0, 1).getUint8() & 0x3f; - } else if (size === 1) { - buff = await buffReadFrombyobReader(reader, buff, 1, 1); - ret = new DataView(buff, 0, 2).getUint16() & 0x3fff; - } else if (size === 2) { - buff = await buffReadFrombyobReader(reader, buff, 1, 3); - ret = new DataView(buff, 0, 4).getUint32() & 0x3fffffff; - } else if (size === 3) { - buff = await buffReadFrombyobReader(reader, buff, 1, 7); - ret = Number(new DataView(buff, 0, 8).getBigUint64() & BigInt('0x3fffffffffffffff')); - } else { - throw new Error('impossible'); - } - } finally { - reader.releaseLock(); - } - return ret; -}; - -const setUint8 = (v) => { - const ret = new Uint8Array(1); - ret[0] = v; - return ret; -}; - -const setUint16 = (v) => { - const ret = new Uint8Array(2); - const view = new DataView(ret.buffer); - view.setUint16(0, v); - return ret; -}; - -const setUint32 = (v) => { - const ret = new Uint8Array(4); - const view = new DataView(ret.buffer); - view.setUint32(0, v); - return ret; -}; - -const setUint64 = (v) => { - const ret = new Uint8Array(8); - const view = new DataView(ret.buffer); - view.setBigUint64(0, v); - return ret; -}; - -export const concatBuffer = (arr) => { - let totalLength = 0; - arr.forEach(element => { - if (element !== undefined) { - totalLength += element.byteLength; - } - }); - const retBuffer = new Uint8Array(totalLength); - let pos = 0; - arr.forEach(element => { - if (element !== undefined) { - retBuffer.set(element, pos); - pos += element.byteLength; - } - }); - return retBuffer; -}; - -export const buffRead = async (readableStream, size) => { - const ret = null; - if (size <= 0) { - return ret; - } - let buff = new Uint8Array(Number(size)); - const reader = readableStream.getReader({ mode: 'byob' }); - - try { - buff = await buffReadFrombyobReader(reader, buff, 0, size); - } finally { - reader.releaseLock(); - } - return buff; -}; - -export const readUntilEof = async (readableStream, blockSize) => { - const chunkArray = []; - let totalLength = 0; - - while (true) { - let bufferChunk = new Uint8Array(blockSize); - const reader = readableStream.getReader({ mode: 'byob' }); - const { value, done } = await reader.read(new Uint8Array(bufferChunk, 0, blockSize)); - if (value !== undefined) { - bufferChunk = value.buffer; - chunkArray.push(bufferChunk.slice(0, value.byteLength)); - totalLength += value.byteLength; - } - reader.releaseLock(); - if (value === undefined) { - throw new Error('error reading incoming data'); - } - if (done) { - break; - } - } - // Concatenate received data - const payload = new Uint8Array(totalLength); - let pos = 0; - for (const element of chunkArray) { - const uint8view = new Uint8Array(element, 0, element.byteLength); - payload.set(uint8view, pos); - pos += element.byteLength; - } - - return payload; -}; - -export const serializeMetadata = (metadata): Uint8Array => { - let ret: Uint8Array; - if (isMetadataValid(metadata)) { - const newData = {}; - // Copy all enumerable own properties - newData.decoderConfig = Object.assign({}, metadata.decoderConfig); - // Description is buffer - if ('description' in metadata.decoderConfig) { - newData.decoderConfig.descriptionInBase64 = arrayBufferToBase64(metadata.decoderConfig.description); - delete newData.description; - } - // Encode - const encoder = new TextEncoder(); - ret = encoder.encode(JSON.stringify(newData)); - } - return ret; -}; - -export const isMetadataValid = (metadata) => metadata !== undefined && 'decoderConfig' in metadata; - -function arrayBufferToBase64(buffer) { - let binary = ''; - const bytes = new Uint8Array(buffer); - const len = bytes.byteLength; - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); -} - -export const deSerializeMetadata = (metadata) => { - const decoder = new TextDecoder(); - const str = decoder.decode(metadata); - const data = JSON.parse(str); - - if (('decoderConfig' in data) && ('descriptionInBase64' in data.decoderConfig)) { - data.decoderConfig.description = base64ToArrayBuffer(data.decoderConfig.descriptionInBase64); - delete data.decoderConfig.descriptionInBase64; - } - return data.decoderConfig; -}; - -const base64ToArrayBuffer = (base64) => { - const binaryString = atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes.buffer; -}; - -export const stringToBytes = (str: string) => { - const dataStrBytes = new TextEncoder().encode(str); - const dataStrLengthBytes = numberToVarInt(dataStrBytes.byteLength); - return concatBuffer([dataStrLengthBytes, dataStrBytes]); -}; - -export const toString = async (receiveStream: ReadableStream) => { - const size = await varIntToNumber(receiveStream); - const buffer = await buffRead(receiveStream, size); - return new TextDecoder().decode(buffer); -} diff --git a/src/lib/moq/utils/mogger.ts b/src/lib/moq/utils/mogger.ts deleted file mode 100644 index b27f7d4..0000000 --- a/src/lib/moq/utils/mogger.ts +++ /dev/null @@ -1,22 +0,0 @@ -type Role = 'Subscriber' | 'Publisher'; - -export class Mogger { - private role: Role; - constructor(role: Role) { - this.role = role; - } - private getCaller() { - const error = new Error(); - const stack = error.stack || ''; - const stackLines = stack.split('\n'); - const callerIndex = stackLines.findIndex(line => line.includes('getCaller')) + 2; - if (stackLines[callerIndex]) return stackLines[callerIndex].trim(); - return 'Unknown'; -} - public info(text: string) { - console.info(`${this.role}: ${text}`); - } - public error(text: string) { - console.error(`${this.role}: ${this.getCaller()}: ${text}`); - } -} diff --git a/yarn.lock b/yarn.lock index 748673a..6f9c787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -397,19 +397,6 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -amazon-ivs-web-broadcast@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/amazon-ivs-web-broadcast/-/amazon-ivs-web-broadcast-1.12.0.tgz#b76e5732c4a68876874695ef4260a3e757f1aca4" - integrity sha512-hMu0i3hbr3Owy4OAvd2S6xScwgJ1uKX5+G8gdBWlc4iMTqgtn3egVCtTf1+hDNZVk9dpia73CqYXlYmYQxvloQ== - dependencies: - axios "^0.27.2" - bowser "^2.11.0" - eventemitter3 "^4.0.7" - lodash "^4.5.0" - reflect-metadata "^0.1.13" - sdp-transform "^2.14.1" - webrtc-adapter "^8.1.1" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -440,19 +427,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -463,11 +437,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -528,13 +497,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -583,11 +545,6 @@ deepmerge@^4.3.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - detect-indent@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" @@ -782,11 +739,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -855,20 +807,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.9: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1076,7 +1014,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21, lodash@^4.5.0: +lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -1115,18 +1053,6 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -1151,6 +1077,13 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" +moqtail@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/moqtail/-/moqtail-1.2.1.tgz#df414db6d528b91f0b9657dcd6bad5f3dc4f69fe" + integrity sha512-hvMn47HneDKQq4sTqheaxe/G0eQom4nEYgNXA0sqeMdJoib6WrLo1wVMLQ/rZz8eHR/pkYS87e4N52NA+aK7iw== + dependencies: + typescript "^5.6.2" + mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" @@ -1330,11 +1263,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reflect-metadata@^0.1.13: - version "0.1.14" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" - integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== - regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -1404,16 +1332,6 @@ sass@^1.58.3: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sdp-transform@^2.14.1: - version "2.14.2" - resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.14.2.tgz#d2cee6a1f7abe44e6332ac6cbb94e8600f32d813" - integrity sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA== - -sdp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.2.0.tgz#8961420552b36663b4d13ddba6f478d1461896a5" - integrity sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw== - semver@^7.3.7: version "7.5.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" @@ -1604,6 +1522,11 @@ typescript@^4.9.3, typescript@^4.9.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + undici@^5.28.3: version "5.28.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" @@ -1639,13 +1562,6 @@ vitefu@^0.2.4: resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.5.tgz#c1b93c377fbdd3e5ddd69840ea3aa70b40d90969" integrity sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q== -webrtc-adapter@^8.1.1: - version "8.2.3" - resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz#85e5e52ea68e808be8d6db85e338aa5c95e80022" - integrity sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ== - dependencies: - sdp "^3.2.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"