UNPKG

@salutejs/jazz-sdk-web-plugins

Version:

Jazz SDK plugins

1,552 lines (1,505 loc) 91.6 kB
import { Signal, Atom, Scope } from 'nrgy'; import { EventError, EventOk, SyncResult, HttpClientFetchError, HttpClientResponseError, JazzSdkPlugin, JazzRoomExtender, JazzRoom, JazzClientExtender, JazzClient, Extender, MakeExtender, QueryAtom, ConfigFlags, Query, JazzRoomParticipantId, LocalAudioOutputDevice, JazzSdk, MediaType, LogLevel, LogEvent, ResultSuccess, ResultFailure, EventBus, DeepUnion, MeetingHistory, Timestamp, JazzRoomParticipantRole, CollectMetricsHandler, CollectMetricsMetric, CollectMetricsMeta, CollectMetricsMetricData, CollectMetricsMetaActions, ResponseExtender, } from '@salutejs/jazz-sdk-web'; import { Controller } from 'nrgy/mvc'; import { Observable } from 'rxjs'; import { Controller as Controller$1 } from 'rx-effects'; import { toQuery, toAction } from 'nrgy/rx-effects'; import { observe } from 'nrgy/rxjs'; import * as ditox from 'ditox'; import { ModuleDeclaration } from 'ditox'; type ServerRecordingEventStarted = { type: 'serverRecordStarted'; }; type ServerRecordingEventStopped = { type: 'serverRecordStopped'; payload: { reason?: 'DURATION_LIMIT_EXCEEDED' | 'STOP_MANUALLY'; }; }; type ServerRecordingEventUploaded = { type: 'serverRecordUploaded'; }; type ServerRecordingEventStartError = { type: 'serverRecordStartError'; payload?: { error: 'forbidden' | 'alreadyStarted' | 'storageCapacityExceeded'; }; }; type ServerRecordingEventStopError = { type: 'serverRecordStopError'; payload?: { error: 'forbidden' | 'notStarted'; }; }; type ServerRecordingEventUploadError = { type: 'serverRecordUploadedError'; }; type ServerRecordingEventError = { type: 'serverRecordError'; }; type ServerRecordingEventAutoStartError = { type: 'serverRecordAutoStartError'; payload?: { error: 'internalError' | 'hasNoPermission' | 'storageSizeExceeded'; }; }; type ServerRecordingEvents = | ServerRecordingEventStarted | ServerRecordingEventStopped | ServerRecordingEventUploaded | ServerRecordingEventStartError | ServerRecordingEventStopError | ServerRecordingEventUploadError | ServerRecordingEventError | ServerRecordingEventAutoStartError; type RoomServerRecordingStatus = | 'inactive' | 'processStarting' | 'recording' | 'processStopping'; type RoomServerRecordingSupportedStatus = | 'supported' | 'pending' | 'unsupported'; type BaseServerRecordingRoomService = { events: Signal<ServerRecordingEvents>; /** * Starts server recording * @throws {ServerRecordingError} When recording fails to start with codes: * - 'FORBIDDEN' * - 'VIDEO_RECORD_ALREADY_STARTED' * - 'VIDEO_RECORD_STORAGE_CAPACITY_EXCEEDED' * - 'VIDEO_RECORD_START_ERROR' */ startServerRecording: () => Promise<void>; /** * Stops server recording * @throws {ServerRecordingError} When recording fails to stop with codes: * - 'FORBIDDEN' * - 'VIDEO_RECORD_STOP_ERROR' * - 'VIDEO_RECORD_NOT_STARTED' */ stopServerRecording: () => Promise<void>; }; type ServerRecordingRoomService = BaseServerRecordingRoomService & { serverSupportedStatus: Atom<RoomServerRecordingSupportedStatus>; isReady: Atom<boolean>; status: Atom<RoomServerRecordingStatus>; canManageServerRecordList: Atom<boolean>; canManageServerRecord: Atom<boolean>; }; declare class ServerRecordingError extends Error { readonly code: string; readonly error?: string; constructor(code: string, message?: string); } type ServerRecordingStartErrorEvent = EventError< | 'FORBIDDEN' | 'VIDEO_RECORD_ALREADY_STARTED' | 'VIDEO_RECORD_STORAGE_CAPACITY_EXCEEDED' | 'VIDEO_RECORD_START_ERROR' >; type ServerRecordingStopErrorEvent = EventError< 'FORBIDDEN' | 'VIDEO_RECORD_STOP_ERROR' | 'VIDEO_RECORD_NOT_STARTED' >; type ServerRecordingMessages = | EventOk<'video-record-started'> | EventOk<'video-record-uploaded'> | EventOk< 'video-record-stopped', // запись остановлена { reason: 'STOP_MANUALLY' | 'DURATION_LIMIT_EXCEEDED'; } > | EventError< 'START_ERROR' | 'AUTOSTART_ERROR' | 'RECORD_ERROR' | 'UPLOAD_ERROR', // ошибка загрузки записи unknown, 'video-record-error' >; type GetVideoStorageResponse = { usedMb: number; availableMb?: number; capacityMb: number; }; type StartVideoRecordErrors = | { type: 'storageQuotaExceeded'; } | { type: 'clientError'; }; type StartVideoRecordResponse = SyncResult< undefined, | HttpClientFetchError | HttpClientResponseError<StartVideoRecordErrors, void, void> >; type GetVideoFindRequest = Readonly<{ pageNumber?: number; pageSize: number; }>; type GetVideoFindResponse = { totalPages: number; records: VideoRecord[]; pageNumber?: number; pageSize?: number; }; type VideoRecord = { id: number; title: string; createdAt: string; durationSeconds: number; publicId?: string; sizeMb?: number; publicAccess?: boolean; watchUrl?: string | null; downloadUrl?: string; permissions: { canTogglePublicAccess: boolean; }; status: 'UPLOADED' | 'STOPPED'; }; type GetVideoResponse = VideoRecord; type RoomRecordingServiceClient = Readonly<{ getVideoStorage: () => Promise<GetVideoStorageResponse>; startVideoRecord: (roomId: string) => Promise<StartVideoRecordResponse>; stopVideoRecord: (roomId: string) => Promise<void>; getVideoFind: (request: GetVideoFindRequest) => Promise<GetVideoFindResponse>; getVideo: (recordId: string) => Promise<GetVideoResponse>; updateVideoTitle: (recordId: number, title: string) => Promise<void>; updateVideoPublicAccess: ( recordId: number, publicAccess: boolean, ) => Promise<void>; deleteVideo: (recordId: number) => Promise<void>; getDownloadLink: (recordPublicId: string) => Promise<string>; getS3DownloadLink: (url: string) => Promise<string | undefined>; }>; declare function serverRecordingPlugin(): JazzSdkPlugin; declare const getServerRecording: <R extends JazzRoomExtender[]>( jazzRoom: JazzRoom<R>, ) => ServerRecordingRoomService; declare const getServerRecordingClient: < R extends JazzRoomExtender[], C extends JazzClientExtender[], >( jazzRoom: JazzRoom<R> | JazzClient<C>, ) => RoomRecordingServiceClient; type ServerRecordingCreateRoomParams = { serverVideoRecordAutoStartEnabled: boolean; }; declare const withServerRecordingCreateRoom: ( params: ServerRecordingCreateRoomParams, ) => Extender; type ServerRecordingJazzRoom = MakeExtender< JazzRoomExtender, { permissions: { canStartServerVideoRecord?: boolean; }; } >; type ServerRecordingJazzClient = MakeExtender< JazzClientExtender, { userInfo: { features: { serverVideoRecordAvailable?: boolean; }; }; } >; type AudioLevelService = { addMediaStream: (stream: MediaStream) => AudioLevel; removeMediaStream: (stream: MediaStream) => void; }; type AudioLevel = { /** * @example 50 * @description the min value is 0 and the max value is 100 */ level: QueryAtom<number>; isActive: QueryAtom<boolean>; }; type AudioMixerEventStopAudio = Readonly<{ type: 'stopAudio'; }>; type AudioMixerEventStoppedAudio = Readonly<{ type: 'stoppedAudio'; }>; type AudioMixerEventStartAudio = Readonly<{ type: 'startAudio'; }>; type AudioMixerEventStartedAudio = Readonly<{ type: 'startedAudio'; }>; type AudioMixerEventAddMediaStream = Readonly<{ type: 'addMediaStream'; payload: { mediaStream: MediaStream; audioElement: HTMLAudioElement; }; }>; type AudioMixerEventRemoveMediaStream = Readonly<{ type: 'removeMediaStream'; payload: { mediaStream: MediaStream; audioElement: HTMLAudioElement; }; }>; type AudioMixerEventGainChanged = Readonly<{ type: 'gainChanged'; payload: { value: number; }; }>; type AudioMixerEvent = | AudioMixerEventStopAudio | AudioMixerEventStoppedAudio | AudioMixerEventStartAudio | AudioMixerEventStartedAudio | AudioMixerEventAddMediaStream | AudioMixerEventRemoveMediaStream | AudioMixerEventGainChanged; type AudioMixinNode = Controller<{ destinationNode: MediaStreamAudioDestinationNode; addStream: (stream: MediaStream) => void; removeStream: (stream: MediaStream) => void; }>; type AudioMixer = Controller<{ isSuspended: Atom<boolean>; isReady: Atom<boolean>; /** * Creating all the elements for audio capture and playback of external audio streams, * after that, loopback is immediately launched, if it is enabled. * Until this function works, the audio will not be played. */ startAudio: () => Promise<void>; /** * Stops loopback if it was enabled and then clears all audio capture and playback elements. */ stopAudio: () => Promise<void>; addMediaStream: (mediaStreams: MediaStream) => void; removeMediaStream: (mediaStreams: MediaStream) => void; removeAllMediaStreams: () => void; outputGain: Atom<number>; setOutputGain: (volume: number) => void; setOutputDevice: (deviceId: string) => Promise<void>; soundCheck: () => void; setRemoteInputMixer: (mixer: InputAudioMixerFactory | undefined) => void; getAudioContext: () => AudioContext; createMixinNode: () => Promise<AudioMixinNode>; setAudioSourcesPlayback: (enabled: boolean) => void; event$: Observable<AudioMixerEvent>; }>; type AudioEffectFactory<T extends Controller> = ( audioContext: AudioContext, ) => T; type AudioSource = Readonly<{ sourceNode: Atom<MediaStreamAudioSourceNode>; scope: Scope; element: HTMLAudioElement; streamId: string; }>; type InputAudioMixer = Controller<{ output: AudioNode; connect: (source: AudioSource) => Controller; }>; type InputAudioMixerFactory = AudioEffectFactory<InputAudioMixer>; type AudioMixerFlags = ConfigFlags<{ sampleRate?: number; latencyHint: string; }>; type AudioTrack = { participantId: JazzRoomParticipantId; mediaStream: MediaStream; }; type AudioMixerParticipantsManager = Controller$1<{ $mutedParticipants: Query<ReadonlySet<JazzRoomParticipantId>>; addTracks: (tracks: AudioTrack[]) => void; removeTracks: (tracks: AudioTrack[]) => void; removeAllTracks: () => void; muteParticipants: ( isMuted: boolean, ids: Array<JazzRoomParticipantId>, ) => void; }>; type AudioOutputMixer = { /** * @deprecated use isSuspended */ $isSuspended: Query<boolean>; isSuspended: Atom<boolean>; event$: Observable<AudioMixerEvent>; startAudio: () => Promise<void>; stopAudio: () => Promise<void>; setOutputDevice: (audioOutput: LocalAudioOutputDevice) => Promise<void>; outputGain: QueryAtom<number>; setOutputGain: (volume: number) => void; addMediaStream: (mediaStream: MediaStream) => void; removeMediaStream: (mediaStream: MediaStream) => void; muteParticipants: ( isMuted: boolean, ids: Array<JazzRoomParticipantId>, ) => void; }; type AudioOutputMixerPluginOptions = Partial<{ flags?: Partial<AudioMixerFlags>; audioLevelAnimationInterval?: number | undefined; checkLoadFirstAudioPackage?: boolean; }>; type AudioOutputMixerContext = { audioMixer: AudioMixer; audioOutputMixer: AudioOutputMixer; audioMixerParticipantsManager: AudioMixerParticipantsManager; audioLevel: AudioLevelService; }; /** * Позволяет локально управлять звуком участников конференций */ declare function audioOutputMixerPlugin( options?: AudioOutputMixerPluginOptions, ): JazzSdkPlugin; declare const AUDIO_GAIN_DEFAULT = 1; declare const MIN_AUDIO_GAIN_VALUE = 0; declare const MAX_AUDIO_GAIN_VALUE = 10; /** * @example * ```js * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins'; * const audioOutputMixer = getAudioOutputMixer(sdk); * const initOutputGain = audioOutputMixer.outputGain.get(); * ``` * @example * ```js * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins'; * const audioOutputMixer = getAudioOutputMixer(sdk); * audioOutputMixer.setOutputGain(5); * ``` * @example * ```js * const { getAudioOutputMixer } from '@salutejs/jazz-sdk-web-plugins'; * const audioOutputMixer = getAudioOutputMixer(sdk); * const unsubscribe = handleEvent( * audioOutputMixer.event$, * 'gainChanged', * ({ payload }) => { * setOutputGain(payload.value); * }, * ); * ``` */ declare function getAudioOutputMixer(sdk: JazzSdk): AudioOutputMixer; type RoomAudioMixerEvent = Readonly< | { type: 'muteParticipantsChanged'; payload: { isMuted: boolean; participantIds: JazzRoomParticipantId[]; }; } | { type: 'muteAllParticipantsChanged'; payload: { isMuted: boolean; }; } >; type AudioOutputMixerManager = { mutedParticipants: QueryAtom<ReadonlySet<JazzRoomParticipantId>>; isMutedAll: QueryAtom<boolean>; muteParticipants: ( isMuted: boolean, participantIds: JazzRoomParticipantId[] | JazzRoomParticipantId, ) => void; muteAllParticipants: (isMutedAll: boolean) => void; event$: Observable<RoomAudioMixerEvent>; }; /** * @example * ```js * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins' * const audioOutputMixerManger = getAudioOutputMixerManager(room) * audioOutputMixerManger.mutedParticipants.get().has(participantId) * ``` * @example * ```js * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins' * const audioOutputMixerManger = getAudioOutputMixerManager(room) * * const unsubscribe = handleEvent( * audioOutputMixerManger.event$, * 'muteParticipantsChanged', * ({ payload }) => { * if (payload.participantIds.some((id) => participantId === id)) { * setIsMuted(payload.isMuted); * } * }, * ); * ``` * @example * ```js * const { getAudioOutputMixerManager } from '@salutejs/jazz-sdk-web-plugins' * const audioOutputMixerManger = getAudioOutputMixerManager(room) * audioOutputMixerManger.muteParticipants(!isMuted, [participantId]); * ``` */ declare function getAudioOutputMixerManager( room: JazzRoom, ): AudioOutputMixerManager; /** * Event name for subscribe to all events in transport */ declare type AllEventTypes = '*'; declare interface BaseEventBusReadonly<EVENTS extends EventLike> extends BaseEventBusSubscriber<EVENTS> { name?: string; } declare interface BaseEventBusSubscriber<EVENTS extends EventLike> { /** * Method for subscribing to bus events. * In addition to events of the type, you can also specify the * event, * which will allow you to subscribe to all bus events. * The method returns a function for unsubscribing the callback * (this can also be done via the off or removeEventListener methods). * * If the onSubscribe lifecycle method is passed, * it will be called when this event is sent. * * If the transport was destroyed, this method will do nothing. * * @example * ```ts * type Events = { event: string }; * const eventBus = createBaseEventBus<Events>(); * * const unsubscriber = eventBus.on('event', (event, payload) => console.log(payload)); * unsubscriber(); * * eventBus.send('event', 'test'); * ``` */ on<EVENT extends string & keyof EVENTS>( event: EVENT, callback: (event: EVENT, payload: EVENTS[EVENT]) => void, ): Unsubscriber; /** * unsubscribe from an event. * If there are no subscribers left for the event, we remove it from the map. * * If the onUnsubscribe lifecycle callback is passed, * it will be called each time this function is called. * * If the transport was destroyed, the method does not work. * * @example * ```ts * type Events = { event: string }; * const eventBus = createBaseEventBus<Events>(); * * function handler(type: string, payload: string): void {} * * eventBus.on('event', handler); * eventBus.off('event', handler); * ``` */ off<EVENT extends string & keyof EVENTS>( event: EVENT, callback: (event: EVENT, payload: EVENTS[EVENT]) => void, ): void; } declare interface BaseTransportNodeReadonly { name?: string; __isRoot: Readonly<false>; /** * A property indicating that a class has been destroyed. * Once resolved, all methods in it stop working and the data is cleared. */ isDestroyed: boolean; /** * Method to get the root node object referenced by the node. */ getTransports: () => TransportRootNodes; } declare interface BaseTransportRoot extends DestroyedNode { name?: string; __isRoot: Readonly<true>; } /** * A node that has a cleanup mechanism. After the method chchchch is executed, * the node becomes inactive because event subscriptions and message sending stop functioning. */ declare interface DestroyedNode { /** * whether the transport is destroyed. * If the transport is destroyed, * then subscriptions and event sending do not work, and the subscriber list is destroyed. * Also, all dependent nodes are automatically unsubscribed from the destroyed node. */ isDestroyed: boolean; destroy(): void; } declare type EventLike = Record<string, unknown>; declare type Namespace = string; declare interface SubscribeNodeSubscribers<EVENTS extends EventLike> { on< EVENTS_KEYS extends keyof EVENTS, TYPE extends string, NAMESPACES extends UtilsTypeFilterTypesWithNamespaces< string & EVENTS_KEYS, TYPE >, EVENT_TYPE extends | `${NAMESPACES}:${AllEventTypes}` | AllEventTypes | (string & EVENTS_KEYS), NEW_NAMESPACE extends UtilsTypeFilterTypesWithNamespaces<EVENT_TYPE, TYPE>, CALLBACK_EVENTS extends EVENT_TYPE extends AllEventTypes ? string & EVENTS_KEYS : EVENT_TYPE extends `${NAMESPACES}:${AllEventTypes}` ? UtilsTypeRemoveNamespaceFromType<string & EVENTS_KEYS, NEW_NAMESPACE> : EVENT_TYPE, CALLBACK_PARAMS extends { [TYPE in CALLBACK_EVENTS]: [event: TYPE, payload: EVENTS[TYPE]]; }, >( event: EVENT_TYPE, callback: (...args: CALLBACK_PARAMS[CALLBACK_EVENTS]) => void, ): Unsubscriber; once< EVENTS_KEYS extends keyof EVENTS, TYPE extends string, NAMESPACES extends UtilsTypeFilterTypesWithNamespaces< string & EVENTS_KEYS, TYPE >, EVENT_TYPE extends | `${NAMESPACES}:${AllEventTypes}` | AllEventTypes | (string & EVENTS_KEYS), NEW_NAMESPACE extends UtilsTypeFilterTypesWithNamespaces<EVENT_TYPE, TYPE>, CALLBACK_EVENTS extends EVENT_TYPE extends AllEventTypes ? string & EVENTS_KEYS : EVENT_TYPE extends `${NAMESPACES}:${AllEventTypes}` ? UtilsTypeRemoveNamespaceFromType<string & EVENTS_KEYS, NEW_NAMESPACE> : EVENT_TYPE, CALLBACK_PARAMS extends { [TYPE in CALLBACK_EVENTS]: [event: TYPE, payload: EVENTS[TYPE]]; }, >( event: EVENT_TYPE, callback: (...args: CALLBACK_PARAMS[CALLBACK_EVENTS]) => void, ): Unsubscriber; off< EVENTS_KEYS extends keyof EVENTS, TYPE extends string, NAMESPACES extends UtilsTypeFilterTypesWithNamespaces< string & EVENTS_KEYS, TYPE >, EVENT_TYPE extends | `${NAMESPACES}:${AllEventTypes}` | AllEventTypes | (string & EVENTS_KEYS), >( type: EVENT_TYPE, callback: (...args: any[]) => void, ): void; } declare interface SubscribeReadonlyNode<EVENTS extends EventLike> extends SubscribeReadonlyNodeExtends<EVENTS> {} declare type SubscribeReadonlyNodeExtends<EVENTS extends EventLike> = BaseTransportNodeReadonly & SubscribeNodeSubscribers<EVENTS>; declare type TransportLifecycleEvents<EVENTS extends EventLike> = { /** * The transport was cleared. After that, * it stops functioning and all data in it is cleared. */ destroy: undefined; /** * Subscribed to some event. * The object indicates what event was subscribed to and whether it is the first. */ subscribe: { event: string & keyof EVENTS; mode: 'on' | 'once'; subscriber: Parameters<TransportRootSubscribers<EVENTS>['on']>[1]; subscribersCount: number; }; /** * Unsubscribed from some event. * The object indicates what event was unsubscribed from and whether there are more subscribers. */ unsubscribe: { event: string & keyof EVENTS; mode: 'on' | 'once'; subscriber: Parameters<TransportRootSubscribers<EVENTS>['off']>[1]; subscribersCount: number; }; }; declare interface TransportReadonlyNode<EVENTS extends EventLike> extends TransportReadonlyNodeBase<EVENTS> { lifecycle: TransportRoot<EVENTS>['lifecycle']; } declare type TransportReadonlyNodeBase<EVENTS extends EventLike> = TransportRootSubscribers<EVENTS> & BaseTransportNodeReadonly; declare interface TransportRoot<EVENTS extends EventLike> extends TransportRootBase<EVENTS> { /** * Sync mode sending events * * @default false */ sync?: Readonly<boolean>; /** * Method for sending an event to listeners. * If the transport was destroyed, * or no one is subscribed to this event, the method will do nothing. * * If there are subscribers to *, * they will listen to all events that were forwarded. * * The method works in 2 modes: synchronous and asynchronous (asynchronous mode is enabled by default). * To change this, you need to pass the 3rd argument. * * @example * ```ts * type Events = { event: string, event_empty: undefined }; * const transport = createTransport<Events>(); * * transport.on('event', (event, payload) => console.log(payload)); * transport.on('event_empty', (event, payload) => console.log(payload)); * transport.on('*', (event, payload) => console.log(payload)); * * transport.send('event', 'test'); * transport.send('event_empty'); * transport.send('event_empty', undefined); * ``` */ send< TYPE extends string & keyof EVENTS, PARAMETERS extends EVENTS[TYPE] extends undefined ? (payload?: EVENTS[TYPE]) => void : (payload: EVENTS[TYPE]) => void, >( type: TYPE, ...other: Parameters<PARAMETERS> ): void; /** * Method for getting a node that has only subscription interfaces (on/once/off). * Recommended for use in public API services to hide methods * for direct control of transport state from the outside. */ asReadonly(): TransportReadonlyNode<EVENTS>; } declare type TransportRootBase<EVENTS extends EventLike> = TransportRootSubscribers<EVENTS> & BaseTransportRoot & { /** * Transport lifecycle event bus. You can subscribe to 3 events: * 1) destroy - the transport was cleared. After that, it stops functioning and all data in it is cleared. * 2) subscribe - subscribed to some event. The object indicates what event was subscribed to and whether it is the first. * 3) unsubscribe - unsubscribed from some event. The object indicates what event was unsubscribed from and whether there are more subscribers. * * When the main transport is destroyed, the lifecycle event bus also dies. * * @example * ```ts * const transport = createTransport<Events>(); * * transport.lifecycle.on('destroy', () => console.log('transport is destroy')); * transport.lifecycle.on('subscribe', ({ event, isFirstSubscribe }) => console.log(`subscribe to event ${event} isFirst=${isFirstSubscribe}`)); * transport.lifecycle.on('unubscribe', ({ event, isHasSubscribers }) => console.log(`unsubscribe from event ${event} isHasSubscribers=${isHasSubscribers}`)); * * const unsubscriber1 = transport.on('event1', () => {}) // subscribe to event event1 isFirst=true * const unsubscriber2 = transport.on('event1', () => {}) // subscribe to event event1 isFirst=false * const unsubscriber3 = transport.on('event2', () => {}) // subscribe to event event2 isFirst=true * * unsubscriber3() // unsubscribe from event event2 isHasSubscribers=false * unsubscriber2() // unsubscribe from event event1 isHasSubscribers=true * unsubscriber1() // unsubscribe from event event1 isHasSubscribers=false * * transport.destroy(); // transport is destroy * ``` */ lifecycle: Readonly< BaseEventBusReadonly<TransportLifecycleEvents<EVENTS>> >; }; /** * List of nodes the node is subscribed to. */ declare type TransportRootNodes = Record<Namespace, Array<TransportRoot<any>>>; declare interface TransportRootSubscribers<EVENTS extends EventLike> { /** * Method for subscribing to bus events. * In addition to events of the type, you can also specify the * event, * which will allow you to subscribe to all bus events. * The method returns a function for unsubscribing the callback * (this can also be done via the off or removeEventListener methods). * * If the onSubscribe lifecycle method is passed, * it will be called when this event is sent. * * If the transport was destroyed, this method will do nothing. * * @example * ```ts * type Events = { event: string }; * const transport = createTransport<Events>(); * * transport.on('event', (event, payload) => console.log(payload)); * const unsubscriber = transport.on('*', (event, payload) => console.log(payload)); * unsubscriber(); * * transport.send('event', 'test'); * ``` */ on< EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes), EVENT extends EVENT_TYPE extends AllEventTypes ? string & keyof EVENTS : EVENT_TYPE, CB extends { [TYPE in EVENT]: [TYPE, EVENTS[TYPE]]; }, >( event: EVENT_TYPE, callback: (...args: CB[EVENT]) => void, ): Unsubscriber; /** * A method for one-time subscription to bus events. * In addition to events of the type, you can also specify an event *, * which will allow you to subscribe to all bus events. * The method returns a function for unsubscribing the callback * (this can also be done via the off or removeEventListener methods). * * If the onSubscribe lifecycle method is passed, * it will be called when this event is sent. * * If the transport was destroyed, this method will do nothing. * * @example * ```ts * type Events = { event: string }; * const transport = createTransport<Events>(); * * transport.once('event', (event, payload) => console.log(payload)); * const unsubscriber = transport.once('*', (event, payload) => console.log(payload)); * unsubscriber(); * * transport.send('event', 'test'); * transport.send('event', 'test'); // not call subscribers * ``` */ once< EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes), EVENT extends EVENT_TYPE extends AllEventTypes ? string & keyof EVENTS : EVENT_TYPE, CB extends { [TYPE in EVENT]: [TYPE, EVENTS[TYPE]]; }, >( event: EVENT_TYPE, callback: (...args: CB[EVENT]) => void, ): Unsubscriber; /** * unsubscribe from an event. * If there are no subscribers left for the event, we remove it from the map. * * If the onUnsubscribe lifecycle callback is passed, * it will be called each time this function is called. * * If the transport was destroyed, the method does not work. * * @example * ```ts * type Events = { event: string }; * const transport = createTransport<Events>(); * * function handler(type: string, payload: string): void {} * * transport.on('event', handler); * transport.off('event', handler); * ``` */ off<EVENT_TYPE extends string & (keyof EVENTS | AllEventTypes)>( event: EVENT_TYPE, callback: (...args: any[]) => void, ): void; } /** * unsubscribe function to unsubscribe from an event. */ declare type Unsubscriber = () => void; /** * Utility type for getting namespace from event name (max size 5 namespaces) * * @example * * UtilsTypeFilterTypesWithNamespaces<'namespace1:event', 'event'> // 'namespace1' * UtilsTypeFilterTypesWithNamespaces<'namespace1:namespace2:event', 'event'> // 'namespace1:namespace2' * UtilsTypeFilterTypesWithNamespaces<'namespace1:namespace2:namespace3:event', 'event'> // 'namespace1:namespace2:namespace3' */ declare type UtilsTypeFilterTypesWithNamespaces< STR extends string, TYPE extends string, > = STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${infer NAMESPACE_4}:${infer NAMESPACE_5}:${TYPE}` ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}:${NAMESPACE_4}:${NAMESPACE_5}` : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${infer NAMESPACE_4}:${TYPE}` ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}:${NAMESPACE_4}` : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${infer NAMESPACE_3}:${TYPE}` ? `${NAMESPACE_1}:${NAMESPACE_2}:${NAMESPACE_3}` : STR extends `${infer NAMESPACE_1}:${infer NAMESPACE_2}:${TYPE}` ? `${NAMESPACE_1}:${NAMESPACE_2}` : STR extends `${infer NAMESPACE}:${TYPE}` ? `${NAMESPACE}` : never; /** * Utility type of extraction from event name type without namespace * * @example * UtilsTypeRemoveNamespaceFromType<'namespace:event', 'namespace'> // 'event' */ declare type UtilsTypeRemoveNamespaceFromType< NAMESPACED_TYPE extends string, NAMESPACE extends string, > = NAMESPACED_TYPE extends `${NAMESPACE}:${infer TYPE}` ? TYPE : never; type VideoElementPoolSettingsVideoSource = Exclude<MediaType, 'audio'>; type VideoElementPoolSettingsPausedSources = { [key in VideoElementPoolSettingsVideoSource]?: boolean; }; type VideoElementPoolSettingsEvents = { pausedAllVideoChange: { isPaused: boolean; }; pauseVideSourcesChange: { pausedVideoSources: VideoElementPoolSettingsPausedSources; }; }; /** * Сервис для работы с общими настройками * возможности запускать видео элементы * * Применяется как глобально, так и для * videoElementPoolForRoom */ type VideoElementPoolSettingsService = { events: TransportReadonlyNode<VideoElementPoolSettingsEvents>; isPausedAllVideo: Atom<boolean>; getIsPausedAllVideo: () => boolean; /** * Остановка или запуск видео элементов. */ setPauseAllVideo: (isPaused: boolean) => void; pausedVideoSources: Atom<VideoElementPoolSettingsPausedSources>; getPausedVideoSources: () => VideoElementPoolSettingsPausedSources; /** * Остановка или запуск видео элементов нужного типа. * Влияет на видео элементы в videoElementPoolForRoom */ playVideoSources: ( sources: | VideoElementPoolSettingsVideoSource | VideoElementPoolSettingsVideoSource[], ) => void; pauseVideoSources: ( sources: | VideoElementPoolSettingsVideoSource | VideoElementPoolSettingsVideoSource[], ) => void; }; type VideoElementPoolStreamSize = { width: number; height: number; }; type VideoElementPoolGlobalEvents = VideoElementPoolSettingsEvents; type VideoElementPoolGlobalElementEvents = { pausedChange: { isPaused: boolean; }; /** * стрим загружен в видео элемент * и готов для отображения на странице */ isLoadingStreamChange: { isLoading: boolean; }; streamUpdate: { stream: MediaStream | null | undefined; }; }; type VideoElementPoolGlobalElementForStream = { videoElement: HTMLVideoElement; /** * Находится ли элемент на паузе. * Элемент может быть приостановлен, если: * 1) stream с активным MediaStreamTrack не установлен * 2) Элемент не отображается, если включена настройка `stopElementsForStreamIfNotInteractionEnabled`. * 3) Все видеоролики остановлены (см метод `changePauseAllVideo`) * и включена настройка `allVideoPauseForStreamElementsEnabled`. */ isPaused: Atom<boolean>; /** * Зависит от того, загружен ли поток в элемент video. * Когда значение свойства становится равным true, элемент будет готов к отображению видео. * * При обновление стрима в элементе значение сбрасывается и по новой ждет, * когда произойдет загрузка */ isLoadedStream: Atom<boolean>; /** * mediaStream, который сейчас используется * для всех видео элементов */ stream: Atom<MediaStream | null | undefined>; /** * размер видео стрима. Если стрима нет или видео замьючено, * то вычисления не происходит и значения высоты и ширины * равны 0 */ streamSize: Atom<VideoElementPoolStreamSize>; on: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['on']; once: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['once']; off: TransportReadonlyNode<VideoElementPoolGlobalElementEvents>['off']; /** * метод для обновления MediaStream в элементе */ setStream: (stream: MediaStream | null | undefined) => void; /** * метод для ручного запуска видео элемента * не запуститься, если это запрещено политиками (см свойство isPaused) */ play: () => void; /** * метод для ручной остановки видео элемента */ pause: () => void; /** * метод для высвобождения видео элемента * и выполнения отписки всех методов * * Если видео элемент больше не нужен, то обязателен к выполнению */ release: () => Promise<void>; }; type VideoElementPoolGlobalSettings = { stream?: MediaStream | null | undefined; videoElementProps?: Partial<HTMLVideoElement>; /** * необходимо ли вычислять размер видео стрима * Будет работать, если: * 1) фича включена (флаг streamSizeCheckEnabled) * 2) видео элемент виден * 3) есть видео стрим с живым видео треком * 4) видео элемент не стоит на паузе * * @default false */ watchStreamSize?: boolean; /** * необходимо ли отслеживать, что элемент виден на странице * * @default VideoElementPoolSettings['stopElementsForStreamIfNotInteractionEnabled'] */ watchIsVisible?: boolean; }; /** * Сервис для работы с видео элементами вне конференций */ type VideoElementPoolGlobalService = { on: TransportReadonlyNode<VideoElementPoolGlobalEvents>['on']; once: TransportReadonlyNode<VideoElementPoolGlobalEvents>['once']; off: TransportReadonlyNode<VideoElementPoolGlobalEvents>['off']; isPausedAllVideo: VideoElementPoolSettingsService['isPausedAllVideo']; getIsPausedAllVideo: VideoElementPoolSettingsService['getIsPausedAllVideo']; /** * Остановка или запуск всех видео элементов в приложение. * Распространяется на все videoElementPoolForRoom. * Все видео элементы ставятся на паузу и из них выгружаются стримы * При обратном запуске работа видео элементов нормализуется * * Имеет более высокий приоритет над настройкой для всех videoElementPoolForRoom * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom * запущено, то все видео будут остановлены * * Влияет на свойство isPaused у каждого элемента */ setPauseAllVideo: VideoElementPoolSettingsService['setPauseAllVideo']; pausedVideoSources: VideoElementPoolSettingsService['pausedVideoSources']; getPausedVideoSources: VideoElementPoolSettingsService['getPausedVideoSources']; /** * Остановка или запуск всех видео данного videoSource в приложение. * Распространяется на все videoElementPoolForRoom. * Все видео элементы данного типа ставятся на паузу и из них выгружаются стримы * При обратном запуске работа видео элементов нормализуется * * Имеет более высокий приоритет над настройкой для всех videoElementPoolForRoom * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom * запущено, то все видео будут остановлены * * Влияет на свойство isPaused у каждого элемента */ playVideoSources: VideoElementPoolSettingsService['playVideoSources']; pauseVideoSources: VideoElementPoolSettingsService['pauseVideoSources']; /** * Метод получения видео элемента для ручного подставления в него стрима * и отслеживания его состояния */ getVideoElementForStream: ( settings?: VideoElementPoolGlobalSettings, ) => VideoElementPoolGlobalElementForStream; }; type VideoSizeSettings = { width: number; height: number; }; type Source = Exclude<MediaType, 'audio'>; type RequestVideoElement = { /** * тип используемого видео */ source: Source; /** * необходимо ли следить за размером видео элемента * по умолчанию считается, что параметр включен * * Если параметр `true`, то если элемент виден, то * к нему подключается resizeObserver. Как только элемент * перестает быть видим - происходим отписка. * Каждый раз, когда меняется размер отправляется запрос * в плагин displayEndpoints * * Если элемент имеет статичные размеры и они точно не изменятся * то можно выключить это свойство для экономии ресурсов * * А также если нужно управлять размером запрашиваемого видео * в ручном режиме через `setUsageVideoSize` * * @default true */ watchResize?: boolean; /** * игнорирование глобального занижения * запрашиваемого качества * * @default false */ ignoreMaxVideoSize?: boolean; }; type DisplayEndpointsEvents = { setMaxRequestVideoSizeAllVideos: { settings: VideoSizeSettings; }; clearMaxRequestVideoSizeAllVideos: undefined; }; /** * Сервис для взаимодействия с реальными displayEndpoints * * В зону ответственности входит отслеживание видимости * и размеров видео элементов, а также запущены они или нет * и в зависимости от этих знаний дергает нужные ручки * * Элемент будет зарегистрирован в реальных displayEndpoints * только если он виден на странице и не стоит на паузе */ type DisplayEndpointsService = { events: TransportReadonlyNode<DisplayEndpointsEvents>; registerElement: ( participantId: JazzRoomParticipantId, request: RequestVideoElement, element: HTMLVideoElement, isMountedInDOMPromise: Promise<void>, ) => void; unregisterElement: (element: HTMLVideoElement) => void; /** * Метод для ручной активации элемента. Используется если у элемента нет стрима, * напр. с подписками в Next. */ activateElement: (element: HTMLVideoElement) => void; deactivateElement: (element: HTMLVideoElement) => void; /** * Метод отписки подписчикоков на resize и intersection */ unsubscribeWatchObserver: (element: HTMLVideoElement) => void; /** * метод отправки в displayEndpoints plugin * обновленных размеров, при этом эти значения не сохраняются * и если передан watResize=true, то при обновление размера * видео элемента эти значения будут затерты */ setUsageVideoSize: ( element: HTMLVideoElement, settings?: VideoSizeSettings, ) => void; /** * жестко устанавливает размер видео (если он виден) * и отключает resizeObserver для этого элемента * * если передать undefined данная политика отключается */ setUsageVideoSizeWithSaving: ( element: HTMLVideoElement, settings: VideoSizeSettings | undefined, ) => void; maxVideoSizeAllVideos: Atom<VideoSizeSettings | undefined>; getMaxVideoSizeAllVideos: () => VideoSizeSettings | undefined; /** * Метод установки верхней планки всех запрашиваемых видео * Нужно для случаев, когда нужно ограничить качество всех видео на странице * * Для игнорирования этой настройки можно передать параметр `ignoreMaxVideoSize=true` * при регистрации видео элемента */ setMaxVideoSizeAllVideos: (videoSize: VideoSizeSettings | undefined) => void; setWatchResize: (element: HTMLVideoElement, isWatch: boolean) => void; clearAllTimers: () => void; }; type RoomVideoElementsVideoEvents = { /** * событие любого обновления стрима в комнтае * то есть (addTrack, removeTrack, trackMuteChange and etc.) * * потенциально есть возможность, * что трек будет unmute, но стрим будет пустой */ trackUpdated: { stream: MediaStream | null | undefined; isMuted: boolean; isPaused: boolean; }; /** * событие остановки видео элемента * см `videoElementPoolForRoom.setPauseAllVideo` * и `videoElementPoolForRoom.playVideoSources`. */ elementsPausedChanged: { stream: MediaStream | null | undefined; isMuted: boolean; isPaused: boolean; }; }; type VideoElementPoolForRoomEvents = VideoElementPoolSettingsEvents & DisplayEndpointsEvents; type VideoElementPoolForRoomVideoSource = Exclude<MediaType, 'audio'>; type VideoElementPoolForRoomVideoSizeSettings = { width: number; height: number; }; type VideoElementPoolForRoomElementEvents = RoomVideoElementsVideoEvents; type VideoElementPoolForRoomElement = { /** * HTMLVideoElement для встройки на страницу * механиками videoElementPool в него будет * подставляться актуальный mediaStream * а также он будет запускаться и останавливаться */ videoElement: HTMLVideoElement; /** * замьючен ли videoSource */ isMuted: Atom<boolean>; /** * стоит ли данный videoSource на паузе * по дефолту состояние повторяет isMuted * * но при размьюченном видео может быть false, если: * 1) все видео элементы стоят на паузе * см `setPauseAllVideo` * данное поведение можно игнорировать * с помощью настройки `ignoreAllVideoPause` * * 2) данный videoSource отключен * см `playVideoSources` * данное поведение можно игнорировать * с помощью настройки `ignoreSourcePause` */ isPaused: Atom<boolean>; /** * mediaStream, который сейчас используется * для всех видео элементов */ stream: Atom<MediaStream | null | undefined>; /** * размер видео стрима. Если стрима нет или видео замьючено, * то вычисления не происходит и значения высоты и ширины * равны 0 */ streamSize: Atom<VideoElementPoolStreamSize>; /** * videoSource. Используется в качестве атома * для удобства взаимодействия и сохранения * единого стиля данных */ source: Atom<VideoElementPoolForRoomVideoSource>; on: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['on']; once: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['once']; off: TransportReadonlyNode<VideoElementPoolForRoomElementEvents>['off']; /** * установка размера запрашиваемого видео * при этом происходит остановка отслеживания размера видео элемента * * для отмены этого поведения нужно передать undefined * или это произойдет автоматически≤ если высота или ширина будут равны 0 */ setVideoSize: ( settings: VideoElementPoolForRoomVideoSizeSettings | undefined, ) => void; /** * динамический запуск или остановка отслеживания размера видео элемента * отслеживание работает только если видео элемент виден на странице * и он не остановлен (см свойство `isPaused`) */ setIsWatchResize: (isWatch: boolean) => void; /** * не рекомендуется к использованию * * Ручной запуск видео элемента * * Метод работает только если данный videoSource * не стоит на паузе и все видео не стоят на паузе * или если есть соответсвующие настройки игнорирования */ play: () => void; /** * не рекомендуется к использованию * * ручная остановка видео элемента */ pause: () => void; release: () => void; }; type VideoElementPoolForRoomRequestVideoElement = { source: VideoElementPoolForRoomVideoSource; /** * игнорирование остановки всех видео элементов * см `setPauseAllVideo` * * @default false */ ignoreAllVideoPause?: boolean; /** * игнорирование остановки конкретного типа видео * см `pauseVideoSources` * * @default false */ ignoreVideoSourcePause?: boolean; /** * игнорирование верхней планки запрашиваемого видео * см `setMaxVideoSizeAllVideos` * * @default false */ ignoreMaxVideoSize?: boolean; /** * нужно ли отслеживать изменение размера видео элемента * Отслеживание работает только если видео элемент виден * и не стоит на паузе * * @default true */ watchResize?: boolean; /** * HTMLVideoElement props, которые можно установить сразу * при инициализации видео элемента */ videoElementProps?: Partial<HTMLVideoElement>; /** * Нужно ли дожидаться, когда видео будет смонтировано в DOM * @default true */ waitForMountingInDOM?: boolean; }; /** * Сервис для работы с видео элементами в комнате */ type VideoElementPoolForRoomService = { on: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['on']; once: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['once']; off: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['off']; isPausedAllVideo: VideoElementPoolSettingsService['isPausedAllVideo']; getIsPausedAllVideo: VideoElementPoolSettingsService['getIsPausedAllVideo']; /** * Остановка или запуск всех видео элементов в комнате. * Все видео элементы ставятся на паузу и из них выгружаются стримы * При обратном запуске работа видео элементов нормализуется * * Не касается элементов с настройкой `ignoreAllVideoPause` * Имеет более низкий приоритет над настройкой для всего videoElementPool * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom * запущено, то все видео будут остановлены * * Влияет на свойство isPaused у каждого элемента * * Если стрим у видео элемента остановлен (isMute), то запуск его не коснется */ setPauseAllVideo: VideoElementPoolSettingsService['setPauseAllVideo']; pausedVideoSources: VideoElementPoolSettingsService['pausedVideoSources']; getPausedVideoSources: VideoElementPoolSettingsService['getPausedVideoSources']; /** * Остановка или запуск всех видео данного videoSource в комнате. * Все видео элементы данного типа ставятся на паузу и из них выгружаются стримы * При обратном запуске работа видео элементов нормализуется * * Не касается элементов с настройкой `ignoreVideoSourcePause` * Имеет более низкий приоритет над настройкой для всего videoElementPool * т.е. если если глобально видео остановлены, а в videoElementPoolForRoom * запущено, то все видео будут остановлены * * Влияет на свойство isPaused у каждого элемента * * Если стрим у видео элемента остановлен (isMute), то запуск его не коснется */ pauseVideoSources: VideoElementPoolSettingsService['pauseVideoSources']; playVideoSources: VideoElementPoolSettingsService['playVideoSources']; maxVideoSizeAllVideos: DisplayEndpointsService['maxVideoSizeAllVideos']; getMaxVideoSizeAllVideos: DisplayEndpointsService['getMaxVideoSizeAllVideos']; /** * Метод установки верхней планки всех запрашиваемых видео * Нужно для случаев, когда нужно ограничить качество всех видео в комнате * * Для игнорирования этой настройки можно передать параметр `ignoreMaxVideoSize` * при получение видео элемента */ setMaxVideoSizeAllVideos: DisplayEndpointsService['setMaxVideoSizeAllVideos']; /** * метод получения видео элемента в videoElementPool * со всеми методами взаимодействия и отслеживания его состояния * * Для конфигурации поведения используйте настройки плагина */ getVideoElement: ( participantId: JazzRoomParticipantId, request: VideoElementPoolForRoomRequestVideoElement, ) => VideoElementPoolForRoomElement; /** * release all elements * not recommended */ release: () => void; }; type VideoElementPoolFlags = ConfigFlags<{ /** * If the video in the conference could not start immediately, * we try to lower and raise it back. * This helps in cases where packet loss has occurred. * * @default true */ videoLossEnabled: boolean; /** * @default 5_000 ms */ videoLossTimeout: number; /** * Allow the function of stopping all video elements * created outside the conference for a separate video stream. * * @default false */ allVideoPauseForStreamElementsEnabled: boolean; /** * When stopping all videos in videoElementPool, do not disable local video elements. * * @default true */ ignoreLocalVideoForPauseAllVideoEnabled: boolean; /** * When stopping a video type in videoElementPool, do not disable the local video element. * * @default true */ ignoreLocalVideoForPauseVideoSourceEnabled: boolean; /** * Allow the video of an element created using getVideoElementForStream * to stop if the element is out of sight * * @default true */ stopElementsForStreamIfNotInteractionEnabled: boolean; /** * Delayed release of the element. * It is necessary that when re-rendering the application, * the deletion and creation of an element with all registrations does not occur. * If the item is "released" and requested back faster than the release occurs, * then it will be reused * * @default 2_000 ms */ delayReleaseVideoElement: number; /** * If the video element is not visible, then an unsubscription from displayEndpoints * will be delayed to avoid "flashing video" during re-renders and other cases * when the element disappears and appears for a short time. * * If rendering occurs, then the minimum time value between * the flag delayReleaseVideoElement and delayStopRequestVideoIfNotInteraction is taken. * * In order for the unsubscription to occur instantly, you need to specify the value 0. * * @default 10_000 ms */ delayStopRequestVideoIfNotInteraction: number; /** * If you need to track the size of the video stream. * The `getVideoElement` method returns the streamSize property, * in which once every N seconds (regulated by the `streamSizeCheckInterval` flag) * the value of the video stream size will be calculated and placed. * * @default false */ streamSizeCheckEnabled: boolean; /** * The interval for checking the size of the video stream. * If you specify 0, it will be equivalent to passing the `streamSizeCheckEnabled` parameter false. * * @default 1_000 ms */ streamSizeCheckInterval: number; /** * Enabling the feature of limiting the number of requested videos in high quality. * If a video in full HD is requested for more than the `maxQualitySubscribersLimit` flag, * its quality is lowered to 0 size (unsunscribe). * When the queue is released, their quality is restored to the originally requested one. * * @default true */ maxQualitySubscribersLimitEnabled: boolean; /** * The maximum n