diff --git a/packages/client/openapitools.json b/packages/client/openapitools.json index 0f02b59fc3..3a7a5b4db4 100644 --- a/packages/client/openapitools.json +++ b/packages/client/openapitools.json @@ -2,6 +2,6 @@ "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { - "version": "7.5.0" + "version": "7.8.0" } } diff --git a/packages/client/src/Call.ts b/packages/client/src/Call.ts index 7b51a12680..e753ec87ca 100644 --- a/packages/client/src/Call.ts +++ b/packages/client/src/Call.ts @@ -54,12 +54,14 @@ import type { SendCallEventResponse, SendReactionRequest, SendReactionResponse, + StartClosedCaptionsResponse, StartHLSBroadcastingResponse, StartRecordingRequest, StartRecordingResponse, StartTranscriptionRequest, StartTranscriptionResponse, StatsOptions, + StopClosedCaptionsResponse, StopHLSBroadcastingResponse, StopLiveResponse, StopRecordingResponse, @@ -74,13 +76,14 @@ import type { UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse, - VideoResolution, + VideoDimension, } from './gen/coordinator'; import { OwnCapability } from './gen/coordinator'; import { AudioTrackType, CallConstructor, CallLeaveOptions, + ClosedCaptionsSettings, JoinCallData, PublishOptions, TrackMuteType, @@ -545,6 +548,7 @@ export class Call { this.dynascaleManager.setSfuClient(undefined); this.state.setCallingState(CallingState.LEFT); + this.state.dispose(); // Call all leave call hooks, e.g. to clean up global event handlers this.leaveCallHooks.forEach((hook) => hook()); @@ -1713,7 +1717,48 @@ export class Call { }; /** - * Sends a `call.permission_request` event to all users connected to the call. The call settings object contains infomration about which permissions can be requested during a call (for example a user might be allowed to request permission to publish audio, but not video). + * Starts the closed captions of the call. + */ + startClosedCaptions = async (): Promise => { + const trx = this.state.setCaptioning(true); // optimistic update + try { + return await this.streamClient.post( + `${this.streamClientBasePath}/start_closed_captions`, + ); + } catch (err) { + trx.rollback(); // revert the optimistic update + throw err; + } + }; + + /** + * Stops the closed captions of the call. + */ + stopClosedCaptions = async (): Promise => { + const trx = this.state.setCaptioning(false); // optimistic update + try { + return await this.streamClient.post( + `${this.streamClientBasePath}/stop_closed_captions`, + ); + } catch (err) { + trx.rollback(); // revert the optimistic update + throw err; + } + }; + + /** + * Updates the closed caption settings. + * + * @param config the closed caption settings to apply + */ + updateClosedCaptionSettings = (config: Partial) => { + this.state.updateClosedCaptionSettings(config); + }; + + /** + * Sends a `call.permission_request` event to all users connected to the call. + * The call settings object contains information about which permissions can be requested during a call + * (for example, a user might be allowed to request permission to publish audio, but not video). */ requestPermissions = async ( data: RequestPermissionRequest, @@ -2311,7 +2356,7 @@ export class Call { * preference has effect on. Affects all participants by default. */ setPreferredIncomingVideoResolution = ( - resolution: VideoResolution | undefined, + resolution: VideoDimension | undefined, sessionIds?: string[], ) => { this.dynascaleManager.setVideoTrackSubscriptionOverrides( diff --git a/packages/client/src/gen/coordinator/index.ts b/packages/client/src/gen/coordinator/index.ts index db4af83134..57fa3ec743 100644 --- a/packages/client/src/gen/coordinator/index.ts +++ b/packages/client/src/gen/coordinator/index.ts @@ -48,15 +48,21 @@ export interface APIError { * @memberof APIError */ more_info: string; + /** + * Flag that indicates if the error is unrecoverable, requests that return unrecoverable errors should not be retried, this error only applies to the request that caused it + * @type {boolean} + * @memberof APIError + */ + unrecoverable?: boolean; } /** - * + * AcceptCallResponse is the payload for accepting a call. * @export * @interface AcceptCallResponse */ export interface AcceptCallResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof AcceptCallResponse */ @@ -252,7 +258,7 @@ export type BlockListOptionsBehaviorEnum = (typeof BlockListOptionsBehaviorEnum)[keyof typeof BlockListOptionsBehaviorEnum]; /** - * + * BlockUserRequest is the payload for blocking a user. * @export * @interface BlockUserRequest */ @@ -265,7 +271,7 @@ export interface BlockUserRequest { user_id: string; } /** - * + * BlockUserResponse is the payload for blocking a user. * @export * @interface BlockUserResponse */ @@ -335,7 +341,7 @@ export interface BroadcastSettingsRequest { hls?: HLSSettingsRequest; } /** - * + * BroadcastSettingsResponse is the payload for broadcasting settings * @export * @interface BroadcastSettingsResponse */ @@ -420,6 +426,87 @@ export interface CallClosedCaption { * @memberof CallClosedCaption */ text: string; + /** + * + * @type {UserResponse} + * @memberof CallClosedCaption + */ + user: UserResponse; +} +/** + * This event is sent when call closed captions has failed + * @export + * @interface CallClosedCaptionsFailedEvent + */ +export interface CallClosedCaptionsFailedEvent { + /** + * + * @type {string} + * @memberof CallClosedCaptionsFailedEvent + */ + call_cid: string; + /** + * + * @type {string} + * @memberof CallClosedCaptionsFailedEvent + */ + created_at: string; + /** + * The type of event: "call.closed_captions_failed" in this case + * @type {string} + * @memberof CallClosedCaptionsFailedEvent + */ + type: string; +} +/** + * This event is sent when call closed caption has started + * @export + * @interface CallClosedCaptionsStartedEvent + */ +export interface CallClosedCaptionsStartedEvent { + /** + * + * @type {string} + * @memberof CallClosedCaptionsStartedEvent + */ + call_cid: string; + /** + * + * @type {string} + * @memberof CallClosedCaptionsStartedEvent + */ + created_at: string; + /** + * The type of event: "call.closed_captions_started" in this case + * @type {string} + * @memberof CallClosedCaptionsStartedEvent + */ + type: string; +} +/** + * This event is sent when call closed captions has stopped + * @export + * @interface CallClosedCaptionsStoppedEvent + */ +export interface CallClosedCaptionsStoppedEvent { + /** + * + * @type {string} + * @memberof CallClosedCaptionsStoppedEvent + */ + call_cid: string; + /** + * + * @type {string} + * @memberof CallClosedCaptionsStoppedEvent + */ + created_at: string; + /** + * The type of event: "call.transcription_stopped" in this case + * @type {string} + * @memberof CallClosedCaptionsStoppedEvent + */ + type: string; } /** * This event is sent when a call is created. Clients receiving this event should check if the ringing @@ -533,6 +620,18 @@ export interface CallEndedEvent { * @interface CallEvent */ export interface CallEvent { + /** + * + * @type {string} + * @memberof CallEvent + */ + category?: string; + /** + * + * @type {string} + * @memberof CallEvent + */ + component?: string; /** * * @type {string} @@ -545,6 +644,24 @@ export interface CallEvent { * @memberof CallEvent */ end_timestamp: number; + /** + * + * @type {boolean} + * @memberof CallEvent + */ + internal: boolean; + /** + * + * @type {Array} + * @memberof CallEvent + */ + issue_tags?: Array; + /** + * + * @type {string} + * @memberof CallEvent + */ + kind: string; /** * * @type {number} @@ -646,7 +763,7 @@ export interface CallHLSBroadcastingStoppedEvent { type: string; } /** - * + * CallIngressResponse is the payload for ingress settings * @export * @interface CallIngressResponse */ @@ -1190,7 +1307,7 @@ export interface CallRejectedEvent { user: UserResponse; } /** - * + * CallRequest is the payload for creating a call. * @export * @interface CallRequest */ @@ -1250,6 +1367,12 @@ export interface CallResponse { * @memberof CallResponse */ blocked_user_ids: Array; + /** + * + * @type {boolean} + * @memberof CallResponse + */ + captioning: boolean; /** * The unique identifier for a call (:) * @type {string} @@ -1457,7 +1580,6 @@ export interface CallSessionEndedEvent { */ type: string; } - /** * This event is sent when the participant counts in a call session are updated * @export @@ -1838,7 +1960,7 @@ export interface CallSettingsResponse { video: VideoSettingsResponse; } /** - * + * CallStateResponseFields is the payload for call state response * @export * @interface CallStateResponseFields */ @@ -2252,10 +2374,10 @@ export interface ChannelConfigWithInfo { partition_size?: number; /** * - * @type {number} + * @type {string} * @memberof ChannelConfigWithInfo */ - partition_ttl?: number; + partition_ttl?: string | null; /** * * @type {boolean} @@ -2304,6 +2426,12 @@ export interface ChannelConfigWithInfo { * @memberof ChannelConfigWithInfo */ search: boolean; + /** + * + * @type {boolean} + * @memberof ChannelConfigWithInfo + */ + skip_last_msg_update_for_system_msgs: boolean; /** * * @type {boolean} @@ -2370,29 +2498,41 @@ export type ChannelConfigWithInfoBlocklistBehaviorEnum = */ export interface ChannelMember { /** - * Expiration date of the ban + * + * @type {string} + * @memberof ChannelMember + */ + archived_at?: string; + /** + * * @type {string} * @memberof ChannelMember */ ban_expires?: string; /** - * Whether member is banned this channel or not + * * @type {boolean} * @memberof ChannelMember */ banned: boolean; /** - * Role of the member in the channel + * * @type {string} * @memberof ChannelMember */ channel_role: string; /** - * Date/time of creation + * * @type {string} * @memberof ChannelMember */ created_at: string; + /** + * + * @type {{ [key: string]: any; }} + * @memberof ChannelMember + */ + custom: { [key: string]: any }; /** * * @type {string} @@ -2400,25 +2540,25 @@ export interface ChannelMember { */ deleted_at?: string; /** - * Date when invite was accepted + * * @type {string} * @memberof ChannelMember */ invite_accepted_at?: string; /** - * Date when invite was rejected + * * @type {string} * @memberof ChannelMember */ invite_rejected_at?: string; /** - * Whether member was invited or not + * * @type {boolean} * @memberof ChannelMember */ invited?: boolean; /** - * Whether member is channel moderator or not + * * @type {boolean} * @memberof ChannelMember */ @@ -2430,7 +2570,13 @@ export interface ChannelMember { */ notifications_muted: boolean; /** - * Whether member is shadow banned in this channel or not + * + * @type {string} + * @memberof ChannelMember + */ + pinned_at?: string; + /** + * * @type {boolean} * @memberof ChannelMember */ @@ -2442,7 +2588,7 @@ export interface ChannelMember { */ status?: string; /** - * Date/time of the last update + * * @type {string} * @memberof ChannelMember */ @@ -2492,11 +2638,57 @@ export interface ChannelMute { updated_at: string; /** * - * @type {UserObject} + * @type {UserResponse} * @memberof ChannelMute */ - user?: UserObject; + user?: UserResponse; } + +/** + * All possibility of string to use + * @export + */ +export const ChannelOwnCapability = { + BAN_CHANNEL_MEMBERS: 'ban-channel-members', + CAST_POLL_VOTE: 'cast-poll-vote', + CONNECT_EVENTS: 'connect-events', + CREATE_ATTACHMENT: 'create-attachment', + CREATE_CALL: 'create-call', + DELETE_ANY_MESSAGE: 'delete-any-message', + DELETE_CHANNEL: 'delete-channel', + DELETE_OWN_MESSAGE: 'delete-own-message', + FLAG_MESSAGE: 'flag-message', + FREEZE_CHANNEL: 'freeze-channel', + JOIN_CALL: 'join-call', + JOIN_CHANNEL: 'join-channel', + LEAVE_CHANNEL: 'leave-channel', + MUTE_CHANNEL: 'mute-channel', + PIN_MESSAGE: 'pin-message', + QUERY_POLL_VOTES: 'query-poll-votes', + QUOTE_MESSAGE: 'quote-message', + READ_EVENTS: 'read-events', + SEARCH_MESSAGES: 'search-messages', + SEND_CUSTOM_EVENTS: 'send-custom-events', + SEND_LINKS: 'send-links', + SEND_MESSAGE: 'send-message', + SEND_POLL: 'send-poll', + SEND_REACTION: 'send-reaction', + SEND_REPLY: 'send-reply', + SEND_TYPING_EVENTS: 'send-typing-events', + SET_CHANNEL_COOLDOWN: 'set-channel-cooldown', + SKIP_SLOW_MODE: 'skip-slow-mode', + SLOW_MODE: 'slow-mode', + TYPING_EVENTS: 'typing-events', + UPDATE_ANY_MESSAGE: 'update-any-message', + UPDATE_CHANNEL: 'update-channel', + UPDATE_CHANNEL_MEMBERS: 'update-channel-members', + UPDATE_OWN_MESSAGE: 'update-own-message', + UPDATE_THREAD: 'update-thread', + UPLOAD_FILE: 'upload-file', +} as const; +export type ChannelOwnCapability = + (typeof ChannelOwnCapability)[keyof typeof ChannelOwnCapability]; + /** * Represents channel in chat * @export @@ -2515,6 +2707,12 @@ export interface ChannelResponse { * @memberof ChannelResponse */ auto_translation_language?: string; + /** + * Whether this channel is blocked by current user or not + * @type {boolean} + * @memberof ChannelResponse + */ + blocked?: boolean; /** * Channel CID (:) * @type {string} @@ -2541,12 +2739,12 @@ export interface ChannelResponse { created_at: string; /** * - * @type {UserObject} + * @type {UserResponse} * @memberof ChannelResponse */ - created_by?: UserObject; + created_by?: UserResponse; /** - * + * Custom data for this object * @type {{ [key: string]: any; }} * @memberof ChannelResponse */ @@ -2619,10 +2817,10 @@ export interface ChannelResponse { muted?: boolean; /** * List of channel capabilities of authenticated user - * @type {Array} + * @type {Array} * @memberof ChannelResponse */ - own_capabilities?: Array; + own_capabilities?: Array; /** * Team the channel belongs to (multi-tenant only) * @type {string} @@ -2637,10 +2835,10 @@ export interface ChannelResponse { truncated_at?: string; /** * - * @type {UserObject} + * @type {UserResponse} * @memberof ChannelResponse */ - truncated_by?: UserObject; + truncated_by?: UserResponse; /** * Type of the channel * @type {string} @@ -2729,7 +2927,7 @@ export interface CollectUserFeedbackRequest { user_session_id: string; } /** - * + * Basic response information * @export * @interface CollectUserFeedbackResponse */ @@ -2828,10 +3026,10 @@ export interface ConnectUserDetailsRequest { name?: string; /** * - * @type {PrivacySettings} + * @type {PrivacySettingsResponse} * @memberof ConnectUserDetailsRequest */ - privacy_settings?: PrivacySettings; + privacy_settings?: PrivacySettingsResponse; /** * * @type {PushNotificationSettingsInput} @@ -2921,7 +3119,7 @@ export interface Coordinates { longitude: number; } /** - * + * Create device request * @export * @interface CreateDeviceRequest */ @@ -3065,83 +3263,121 @@ export interface CustomVideoEvent { user: UserResponse; } /** - * + * DeleteCallRequest is the payload for deleting a call. + * @export + * @interface DeleteCallRequest + */ +export interface DeleteCallRequest { + /** + * if true the call will be hard deleted along with all related data + * @type {boolean} + * @memberof DeleteCallRequest + */ + hard?: boolean; +} +/** + * DeleteCallResponse is the payload for deleting a call. + * @export + * @interface DeleteCallResponse + */ +export interface DeleteCallResponse { + /** + * + * @type {CallResponse} + * @memberof DeleteCallResponse + */ + call: CallResponse; + /** + * + * @type {string} + * @memberof DeleteCallResponse + */ + duration: string; + /** + * + * @type {string} + * @memberof DeleteCallResponse + */ + task_id?: string; +} +/** + * Response for DeleteRecording * @export * @interface DeleteRecordingResponse */ export interface DeleteRecordingResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof DeleteRecordingResponse */ duration: string; } /** - * + * DeleteTranscriptionResponse is the payload for deleting a transcription. * @export * @interface DeleteTranscriptionResponse */ export interface DeleteTranscriptionResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof DeleteTranscriptionResponse */ duration: string; } /** - * + * Response for Device * @export - * @interface Device + * @interface DeviceResponse */ -export interface Device { +export interface DeviceResponse { /** * Date/time of creation * @type {string} - * @memberof Device + * @memberof DeviceResponse */ created_at: string; /** * Whether device is disabled or not * @type {boolean} - * @memberof Device + * @memberof DeviceResponse */ disabled?: boolean; /** * Reason explaining why device had been disabled * @type {string} - * @memberof Device + * @memberof DeviceResponse */ disabled_reason?: string; /** * Device ID * @type {string} - * @memberof Device + * @memberof DeviceResponse */ id: string; /** * Push provider * @type {string} - * @memberof Device + * @memberof DeviceResponse */ push_provider: string; /** * Push provider name * @type {string} - * @memberof Device + * @memberof DeviceResponse */ push_provider_name?: string; /** * User ID * @type {string} - * @memberof Device + * @memberof DeviceResponse */ user_id: string; /** * When true the token is for Apple VoIP push notifications * @type {boolean} - * @memberof Device + * @memberof DeviceResponse */ voip?: boolean; } @@ -3242,13 +3478,19 @@ export interface EgressRTMPResponse { * @type {string} * @memberof EgressRTMPResponse */ - stream_key: string; + started_at: string; /** * * @type {string} * @memberof EgressRTMPResponse */ - url: string; + stream_key?: string; + /** + * + * @type {string} + * @memberof EgressRTMPResponse + */ + stream_url?: string; } /** * @@ -3276,13 +3518,13 @@ export interface EgressResponse { rtmps: Array; } /** - * + * Response for ending a call * @export * @interface EndCallResponse */ export interface EndCallResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof EndCallResponse */ @@ -3419,7 +3661,7 @@ export interface GetCallResponse { own_capabilities: Array; } /** - * + * Basic response information * @export * @interface GetCallStatsResponse */ @@ -3450,16 +3692,16 @@ export interface GetCallStatsResponse { duration: string; /** * - * @type {Stats} + * @type {TimeStats} * @memberof GetCallStatsResponse */ - jitter?: Stats; + jitter?: TimeStats; /** * - * @type {Stats} + * @type {TimeStats} * @memberof GetCallStatsResponse */ - latency?: Stats; + latency?: TimeStats; /** * * @type {number} @@ -3510,7 +3752,7 @@ export interface GetCallStatsResponse { sfus: Array; } /** - * + * Basic response information * @export * @interface GetEdgesResponse */ @@ -3632,6 +3874,12 @@ export interface GoLiveRequest { * @memberof GoLiveRequest */ start_recording?: boolean; + /** + * + * @type {boolean} + * @memberof GoLiveRequest + */ + start_rtmp_broadcasts?: boolean; /** * * @type {boolean} @@ -3646,7 +3894,7 @@ export interface GoLiveRequest { transcription_storage_name?: string; } /** - * + * Basic response information * @export * @interface GoLiveResponse */ @@ -3690,7 +3938,7 @@ export interface HLSSettingsRequest { quality_tracks: Array; } /** - * + * HLSSettings is the payload for HLS settings * @export * @interface HLSSettingsResponse */ @@ -3725,7 +3973,7 @@ export interface HealthCheckEvent { * @type {string} * @memberof HealthCheckEvent */ - cid: string; + cid?: string; /** * * @type {string} @@ -3740,10 +3988,16 @@ export interface HealthCheckEvent { created_at: string; /** * - * @type {OwnUser} + * @type {OwnUserResponse} + * @memberof HealthCheckEvent + */ + me?: OwnUserResponse; + /** + * + * @type {string} * @memberof HealthCheckEvent */ - me?: OwnUser; + received_at?: string; /** * * @type {string} @@ -3893,13 +4147,13 @@ export interface JoinCallResponse { */ export interface LabelThresholds { /** - * Threshold for automatic message block + * * @type {number} * @memberof LabelThresholds */ block?: number; /** - * Threshold for automatic message flag + * * @type {number} * @memberof LabelThresholds */ @@ -3944,17 +4198,17 @@ export interface LimitsSettingsResponse { max_participants?: number; } /** - * + * List devices response * @export * @interface ListDevicesResponse */ export interface ListDevicesResponse { /** * List of devices - * @type {Array} + * @type {Array} * @memberof ListDevicesResponse */ - devices: Array; + devices: Array; /** * * @type {string} @@ -3963,7 +4217,7 @@ export interface ListDevicesResponse { duration: string; } /** - * + * Response for listing recordings * @export * @interface ListRecordingsResponse */ @@ -3994,7 +4248,7 @@ export interface ListTranscriptionsResponse { */ duration: string; /** - * + * List of transcriptions for the call * @type {Array} * @memberof ListTranscriptionsResponse */ @@ -4025,37 +4279,6 @@ export interface Location { */ subdivision_iso_code: string; } -/** - * - * @export - * @interface MOSStats - */ -export interface MOSStats { - /** - * - * @type {number} - * @memberof MOSStats - */ - average_score: number; - /** - * - * @type {Array} - * @memberof MOSStats - */ - histogram_duration_seconds: Array; - /** - * - * @type {number} - * @memberof MOSStats - */ - max_score: number; - /** - * - * @type {number} - * @memberof MOSStats - */ - min_score: number; -} /** * * @export @@ -4088,7 +4311,7 @@ export interface MediaPubSubHint { video_subscribed: boolean; } /** - * + * MemberRequest is the payload for adding a member to a call. * @export * @interface MemberRequest */ @@ -4113,7 +4336,7 @@ export interface MemberRequest { user_id: string; } /** - * + * MemberResponse is the payload for a member of a call. * @export * @interface MemberResponse */ @@ -4205,7 +4428,7 @@ export interface MuteUsersRequest { video?: boolean; } /** - * + * MuteUsersResponse is the response payload for the mute users endpoint. * @export * @interface MuteUsersResponse */ @@ -4303,9 +4526,11 @@ export const OwnCapability = { SEND_AUDIO: 'send-audio', SEND_VIDEO: 'send-video', START_BROADCAST_CALL: 'start-broadcast-call', + START_CLOSED_CAPTIONS_CALL: 'start-closed-captions-call', START_RECORD_CALL: 'start-record-call', START_TRANSCRIPTION_CALL: 'start-transcription-call', STOP_BROADCAST_CALL: 'stop-broadcast-call', + STOP_CLOSED_CAPTIONS_CALL: 'stop-closed-captions-call', STOP_RECORD_CALL: 'stop-record-call', STOP_TRANSCRIPTION_CALL: 'stop-transcription-call', UPDATE_CALL: 'update-call', @@ -4315,157 +4540,6 @@ export const OwnCapability = { } as const; export type OwnCapability = (typeof OwnCapability)[keyof typeof OwnCapability]; -/** - * - * @export - * @interface OwnUser - */ -export interface OwnUser { - /** - * - * @type {boolean} - * @memberof OwnUser - */ - banned: boolean; - /** - * - * @type {Array} - * @memberof OwnUser - */ - blocked_user_ids?: Array; - /** - * - * @type {Array} - * @memberof OwnUser - */ - channel_mutes: Array; - /** - * - * @type {string} - * @memberof OwnUser - */ - created_at: string; - /** - * - * @type {{ [key: string]: any; }} - * @memberof OwnUser - */ - custom: { [key: string]: any }; - /** - * - * @type {string} - * @memberof OwnUser - */ - deactivated_at?: string; - /** - * - * @type {string} - * @memberof OwnUser - */ - deleted_at?: string; - /** - * - * @type {Array} - * @memberof OwnUser - */ - devices: Array; - /** - * - * @type {string} - * @memberof OwnUser - */ - id: string; - /** - * - * @type {boolean} - * @memberof OwnUser - */ - invisible?: boolean; - /** - * - * @type {string} - * @memberof OwnUser - */ - language: string; - /** - * - * @type {string} - * @memberof OwnUser - */ - last_active?: string; - /** - * - * @type {Array} - * @memberof OwnUser - */ - latest_hidden_channels?: Array; - /** - * - * @type {Array} - * @memberof OwnUser - */ - mutes: Array; - /** - * - * @type {boolean} - * @memberof OwnUser - */ - online: boolean; - /** - * - * @type {PrivacySettings} - * @memberof OwnUser - */ - privacy_settings?: PrivacySettings; - /** - * - * @type {PushNotificationSettings} - * @memberof OwnUser - */ - push_notifications?: PushNotificationSettings; - /** - * - * @type {string} - * @memberof OwnUser - */ - role: string; - /** - * - * @type {Array} - * @memberof OwnUser - */ - teams?: Array; - /** - * - * @type {number} - * @memberof OwnUser - */ - total_unread_count: number; - /** - * - * @type {number} - * @memberof OwnUser - */ - unread_channels: number; - /** - * - * @type {number} - * @memberof OwnUser - */ - unread_count: number; - /** - * - * @type {number} - * @memberof OwnUser - */ - unread_threads: number; - /** - * - * @type {string} - * @memberof OwnUser - */ - updated_at: string; -} /** * * @export @@ -4516,10 +4590,10 @@ export interface OwnUserResponse { deleted_at?: string; /** * - * @type {Array} + * @type {Array} * @memberof OwnUserResponse */ - devices: Array; + devices: Array; /** * * @type {string} @@ -4616,6 +4690,12 @@ export interface OwnUserResponse { * @memberof OwnUserResponse */ unread_channels: number; + /** + * + * @type {number} + * @memberof OwnUserResponse + */ + unread_count: number; /** * * @type {number} @@ -4668,7 +4748,7 @@ export interface PermissionRequestEvent { user: UserResponse; } /** - * + * PinRequest is the payload for pinning a message. * @export * @interface PinRequest */ @@ -4687,7 +4767,7 @@ export interface PinRequest { user_id: string; } /** - * + * Basic response information * @export * @interface PinResponse */ @@ -4718,7 +4798,6 @@ export interface PrivacySettings { */ typing_indicators?: TypingIndicators; } - /** * * @export @@ -4727,16 +4806,16 @@ export interface PrivacySettings { export interface PrivacySettingsResponse { /** * - * @type {ReadReceipts} + * @type {ReadReceiptsResponse} * @memberof PrivacySettingsResponse */ - read_receipts?: ReadReceipts; + read_receipts?: ReadReceiptsResponse; /** * - * @type {TypingIndicators} + * @type {TypingIndicatorsResponse} * @memberof PrivacySettingsResponse */ - typing_indicators?: TypingIndicators; + typing_indicators?: TypingIndicatorsResponse; } /** * @@ -4820,7 +4899,6 @@ export interface PushNotificationSettingsResponse { */ disabled_until?: string; } - /** * * @export @@ -4871,7 +4949,7 @@ export interface QueryCallMembersRequest { type: string; } /** - * + * Basic response information * @export * @interface QueryCallMembersResponse */ @@ -4939,7 +5017,7 @@ export interface QueryCallStatsRequest { sort?: Array; } /** - * + * Basic response information * @export * @interface QueryCallStatsResponse */ @@ -5000,7 +5078,7 @@ export interface QueryCallsRequest { */ prev?: string; /** - * + * Array of sort parameters * @type {Array} * @memberof QueryCallsRequest */ @@ -5098,6 +5176,19 @@ export interface ReadReceipts { * @type {boolean} * @memberof ReadReceipts */ + enabled: boolean; +} +/** + * + * @export + * @interface ReadReceiptsResponse + */ +export interface ReadReceiptsResponse { + /** + * + * @type {boolean} + * @memberof ReadReceiptsResponse + */ enabled?: boolean; } /** @@ -5156,7 +5247,7 @@ export type RecordSettingsRequestQualityEnum = (typeof RecordSettingsRequestQualityEnum)[keyof typeof RecordSettingsRequestQualityEnum]; /** - * + * RecordSettings is the payload for recording settings * @export * @interface RecordSettingsResponse */ @@ -5200,7 +5291,7 @@ export interface RejectCallRequest { */ export interface RejectCallResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof RejectCallResponse */ @@ -5226,14 +5317,14 @@ export interface RequestPermissionRequest { */ export interface RequestPermissionResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof RequestPermissionResponse */ duration: string; } /** - * + * Basic response information * @export * @interface Response */ @@ -5402,7 +5493,7 @@ export interface ScreensharingSettingsResponse { target_resolution?: TargetResolution; } /** - * + * Send a call event to the other user * @export * @interface SendCallEventRequest */ @@ -5421,7 +5512,7 @@ export interface SendCallEventRequest { */ export interface SendCallEventResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof SendCallEventResponse */ @@ -5453,7 +5544,7 @@ export interface SendReactionRequest { type: string; } /** - * + * Basic response information * @export * @interface SendReactionResponse */ @@ -5493,17 +5584,30 @@ export interface SortParamRequest { /** * * @export + * @interface StartClosedCaptionsResponse + */ +export interface StartClosedCaptionsResponse { + /** + * + * @type {string} + * @memberof StartClosedCaptionsResponse + */ + duration: string; +} +/** + * StartHLSBroadcastingResponse is the payload for starting an HLS broadcasting. + * @export * @interface StartHLSBroadcastingResponse */ export interface StartHLSBroadcastingResponse { /** - * Duration of the request in milliseconds + * * @type {string} * @memberof StartHLSBroadcastingResponse */ duration: string; /** - * + * the URL of the HLS playlist * @type {string} * @memberof StartHLSBroadcastingResponse */ @@ -5523,13 +5627,13 @@ export interface StartRecordingRequest { recording_external_storage?: string; } /** - * + * StartRecordingResponse is the response payload for the start recording endpoint. * @export * @interface StartRecordingResponse */ export interface StartRecordingResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof StartRecordingResponse */ @@ -5555,7 +5659,7 @@ export interface StartTranscriptionRequest { */ export interface StartTranscriptionResponse { /** - * + * Duration of the request in milliseconds * @type {string} * @memberof StartTranscriptionResponse */ @@ -5564,37 +5668,31 @@ export interface StartTranscriptionResponse { /** * * @export - * @interface Stats + * @interface StatsOptions */ -export interface Stats { - /** - * - * @type {number} - * @memberof Stats - */ - average_seconds: number; +export interface StatsOptions { /** * * @type {number} - * @memberof Stats + * @memberof StatsOptions */ - max_seconds: number; + reporting_interval_ms: number; } /** - * + * Basic response information * @export - * @interface StatsOptions + * @interface StopClosedCaptionsResponse */ -export interface StatsOptions { +export interface StopClosedCaptionsResponse { /** - * - * @type {number} - * @memberof StatsOptions + * Duration of the request in milliseconds + * @type {string} + * @memberof StopClosedCaptionsResponse */ - reporting_interval_ms: number; + duration: string; } /** - * + * Basic response information * @export * @interface StopHLSBroadcastingResponse */ @@ -5619,14 +5717,14 @@ export interface StopLiveResponse { */ call: CallResponse; /** - * Duration of the request in milliseconds + * * @type {string} * @memberof StopLiveResponse */ duration: string; } /** - * + * Basic response information * @export * @interface StopRecordingResponse */ @@ -5639,7 +5737,7 @@ export interface StopRecordingResponse { duration: string; } /** - * + * Basic response information * @export * @interface StopTranscriptionResponse */ @@ -5708,7 +5806,7 @@ export interface TargetResolution { width: number; } /** - * Sets thresholds for AI moderation + * * @export * @interface Thresholds */ @@ -5771,6 +5869,25 @@ export interface ThumbnailsSettingsResponse { */ enabled: boolean; } +/** + * + * @export + * @interface TimeStats + */ +export interface TimeStats { + /** + * + * @type {number} + * @memberof TimeStats + */ + average_seconds: number; + /** + * + * @type {number} + * @memberof TimeStats + */ + max_seconds: number; +} /** * * @export @@ -5782,7 +5899,7 @@ export interface TranscriptionSettingsRequest { * @type {string} * @memberof TranscriptionSettingsRequest */ - closed_caption_mode?: string; + closed_caption_mode?: TranscriptionSettingsRequestClosedCaptionModeEnum; /** * * @type {Array} @@ -5797,6 +5914,17 @@ export interface TranscriptionSettingsRequest { mode: TranscriptionSettingsRequestModeEnum; } +/** + * @export + */ +export const TranscriptionSettingsRequestClosedCaptionModeEnum = { + AVAILABLE: 'available', + DISABLED: 'disabled', + AUTO_ON: 'auto-on', +} as const; +export type TranscriptionSettingsRequestClosedCaptionModeEnum = + (typeof TranscriptionSettingsRequestClosedCaptionModeEnum)[keyof typeof TranscriptionSettingsRequestClosedCaptionModeEnum]; + /** * @export */ @@ -5819,7 +5947,7 @@ export interface TranscriptionSettingsResponse { * @type {string} * @memberof TranscriptionSettingsResponse */ - closed_caption_mode: string; + closed_caption_mode: TranscriptionSettingsResponseClosedCaptionModeEnum; /** * * @type {Array} @@ -5834,6 +5962,17 @@ export interface TranscriptionSettingsResponse { mode: TranscriptionSettingsResponseModeEnum; } +/** + * @export + */ +export const TranscriptionSettingsResponseClosedCaptionModeEnum = { + AVAILABLE: 'available', + DISABLED: 'disabled', + AUTO_ON: 'auto-on', +} as const; +export type TranscriptionSettingsResponseClosedCaptionModeEnum = + (typeof TranscriptionSettingsResponseClosedCaptionModeEnum)[keyof typeof TranscriptionSettingsResponseClosedCaptionModeEnum]; + /** * @export */ @@ -5856,11 +5995,24 @@ export interface TypingIndicators { * @type {boolean} * @memberof TypingIndicators */ - enabled?: boolean; + enabled: boolean; } /** * * @export + * @interface TypingIndicatorsResponse + */ +export interface TypingIndicatorsResponse { + /** + * + * @type {boolean} + * @memberof TypingIndicatorsResponse + */ + enabled?: boolean; +} +/** + * UnblockUserRequest is the payload for unblocking a user. + * @export * @interface UnblockUserRequest */ export interface UnblockUserRequest { @@ -5872,7 +6024,7 @@ export interface UnblockUserRequest { user_id: string; } /** - * + * UnblockUserResponse is the payload for unblocking a user. * @export * @interface UnblockUserResponse */ @@ -5917,7 +6069,7 @@ export interface UnblockedUserEvent { user: UserResponse; } /** - * + * UnpinRequest is the payload for unpinning a message. * @export * @interface UnpinRequest */ @@ -5936,7 +6088,7 @@ export interface UnpinRequest { user_id: string; } /** - * + * UnpinResponse is the payload for unpinning a message. * @export * @interface UnpinResponse */ @@ -5949,7 +6101,7 @@ export interface UnpinResponse { duration: string; } /** - * + * Update call members * @export * @interface UpdateCallMembersRequest */ @@ -5968,7 +6120,7 @@ export interface UpdateCallMembersRequest { update_members?: Array; } /** - * + * Basic response information * @export * @interface UpdateCallMembersResponse */ @@ -5987,7 +6139,7 @@ export interface UpdateCallMembersResponse { members: Array; } /** - * + * Request for updating a call * @export * @interface UpdateCallRequest */ @@ -6012,7 +6164,7 @@ export interface UpdateCallRequest { starts_at?: string; } /** - * Represents a call + * Response for updating a call * @export * @interface UpdateCallResponse */ @@ -6074,7 +6226,7 @@ export interface UpdateUserPermissionsRequest { user_id: string; } /** - * + * Basic response information * @export * @interface UpdateUserPermissionsResponse */ @@ -6126,13 +6278,128 @@ export interface UpdatedCallPermissionsEvent { /** * * @export - * @interface UserBannedEvent + * @interface User */ -export interface UserBannedEvent { +export interface UserObject { /** * * @type {string} - * @memberof UserBannedEvent + * @memberof User + */ + ban_expires?: string; + /** + * + * @type {boolean} + * @memberof User + */ + banned: boolean; + /** + * + * @type {string} + * @memberof User + */ + readonly created_at?: string; + /** + * + * @type {{ [key: string]: any; }} + * @memberof User + */ + custom: { [key: string]: any }; + /** + * + * @type {string} + * @memberof User + */ + readonly deactivated_at?: string; + /** + * + * @type {string} + * @memberof User + */ + readonly deleted_at?: string; + /** + * + * @type {string} + * @memberof User + */ + id: string; + /** + * + * @type {boolean} + * @memberof User + */ + invisible?: boolean; + /** + * + * @type {string} + * @memberof User + */ + language?: string; + /** + * + * @type {string} + * @memberof User + */ + readonly last_active?: string; + /** + * + * @type {string} + * @memberof User + */ + readonly last_engaged_at?: string; + /** + * + * @type {boolean} + * @memberof User + */ + readonly online: boolean; + /** + * + * @type {PrivacySettings} + * @memberof User + */ + privacy_settings?: PrivacySettings; + /** + * + * @type {PushNotificationSettings} + * @memberof User + */ + push_notifications?: PushNotificationSettings; + /** + * + * @type {string} + * @memberof User + */ + revoke_tokens_issued_before?: string; + /** + * + * @type {string} + * @memberof User + */ + role: string; + /** + * + * @type {Array} + * @memberof User + */ + teams?: Array; + /** + * + * @type {string} + * @memberof User + */ + readonly updated_at?: string; +} +/** + * + * @export + * @interface UserBannedEvent + */ +export interface UserBannedEvent { + /** + * + * @type {string} + * @memberof UserBannedEvent */ channel_id: string; /** @@ -6273,254 +6540,265 @@ export interface UserDeletedEvent { /** * * @export - * @interface UserInfoResponse + * @interface UserEventPayload */ -export interface UserInfoResponse { +export interface UserEventPayload { + /** + * + * @type {boolean} + * @memberof UserEventPayload + */ + banned: boolean; + /** + * + * @type {Array} + * @memberof UserEventPayload + */ + blocked_user_ids: Array; + /** + * + * @type {string} + * @memberof UserEventPayload + */ + created_at: string; /** * * @type {{ [key: string]: any; }} - * @memberof UserInfoResponse + * @memberof UserEventPayload */ custom: { [key: string]: any }; /** * * @type {string} - * @memberof UserInfoResponse + * @memberof UserEventPayload */ - image: string; + deactivated_at?: string; /** * * @type {string} - * @memberof UserInfoResponse + * @memberof UserEventPayload */ - name: string; + deleted_at?: string; /** * - * @type {Array} - * @memberof UserInfoResponse + * @type {string} + * @memberof UserEventPayload */ - roles: Array; -} -/** - * - * @export - * @interface UserMute - */ -export interface UserMute { + id: string; /** - * Date/time of creation + * * @type {string} - * @memberof UserMute + * @memberof UserEventPayload */ - created_at: string; + image?: string; /** - * Date/time of mute expiration + * + * @type {boolean} + * @memberof UserEventPayload + */ + invisible?: boolean; + /** + * * @type {string} - * @memberof UserMute + * @memberof UserEventPayload */ - expires?: string; + language: string; /** * - * @type {UserObject} - * @memberof UserMute + * @type {string} + * @memberof UserEventPayload */ - target?: UserObject; + last_active?: string; /** - * Date/time of the last update + * * @type {string} - * @memberof UserMute + * @memberof UserEventPayload */ - updated_at: string; + name?: string; /** * - * @type {UserObject} - * @memberof UserMute + * @type {boolean} + * @memberof UserEventPayload */ - user?: UserObject; -} -/** - * - * @export - * @interface UserMuteResponse - */ -export interface UserMuteResponse { + online: boolean; /** * - * @type {string} - * @memberof UserMuteResponse + * @type {PrivacySettingsResponse} + * @memberof UserEventPayload */ - created_at: string; + privacy_settings?: PrivacySettingsResponse; /** * * @type {string} - * @memberof UserMuteResponse + * @memberof UserEventPayload */ - expires?: string; + revoke_tokens_issued_before?: string; /** * - * @type {UserResponse} - * @memberof UserMuteResponse + * @type {string} + * @memberof UserEventPayload */ - target?: UserResponse; + role: string; /** * - * @type {string} - * @memberof UserMuteResponse + * @type {Array} + * @memberof UserEventPayload */ - updated_at: string; + teams: Array; /** * - * @type {UserResponse} - * @memberof UserMuteResponse + * @type {string} + * @memberof UserEventPayload */ - user?: UserResponse; + updated_at: string; } - /** * * @export - * @interface UserMutedEvent + * @interface UserFlaggedEvent */ -export interface UserMutedEvent { +export interface UserFlaggedEvent { /** * * @type {string} - * @memberof UserMutedEvent + * @memberof UserFlaggedEvent */ created_at: string; /** * * @type {string} - * @memberof UserMutedEvent + * @memberof UserFlaggedEvent */ target_user?: string; /** * * @type {Array} - * @memberof UserMutedEvent + * @memberof UserFlaggedEvent */ target_users?: Array; /** * * @type {string} - * @memberof UserMutedEvent + * @memberof UserFlaggedEvent */ type: string; /** * * @type {UserObject} - * @memberof UserMutedEvent + * @memberof UserFlaggedEvent */ user?: UserObject; } /** - * Represents chat user + * * @export - * @interface UserObject + * @interface UserInfoResponse */ -export interface UserObject { - /** - * Expiration date of the ban - * @type {string} - * @memberof UserObject - */ - ban_expires?: string; - /** - * Whether a user is banned or not - * @type {boolean} - * @memberof UserObject - */ - banned: boolean; - /** - * Date/time of creation - * @type {string} - * @memberof UserObject - */ - readonly created_at?: string; +export interface UserInfoResponse { /** * * @type {{ [key: string]: any; }} - * @memberof UserObject + * @memberof UserInfoResponse */ custom: { [key: string]: any }; /** - * Date of deactivation + * * @type {string} - * @memberof UserObject + * @memberof UserInfoResponse */ - readonly deactivated_at?: string; + id: string; /** - * Date/time of deletion + * * @type {string} - * @memberof UserObject + * @memberof UserInfoResponse */ - readonly deleted_at?: string; + image: string; /** - * Unique user identifier + * * @type {string} - * @memberof UserObject + * @memberof UserInfoResponse */ - id: string; + name: string; /** * - * @type {boolean} - * @memberof UserObject + * @type {Array} + * @memberof UserInfoResponse */ - invisible?: boolean; + roles: Array; +} +/** + * + * @export + * @interface UserMuteResponse + */ +export interface UserMuteResponse { /** - * Preferred language of a user + * * @type {string} - * @memberof UserObject + * @memberof UserMuteResponse */ - language?: string; + created_at: string; /** - * Date of last activity + * * @type {string} - * @memberof UserObject + * @memberof UserMuteResponse */ - readonly last_active?: string; + expires?: string; /** - * Whether a user online or not - * @type {boolean} - * @memberof UserObject + * + * @type {UserResponse} + * @memberof UserMuteResponse */ - readonly online: boolean; + target?: UserResponse; /** * - * @type {PrivacySettings} - * @memberof UserObject + * @type {string} + * @memberof UserMuteResponse */ - privacy_settings?: PrivacySettings; + updated_at: string; /** * - * @type {PushNotificationSettings} - * @memberof UserObject + * @type {UserResponse} + * @memberof UserMuteResponse */ - push_notifications?: PushNotificationSettings; + user?: UserResponse; +} +/** + * + * @export + * @interface UserMutedEvent + */ +export interface UserMutedEvent { /** - * Revocation date for tokens + * * @type {string} - * @memberof UserObject + * @memberof UserMutedEvent */ - revoke_tokens_issued_before?: string; + created_at: string; /** - * Determines the set of user permissions + * * @type {string} - * @memberof UserObject + * @memberof UserMutedEvent */ - role: string; + target_user?: string; /** - * List of teams user is a part of + * * @type {Array} - * @memberof UserObject + * @memberof UserMutedEvent */ - teams?: Array; + target_users?: Array; /** - * Date/time of the last update + * * @type {string} - * @memberof UserObject + * @memberof UserMutedEvent */ - readonly updated_at?: string; + type: string; + /** + * + * @type {UserObject} + * @memberof UserMutedEvent + */ + user?: UserObject; } /** * @@ -6573,7 +6851,7 @@ export interface UserReactivatedEvent { user?: UserObject; } /** - * + * User request object * @export * @interface UserRequest */ @@ -6616,10 +6894,10 @@ export interface UserRequest { name?: string; /** * - * @type {PrivacySettings} + * @type {PrivacySettingsResponse} * @memberof UserRequest */ - privacy_settings?: PrivacySettings; + privacy_settings?: PrivacySettingsResponse; /** * * @type {PushNotificationSettingsInput} @@ -6628,7 +6906,7 @@ export interface UserRequest { push_notifications?: PushNotificationSettingsInput; } /** - * + * User response object * @export * @interface UserResponse */ @@ -6792,16 +7070,22 @@ export interface UserSessionStats { geolocation?: GeolocationResult; /** * - * @type {Stats} + * @type {string} * @memberof UserSessionStats */ - jitter?: Stats; + group: string; /** * - * @type {Stats} + * @type {TimeStats} * @memberof UserSessionStats */ - latency?: Stats; + jitter?: TimeStats; + /** + * + * @type {TimeStats} + * @memberof UserSessionStats + */ + latency?: TimeStats; /** * * @type {number} @@ -6850,6 +7134,12 @@ export interface UserSessionStats { * @memberof UserSessionStats */ max_receiving_video_quality?: VideoQuality; + /** + * + * @type {number} + * @memberof UserSessionStats + */ + min_event_ts: number; /** * * @type {string} @@ -6882,22 +7172,16 @@ export interface UserSessionStats { published_tracks?: Array; /** * - * @type {MOSStats} + * @type {TimeStats} * @memberof UserSessionStats */ - publisher_audio_mos?: MOSStats; + publisher_jitter?: TimeStats; /** * - * @type {Stats} + * @type {TimeStats} * @memberof UserSessionStats */ - publisher_jitter?: Stats; - /** - * - * @type {Stats} - * @memberof UserSessionStats - */ - publisher_latency?: Stats; + publisher_latency?: TimeStats; /** * * @type {number} @@ -6986,22 +7270,16 @@ export interface UserSessionStats { session_id: string; /** * - * @type {MOSStats} - * @memberof UserSessionStats - */ - subscriber_audio_mos?: MOSStats; - /** - * - * @type {Stats} + * @type {TimeStats} * @memberof UserSessionStats */ - subscriber_jitter?: Stats; + subscriber_jitter?: TimeStats; /** * - * @type {Stats} + * @type {TimeStats} * @memberof UserSessionStats */ - subscriber_latency?: Stats; + subscriber_latency?: TimeStats; /** * * @type {number} @@ -7134,72 +7412,109 @@ export interface UserUnbannedEvent { /** * * @export - * @interface UserUpdatedEvent + * @interface UserUnmutedEvent */ -export interface UserUpdatedEvent { +export interface UserUnmutedEvent { /** * * @type {string} - * @memberof UserUpdatedEvent + * @memberof UserUnmutedEvent */ created_at: string; /** * * @type {string} - * @memberof UserUpdatedEvent + * @memberof UserUnmutedEvent */ - received_at?: string; + target_user?: string; + /** + * + * @type {Array} + * @memberof UserUnmutedEvent + */ + target_users?: Array; /** * * @type {string} - * @memberof UserUpdatedEvent + * @memberof UserUnmutedEvent */ type: string; /** * * @type {UserObject} - * @memberof UserUpdatedEvent + * @memberof UserUnmutedEvent */ user?: UserObject; } /** * * @export - * @interface VideoQuality + * @interface UserUpdatedEvent */ -export interface VideoQuality { +export interface UserUpdatedEvent { /** * - * @type {VideoResolution} - * @memberof VideoQuality + * @type {string} + * @memberof UserUpdatedEvent */ - resolution?: VideoResolution; + created_at: string; /** * * @type {string} - * @memberof VideoQuality + * @memberof UserUpdatedEvent */ - usage_type?: string; + received_at?: string; + /** + * + * @type {string} + * @memberof UserUpdatedEvent + */ + type: string; + /** + * + * @type {UserEventPayload} + * @memberof UserUpdatedEvent + */ + user: UserEventPayload; } /** * * @export - * @interface VideoResolution + * @interface VideoDimension */ -export interface VideoResolution { +export interface VideoDimension { /** * * @type {number} - * @memberof VideoResolution + * @memberof VideoDimension */ height: number; /** * * @type {number} - * @memberof VideoResolution + * @memberof VideoDimension */ width: number; } +/** + * + * @export + * @interface VideoQuality + */ +export interface VideoQuality { + /** + * + * @type {VideoDimension} + * @memberof VideoQuality + */ + resolution?: VideoDimension; + /** + * + * @type {string} + * @memberof VideoQuality + */ + usage_type?: string; +} /** * * @export @@ -7299,7 +7614,7 @@ export type VideoSettingsResponseCameraFacingEnum = (typeof VideoSettingsResponseCameraFacingEnum)[keyof typeof VideoSettingsResponseCameraFacingEnum]; /** - * + * Websocket auth message * @export * @interface WSAuthMessage */ @@ -7332,6 +7647,9 @@ export type WSEvent = | ({ type: 'call.accepted' } & CallAcceptedEvent) | ({ type: 'call.blocked_user' } & BlockedUserEvent) | ({ type: 'call.closed_caption' } & ClosedCaptionEvent) + | ({ type: 'call.closed_captions_failed' } & CallClosedCaptionsFailedEvent) + | ({ type: 'call.closed_captions_started' } & CallClosedCaptionsStartedEvent) + | ({ type: 'call.closed_captions_stopped' } & CallClosedCaptionsStoppedEvent) | ({ type: 'call.created' } & CallCreatedEvent) | ({ type: 'call.deleted' } & CallDeletedEvent) | ({ type: 'call.ended' } & CallEndedEvent) diff --git a/packages/client/src/store/CallState.ts b/packages/client/src/store/CallState.ts index 5a379387f8..0d65edbb74 100644 --- a/packages/client/src/store/CallState.ts +++ b/packages/client/src/store/CallState.ts @@ -9,6 +9,7 @@ import type { Patch } from './rxUtils'; import * as RxUtils from './rxUtils'; import { CallingState } from './CallingState'; import { + type ClosedCaptionsSettings, type StreamVideoParticipant, type StreamVideoParticipantPatch, type StreamVideoParticipantPatches, @@ -19,6 +20,7 @@ import { import { CallStatsReport } from '../stats'; import { BlockedUserEvent, + CallClosedCaption, CallHLSBroadcastingStartedEvent, CallIngressResponse, CallMemberAddedEvent, @@ -32,6 +34,7 @@ import { CallSessionParticipantLeftEvent, CallSessionResponse, CallSettingsResponse, + ClosedCaptionEvent, EgressResponse, MemberResponse, OwnCapability, @@ -97,6 +100,7 @@ export class CallState { CallSettingsResponse | undefined >(undefined); private transcribingSubject = new BehaviorSubject(false); + private captioningSubject = new BehaviorSubject(false); private endedBySubject = new BehaviorSubject( undefined, ); @@ -117,6 +121,7 @@ export class CallState { private callStatsReportSubject = new BehaviorSubject< CallStatsReport | undefined >(undefined); + private closedCaptionsSubject = new BehaviorSubject([]); // These are tracks that were delivered to the Subscriber's onTrack event // that we couldn't associate with a participant yet. @@ -275,6 +280,11 @@ export class CallState { */ transcribing$: Observable; + /** + * Will provide the closed captioning state of this call. + */ + captioning$: Observable; + /** * Will provide the user who ended this call. */ @@ -285,15 +295,24 @@ export class CallState { */ thumbnails$: Observable; + /** + * The queue of closed captions. + */ + closedCaptions$: Observable; + readonly logger = getLogger(['CallState']); /** * A list of comparators that are used to sort the participants. - * - * @private */ private sortParticipantsBy = defaultSortPreset; + /** + * The closed captions configuration. + */ + private closedCaptionsSettings: ClosedCaptionsSettings | undefined; + private closedCaptionsTasks = new Map(); + private readonly eventHandlers: { [EventType in WSEvent['type']]: | ((event: Extract) => void) @@ -357,6 +376,7 @@ export class CallState { this.settings$ = this.settingsSubject.asObservable(); this.endedBy$ = this.endedBySubject.asObservable(); this.thumbnails$ = this.thumbnailsSubject.asObservable(); + this.closedCaptions$ = this.closedCaptionsSubject.asObservable(); /** * Performs shallow comparison of two arrays. @@ -390,10 +410,10 @@ export class CallState { this.participantCount$ = duc(this.participantCountSubject); this.recording$ = duc(this.recordingSubject); this.transcribing$ = duc(this.transcribingSubject); + this.captioning$ = duc(this.captioningSubject); this.eventHandlers = { // these events are not updating the call state: - 'call.closed_caption': undefined, 'call.deleted': undefined, 'call.permission_request': undefined, 'call.recording_ready': undefined, @@ -415,6 +435,16 @@ export class CallState { // events that update call state: 'call.accepted': (e) => this.updateFromCallResponse(e.call), 'call.blocked_user': this.blockUser, + 'call.closed_caption': this.updateFromClosedCaptions, + 'call.closed_captions_failed': () => { + this.setCurrentValue(this.captioningSubject, false); + }, + 'call.closed_captions_started': () => { + this.setCurrentValue(this.captioningSubject, true); + }, + 'call.closed_captions_stopped': () => { + this.setCurrentValue(this.captioningSubject, false); + }, 'call.created': (e) => this.updateFromCallResponse(e.call), 'call.ended': (e) => { this.updateFromCallResponse(e.call); @@ -464,6 +494,16 @@ export class CallState { }; } + /** + * Runs the cleanup tasks. + */ + dispose = () => { + for (const [ccKey, taskId] of this.closedCaptionsTasks.entries()) { + clearTimeout(taskId); + this.closedCaptionsTasks.delete(ccKey); + } + }; + /** * Sets the list of criteria that are used to sort the participants. * To disable sorting, you can pass `noopComparator()`. @@ -533,6 +573,23 @@ export class CallState { return this.setCurrentValue(this.startedAtSubject, startedAt); }; + /** + * Returns whether closed captions are enabled in the current call. + */ + get captioning() { + return this.getCurrentValue(this.captioning$); + } + + /** + * Sets the closed captioning state of the current call. + * + * @internal + * @param captioning the closed captioning state. + */ + setCaptioning = (captioning: boolean) => { + return RxUtils.updateValue(this.captioningSubject, captioning); + }; + /** * The server-side counted number of anonymous participants connected to the current call. * This number includes the anonymous participants as well. @@ -792,6 +849,13 @@ export class CallState { return this.getCurrentValue(this.thumbnails$); } + /** + * Returns the current queue of closed captions. + */ + get closedCaptions() { + return this.getCurrentValue(this.closedCaptions$); + } + /** * Will try to find the participant with the given sessionId in the current call. * @@ -840,7 +904,6 @@ export class CallState { const thePatch = typeof patch === 'function' ? patch(participant) : patch; const updatedParticipant: StreamVideoParticipant = { - // FIXME OL: this is not a deep merge, we might want to revisit this ...participant, ...thePatch, }; @@ -912,7 +975,6 @@ export class CallState { * * @param trackType the kind of subscription to update. * @param changes the list of subscription changes to do. - * @param type the debounce type to use for the update. */ updateParticipantTracks = ( trackType: VideoTrackType, @@ -1040,6 +1102,15 @@ export class CallState { return orphans; }; + /** + * Updates the closed captions settings. + * + * @param config the new closed captions settings. + */ + updateClosedCaptionSettings = (config: Partial) => { + this.closedCaptionsSettings = { ...this.closedCaptionsSettings, ...config }; + }; + /** * Updates the call state with the data received from the server. * @@ -1066,6 +1137,7 @@ export class CallState { this.updateParticipantCountFromSession(s); this.setCurrentValue(this.settingsSubject, call.settings); this.setCurrentValue(this.transcribingSubject, call.transcribing); + this.setCurrentValue(this.captioningSubject, call.captioning); this.setCurrentValue(this.thumbnailsSubject, call.thumbnails); }; @@ -1299,4 +1371,42 @@ export class CallState { this.setCurrentValue(this.ownCapabilitiesSubject, event.own_capabilities); } }; + + private updateFromClosedCaptions = (event: ClosedCaptionEvent) => { + this.setCurrentValue(this.closedCaptionsSubject, (queue) => { + const { closed_caption } = event; + + const keyOf = (c: CallClosedCaption) => `${c.speaker_id}/${c.start_time}`; + const currentKey = keyOf(closed_caption); + + const duplicate = queue.some((caption) => keyOf(caption) === currentKey); + if (duplicate) return queue; + + const nextQueue = [...queue, closed_caption]; + + const { retentionTimeInMs = 2700, queueSize = 2 } = + this.closedCaptionsSettings || {}; + // schedule the removal of the closed caption after the retention time + if (retentionTimeInMs > 0) { + const taskId = setTimeout(() => { + this.setCurrentValue(this.closedCaptionsSubject, (captions) => + captions.filter((caption) => caption !== closed_caption), + ); + this.closedCaptionsTasks.delete(currentKey); + }, retentionTimeInMs); + this.closedCaptionsTasks.set(currentKey, taskId); + + // cancel the cleanup tasks for the closed captions that are no longer in the queue + for (let i = 0; i < nextQueue.length - queueSize; i++) { + const key = keyOf(nextQueue[i]); + const task = this.closedCaptionsTasks.get(key); + clearTimeout(task); + this.closedCaptionsTasks.delete(key); + } + } + + // trim the queue + return nextQueue.slice(-queueSize); + }); + }; } diff --git a/packages/client/src/store/__tests__/CallState.test.ts b/packages/client/src/store/__tests__/CallState.test.ts index 6d52a5ec39..9ec77c2a9d 100644 --- a/packages/client/src/store/__tests__/CallState.test.ts +++ b/packages/client/src/store/__tests__/CallState.test.ts @@ -967,4 +967,105 @@ describe('CallState', () => { expect(state['orphanedTracks'].length).toBe(0); }); }); + + describe('closed captions', () => { + it('should add closed captions to the queue', () => { + const state = new CallState(); + state.updateFromEvent({ + type: 'call.closed_caption', + // @ts-expect-error incomplete data + closed_caption: { + speaker_id: '123', + text: 'Hello world', + start_time: '2021-01-01T00:00:00.000Z', + end_time: '2021-01-01T00:02:00.000Z', + }, + }); + expect(state.closedCaptions.length).toBe(1); + }); + + it('should maintain predefined queue size', () => { + const state = new CallState(); + state.updateClosedCaptionSettings({ queueSize: 2 }); + for (let i = 0; i < 5; i++) { + state.updateFromEvent({ + type: 'call.closed_caption', + // @ts-expect-error incomplete data + closed_caption: { + speaker_id: `123-${i}`, + text: `Hello world ${i}`, + start_time: '2021-01-01T00:00:00.000Z', + end_time: '2021-01-01T00:02:00.000Z', + }, + }); + } + expect(state.closedCaptions.length).toBe(2); + expect(state['closedCaptionsTasks'].size).toBe(2); + expect(state.closedCaptions.map((cc) => cc.text)).toEqual([ + 'Hello world 3', + 'Hello world 4', + ]); + }); + + it('should remove stale captions from the queue', () => { + const state = new CallState(); + vi.useFakeTimers(); + state.updateFromEvent({ + type: 'call.closed_caption', + // @ts-expect-error incomplete data + closed_caption: { + speaker_id: `123`, + text: `Hello world`, + start_time: '2021-01-01T00:00:00.000Z', + end_time: '2021-01-01T00:02:00.000Z', + }, + }); + expect(state.closedCaptions.length).toBe(1); + expect(state['closedCaptionsTasks'].size).toBe(1); + + vi.runAllTimers(); + expect(state.closedCaptions.length).toBe(0); + expect(state['closedCaptionsTasks'].size).toBe(0); + }); + + it('should remove stale captions from the queue after timer runs', () => { + const state = new CallState(); + state.updateClosedCaptionSettings({ retentionTimeInMs: 100 }); + vi.useFakeTimers(); + state.updateFromEvent({ + type: 'call.closed_caption', + // @ts-expect-error incomplete data + closed_caption: { + speaker_id: `123`, + text: `Hello world`, + start_time: '2021-01-01T00:00:00.000Z', + end_time: '2021-01-01T00:02:00.000Z', + }, + }); + expect(state.closedCaptions.length).toBe(1); + expect(state['closedCaptionsTasks'].size).toBe(1); + + vi.advanceTimersByTime(101); + expect(state.closedCaptions.length).toBe(0); + expect(state['closedCaptionsTasks'].size).toBe(0); + }); + + it('dispose cancels all cleanup tasks', () => { + const state = new CallState(); + state.updateFromEvent({ + type: 'call.closed_caption', + // @ts-expect-error incomplete data + closed_caption: { + speaker_id: `123`, + text: `Hello world`, + start_time: '2021-01-01T00:00:00.000Z', + }, + }); + expect(state.closedCaptions.length).toBe(1); + expect(state['closedCaptionsTasks'].size).toBe(1); + + state.dispose(); + expect(state['closedCaptionsTasks'].size).toBe(0); + }); + }); }); diff --git a/packages/client/src/store/rxUtils.ts b/packages/client/src/store/rxUtils.ts index 9f9d7f638e..3b647b1a85 100644 --- a/packages/client/src/store/rxUtils.ts +++ b/packages/client/src/store/rxUtils.ts @@ -1,4 +1,4 @@ -import { combineLatest, Observable, Subject } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { withoutConcurrency } from '../helpers/concurrency'; import { getLogger } from '../logger'; @@ -59,6 +59,28 @@ export const setCurrentValue = (subject: Subject, update: Patch) => { return next; }; +/** + * Updates the value of the provided Subject and returns the previous value + * and a function to roll back the update. + * This is useful when you want to optimistically update a value + * and roll back the update if an error occurs. + * + * @param subject the subject to update. + * @param update the update to apply to the subject. + */ +export const updateValue = ( + subject: BehaviorSubject, + update: Patch, +) => { + const lastValue = subject.getValue(); + const value = setCurrentValue(subject, update); + return { + lastValue, + value, + rollback: () => setCurrentValue(subject, lastValue), + }; +}; + /** * Creates a subscription and returns a function to unsubscribe. * diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 3cb8a59858..b9fd0f8111 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -122,6 +122,21 @@ export type ParticipantPin = { pinnedAt: number; }; +export type ClosedCaptionsSettings = { + /** + * The time in milliseconds to keep a closed caption in the queue. + * Default is 2700 ms. + */ + retentionTimeInMs?: number; + /** + * The maximum number of closed captions to keep in the queue. + * When the queue is full, the oldest closed caption will be removed. + * + * Default is 2. + */ + queueSize?: number; +}; + /** * A partial representation of the StreamVideoParticipant. */ diff --git a/packages/react-bindings/src/hooks/callStateHooks.ts b/packages/react-bindings/src/hooks/callStateHooks.ts index e4e7a825ae..7e8473eb05 100644 --- a/packages/react-bindings/src/hooks/callStateHooks.ts +++ b/packages/react-bindings/src/hooks/callStateHooks.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { Call, + CallClosedCaption, CallIngressResponse, CallSessionResponse, CallSettingsResponse, @@ -479,3 +480,19 @@ export const useIncomingVideoSettings = () => { ); return settings; }; + +/** + * Returns the current call's closed captions queue. + */ +export const useCallClosedCaptions = (): CallClosedCaption[] => { + const { closedCaptions$ } = useCallState(); + return useObservableValue(closedCaptions$); +}; + +/** + * Returns the current call's closed captions queue. + */ +export const useIsCallCaptioningInProgress = (): boolean => { + const { captioning$ } = useCallState(); + return useObservableValue(captioning$); +}; diff --git a/packages/react-bindings/src/hooks/callUtilHooks.ts b/packages/react-bindings/src/hooks/callUtilHooks.ts index e8ce9ef83f..6317a16110 100644 --- a/packages/react-bindings/src/hooks/callUtilHooks.ts +++ b/packages/react-bindings/src/hooks/callUtilHooks.ts @@ -35,6 +35,7 @@ export const useToggleCallRecording = () => { } } catch (e) { console.error(`Failed start recording`, e); + throw e; } }, [call, isCallRecordingInProgress]); diff --git a/sample-apps/client/ts-quickstart/index.html b/sample-apps/client/ts-quickstart/index.html index d9eec53e42..baf13beb05 100644 --- a/sample-apps/client/ts-quickstart/index.html +++ b/sample-apps/client/ts-quickstart/index.html @@ -1,4 +1,4 @@ - + @@ -10,6 +10,7 @@
+
diff --git a/sample-apps/client/ts-quickstart/package.json b/sample-apps/client/ts-quickstart/package.json index 21430dcb77..e08a12d6e2 100644 --- a/sample-apps/client/ts-quickstart/package.json +++ b/sample-apps/client/ts-quickstart/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --host 0.0.0.0 --https", + "dev": "https=1 vite --host 0.0.0.0", "build": "tsc && vite build", "preview": "vite preview" }, diff --git a/sample-apps/client/ts-quickstart/src/closed-captions.ts b/sample-apps/client/ts-quickstart/src/closed-captions.ts new file mode 100644 index 0000000000..5557515252 --- /dev/null +++ b/sample-apps/client/ts-quickstart/src/closed-captions.ts @@ -0,0 +1,64 @@ +import { Call, CallClosedCaption } from '@stream-io/video-client'; + +export class ClosedCaptionManager { + status: 'on' | 'off' = 'off'; + private unsubscribe?: () => void; + private captionContainer?: HTMLElement; + + constructor(private call: Call) {} + + renderToggleElement() { + const button = document.createElement('button'); + button.textContent = + this.status === 'on' + ? 'Turn off closed captions' + : 'Turn on closed captions'; + + button.addEventListener('click', async () => { + this.status === 'on' ? this.hideCaptions() : this.showCaptions(); + button.textContent = + this.status === 'on' + ? 'Turn off closed captions' + : 'Turn on closed captions'; + }); + + return button; + } + + renderCaptionContainer() { + this.captionContainer = document.createElement('div'); + + return this.captionContainer; + } + + async showCaptions() { + this.status = 'on'; + await this.call.startClosedCaptions(); + this.unsubscribe = this.call.state.closedCaptions$.subscribe((captions) => { + this.updateDisplayedCaptions(captions); + }).unsubscribe; + } + + async hideCaptions() { + this.status = 'off'; + await this.call.stopClosedCaptions(); + this.cleanup(); + } + + cleanup() { + this.unsubscribe?.(); + } + + private updateDisplayedCaptions(captions: CallClosedCaption[]) { + if (!this.captionContainer) { + console.warn( + 'Render caption container before turning on closed captions', + ); + return; + } + + this.captionContainer.innerHTML = captions + .map((caption) => `${caption.user.name}: ${caption.text}`) + .join('
'); + } +} diff --git a/sample-apps/client/ts-quickstart/src/main.ts b/sample-apps/client/ts-quickstart/src/main.ts index 8a23d74f56..81fa3f03bd 100644 --- a/sample-apps/client/ts-quickstart/src/main.ts +++ b/sample-apps/client/ts-quickstart/src/main.ts @@ -10,6 +10,7 @@ import { renderVolumeControl, } from './device-selector'; import { isMobile } from './mobile'; +import { ClosedCaptionManager } from './closed-captions'; const searchParams = new URLSearchParams(window.location.search); const extractPayloadFromToken = (token: string) => { @@ -50,32 +51,42 @@ call.screenShare.setSettings({ maxBitrate: 1500000, }); -call.join({ create: true }).then(async () => { - // render mic and camera controls - const controls = renderControls(call); - const container = document.getElementById('call-controls')!; - container.appendChild(controls.audioButton); - container.appendChild(controls.videoButton); - container.appendChild(controls.screenShareButton); - - container.appendChild(renderAudioDeviceSelector(call)); - - // render device selectors - if (isMobile.any()) { - container.appendChild(controls.flipButton); - } else { - container.appendChild(renderVideoDeviceSelector(call)); - } - - const audioOutputSelector = renderAudioOutputSelector(call); - if (audioOutputSelector) { - container.appendChild(audioOutputSelector); - } - - container.appendChild(renderVolumeControl(call)); -}); +const container = document.getElementById('call-controls')!; + +// render mic and camera controls +const controls = renderControls(call); +container.appendChild(controls.audioButton); +container.appendChild(controls.videoButton); +container.appendChild(controls.screenShareButton); + +container.appendChild(renderAudioDeviceSelector(call)); + +// render device selectors +if (isMobile.any()) { + container.appendChild(controls.flipButton); +} else { + container.appendChild(renderVideoDeviceSelector(call)); +} + +const audioOutputSelector = renderAudioOutputSelector(call); +if (audioOutputSelector) { + container.appendChild(audioOutputSelector); +} + +container.appendChild(renderVolumeControl(call)); + +// Closed caption controls +const closedCaptionManager = new ClosedCaptionManager(call); +container.appendChild(closedCaptionManager.renderToggleElement()); + +const captionContainer = document.getElementById('closed-captions'); +captionContainer?.appendChild(closedCaptionManager.renderCaptionContainer()); + +call.join({ create: true }); window.addEventListener('beforeunload', () => { + // Make sure to remove your event listeners when you leave a call + closedCaptionManager?.cleanup(); call.leave(); }); diff --git a/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx b/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx index 9e20823073..dde760a0d9 100644 --- a/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx @@ -3,10 +3,10 @@ import { ChatButton, HangUpCallButton, ReactionsButton, + ScreenShareToggleButton, ToggleAudioPublishingButton, ToggleCameraFaceButton, ToggleVideoPublishingButton, - ScreenShareToggleButton, useCallStateHooks, } from '@stream-io/video-react-native-sdk'; import React from 'react'; @@ -15,6 +15,7 @@ import { appTheme } from '../theme'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Z_INDEX } from '../constants'; import { VideoEffectsButton } from './VideoEffectsButton'; +import { ClosedCaptions } from './ClosedCaptions'; export type CallControlsComponentProps = Pick< CallContentProps, @@ -33,8 +34,10 @@ export const CallControlsComponent = ({ landscape, }: CallControlsComponentProps) => { const { bottom } = useSafeAreaInsets(); - const { useMicrophoneState } = useCallStateHooks(); + const { useMicrophoneState, useIsCallCaptioningInProgress } = + useCallStateHooks(); const { isSpeakingWhileMuted } = useMicrophoneState(); + const isCaptioningInProgress = useIsCallCaptioningInProgress(); const landscapeStyles: ViewStyle = { flexDirection: landscape ? 'column-reverse' : 'row', paddingHorizontal: landscape ? 12 : 0, @@ -49,6 +52,7 @@ export const CallControlsComponent = ({ You are muted. Unmute to speak. )} + {isCaptioningInProgress && } diff --git a/sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx b/sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx new file mode 100644 index 0000000000..f693329875 --- /dev/null +++ b/sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { useCallStateHooks } from '@stream-io/video-react-native-sdk'; +import { appTheme } from '../theme'; + +export const ClosedCaptions = () => { + const { useCallClosedCaptions } = useCallStateHooks(); + const closedCaptions = useCallClosedCaptions(); + return ( + + {closedCaptions.map(({ user, start_time, text }) => ( + + {user.name}: + {text} + + ))} + + ); +}; + +const styles = StyleSheet.create({ + rootContainer: { + backgroundColor: appTheme.colors.static_overlay, + paddingVertical: 6, + paddingHorizontal: 6, + height: 50, + }, + closedCaptionItem: { + flexDirection: 'row', + gap: 8, + }, + speakerName: { + color: appTheme.colors.light_gray, + }, + closedCaption: { + color: appTheme.colors.static_white, + }, +}); diff --git a/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx b/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx index 7b5493d837..5502102c0f 100644 --- a/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx +++ b/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx @@ -67,9 +67,9 @@ export const ParticipantsInfoList = ({ const inviteHandler = async () => { try { await Share.share({ - url: `https://stream-calls-dogfood.vercel.app/join/${call?.id}`, + url: `https://pronto.getstream.io/join/${call?.id}`, title: 'Stream Calls | Join Call', - message: `Join me on the call using this link https://stream-calls-dogfood.vercel.app/join/${call?.id}`, + message: `Join me on the call using this link https://pronto.getstream.io/join/${call?.id}`, }); } catch (error: any) { console.log(error.message); diff --git a/sample-apps/react/react-dogfood/components/ActiveCall.tsx b/sample-apps/react/react-dogfood/components/ActiveCall.tsx index eed08f78f1..73bed30ad5 100644 --- a/sample-apps/react/react-dogfood/components/ActiveCall.tsx +++ b/sample-apps/react/react-dogfood/components/ActiveCall.tsx @@ -27,7 +27,11 @@ import { InvitePanel, InvitePopup } from './InvitePanel/InvitePanel'; import { ChatWrapper } from './ChatWrapper'; import { ChatUI } from './ChatUI'; import { CallStatsSidebar, ToggleStatsButton } from './CallStatsWrapper'; -import { ClosedCaptions, ClosedCaptionsSidebar } from './ClosedCaptions'; +import { + ClosedCaptions, + ClosedCaptionsSidebar, + ToggleClosedCaptionsButton, +} from './ClosedCaptions'; import { ToggleSettingsTabModal } from './Settings/SettingsTabModal'; import { IncomingVideoSettingsButton } from './IncomingVideoSettings'; import { ToggleEffectsButton } from './ToggleEffectsButton'; @@ -246,23 +250,9 @@ export const ActiveCall = (props: ActiveCallProps) => {
- {isPronto && ( -
- - { - setSidebarContent( - showClosedCaptions ? null : 'closed-captions', - ); - }} - > - - - -
- )} +
+ +
@@ -280,6 +270,23 @@ export const ActiveCall = (props: ActiveCallProps) => {
+ {isPronto && ( +
+ + { + setSidebarContent( + showClosedCaptions ? null : 'closed-captions', + ); + }} + > + + + +
+ )}
{ - const [queue, setQueue] = useState(initialQueue); - - const addToQueue = useCallback((newCaption: CallClosedCaption) => { - setQueue((prevQueue) => { - const key = `${newCaption.speaker_id}-${newCaption.start_time}`; - const isDuplicate = prevQueue.some( - (caption) => `${caption.speaker_id}-${caption.start_time}` === key, - ); - - if (isDuplicate) { - return prevQueue; - } - - return [...prevQueue, newCaption]; - }); - }, []); - - return [queue, addToQueue, setQueue] as const; +export const ToggleClosedCaptionsButton = () => { + const call = useCall(); + const { useIsCallCaptioningInProgress, useHasPermissions } = + useCallStateHooks(); + const isCaptioned = useIsCallCaptioningInProgress(); + const canToggle = useHasPermissions( + OwnCapability.START_CLOSED_CAPTIONS_CALL, + OwnCapability.STOP_CLOSED_CAPTIONS_CALL, + ); + return ( + + { + if (!call) return; + try { + if (isCaptioned) { + await call.stopClosedCaptions(); + } else { + await call.startClosedCaptions(); + } + } catch (e) { + console.error('Failed to toggle closed captions', e); + } + }} + > + + + + ); }; export const ClosedCaptions = () => { - const call = useCall(); - const [queue, addToQueue, setQueue] = useDeduplicatedQueue(); - - useEffect(() => { - if (!call) return; - return call.on('call.closed_caption', (e) => { - if (e.type !== 'call.closed_caption') return; - if (e.closed_caption.text.trim() === '') return; - addToQueue(e.closed_caption); - }); - }, [call, addToQueue]); - - useEffect(() => { - const id = setTimeout(() => { - setQueue((prevQueue) => - prevQueue.length !== 0 ? prevQueue.slice(1) : prevQueue, - ); - }, 2700); - return () => clearTimeout(id); - }, [queue, setQueue]); - - const userNameMapping = useUserIdToUserNameMapping(); - + const { useCallClosedCaptions } = useCallStateHooks(); + const closedCaptions = useCallClosedCaptions(); return (
- {queue.slice(-2).map(({ speaker_id, text, start_time }) => ( -

- - {userNameMapping[speaker_id] || speaker_id}: - - {text} -

- ))} +
); }; export const ClosedCaptionsSidebar = () => { const call = useCall(); - const [queue, addToQueue] = useDeduplicatedQueue(); - + const [queue, addToQueue] = useState([]); useEffect(() => { if (!call) return; return call.on('call.closed_caption', (e) => { - if (e.type !== 'call.closed_caption') return; - if (e.closed_caption.text.trim() === '') return; - addToQueue(e.closed_caption); + addToQueue((q) => [...q, e.closed_caption]); }); - }, [call, addToQueue]); - - const userNameMapping = useUserIdToUserNameMapping(); - + }, [call]); return (

Closed Captions

- {queue.map(({ speaker_id, text, start_time }) => ( -

- - {userNameMapping[speaker_id] || speaker_id}: - - {text} -

- ))} +
); }; -const useUserIdToUserNameMapping = () => { - const { useCallSession } = useCallStateHooks(); - const session = useCallSession(); - return useMemo(() => { - if (!session) return {}; - return session.participants.reduce>( - (result, participant) => { - result[participant.user.id] = participant.user.name; - return result; - }, - {}, - ); - }, [session]); +const ClosedCaptionList = (props: { queue: CallClosedCaption[] }) => { + const { queue } = props; + return queue.map(({ user, text, start_time }) => ( +

+ {user.name}: + {text} +

+ )); };