@stream-io/video-client 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.browser.es.js +863 -455
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +867 -453
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +863 -455
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +18 -1
  9. package/dist/src/devices/CameraManager.d.ts +31 -0
  10. package/dist/src/devices/CameraManagerState.d.ts +28 -0
  11. package/dist/src/devices/InputMediaDeviceManager.d.ts +47 -0
  12. package/dist/src/devices/InputMediaDeviceManagerState.d.ts +69 -0
  13. package/dist/src/devices/MicrophoneManager.d.ts +19 -0
  14. package/dist/src/devices/MicrophoneManagerState.d.ts +4 -0
  15. package/dist/src/devices/__tests__/CameraManager.test.d.ts +1 -0
  16. package/dist/src/devices/__tests__/InputMediaDeviceManager.test.d.ts +1 -0
  17. package/dist/src/devices/__tests__/MicrophoneManager.test.d.ts +1 -0
  18. package/dist/src/devices/__tests__/mocks.d.ts +13 -0
  19. package/dist/src/devices/index.d.ts +6 -0
  20. package/dist/src/types.d.ts +4 -0
  21. package/dist/version.d.ts +1 -1
  22. package/package.json +1 -1
  23. package/src/Call.ts +100 -3
  24. package/src/__tests__/StreamVideoClient.test.ts +3 -0
  25. package/src/devices/CameraManager.ts +73 -0
  26. package/src/devices/CameraManagerState.ts +61 -0
  27. package/src/devices/InputMediaDeviceManager.ts +121 -0
  28. package/src/devices/InputMediaDeviceManagerState.ts +111 -0
  29. package/src/devices/MicrophoneManager.ts +45 -0
  30. package/src/devices/MicrophoneManagerState.ts +9 -0
  31. package/src/devices/__tests__/CameraManager.test.ts +150 -0
  32. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +159 -0
  33. package/src/devices/__tests__/MicrophoneManager.test.ts +103 -0
  34. package/src/devices/__tests__/mocks.ts +98 -0
  35. package/src/devices/index.ts +6 -0
  36. package/src/rtc/Publisher.ts +11 -19
  37. package/src/rtc/videoLayers.ts +7 -2
  38. package/src/types.ts +4 -0
