UNPKG

@salutejs/jazz-sdk-web-plugins

Version:

Jazz SDK plugins

1,609 lines (1,559 loc) 159 kB
import { Atom, Signal, Scope } from 'nrgy'; import { EventError, EventOk, SyncResult, HttpClientFetchError, HttpClientResponseError, JazzSdkPlugin, JazzRoom, JazzClient, QueryAtom, Query, ConfigFlags, JazzRoomParticipantId, LocalAudioOutputDevice, JazzSdk, MediaType, JazzRoomVideoRequest, JazzRoomVideoQuality, JazzRoomVideoSource, LogLevel, LogEvent, } from '@salutejs/jazz-sdk-web'; import { Controller } from 'rx-effects'; import { Observable } from 'rxjs'; import { toQuery, toAction } from 'nrgy/rx-effects'; import { observe } from 'nrgy/rxjs'; 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' >; /** * Symbols used internally within ts-pattern to construct and discriminate * Guard, Not, and Select, and AnonymousSelect patterns * * Symbols have the advantage of not appearing in auto-complete suggestions in * user defined patterns, and eliminate the risk of property * overlap between ts-pattern internals and user defined patterns. * * These symbols have to be visible to tsc for type inference to work, but * users should not import them * @module * @private * @internal */ declare const toExclude: unique symbol; type toExclude = typeof toExclude; declare const matcher: unique symbol; type matcher = typeof matcher; type ValueOf<a> = a extends any[] ? a[number] : a[keyof a]; type Cast<a, b> = a extends b ? a : never; type BuiltInObjects = | Function | Date | RegExp | Generator | { readonly [Symbol.toStringTag]: string; } | any[]; type IsPlainObject<o, excludeUnion = BuiltInObjects> = o extends object ? o extends string | excludeUnion ? false : true : false; type Compute<a extends any> = a extends BuiltInObjects ? a : { [k in keyof a]: a[k]; }; type Primitives = | number | boolean | string | undefined | null | symbol | bigint; type None = { type: 'none'; }; type Some<key extends string> = { type: 'some'; key: key; }; type SelectionType = None | Some<string>; type MatcherType = | 'not' | 'optional' | 'or' | 'and' | 'array' | 'select' | 'default'; type MatcherProtocol< input, narrowed, matcherType extends MatcherType, selections extends SelectionType, excluded, > = { match: <I>(value: I | input) => MatchResult; getSelectionKeys?: () => string[]; matcherType?: matcherType; }; type MatchResult = { matched: boolean; selections?: Record<string, any>; }; /** * A `Matcher` is an object implementing the match * protocol. It must define a `symbols.matcher` property * which returns an object with a `match()` method, taking * the input value and returning whether the pattern matches * or not, along with optional selections. */ interface Matcher< input, narrowed, matcherType extends MatcherType = 'default', selections extends SelectionType = None, excluded = narrowed, > { [matcher](): MatcherProtocol< input, narrowed, matcherType, selections, excluded >; } type UnknownMatcher = Matcher<unknown, unknown, any, any>; type OptionalP<input, p> = Matcher<input, p, 'optional'>; type GuardP<input, narrowed> = Matcher<input, narrowed>; interface ToExclude<a> { [toExclude]: a; } type UnknownPattern = | readonly [] | readonly [UnknownPattern, ...UnknownPattern[]] | { readonly [k: string]: UnknownPattern; } | Set<UnknownPattern> | Map<unknown, UnknownPattern> | Primitives | UnknownMatcher; /** * `Pattern<a>` is the generic type for patterns matching a value of type `a`. A pattern can be any (nested) javascript value. * * They can also be wildcards, like `P._`, `P.string`, `P.number`, * or other matchers, like `P.when(predicate)`, `P.not(pattern)`, etc. * * [Read documentation for `P.Pattern` on GitHub](https://github.com/gvergnaud/ts-pattern#patterns) * * @example * const pattern: P.Pattern<User> = { name: P.string } */ type Pattern<a> = | Matcher<a, unknown, any, any> | (a extends Primitives ? a : unknown extends a ? UnknownPattern : a extends readonly (infer i)[] ? a extends readonly [any, ...any] ? { readonly [index in keyof a]: Pattern<a[index]>; } : readonly [] | readonly [Pattern<i>, ...Pattern<i>[]] : a extends Map<infer k, infer v> ? Map<k, Pattern<v>> : a extends Set<infer v> ? Set<Pattern<v>> : a extends object ? { readonly [k in keyof a]?: Pattern<Exclude<a[k], undefined>>; } : a); type OptionalKeys<p> = ValueOf<{ [k in keyof p]: p[k] extends Matcher<any, any, infer matcherType> ? matcherType extends 'optional' ? k : never : never; }>; type ReduceUnion<tuple extends any[], output = never> = tuple extends readonly [ infer p, ...infer tail, ] ? ReduceUnion<tail, output | InvertPattern<p>> : output; type ReduceIntersection< tuple extends any[], output = unknown, > = tuple extends readonly [infer p, ...infer tail] ? ReduceIntersection<tail, output & InvertPattern<p>> : output; /** * ### InvertPattern * Since patterns have special wildcard values, we need a way * to transform a pattern into the type of value it represents */ type InvertPattern<p> = p extends Matcher<infer input, infer narrowed, infer matcherType, any> ? { not: ToExclude<InvertPattern<narrowed>>; select: InvertPattern<narrowed>; array: InvertPattern<narrowed>[]; optional: InvertPattern<narrowed> | undefined; and: ReduceIntersection<Cast<narrowed, any[]>>; or: ReduceUnion<Cast<narrowed, any[]>>; default: [narrowed] extends [never] ? input : narrowed; }[matcherType] : p extends Primitives ? p : p extends readonly (infer pp)[] ? p extends readonly [infer p1, infer p2, infer p3, infer p4, infer p5] ? [ InvertPattern<p1>, InvertPattern<p2>, InvertPattern<p3>, InvertPattern<p4>, InvertPattern<p5>, ] : p extends readonly [infer p1, infer p2, infer p3, infer p4] ? [ InvertPattern<p1>, InvertPattern<p2>, InvertPattern<p3>, InvertPattern<p4>, ] : p extends readonly [infer p1, infer p2, infer p3] ? [InvertPattern<p1>, InvertPattern<p2>, InvertPattern<p3>] : p extends readonly [infer p1, infer p2] ? [InvertPattern<p1>, InvertPattern<p2>] : p extends readonly [infer p1] ? [InvertPattern<p1>] : p extends readonly [] ? [] : InvertPattern<pp>[] : p extends Map<infer pk, infer pv> ? Map<pk, InvertPattern<pv>> : p extends Set<infer pv> ? Set<InvertPattern<pv>> : IsPlainObject<p> extends true ? OptionalKeys<p> extends infer optKeys ? [optKeys] extends [never] ? { [k in Exclude<keyof p, optKeys>]: InvertPattern<p[k]>; } : Compute< { [k in Exclude<keyof p, optKeys>]: InvertPattern<p[k]>; } & { [k in Cast<optKeys, keyof p>]?: InvertPattern<p[k]>; } > : never : p; type PInfer<p extends Pattern<any>> = InvertPattern<p>; declare const getVideoStoragePattern: { usedMb: GuardP<unknown, number>; availableMb: OptionalP<unknown, GuardP<unknown, number>>; capacityMb: GuardP<unknown, 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; }; }; type GetVideoResponse = VideoRecord; type GetVideoStorageResponse = PInfer<typeof getVideoStoragePattern>; 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: ( jazzRoom: JazzRoom, ) => ServerRecordingRoomService; declare const getServerRecordingClient: ( jazzRoom: JazzRoom | JazzClient, ) => RoomRecordingServiceClient; 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: Query<boolean>; isReady: Query<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: Query<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<{ loopbackEnabled: boolean; loopbackMaxChromiumVersion: number; loopbackRestartOnDisconnect: boolean; loopbackMungeCandidates: boolean; loopbackStunServer?: string; }>; type AudioTrack = { participantId: JazzRoomParticipantId; mediaStream: MediaStream; }; type AudioMixerParticipantsManager = Controller<{ $mutedParticipants: Query<ReadonlySet<JazzRoomParticipantId>>; addTracks: (tracks: AudioTrack[]) => void; removeTracks: (tracks: AudioTrack[]) => void; removeAllTracks: () => void; muteParticipants: ( isMuted: boolean, ids: Array<JazzRoomParticipantId>, ) => void; }>; type AudioOutputMixer = { $isSuspended: Query<boolean>; event$: Observable<AudioMixerEvent>; startAudio: () => Promise<void>; stopAudio: () => Promise<void>; setOutputDevice: (audioOutput: LocalAudioOutputDevice) => Promise<void>; outputGain: Query<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>; }>; 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: Query<ReadonlySet<JazzRoomParticipantId>>; isMutedAll: Query<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; 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>; } declare interface DestroyedNode { 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>> >; }; 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; }; }; /** * @deprecated please use on/once/off API */ type VideoElementPoolSettingsEventsDeprecated = | { type: 'pausedAllVideoChange'; payload: { isPaused: boolean; }; } | { type: 'pauseVideSourcesChange'; payload: { pausedVideoSources: VideoElementPoolSettingsPausedSources; }; }; /** * Сервис для работы с общими настройками * возможности запускать видео элементы * * Применяется как глобально, так и для * videoElementPoolForRoom */ type VideoElementPoolSettingsService = { eventsSignal: Signal<VideoElementPoolSettingsEventsDeprecated>; 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 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; }; /** * @deprecated please use on/once/off API */ type DisplayEndpointsEventsDeprecated = | { type: 'setMaxRequestVideoSizeAllVideos'; payload: { settings: VideoSizeSettings; }; } | { type: 'clearMaxRequestVideoSizeAllVideos'; }; /** * Сервис для взаимодействия с реальными displayEndpoints * * В зону ответственности входит отслеживание видимости * и размеров видео элементов, а также запущены они или нет * и в зависимости от этих знаний дергает нужные ручки * * Элемент будет зарегистрирован в реальных displayEndpoints * только если он виден на странице и не стоит на паузе */ type DisplayEndpointsService = { events: TransportReadonlyNode<DisplayEndpointsEvents>; registerElement: ( participantId: JazzRoomParticipantId, request: RequestVideoElement, element: HTMLVideoElement, ) => void; unregisterElement: (element: HTMLVideoElement) => void; /** * Метод для ручной активации элемента. Используется если у элемента нет стрима, * напр. с подписками в Next. */ activateElement: (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; }; 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 640x480. * When the queue is released, their quality is restored to the originally requested one. * * @default true */ maxQualitySubscribersLimitEnabled: boolean; /** * The maximum number of videos requested in full HD quality. * It works when the maxQualitySubscribersLimitEnabled flag is enabled. * If more videos are requested, their quality is lowered to 640x480. * * @default 16 */ maxQualitySubscribersLimit: number; /** * the mode of operation of the plugin * * if you select `scoped`, then each room will have its own isolated version * * if `singleton` is selected, only 1 instance will be created, * which will be used for all rooms and all video elements will fall into it. * It may be useful if the plugin is used in child windows of the electron application. * * It is `recommended` to use the `scoped` mode * @default scoped */ mode: 'scoped' | 'singleton'; }>; 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; }; }; /** * @deprecated please use on/once/off new API */ type RoomVideoElementsVideoEventsDeprecated$1 = /** * событие любого обновления стрима в комнтае * то есть (addTrack, removeTrack, trackMuteChange and etc.) * * потенциально есть возможность, * что трек будет unmute, но стрим будет пустой */ | { type: 'trackUpdated'; payload: { stream: MediaStream | null | undefined; isMuted: boolean; isPaused: boolean; }; } /** * событие остановки видео элемента * см `videoElementPoolForRoom.setPauseAllVideo` * и `videoElementPoolForRoom.playVideoSources`. */ | { type: 'elementsPausedChanged'; payload: { stream: MediaStream | null | undefined; isMuted: boolean; isPaused: boolean; }; }; type VideoElementPoolSettings = VideoElementPoolFlags; type VideoElementPoolPluginOptions = Partial<VideoElementPoolSettings>; type VideoElementPoolForRoomEvents = VideoElementPoolSettingsEvents & DisplayEndpointsEvents; /** * @deprecated please use on/once/off new API */ type VideoElementPoolForRoomEventsDeprecated = VideoElementPoolSettingsEventsDeprecated & DisplayEndpointsEventsDeprecated; type VideoElementPoolForRoomVideoSource = Exclude<MediaType, 'audio'>; type VideoElementPoolForRoomVideoSizeSettings = { width: number; height: number; }; type VideoElementPoolForRoomElementEvents = RoomVideoElementsVideoEvents; /** * @deprecated use on/once/off new API */ type VideoElementPoolForRoomElementEventsDeprecated = RoomVideoElementsVideoEventsDeprecated$1; 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>; /** * @deprecated use on/once/off new API */ events: Signal<VideoElementPoolForRoomElementEventsDeprecated>; 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>; }; /** * Сервис для работы с видео элементами в комнате */ type VideoElementPoolForRoomService = { /** * @deprecated use on/once/off new API */ events: Signal<VideoElementPoolForRoomEventsDeprecated>; on: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['on']; once: SubscribeReadonlyNode<VideoElementPoolForRoomEvents>['once'];