Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

Introducing meeting recording and Realtime HTTP Endpoints🎉 #690

Merged
merged 15 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"yargs": "^17.7.2"
},
"dependencies": {
"@superviz/socket-client": "1.4.0",
"@superviz/socket-client": "1.5.1",
"ably": "^1.2.45",
"bowser": "^2.11.0",
"bowser-jr": "^1.0.6",
Expand Down
2 changes: 1 addition & 1 deletion src/common/types/events.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export enum MeetingControlsEvent {
TOGGLE_MICROPHONE = 'meeting-controls.toggle-microphone',
TOGGLE_CAM = 'meeting-controls.toggle-cam',
TOGGLE_SCREENSHARE = 'meeting-controls.toggle-screenshare',
TOGGLE_TRANSCRIPT = 'meeting-controls.toggle-transcript',
TOGGLE_RECORDING = 'meeting-controls.toggle-recording',
HANG_UP = 'hang-up',
CALLBACK_CALLED = 'callback-called',
}
Expand Down
5 changes: 3 additions & 2 deletions src/common/types/stores.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export enum StoreType {

type Subject<T extends (...args: any[]) => any, K extends keyof ReturnType<T>> = ReturnType<T>[K];

type StoreApiWithoutDestroy<T extends (...args: any[]) => any> = {
type IncompleteStoreApi<T extends (...args: any[]) => any> = {
[K in keyof ReturnType<T>]: {
subscribe(callback?: (value: Subject<T, K>['value']) => void): void;
subject: Subject<T, K>;
Expand All @@ -22,8 +22,9 @@ type StoreApiWithoutDestroy<T extends (...args: any[]) => any> = {
};
};

type StoreApi<T extends (...args: any[]) => any> = Omit<StoreApiWithoutDestroy<T>, 'destroy'> & {
type StoreApi<T extends (...args: any[]) => any> = IncompleteStoreApi<T> & {
destroy(): void;
restart(): void;
};

type GlobalStore = StoreType.GLOBAL | `${StoreType.GLOBAL}`;
Expand Down
8 changes: 5 additions & 3 deletions src/components/realtime/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,10 @@ export class Channel extends Observable {
this.logger.log('message received', event);
this.publishEventToClient(event.data.name, {
data: event.data.payload,
participantId: event.presence.id,
participantId: event?.presence?.id || null,
name: event.data.name,
timestamp: event.timestamp,
connectionId: event.connectionId,
} as RealtimeMessage);
});
}
Expand Down Expand Up @@ -172,10 +173,11 @@ export class Channel extends Observable {

group[event.data.name].push({
data: event.data.payload,
connectionId: event.connectionId,
name: event.data.name,
participantId: event.presence.id,
participantId: event.presence?.id,
timestamp: event.timestamp,
});
} as RealtimeMessage);

return group;
},
Expand Down
3 changes: 2 additions & 1 deletion src/components/realtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type RealtimeData = {

export type RealtimeMessage = {
name: string;
participantId: string;
connectionId: string;
participantId: string | null;
data: unknown;
timestamp: number;
};
12 changes: 6 additions & 6 deletions src/components/video/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock';
import { MOCK_OBSERVER_HELPER } from '../../../__mocks__/observer-helper.mock';
import { MOCK_AVATAR, MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock';
import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock';
import { ROOM_STATE_MOCK } from '../../../__mocks__/roomState.mock';
import {
DeviceEvent,
FrameEvent,
Expand All @@ -25,7 +24,6 @@ import { useStore } from '../../common/utils/use-store';
import { IOC } from '../../services/io';
import { Presence3DManager } from '../../services/presence-3d-manager';
import { ParticipantInfo } from '../../services/realtime/base/types';
import { RoomStateService } from '../../services/roomState';
import { VideoFrameState } from '../../services/video-conference-manager/types';
import { ComponentNames } from '../types';

Expand Down Expand Up @@ -97,12 +95,13 @@ describe('VideoConference', () => {
let VideoConferenceInstance: VideoConference;

const { localParticipant, hasJoinedRoom } = useStore(StoreType.GLOBAL);
localParticipant.publish(MOCK_LOCAL_PARTICIPANT);
hasJoinedRoom.publish(true);
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();

localParticipant.publish(MOCK_LOCAL_PARTICIPANT);
hasJoinedRoom.publish(true);

VideoConferenceInstance = new VideoConference({
userType: 'host',
allowGuests: false,
Expand All @@ -118,6 +117,7 @@ describe('VideoConference', () => {
useStore,
});

VideoConferenceInstance['startVideo']();
VideoConferenceInstance['onFrameStateChange'](VideoFrameState.INITIALIZED);
});

Expand Down Expand Up @@ -870,10 +870,10 @@ describe('VideoConference', () => {
});

test('should toggle transcript', () => {
VideoConferenceInstance['toggleTranscript']();
VideoConferenceInstance['toggleRecording']();

expect(VIDEO_MANAGER_MOCK.publishMessageToFrame).toBeCalledWith(
MeetingControlsEvent.TOGGLE_TRANSCRIPT,
MeetingControlsEvent.TOGGLE_RECORDING,
);
});

Expand Down
14 changes: 7 additions & 7 deletions src/components/video/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PresenceEvent, PresenceEvents, SocketEvent } from '@superviz/socket-client';
import { PresenceEvent, PresenceEvents } from '@superviz/socket-client';

import { ColorsVariables } from '../../common/types/colors.types';
import {
Expand Down Expand Up @@ -117,12 +117,12 @@ export class VideoConference extends BaseComponent {
}

/**
* @function toggleTranscript
* @description open/close meeting transcript
* @function toggleRecording
* @description open/close meeting recording
* @returns {void}
*/
public toggleTranscript(): void {
return this.videoManager?.publishMessageToFrame(MeetingControlsEvent.TOGGLE_TRANSCRIPT);
public toggleRecording(): void {
return this.videoManager?.publishMessageToFrame(MeetingControlsEvent.TOGGLE_RECORDING);
}

/**
Expand Down Expand Up @@ -178,7 +178,7 @@ export class VideoConference extends BaseComponent {
private startVideo = (): void => {
this.videoConfig = {
language: this.params?.language,
canUseTranscription: this.params?.transcriptOff === false,
canUseRecording: !!this.params?.enableRecording,
canShowAudienceList: this.params?.showAudienceList ?? true,
canUseChat: !this.params?.chatOff,
canUseCams: !this.params?.camsOff,
Expand Down Expand Up @@ -523,12 +523,12 @@ export class VideoConference extends BaseComponent {
private onParticipantLeft = (_: Participant): void => {
this.logger.log('video conference @ on participant left', this.localParticipant);

this.videoManager.leave();
this.connectionService.removeListeners();
this.publish(MeetingEvent.DESTROY);
this.publish(MeetingEvent.MY_PARTICIPANT_LEFT, this.localParticipant);

this.unsubscribeFromVideoEvents();
this.videoManager.leave();
this.videoManager = undefined;
this.connectionService = undefined;

Expand Down
4 changes: 2 additions & 2 deletions src/components/video/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface VideoComponentOptions {
camsOff?: boolean;
screenshareOff?: boolean;
chatOff?: boolean;
transcriptOff?: boolean;
enableRecording?: boolean;
defaultAvatars?: boolean;
offset?: Offset;
enableFollow?: boolean;
Expand All @@ -38,7 +38,7 @@ export interface VideoComponentOptions {
callbacks?: {
onToggleMicrophone?: () => void;
onToggleCam?: () => void;
onToggleTranscript?: () => void;
onToggleRecording?: () => void;
onToggleChat?: () => void;
onToggleScreenShare?: () => void;
onLeaveMeeting?: () => void;
Expand Down
1 change: 0 additions & 1 deletion src/components/who-is-online/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
MOCK_ABLY_PARTICIPANT_DATA_1,
} from '../../../__mocks__/participants.mock';
import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock';
import { ROOM_STATE_MOCK } from '../../../__mocks__/roomState.mock';
import { RealtimeEvent, WhoIsOnlineEvent } from '../../common/types/events.types';
import { MeetingColorsHex } from '../../common/types/meeting-colors.types';
import { StoreType } from '../../common/types/stores.types';
Expand Down
2 changes: 1 addition & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const init = async (apiKey: string, options: SuperVizSdkOptions): Promise<Launch
ApiService.createOrUpdateParticipant({
name: participant.name,
participantId: participant.id,
avatar: participant.avatar?.imageUrl,
avatar: participant.avatar?.imageUrl,
});

return LauncherFacade(options);
Expand Down
1 change: 0 additions & 1 deletion src/core/launcher/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { DefaultAttachComponentOptions } from '../../components/base/types';
import { ComponentNames } from '../../components/types';
import { IOC } from '../../services/io';
import LimitsService from '../../services/limits';
import { AblyParticipant } from '../../services/realtime/ably/types';
import { useGlobalStore } from '../../services/stores';

import { LauncherFacade, LauncherOptions } from './types';
Expand Down
4 changes: 1 addition & 3 deletions src/core/launcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,9 @@ export class Launcher extends Observable implements DefaultLauncher {
this.removeComponent(component);
});

const { localParticipant } = useStore(StoreType.GLOBAL);

this.activeComponents = [];
this.activeComponentsInstances = [];
localParticipant.publish(undefined);

useGlobalStore().destroy();

this.eventBus.destroy();
Expand Down
2 changes: 1 addition & 1 deletion src/services/stores/global/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import subject from '../subject';
const instance: Singleton<GlobalStore> = CreateSingleton<GlobalStore>();

export class GlobalStore {
public localParticipant = subject<Participant>(null);
public localParticipant = subject<Participant>({} as Participant);
public participants = subject<Record<string, ParticipantInfo>>({});
public group = subject<Group>(null);
public isDomainWhitelisted = subject<boolean>(true);
Expand Down
1 change: 1 addition & 0 deletions src/services/stores/presence3D/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Presence3DStore {

public destroy() {
this.hasJoined3D.destroy();
this.participants.destroy();
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/services/stores/subject/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('base subject for all stores', () => {
const unsubscribe = jest.fn();

instance['subscribe'](testValues.id, callback);
instance['subscriptions'].get(testValues.id)!.unsubscribe = unsubscribe;
instance['subscriptions'].get(testValues.id)![0].unsubscribe = unsubscribe;

instance['unsubscribe'](testValues.id);

Expand All @@ -97,16 +97,14 @@ describe('base subject for all stores', () => {
instance = subject<string>(testValues.str1);

const callback = jest.fn();
const unsubscribe = jest.fn();

instance['subscribe'](testValues.id, callback);
instance['subscriptions'].get(testValues.id)!.unsubscribe = unsubscribe;

instance['destroy']();

expect(instance['subscriptions'].size).toBe(0);
expect(instance['subscriptions'].get(testValues.id)).toBeUndefined();
expect(unsubscribe).toHaveBeenCalled();
expect(instance.state).toBe(instance['firstState']);
});
});

Expand Down
29 changes: 25 additions & 4 deletions src/services/stores/subject/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import { PublicSubject } from '../common/types';

export class Subject<T> {
public state: T;
private firstState: T;

private subject: BehaviorSubject<T>;
private subscriptions: Map<string | this, Subscription> = new Map();
private subscriptions: Map<string | this, Subscription[]> = new Map();

constructor(state: T, subject: BehaviorSubject<T>) {
this.state = state;
this.firstState = state;

this.subject = subject.pipe(
distinctUntilChanged(),
shareReplay({ bufferSize: 1, refCount: true }),
Expand All @@ -27,17 +31,34 @@ export class Subject<T> {

public subscribe = (subscriptionId: string | this, callback: (value: T) => void) => {
const subscription = this.subject.subscribe(callback);
this.subscriptions.set(subscriptionId, subscription);

if (this.subscriptions.has(subscriptionId)) {
this.subscriptions.get(subscriptionId).push(subscription);
return;
}

this.subscriptions.set(subscriptionId, [subscription]);
};

public unsubscribe(subscriptionId: string) {
this.subscriptions.get(subscriptionId)?.unsubscribe();
this.subscriptions.get(subscriptionId)?.forEach((subscription) => subscription.unsubscribe());
this.subscriptions.delete(subscriptionId);
}

public destroy() {
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
this.subscriptions.clear();
this.subject.complete();

this.restart();
}

private restart() {
this.state = this.firstState;

this.subject = new BehaviorSubject<T>(this.firstState).pipe(
distinctUntilChanged(),
shareReplay({ bufferSize: 1, refCount: true }),
) as BehaviorSubject<T>;
}

public expose(): PublicSubject<T> {
Expand Down
2 changes: 0 additions & 2 deletions src/services/stores/who-is-online/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export class WhoIsOnlineStore {
this.everyoneFollowsMe.destroy();
this.privateMode.destroy();
this.following.destroy();

instance.value = null;
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/services/video-conference-manager/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const createVideoConfrenceManager = (options?: VideoManagerOptions) => {
const defaultOptions: VideoManagerOptions = {
browserService: new BrowserService(),
camerasPosition: CamerasPosition.RIGHT,
canUseTranscription: true,
canUseRecording: true,
canShowAudienceList: true,
canUseCams: true,
canUseChat: true,
Expand Down Expand Up @@ -284,7 +284,7 @@ describe('VideoConferenceManager', () => {
const callbacks = {
onToggleMicrophone: jest.fn(),
onToggleCam: jest.fn(),
onToggleTranscript: jest.fn(),
onToggleRecording: jest.fn(),
onToggleChat: jest.fn(),
onToggleScreenShare: jest.fn(),
onClickHangup: jest.fn(),
Expand All @@ -304,7 +304,7 @@ describe('VideoConferenceManager', () => {
JSON.stringify({
onToggleMicrophone: true,
onToggleCam: true,
onToggleTranscript: true,
onToggleRecording: true,
onToggleChat: true,
onToggleScreenShare: true,
onClickHangup: true,
Expand Down
6 changes: 3 additions & 3 deletions src/services/video-conference-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default class VideoConfereceManager {
canUseGather,
canShowAudienceList,
canUseDefaultToolbar,
canUseTranscription,
canUseRecording,
browserService,
offset,
locales,
Expand Down Expand Up @@ -116,7 +116,7 @@ export default class VideoConfereceManager {
canUseGather,
canUseScreenshare,
canUseDefaultAvatars,
canUseTranscription,
canUseRecording,
canShowAudienceList,
camerasPosition: positions.camerasPosition ?? CamerasPosition.RIGHT,
canUseDefaultToolbar,
Expand Down Expand Up @@ -323,7 +323,7 @@ export default class VideoConfereceManager {
const callbacks = {
onToggleMicrophone: !!this.callbacks.onToggleMicrophone,
onToggleCam: !!this.callbacks.onToggleCam,
onToggleTranscript: !!this.callbacks.onToggleTranscript,
onToggleRecording: !!this.callbacks.onToggleRecording,
onToggleChat: !!this.callbacks.onToggleChat,
onToggleScreenShare: !!this.callbacks.onToggleScreenShare,
onClickHangup: !!this.callbacks.onClickHangup,
Expand Down
Loading
Loading