@@ -0,0 +1,121 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { CallingState } from '../store';
4
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
5
+ import { disposeOfMediaStream } from './devices';
6
+ import { isReactNative } from '../helpers/platforms';
7
+
8
+ export abstract class InputMediaDeviceManager<
9
+ T extends InputMediaDeviceManagerState,
10
+ > {
11
+ constructor(protected readonly call: Call, public readonly state: T) {}
12
+
13
+ /**
14
+ * Lists the available audio/video devices
15
+ *
16
+ * Note: It prompts the user for a permission to use devices (if not already granted)
17
+ *
18
+ * @returns an Observable that will be updated if a device is connected or disconnected
19
+ */
20
+ listDevices() {
21
+ return this.getDevices();
22
+ }
23
+
24
+ /**
25
+ * Starts camera/microphone
26
+ */
27
+ async enable() {
28
+ if (this.state.status === 'enabled') {
29
+ return;
30
+ }
31
+ await this.startStream();
32
+ this.state.setStatus('enabled');
33
+ }
34
+
35
+ /**
36
+ * Stops camera/microphone
37
+ * @returns
38
+ */
39
+ async disable() {
40
+ if (this.state.status === 'disabled') {
41
+ return;
42
+ }
43
+ await this.stopStream();
44
+ this.state.setStatus('disabled');
45
+ }
46
+
47
+ /**
48
+ * If current device statis is disabled, it will enable the device, else it will disable it.
49
+ * @returns
50
+ */
51
+ async toggle() {
52
+ if (this.state.status === 'enabled') {
53
+ return this.disable();
54
+ } else {
55
+ return this.enable();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Select device
61
+ *
62
+ * Note: this method is not supported in React Native
63
+ *
64
+ * @param deviceId
65
+ */
66
+ async select(deviceId: string | undefined) {
67
+ if (isReactNative()) {
68
+ throw new Error('This method is not supported in React Native');
69
+ }
70
+ if (deviceId === this.state.selectedDevice) {
71
+ return;
72
+ }
73
+ this.state.setDevice(deviceId);
74
+ await this.applySettingsToStream();
75
+ }
76
+
77
+ protected async applySettingsToStream() {
78
+ if (this.state.status === 'enabled') {
79
+ await this.stopStream();
80
+ await this.startStream();
81
+ }
82
+ }
83
+
84
+ abstract pause(): void;
85
+
86
+ abstract resume(): void;
87
+
88
+ protected abstract getDevices(): Observable<MediaDeviceInfo[]>;
89
+
90
+ protected abstract getStream(
91
+ constraints: MediaTrackConstraints,
92
+ ): Promise<MediaStream>;
93
+
94
+ protected abstract publishStream(stream: MediaStream): Promise<void>;
95
+
96
+ protected abstract stopPublishStream(): Promise<void>;
97
+
98
+ private async stopStream() {
99
+ if (!this.state.mediaStream) {
100
+ return;
101
+ }
102
+ if (this.call.state.callingState === CallingState.JOINED) {
103
+ await this.stopPublishStream();
104
+ } else if (this.state.mediaStream) {
105
+ disposeOfMediaStream(this.state.mediaStream);
106
+ }
107
+ this.state.setMediaStream(undefined);
108
+ }
109
+
110
+ private async startStream() {
111
+ if (this.state.mediaStream) {
112
+ return;
113
+ }
114
+ const constraints = { deviceId: this.state.selectedDevice };
115
+ const stream = await this.getStream(constraints);
116
+ if (this.call.state.callingState === CallingState.JOINED) {
117
+ await this.publishStream(stream);
118
+ }
119
+ this.state.setMediaStream(stream);
120
+ }
121
+ }
@@ -0,0 +1,111 @@
1
+ import { BehaviorSubject, Observable, distinctUntilChanged } from 'rxjs';
2
+ import { RxUtils } from '../store';
3
+
4
+ export type InputDeviceStatus = 'enabled' | 'disabled' | undefined;
5
+
6
+ export abstract class InputMediaDeviceManagerState {
7
+ protected statusSubject = new BehaviorSubject<InputDeviceStatus>(undefined);
8
+ protected mediaStreamSubject = new BehaviorSubject<MediaStream | undefined>(
9
+ undefined,
10
+ );
11
+ protected selectedDeviceSubject = new BehaviorSubject<string | undefined>(
12
+ undefined,
13
+ );
14
+
15
+ /**
16
+ * An Observable that emits the current media stream, or `undefined` if the device is currently disabled.
17
+ *
18
+ */
19
+ mediaStream$: Observable<MediaStream | undefined>;
20
+
21
+ /**
22
+ * An Observable that emits the currently selected device
23
+ */
24
+ selectedDevice$: Observable<string | undefined>;
25
+
26
+ /**
27
+ * An Observable that emits the device status
28
+ */
29
+ status$: Observable<InputDeviceStatus>;
30
+
31
+ constructor() {
32
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
33
+ this.selectedDevice$ = this.selectedDeviceSubject
34
+ .asObservable()
35
+ .pipe(distinctUntilChanged());
36
+ this.status$ = this.statusSubject.asObservable();
37
+ }
38
+
39
+ /**
40
+ * The device status
41
+ */
42
+ get status() {
43
+ return this.getCurrentValue(this.status$);
44
+ }
45
+
46
+ /**
47
+ * The currently selected device
48
+ */
49
+ get selectedDevice() {
50
+ return this.getCurrentValue(this.selectedDevice$);
51
+ }
52
+
53
+ /**
54
+ * The current media stream, or `undefined` if the device is currently disabled.
55
+ */
56
+ get mediaStream() {
57
+ return this.getCurrentValue(this.mediaStream$);
58
+ }
59
+
60
+ /**
61
+ * Gets the current value of an observable, or undefined if the observable has
62
+ * not emitted a value yet.
63
+ *
64
+ * @param observable$ the observable to get the value from.
65
+ */
66
+ getCurrentValue = RxUtils.getCurrentValue;
67
+
68
+ /**
69
+ * @internal
70
+ * @param status
71
+ */
72
+ setStatus(status: InputDeviceStatus) {
73
+ this.setCurrentValue(this.statusSubject, status);
74
+ }
75
+
76
+ /**
77
+ * @internal
78
+ * @param stream
79
+ */
80
+ setMediaStream(stream: MediaStream | undefined) {
81
+ this.setCurrentValue(this.mediaStreamSubject, stream);
82
+ if (stream) {
83
+ this.setDevice(this.getDeviceIdFromStream(stream));
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @internal
89
+ * @param stream
90
+ */
91
+ setDevice(deviceId: string | undefined) {
92
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
93
+ }
94
+
95
+ /**
96
+ * Updates the value of the provided Subject.
97
+ * An `update` can either be a new value or a function which takes
98
+ * the current value and returns a new value.
99
+ *
100
+ * @internal
101
+ *
102
+ * @param subject the subject to update.
103
+ * @param update the update to apply to the subject.
104
+ * @return the updated value.
105
+ */
106
+ protected setCurrentValue = RxUtils.setCurrentValue;
107
+
108
+ protected abstract getDeviceIdFromStream(
109
+ stream: MediaStream,
110
+ ): string | undefined;
111
+ }
@@ -0,0 +1,45 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { InputMediaDeviceManager } from './InputMediaDeviceManager';
4
+ import { MicrophoneManagerState } from './MicrophoneManagerState';
5
+ import { getAudioDevices, getAudioStream } from './devices';
6
+ import { TrackType } from '../gen/video/sfu/models/models';
7
+
8
+ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManagerState> {
9
+ constructor(call: Call) {
10
+ super(call, new MicrophoneManagerState());
11
+ }
12
+
13
+ protected getDevices(): Observable<MediaDeviceInfo[]> {
14
+ return getAudioDevices();
15
+ }
16
+ protected getStream(
17
+ constraints: MediaTrackConstraints,
18
+ ): Promise<MediaStream> {
19
+ return getAudioStream(constraints);
20
+ }
21
+ protected publishStream(stream: MediaStream): Promise<void> {
22
+ return this.call.publishAudioStream(stream);
23
+ }
24
+ protected stopPublishStream(): Promise<void> {
25
+ return this.call.stopPublish(TrackType.AUDIO);
26
+ }
27
+
28
+ /**
29
+ * Disables the audio tracks of the microphone
30
+ */
31
+ pause() {
32
+ this.state.mediaStream?.getAudioTracks().forEach((track) => {
33
+ track.enabled = false;
34
+ });
35
+ }
36
+
37
+ /**
38
+ * (Re)enables the audio tracks of the microphone
39
+ */
40
+ resume() {
41
+ this.state.mediaStream?.getAudioTracks().forEach((track) => {
42
+ track.enabled = true;
43
+ });
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
2
+
3
+ export class MicrophoneManagerState extends InputMediaDeviceManagerState {
4
+ protected getDeviceIdFromStream(stream: MediaStream): string | undefined {
5
+ return stream.getAudioTracks()[0]?.getSettings().deviceId as
6
+ | string
7
+ | undefined;
8
+ }
9
+ }
@@ -0,0 +1,150 @@
1
+ import { Call } from '../../Call';
2
+ import { StreamClient } from '../../coordinator/connection/client';
3
+ import { CallingState, StreamVideoWriteableStateStore } from '../../store';
4
+
5
+ import { afterEach, beforeEach, describe, vi, it, expect, Mock } from 'vitest';
6
+ import { mockCall, mockVideoDevices, mockVideoStream } from './mocks';
7
+ import { getVideoStream } from '../devices';
8
+ import { TrackType } from '../../gen/video/sfu/models/models';
9
+ import { CameraManager } from '../CameraManager';
10
+ import { of } from 'rxjs';
11
+ import { CallSettingsResponse } from '../../gen/coordinator';
12
+
13
+ vi.mock('../devices.ts', () => {
14
+ console.log('MOCKING devices API');
15
+ return {
16
+ disposeOfMediaStream: vi.fn(),
17
+ getVideoDevices: vi.fn(() => {
18
+ return of(mockVideoDevices);
19
+ }),
20
+ getVideoStream: vi.fn(() => Promise.resolve(mockVideoStream())),
21
+ };
22
+ });
23
+
24
+ vi.mock('../../Call.ts', () => {
25
+ console.log('MOCKING Call');
26
+ return {
27
+ Call: vi.fn(() => mockCall()),
28
+ };
29
+ });
30
+
31
+ describe('CameraManager', () => {
32
+ let manager: CameraManager;
33
+
34
+ beforeEach(() => {
35
+ manager = new CameraManager(
36
+ new Call({
37
+ id: '',
38
+ type: '',
39
+ streamClient: new StreamClient('abc123'),
40
+ clientStore: new StreamVideoWriteableStateStore(),
41
+ }),
42
+ );
43
+ });
44
+
45
+ it('list devices', () => {
46
+ const spy = vi.fn();
47
+ manager.listDevices().subscribe(spy);
48
+
49
+ expect(spy).toHaveBeenCalledWith(mockVideoDevices);
50
+ });
51
+
52
+ it('get stream', async () => {
53
+ await manager.enable();
54
+
55
+ expect(getVideoStream).toHaveBeenCalledWith({
56
+ deviceId: undefined,
57
+ });
58
+ });
59
+
60
+ it('should get device id from stream', async () => {
61
+ expect(manager.state.selectedDevice).toBeUndefined();
62
+
63
+ await manager.enable();
64
+
65
+ expect(manager.state.selectedDevice).toBeDefined();
66
+ });
67
+
68
+ it('publish stream', async () => {
69
+ // @ts-expect-error
70
+ manager['call'].state.callingState = CallingState.JOINED;
71
+
72
+ await manager.enable();
73
+
74
+ expect(manager['call'].publishVideoStream).toHaveBeenCalledWith(
75
+ manager.state.mediaStream,
76
+ );
77
+ });
78
+
79
+ it('stop publish stream', async () => {
80
+ // @ts-expect-error
81
+ manager['call'].state.callingState = CallingState.JOINED;
82
+ await manager.enable();
83
+
84
+ await manager.disable();
85
+
86
+ expect(manager['call'].stopPublish).toHaveBeenCalledWith(TrackType.VIDEO);
87
+ });
88
+
89
+ it('flip', async () => {
90
+ await manager.selectDirection('front');
91
+
92
+ await manager.flip();
93
+
94
+ expect(manager.state.direction).toBe('back');
95
+ });
96
+
97
+ it('select camera direction', async () => {
98
+ expect(manager.state.direction).toBe(undefined);
99
+
100
+ await manager.enable();
101
+
102
+ expect(getVideoStream).toHaveBeenCalledWith({ deviceId: undefined });
103
+
104
+ await manager.selectDirection('front');
105
+
106
+ expect(getVideoStream).toHaveBeenCalledWith({
107
+ deviceId: undefined,
108
+ facingMode: 'user',
109
+ });
110
+
111
+ await manager.selectDirection('back');
112
+
113
+ expect(getVideoStream).toHaveBeenCalledWith({
114
+ deviceId: undefined,
115
+ facingMode: 'environment',
116
+ });
117
+ });
118
+
119
+ it(`shouldn't set deviceId and facingMode at the same time`, async () => {
120
+ await manager.enable();
121
+
122
+ await manager.flip();
123
+
124
+ expect(getVideoStream).toHaveBeenCalledWith({ facingMode: 'environment' });
125
+
126
+ const deviceId = mockVideoDevices[1].deviceId;
127
+ await manager.select(deviceId);
128
+
129
+ expect((getVideoStream as Mock).mock.lastCall[0]).toEqual({
130
+ deviceId,
131
+ });
132
+ });
133
+
134
+ it('should pause and resume tracks', async () => {
135
+ await manager.enable();
136
+
137
+ manager.pause();
138
+
139
+ expect(manager.state.mediaStream?.getVideoTracks()[0].enabled).toBe(false);
140
+
141
+ manager.resume();
142
+
143
+ expect(manager.state.mediaStream?.getVideoTracks()[0].enabled).toBe(true);
144
+ });
145
+
146
+ afterEach(() => {
147
+ vi.clearAllMocks();
148
+ vi.resetModules();
149
+ });
150
+ });
@@ -0,0 +1,159 @@
1
+ import { Call } from '../../Call';
2
+ import { StreamClient } from '../../coordinator/connection/client';
3
+ import { CallingState, StreamVideoWriteableStateStore } from '../../store';
4
+
5
+ import { afterEach, beforeEach, describe, vi, it, expect } from 'vitest';
6
+ import { mockCall, mockVideoDevices, mockVideoStream } from './mocks';
7
+ import { InputMediaDeviceManager } from '../InputMediaDeviceManager';
8
+ import { InputMediaDeviceManagerState } from '../InputMediaDeviceManagerState';
9
+ import { of } from 'rxjs';
10
+
11
+ vi.mock('../../Call.ts', () => {
12
+ console.log('MOCKING Call');
13
+ return {
14
+ Call: vi.fn(() => mockCall()),
15
+ };
16
+ });
17
+
18
+ class TestInputMediaDeviceManagerState extends InputMediaDeviceManagerState {
19
+ public getDeviceIdFromStream = vi.fn(() => 'mock-device-id');
20
+ }
21
+
22
+ class TestInputMediaDeviceManager extends InputMediaDeviceManager<TestInputMediaDeviceManagerState> {
23
+ public getDevices = vi.fn(() => of(mockVideoDevices));
24
+ public getStream = vi.fn(() => Promise.resolve(mockVideoStream()));
25
+ public publishStream = vi.fn();
26
+ public stopPublishStream = vi.fn();
27
+ public pause = vi.fn();
28
+ public resume = vi.fn();
29
+
30
+ constructor(call: Call) {
31
+ super(call, new TestInputMediaDeviceManagerState());
32
+ }
33
+ }
34
+
35
+ describe('InputMediaDeviceManager.test', () => {
36
+ let manager: TestInputMediaDeviceManager;
37
+
38
+ beforeEach(() => {
39
+ manager = new TestInputMediaDeviceManager(
40
+ new Call({
41
+ id: '',
42
+ type: '',
43
+ streamClient: new StreamClient('abc123'),
44
+ clientStore: new StreamVideoWriteableStateStore(),
45
+ }),
46
+ );
47
+ });
48
+
49
+ it('list devices', () => {
50
+ const spy = vi.fn();
51
+ manager.listDevices().subscribe(spy);
52
+
53
+ expect(spy).toHaveBeenCalledWith(mockVideoDevices);
54
+ });
55
+
56
+ it('enable device - before joined to call', async () => {
57
+ vi.spyOn(manager, 'getStream');
58
+
59
+ await manager.enable();
60
+
61
+ expect(manager.getStream).toHaveBeenCalledWith({
62
+ deviceId: undefined,
63
+ });
64
+
65
+ expect(manager.state.mediaStream).toBeDefined();
66
+ expect(manager.state.status).toBe('enabled');
67
+ });
68
+
69
+ it('enable device - after joined to call', async () => {
70
+ // @ts-expect-error
71
+ manager['call'].state.callingState = CallingState.JOINED;
72
+
73
+ await manager.enable();
74
+
75
+ expect(manager.publishStream).toHaveBeenCalledWith(
76
+ manager.state.mediaStream,
77
+ );
78
+ });
79
+
80
+ it('enable device should set device id', async () => {
81
+ expect(manager.state.selectedDevice).toBeUndefined();
82
+
83
+ await manager.enable();
84
+
85
+ expect(manager.state.selectedDevice).toBeDefined();
86
+ });
87
+
88
+ it('disable device - before joined to call', async () => {
89
+ await manager.disable();
90
+
91
+ expect(manager.state.mediaStream).toBeUndefined();
92
+ expect(manager.state.status).toBe('disabled');
93
+ });
94
+
95
+ it('disable camera - after joined to call', async () => {
96
+ // @ts-expect-error
97
+ manager['call'].state.callingState = CallingState.JOINED;
98
+ await manager.enable();
99
+
100
+ await manager.disable();
101
+
102
+ expect(manager.stopPublishStream).toHaveBeenCalledWith();
103
+ });
104
+
105
+ it('toggle device', async () => {
106
+ vi.spyOn(manager, 'disable');
107
+ vi.spyOn(manager, 'enable');
108
+
109
+ await manager.toggle();
110
+
111
+ expect(manager.enable).toHaveBeenCalled();
112
+
113
+ await manager.toggle();
114
+
115
+ expect(manager.disable).toHaveBeenCalled();
116
+ });
117
+
118
+ it('select device when status is disabled', async () => {
119
+ const deviceId = mockVideoDevices[0].deviceId;
120
+ await manager.select(deviceId);
121
+
122
+ expect(manager.state.selectedDevice).toBe(deviceId);
123
+ expect(manager.getStream).not.toHaveBeenCalledWith();
124
+ expect(manager.publishStream).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it('select device when status is enabled and in call', async () => {
128
+ // @ts-expect-error
129
+ manager['call'].state.callingState = CallingState.JOINED;
130
+ await manager.enable();
131
+
132
+ const deviceId = mockVideoDevices[1].deviceId;
133
+ await manager.select(deviceId);
134
+
135
+ expect(manager.stopPublishStream).toHaveBeenCalledWith();
136
+ expect(manager.getStream).toHaveBeenCalledWith({
137
+ deviceId,
138
+ });
139
+ expect(manager.publishStream).toHaveBeenCalled();
140
+ });
141
+
142
+ it(`changing media stream constraints shouldn't toggle status`, async () => {
143
+ await manager.enable();
144
+ const spy = vi.fn();
145
+ manager.state.status$.subscribe(spy);
146
+
147
+ expect(spy.mock.calls.length).toBe(1);
148
+
149
+ const deviceId = mockVideoDevices[1].deviceId;
150
+ await manager.select(deviceId);
151
+
152
+ expect(spy.mock.calls.length).toBe(1);
153
+ });
154
+
155
+ afterEach(() => {
156
+ vi.clearAllMocks();
157
+ vi.resetModules();
158
+ });
159
+ });
@@ -0,0 +1,103 @@
1
+ import { Call } from '../../Call';
2
+ import { StreamClient } from '../../coordinator/connection/client';
3
+ import { CallingState, StreamVideoWriteableStateStore } from '../../store';
4
+
5
+ import { afterEach, beforeEach, describe, vi, it, expect } from 'vitest';
6
+ import { mockCall, mockAudioDevices, mockAudioStream } from './mocks';
7
+ import { getAudioStream } from '../devices';
8
+ import { TrackType } from '../../gen/video/sfu/models/models';
9
+ import { MicrophoneManager } from '../MicrophoneManager';
10
+ import { of } from 'rxjs';
11
+
12
+ vi.mock('../devices.ts', () => {
13
+ console.log('MOCKING devices API');
14
+ return {
15
+ disposeOfMediaStream: vi.fn(),
16
+ getAudioDevices: vi.fn(() => {
17
+ return of(mockAudioDevices);
18
+ }),
19
+ getAudioStream: vi.fn(() => Promise.resolve(mockAudioStream())),
20
+ };
21
+ });
22
+
23
+ vi.mock('../../Call.ts', () => {
24
+ console.log('MOCKING Call');
25
+ return {
26
+ Call: vi.fn(() => mockCall()),
27
+ };
28
+ });
29
+
30
+ describe('MicrophoneManager', () => {
31
+ let manager: MicrophoneManager;
32
+
33
+ beforeEach(() => {
34
+ manager = new MicrophoneManager(
35
+ new Call({
36
+ id: '',
37
+ type: '',
38
+ streamClient: new StreamClient('abc123'),
39
+ clientStore: new StreamVideoWriteableStateStore(),
40
+ }),
41
+ );
42
+ });
43
+ it('list devices', () => {
44
+ const spy = vi.fn();
45
+ manager.listDevices().subscribe(spy);
46
+
47
+ expect(spy).toHaveBeenCalledWith(mockAudioDevices);
48
+ });
49
+
50
+ it('get stream', async () => {
51
+ await manager.enable();
52
+
53
+ expect(getAudioStream).toHaveBeenCalledWith({
54
+ deviceId: undefined,
55
+ });
56
+ });
57
+
58
+ it('should get device id from stream', async () => {
59
+ expect(manager.state.selectedDevice).toBeUndefined();
60
+
61
+ await manager.enable();
62
+
63
+ expect(manager.state.selectedDevice).toBeDefined();
64
+ });
65
+
66
+ it('publish stream', async () => {
67
+ // @ts-expect-error
68
+ manager['call'].state.callingState = CallingState.JOINED;
69
+
70
+ await manager.enable();
71
+
72
+ expect(manager['call'].publishAudioStream).toHaveBeenCalledWith(
73
+ manager.state.mediaStream,
74
+ );
75
+ });
76
+
77
+ it('stop publish stream', async () => {
78
+ // @ts-expect-error
79
+ manager['call'].state.callingState = CallingState.JOINED;
80
+ await manager.enable();
81
+
82
+ await manager.disable();
83
+
84
+ expect(manager['call'].stopPublish).toHaveBeenCalledWith(TrackType.AUDIO);
85
+ });
86
+
87
+ it('should pause and resume tracks', async () => {
88
+ await manager.enable();
89
+
90
+ manager.pause();
91
+
92
+ expect(manager.state.mediaStream?.getAudioTracks()[0].enabled).toBe(false);
93
+
94
+ manager.resume();
95
+
96
+ expect(manager.state.mediaStream?.getAudioTracks()[0].enabled).toBe(true);
97
+ });
98
+
99
+ afterEach(() => {
100
+ vi.clearAllMocks();
101
+ vi.resetModules();
102
+ });
103
+ });