@fishjam-cloud/react-client 0.5.0
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.
- package/dist/Client.d.ts +210 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +589 -0
- package/dist/DeviceManager.d.ts +49 -0
- package/dist/DeviceManager.d.ts.map +1 -0
- package/dist/DeviceManager.js +157 -0
- package/dist/ScreenShareManager.d.ts +57 -0
- package/dist/ScreenShareManager.d.ts.map +1 -0
- package/dist/ScreenShareManager.js +135 -0
- package/dist/constraints.d.ts +26 -0
- package/dist/constraints.d.ts.map +1 -0
- package/dist/constraints.js +47 -0
- package/dist/create.d.ts +11 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +228 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/mediaInitializer.d.ts +11 -0
- package/dist/mediaInitializer.d.ts.map +1 -0
- package/dist/mediaInitializer.js +48 -0
- package/dist/state.types.d.ts +60 -0
- package/dist/state.types.d.ts.map +1 -0
- package/dist/state.types.js +1 -0
- package/dist/trackManager.d.ts +24 -0
- package/dist/trackManager.d.ts.map +1 -0
- package/dist/trackManager.js +99 -0
- package/dist/types.d.ts +213 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/useSetupMedia.d.ts +3 -0
- package/dist/useSetupMedia.d.ts.map +1 -0
- package/dist/useSetupMedia.js +262 -0
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +22 -0
- package/dist/utils/localStorage.d.ts +5 -0
- package/dist/utils/localStorage.d.ts.map +1 -0
- package/dist/utils/localStorage.js +21 -0
- package/dist/utils/media.d.ts +12 -0
- package/dist/utils/media.d.ts.map +1 -0
- package/dist/utils/media.js +64 -0
- package/package.json +65 -0
- package/readme.md +151 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { prepareMediaTrackConstraints, toMediaTrackConstraints } from "./constraints";
|
|
2
|
+
import EventEmitter from "events";
|
|
3
|
+
import { getDeviceInfo, getLocalStorageConfig, prepareDeviceState } from "./utils/media";
|
|
4
|
+
import { parseUserMediaError } from "./utils/errors";
|
|
5
|
+
export class DeviceManager extends EventEmitter {
|
|
6
|
+
constraints;
|
|
7
|
+
storageConfig;
|
|
8
|
+
status = "uninitialized";
|
|
9
|
+
deviceType;
|
|
10
|
+
deviceState = {
|
|
11
|
+
media: null,
|
|
12
|
+
mediaStatus: "Not requested",
|
|
13
|
+
devices: null,
|
|
14
|
+
devicesStatus: "Not requested",
|
|
15
|
+
error: null,
|
|
16
|
+
};
|
|
17
|
+
constructor(deviceType, defaultConfig) {
|
|
18
|
+
super();
|
|
19
|
+
this.storageConfig = this.createStorageConfig(defaultConfig?.storage);
|
|
20
|
+
this.deviceType = deviceType;
|
|
21
|
+
this.constraints = toMediaTrackConstraints(defaultConfig?.trackConstraints ?? true);
|
|
22
|
+
}
|
|
23
|
+
createStorageConfig(storage) {
|
|
24
|
+
if (storage === false)
|
|
25
|
+
return null;
|
|
26
|
+
if (storage === true || storage === undefined)
|
|
27
|
+
return getLocalStorageConfig(this.deviceType);
|
|
28
|
+
return storage;
|
|
29
|
+
}
|
|
30
|
+
getStatus() {
|
|
31
|
+
return this.status;
|
|
32
|
+
}
|
|
33
|
+
getConstraints(currentConstraints) {
|
|
34
|
+
if (currentConstraints === false)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (currentConstraints === undefined || currentConstraints === true)
|
|
37
|
+
return this.constraints;
|
|
38
|
+
return currentConstraints ?? this.constraints;
|
|
39
|
+
}
|
|
40
|
+
getMedia = () => this.deviceState.media;
|
|
41
|
+
getTracks = () => {
|
|
42
|
+
if (this.deviceType === "audio") {
|
|
43
|
+
return this.deviceState.media?.stream?.getAudioTracks() ?? [];
|
|
44
|
+
}
|
|
45
|
+
return this.deviceState.media?.stream?.getVideoTracks() ?? [];
|
|
46
|
+
};
|
|
47
|
+
initialize = (stream, track, devices, requestedMedia, error = null) => {
|
|
48
|
+
this.deviceState = prepareDeviceState(stream, track, devices, error, requestedMedia);
|
|
49
|
+
const deviceInfo = this.deviceState.media?.deviceInfo;
|
|
50
|
+
if (deviceInfo)
|
|
51
|
+
this.saveLastDevice(deviceInfo);
|
|
52
|
+
this.status = "initialized";
|
|
53
|
+
this.setupOnEndedCallback();
|
|
54
|
+
return this.deviceState;
|
|
55
|
+
};
|
|
56
|
+
setupOnEndedCallback() {
|
|
57
|
+
if (this.deviceState?.media?.track) {
|
|
58
|
+
this.deviceState.media.track.addEventListener("ended", async (event) => await this.onTrackEnded(event.target.id));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
onTrackEnded = async (trackId) => {
|
|
62
|
+
if (trackId === this?.deviceState.media?.track?.id) {
|
|
63
|
+
await this.stop();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
getLastDevice() {
|
|
67
|
+
return this.storageConfig?.getLastDevice?.() ?? null;
|
|
68
|
+
}
|
|
69
|
+
saveLastDevice(info) {
|
|
70
|
+
if (!this.storageConfig)
|
|
71
|
+
console.warn("Device manager storage has been disabled");
|
|
72
|
+
this.storageConfig?.saveLastDevice(info);
|
|
73
|
+
}
|
|
74
|
+
// todo `audioDeviceId / videoDeviceId === true` means use last device
|
|
75
|
+
async start(deviceId) {
|
|
76
|
+
const shouldRestart = !!deviceId && deviceId !== this.deviceState.media?.deviceInfo?.deviceId;
|
|
77
|
+
const newDevice = deviceId === true ? this.getLastDevice()?.deviceId || true : deviceId;
|
|
78
|
+
const trackConstraints = this.constraints;
|
|
79
|
+
const exactConstraints = shouldRestart && prepareMediaTrackConstraints(newDevice, trackConstraints);
|
|
80
|
+
if (!exactConstraints)
|
|
81
|
+
return;
|
|
82
|
+
this.deviceState.mediaStatus = "Requesting";
|
|
83
|
+
this.emit("devicesStarted", { ...this.deviceState, restarting: shouldRestart, constraints: newDevice }, this.deviceState);
|
|
84
|
+
try {
|
|
85
|
+
const stream = await navigator.mediaDevices.getUserMedia({ [this.deviceType]: exactConstraints });
|
|
86
|
+
const getTrack = () => {
|
|
87
|
+
const tracks = this.deviceType === "audio" ? stream.getAudioTracks() : stream.getVideoTracks();
|
|
88
|
+
return tracks[0] ?? null;
|
|
89
|
+
};
|
|
90
|
+
const currentDeviceId = getTrack()?.getSettings()?.deviceId;
|
|
91
|
+
const deviceInfo = currentDeviceId ? getDeviceInfo(currentDeviceId, this.deviceState.devices ?? []) : null;
|
|
92
|
+
if (deviceInfo) {
|
|
93
|
+
this.saveLastDevice?.(deviceInfo);
|
|
94
|
+
}
|
|
95
|
+
// The device manager assumes that there is only one audio and video track.
|
|
96
|
+
// All previous tracks are deactivated even if the browser is able to handle multiple active sessions. (Chrome, Firefox)
|
|
97
|
+
//
|
|
98
|
+
// Safari always deactivates the track and emits the `ended` event.
|
|
99
|
+
// Its handling is asynchronous and can be executed even before returning a value from the re-execution of `getUserMedia`.
|
|
100
|
+
// In such a case, the tracks are already deactivated at this point (logic in `onTrackEnded` method).
|
|
101
|
+
// The track is null, so the stop method will not execute.
|
|
102
|
+
//
|
|
103
|
+
// However, if Safari has not yet handled this event, the tracks are manually stopped at this point.
|
|
104
|
+
// Manually stopping tracks on its own does not generate the `ended` event.
|
|
105
|
+
// The ended event in Safari has already been emitted and will be handled in the future.
|
|
106
|
+
// Therefore, in the `onTrackEnded` method, events for already stopped tracks are filtered out to prevent the state from being damaged.
|
|
107
|
+
if (shouldRestart) {
|
|
108
|
+
this.deviceState?.media?.track?.stop();
|
|
109
|
+
this.deviceState.media = {
|
|
110
|
+
stream: stream,
|
|
111
|
+
track: getTrack(),
|
|
112
|
+
deviceInfo,
|
|
113
|
+
enabled: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
this.setupOnEndedCallback();
|
|
117
|
+
this.deviceState.mediaStatus = "OK";
|
|
118
|
+
this.emit("devicesReady", { ...this.deviceState, restarted: shouldRestart }, this.deviceState);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
const parsedError = parseUserMediaError(err);
|
|
122
|
+
const event = {
|
|
123
|
+
parsedError,
|
|
124
|
+
constraints: exactConstraints,
|
|
125
|
+
};
|
|
126
|
+
if (exactConstraints) {
|
|
127
|
+
this.deviceState.error = parsedError;
|
|
128
|
+
}
|
|
129
|
+
this.emit("error", event, this.deviceState);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async stop() {
|
|
133
|
+
this.deviceState.media?.track?.stop();
|
|
134
|
+
this.deviceState.media = null;
|
|
135
|
+
this.emit("deviceStopped", this.deviceState);
|
|
136
|
+
}
|
|
137
|
+
disable() {
|
|
138
|
+
if (!this.deviceState.media || !this.deviceState.media?.track) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.deviceState.media.track.enabled = false;
|
|
142
|
+
this.deviceState.media.enabled = false;
|
|
143
|
+
this.emit("deviceDisabled", this.deviceState);
|
|
144
|
+
}
|
|
145
|
+
enable() {
|
|
146
|
+
if (!this.deviceState.media || !this.deviceState.media?.track) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.deviceState.media.track.enabled = true;
|
|
150
|
+
this.deviceState.media.enabled = true;
|
|
151
|
+
this.emit("deviceEnabled", this.deviceState);
|
|
152
|
+
}
|
|
153
|
+
setConfig(storage, constraints) {
|
|
154
|
+
this.storageConfig = this.createStorageConfig(storage);
|
|
155
|
+
this.constraints = constraints ? toMediaTrackConstraints(constraints) : undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type TypedEmitter from "typed-emitter";
|
|
2
|
+
import type { DeviceError, DevicesStatus } from "./types";
|
|
3
|
+
import type { TrackKind } from "@fishjam-cloud/ts-client";
|
|
4
|
+
export type TrackType = TrackKind | "audiovideo";
|
|
5
|
+
export type MediaDeviceType = "displayMedia" | "userMedia";
|
|
6
|
+
export type DisplayMediaManagerEvents = {
|
|
7
|
+
deviceReady: (event: {
|
|
8
|
+
type: TrackType;
|
|
9
|
+
}, state: ScreenShareDeviceState) => void;
|
|
10
|
+
deviceStopped: (event: {
|
|
11
|
+
type: TrackType;
|
|
12
|
+
}, state: ScreenShareDeviceState) => void;
|
|
13
|
+
deviceEnabled: (event: {
|
|
14
|
+
type: TrackType;
|
|
15
|
+
}, state: ScreenShareDeviceState) => void;
|
|
16
|
+
deviceDisabled: (event: {
|
|
17
|
+
type: TrackType;
|
|
18
|
+
}, state: ScreenShareDeviceState) => void;
|
|
19
|
+
error: (event: {
|
|
20
|
+
type: TrackType;
|
|
21
|
+
error: DeviceError | null;
|
|
22
|
+
rawError: any;
|
|
23
|
+
}, state: ScreenShareDeviceState) => void;
|
|
24
|
+
};
|
|
25
|
+
export interface ScreenShareManagerConfig {
|
|
26
|
+
audioTrackConstraints?: boolean | MediaTrackConstraints;
|
|
27
|
+
videoTrackConstraints?: boolean | MediaTrackConstraints;
|
|
28
|
+
}
|
|
29
|
+
export type ScreenShareMedia = {
|
|
30
|
+
stream: MediaStream | null;
|
|
31
|
+
track: MediaStreamTrack | null;
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
};
|
|
34
|
+
export type ScreenShareDeviceState = {
|
|
35
|
+
status: DevicesStatus;
|
|
36
|
+
audioMedia: ScreenShareMedia | null;
|
|
37
|
+
videoMedia: ScreenShareMedia | null;
|
|
38
|
+
error: DeviceError | null;
|
|
39
|
+
};
|
|
40
|
+
declare const ScreenShareManager_base: new () => TypedEmitter<DisplayMediaManagerEvents>;
|
|
41
|
+
export declare class ScreenShareManager extends ScreenShareManager_base {
|
|
42
|
+
private readonly defaultConfig?;
|
|
43
|
+
private config?;
|
|
44
|
+
private data;
|
|
45
|
+
getSnapshot(): ScreenShareDeviceState;
|
|
46
|
+
constructor(defaultConfig?: ScreenShareManagerConfig);
|
|
47
|
+
setConfig(config: ScreenShareManagerConfig): void;
|
|
48
|
+
private getType;
|
|
49
|
+
getMedia: () => ScreenShareMedia | null;
|
|
50
|
+
start(config?: ScreenShareManagerConfig): Promise<void>;
|
|
51
|
+
private setupOnEndedCallback;
|
|
52
|
+
private onTrackEnded;
|
|
53
|
+
stop(type: TrackType): Promise<void>;
|
|
54
|
+
setEnable(type: TrackType, value: boolean): void;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=ScreenShareManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScreenShareManager.d.ts","sourceRoot":"","sources":["../src/ScreenShareManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,YAAY,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AACjD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,WAAW,CAAC;AAE3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACjF,aAAa,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACnF,cAAc,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACpF,KAAK,EAAE,CACL,KAAK,EAAE;QACL,IAAI,EAAE,SAAS,CAAC;QAChB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,GAAG,CAAC;KACf,EACD,KAAK,EAAE,sBAAsB,KAC1B,IAAI,CAAC;CACX,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,qBAAqB,CAAC,EAAE,OAAO,GAAG,qBAAqB,CAAC;IACxD,qBAAqB,CAAC,EAAE,OAAO,GAAG,qBAAqB,CAAC;CACzD;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3B,CAAC;iDAEiE,YAAY,CAAC,yBAAyB,CAAC;AAA1G,qBAAa,kBAAmB,SAAQ,uBAAmE;IACzG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA2B;IAC1D,OAAO,CAAC,MAAM,CAAC,CAA2B;IAE1C,OAAO,CAAC,IAAI,CAKV;IAGK,WAAW,IAAI,sBAAsB;gBAIhC,aAAa,CAAC,EAAE,wBAAwB;IAK7C,SAAS,CAAC,MAAM,EAAE,wBAAwB;IAIjD,OAAO,CAAC,OAAO;IAOR,QAAQ,gCAA8B;IAEhC,KAAK,CAAC,MAAM,CAAC,EAAE,wBAAwB;IA0CpD,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,YAAY,CAKlB;IAEW,IAAI,CAAC,IAAI,EAAE,SAAS;IA2B1B,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO;CAwBjD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
2
|
+
import { parseUserMediaError } from "./utils/errors";
|
|
3
|
+
export class ScreenShareManager extends EventEmitter {
|
|
4
|
+
defaultConfig;
|
|
5
|
+
config;
|
|
6
|
+
data = {
|
|
7
|
+
audioMedia: null,
|
|
8
|
+
videoMedia: null,
|
|
9
|
+
status: "Not requested",
|
|
10
|
+
error: null,
|
|
11
|
+
};
|
|
12
|
+
// todo add nested read only
|
|
13
|
+
getSnapshot() {
|
|
14
|
+
return this.data;
|
|
15
|
+
}
|
|
16
|
+
constructor(defaultConfig) {
|
|
17
|
+
super();
|
|
18
|
+
this.defaultConfig = defaultConfig;
|
|
19
|
+
}
|
|
20
|
+
setConfig(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
getType(options) {
|
|
24
|
+
if (options.audio && options.video)
|
|
25
|
+
return "audiovideo";
|
|
26
|
+
if (options.audio)
|
|
27
|
+
return "audio";
|
|
28
|
+
if (options.video)
|
|
29
|
+
return "video";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
getMedia = () => this.data.videoMedia;
|
|
33
|
+
async start(config) {
|
|
34
|
+
const options = {
|
|
35
|
+
video: config?.videoTrackConstraints ??
|
|
36
|
+
this.config?.videoTrackConstraints ??
|
|
37
|
+
this.defaultConfig?.videoTrackConstraints,
|
|
38
|
+
audio: config?.audioTrackConstraints ??
|
|
39
|
+
this.config?.audioTrackConstraints ??
|
|
40
|
+
this.defaultConfig?.audioTrackConstraints,
|
|
41
|
+
};
|
|
42
|
+
const type = this.getType(options);
|
|
43
|
+
if (!type)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
const newStream = await navigator.mediaDevices.getDisplayMedia(options);
|
|
47
|
+
this.data = {
|
|
48
|
+
error: null,
|
|
49
|
+
videoMedia: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
stream: newStream,
|
|
52
|
+
track: newStream?.getVideoTracks()[0] ?? null,
|
|
53
|
+
},
|
|
54
|
+
audioMedia: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
stream: newStream,
|
|
57
|
+
track: newStream?.getAudioTracks()[0] ?? null,
|
|
58
|
+
},
|
|
59
|
+
status: "OK",
|
|
60
|
+
};
|
|
61
|
+
this.setupOnEndedCallback();
|
|
62
|
+
this.emit("deviceReady", { type: type }, this.data);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const parsedError = parseUserMediaError(error);
|
|
66
|
+
this.emit("error", { type, error: parsedError, rawError: error }, this.data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
setupOnEndedCallback() {
|
|
70
|
+
if (this.data.videoMedia?.track) {
|
|
71
|
+
this.data.videoMedia.track.addEventListener("ended", async (event) => await this.onTrackEnded("video", event.target.id));
|
|
72
|
+
}
|
|
73
|
+
if (this.data.audioMedia?.track) {
|
|
74
|
+
this.data.audioMedia.track.addEventListener("ended", async (event) => await this.onTrackEnded("audio", event.target.id));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
onTrackEnded = async (kind, trackId) => {
|
|
78
|
+
const mediaType = kind === "video" ? "videoMedia" : "audioMedia";
|
|
79
|
+
if (trackId === this?.data[mediaType]?.track?.id) {
|
|
80
|
+
await this.stop("audiovideo");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
async stop(type) {
|
|
84
|
+
if (type === "video") {
|
|
85
|
+
for (const track of this.data?.videoMedia?.stream?.getTracks() ?? []) {
|
|
86
|
+
track.stop();
|
|
87
|
+
}
|
|
88
|
+
this.data.videoMedia = null;
|
|
89
|
+
}
|
|
90
|
+
else if (type === "audio") {
|
|
91
|
+
// todo test it
|
|
92
|
+
for (const track of this.data?.audioMedia?.stream?.getTracks() ?? []) {
|
|
93
|
+
track.stop();
|
|
94
|
+
}
|
|
95
|
+
this.data.audioMedia = null;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
for (const track of this.data?.videoMedia?.stream?.getTracks() ?? []) {
|
|
99
|
+
track.stop();
|
|
100
|
+
}
|
|
101
|
+
this.data.videoMedia = null;
|
|
102
|
+
for (const track of this.data?.audioMedia?.stream?.getTracks() ?? []) {
|
|
103
|
+
track.stop();
|
|
104
|
+
}
|
|
105
|
+
this.data.audioMedia = null;
|
|
106
|
+
}
|
|
107
|
+
this.emit("deviceStopped", { type }, this.data);
|
|
108
|
+
}
|
|
109
|
+
setEnable(type, value) {
|
|
110
|
+
if (type === "video" && this.data.videoMedia?.track) {
|
|
111
|
+
this.data.videoMedia.track.enabled = value;
|
|
112
|
+
this.data.videoMedia.enabled = value;
|
|
113
|
+
}
|
|
114
|
+
else if (type === "audio" && this.data.audioMedia?.track) {
|
|
115
|
+
this.data.audioMedia.track.enabled = value;
|
|
116
|
+
this.data.audioMedia.enabled = value;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (this.data.videoMedia?.track) {
|
|
120
|
+
this.data.videoMedia.track.enabled = value;
|
|
121
|
+
this.data.videoMedia.enabled = value;
|
|
122
|
+
}
|
|
123
|
+
if (this.data.audioMedia?.track) {
|
|
124
|
+
this.data.audioMedia.track.enabled = value;
|
|
125
|
+
this.data.audioMedia.enabled = value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (value) {
|
|
129
|
+
this.emit("deviceEnabled", { type }, this.data);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.emit("deviceDisabled", { type }, this.data);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const AUDIO_TRACK_CONSTRAINTS: MediaTrackConstraints;
|
|
2
|
+
export declare const VIDEO_TRACK_CONSTRAINTS: MediaTrackConstraints;
|
|
3
|
+
export declare const SCREEN_SHARING_MEDIA_CONSTRAINTS: MediaStreamConstraints;
|
|
4
|
+
export declare const toMediaTrackConstraints: (constraint?: boolean | MediaTrackConstraints) => MediaTrackConstraints | undefined;
|
|
5
|
+
export declare const prepareMediaTrackConstraints: (deviceId: string | undefined | boolean, constraints: MediaTrackConstraints | undefined) => MediaTrackConstraints | boolean;
|
|
6
|
+
export declare const getExactDeviceConstraint: (constraints: MediaTrackConstraints | undefined, deviceId: string | undefined) => {
|
|
7
|
+
deviceId: {
|
|
8
|
+
exact: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
advanced?: MediaTrackConstraintSet[];
|
|
11
|
+
aspectRatio?: ConstrainDouble;
|
|
12
|
+
autoGainControl?: ConstrainBoolean;
|
|
13
|
+
channelCount?: ConstrainULong;
|
|
14
|
+
displaySurface?: ConstrainDOMString;
|
|
15
|
+
echoCancellation?: ConstrainBoolean;
|
|
16
|
+
facingMode?: ConstrainDOMString;
|
|
17
|
+
frameRate?: ConstrainDouble;
|
|
18
|
+
groupId?: ConstrainDOMString;
|
|
19
|
+
height?: ConstrainULong;
|
|
20
|
+
noiseSuppression?: ConstrainBoolean;
|
|
21
|
+
sampleRate?: ConstrainULong;
|
|
22
|
+
sampleSize?: ConstrainULong;
|
|
23
|
+
width?: ConstrainULong;
|
|
24
|
+
};
|
|
25
|
+
export declare const prepareConstraints: (deviceIdToStart: string | undefined, constraints: MediaTrackConstraints | undefined) => MediaTrackConstraints | undefined | boolean;
|
|
26
|
+
//# sourceMappingURL=constraints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constraints.d.ts","sourceRoot":"","sources":["../src/constraints.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,EAAE,qBAErC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,qBAerC,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,sBAM9C,CAAC;AAEF,eAAO,MAAM,uBAAuB,gBACrB,OAAO,GAAG,qBAAqB,KAC3C,qBAAqB,GAAG,SAK1B,CAAC;AAEF,eAAO,MAAM,4BAA4B,aAC7B,MAAM,GAAG,SAAS,GAAG,OAAO,eACzB,qBAAqB,GAAG,SAAS,KAC7C,qBAAqB,GAAG,OAK1B,CAAC;AAEF,eAAO,MAAM,wBAAwB,gBACtB,qBAAqB,GAAG,SAAS,YACpC,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;;;CAI5B,CAAC;AAEH,eAAO,MAAM,kBAAkB,oBACZ,MAAM,GAAG,SAAS,eACtB,qBAAqB,GAAG,SAAS,KAC7C,qBAAqB,GAAG,SAAS,GAAG,OAEtC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const AUDIO_TRACK_CONSTRAINTS = {
|
|
2
|
+
advanced: [{ autoGainControl: true }, { noiseSuppression: true }, { echoCancellation: true }],
|
|
3
|
+
};
|
|
4
|
+
export const VIDEO_TRACK_CONSTRAINTS = {
|
|
5
|
+
width: {
|
|
6
|
+
max: 1280,
|
|
7
|
+
ideal: 1280,
|
|
8
|
+
min: 640,
|
|
9
|
+
},
|
|
10
|
+
height: {
|
|
11
|
+
max: 720,
|
|
12
|
+
ideal: 720,
|
|
13
|
+
min: 320,
|
|
14
|
+
},
|
|
15
|
+
frameRate: {
|
|
16
|
+
max: 30,
|
|
17
|
+
ideal: 24,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export const SCREEN_SHARING_MEDIA_CONSTRAINTS = {
|
|
21
|
+
video: {
|
|
22
|
+
frameRate: { ideal: 20, max: 25 },
|
|
23
|
+
width: { max: 1920, ideal: 1920 },
|
|
24
|
+
height: { max: 1080, ideal: 1080 },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export const toMediaTrackConstraints = (constraint) => {
|
|
28
|
+
if (typeof constraint === "boolean") {
|
|
29
|
+
return constraint ? {} : undefined;
|
|
30
|
+
}
|
|
31
|
+
return constraint;
|
|
32
|
+
};
|
|
33
|
+
export const prepareMediaTrackConstraints = (deviceId, constraints) => {
|
|
34
|
+
if (!deviceId)
|
|
35
|
+
return false;
|
|
36
|
+
if (deviceId === true)
|
|
37
|
+
return { ...constraints };
|
|
38
|
+
const exactId = deviceId ? { deviceId: { exact: deviceId } } : {};
|
|
39
|
+
return { ...constraints, ...exactId };
|
|
40
|
+
};
|
|
41
|
+
export const getExactDeviceConstraint = (constraints, deviceId) => ({
|
|
42
|
+
...constraints,
|
|
43
|
+
deviceId: { exact: deviceId },
|
|
44
|
+
});
|
|
45
|
+
export const prepareConstraints = (deviceIdToStart, constraints) => {
|
|
46
|
+
return deviceIdToStart ? getExactDeviceConstraint(constraints, deviceIdToStart) : constraints;
|
|
47
|
+
};
|
package/dist/create.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CreateConfig } from "@fishjam-cloud/ts-client";
|
|
2
|
+
import type { DeviceManagerConfig, CreateFishjamClient } from "./types";
|
|
3
|
+
import type { ScreenShareManagerConfig } from "./ScreenShareManager";
|
|
4
|
+
/**
|
|
5
|
+
* Create a client that can be used with a context.
|
|
6
|
+
* Returns context provider, and two hooks to interact with the context.
|
|
7
|
+
*
|
|
8
|
+
* @returns ContextProvider, useSelector, useConnect
|
|
9
|
+
*/
|
|
10
|
+
export declare const create: <PeerMetadata, TrackMetadata>(config?: CreateConfig<PeerMetadata, TrackMetadata>, deviceManagerDefaultConfig?: DeviceManagerConfig, screenShareManagerDefaultConfig?: ScreenShareManagerConfig) => CreateFishjamClient<PeerMetadata, TrackMetadata>;
|
|
11
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../src/create.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EACV,mBAAmB,EAGnB,mBAAmB,EAMpB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAGrE;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,YAAY,EAAE,aAAa,WACvC,YAAY,CAAC,YAAY,EAAE,aAAa,CAAC,+BACrB,mBAAmB,oCACd,wBAAwB,KACzD,mBAAmB,CAAC,YAAY,EAAE,aAAa,CAkRjD,CAAC"}
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { Client } from "./Client";
|
|
4
|
+
import { createUseSetupMediaHook } from "./useSetupMedia";
|
|
5
|
+
/**
|
|
6
|
+
* Create a client that can be used with a context.
|
|
7
|
+
* Returns context provider, and two hooks to interact with the context.
|
|
8
|
+
*
|
|
9
|
+
* @returns ContextProvider, useSelector, useConnect
|
|
10
|
+
*/
|
|
11
|
+
export const create = (config, deviceManagerDefaultConfig, screenShareManagerDefaultConfig) => {
|
|
12
|
+
const FishjamContext = createContext(undefined);
|
|
13
|
+
const FishjamContextProvider = ({ children, }) => {
|
|
14
|
+
const memoClient = useMemo(() => {
|
|
15
|
+
return new Client({
|
|
16
|
+
clientConfig: config,
|
|
17
|
+
deviceManagerDefaultConfig,
|
|
18
|
+
screenShareManagerDefaultConfig,
|
|
19
|
+
});
|
|
20
|
+
}, []);
|
|
21
|
+
const clientRef = useRef(memoClient);
|
|
22
|
+
const mutationRef = useRef(false);
|
|
23
|
+
const subscribe = useCallback((cb) => {
|
|
24
|
+
const client = clientRef.current;
|
|
25
|
+
const callback = () => {
|
|
26
|
+
mutationRef.current = true;
|
|
27
|
+
cb();
|
|
28
|
+
};
|
|
29
|
+
client.on("socketOpen", callback);
|
|
30
|
+
client.on("socketError", callback);
|
|
31
|
+
client.on("socketClose", callback);
|
|
32
|
+
client.on("authSuccess", callback);
|
|
33
|
+
client.on("authError", callback);
|
|
34
|
+
client.on("disconnected", callback);
|
|
35
|
+
client.on("joined", callback);
|
|
36
|
+
client.on("joinError", callback);
|
|
37
|
+
client.on("peerJoined", callback);
|
|
38
|
+
client.on("peerUpdated", callback);
|
|
39
|
+
client.on("peerLeft", callback);
|
|
40
|
+
client.on("reconnected", callback);
|
|
41
|
+
client.on("reconnectionRetriesLimitReached", callback);
|
|
42
|
+
client.on("reconnectionStarted", callback);
|
|
43
|
+
client.on("componentAdded", callback);
|
|
44
|
+
client.on("componentUpdated", callback);
|
|
45
|
+
client.on("componentRemoved", callback);
|
|
46
|
+
client.on("trackReady", callback);
|
|
47
|
+
client.on("trackAdded", callback);
|
|
48
|
+
client.on("trackRemoved", callback);
|
|
49
|
+
client.on("trackUpdated", callback);
|
|
50
|
+
client.on("bandwidthEstimationChanged", callback);
|
|
51
|
+
client.on("encodingChanged", callback);
|
|
52
|
+
client.on("voiceActivityChanged", callback);
|
|
53
|
+
client.on("deviceDisabled", callback);
|
|
54
|
+
client.on("deviceEnabled", callback);
|
|
55
|
+
client.on("managerInitialized", callback);
|
|
56
|
+
client.on("managerStarted", callback);
|
|
57
|
+
client.on("deviceStopped", callback);
|
|
58
|
+
client.on("deviceReady", callback);
|
|
59
|
+
client.on("devicesStarted", callback);
|
|
60
|
+
client.on("devicesReady", callback);
|
|
61
|
+
client.on("error", callback);
|
|
62
|
+
client.on("targetTrackEncodingRequested", callback);
|
|
63
|
+
client.on("localTrackAdded", callback);
|
|
64
|
+
client.on("localTrackRemoved", callback);
|
|
65
|
+
client.on("localTrackReplaced", callback);
|
|
66
|
+
client.on("localTrackMuted", callback);
|
|
67
|
+
client.on("localTrackUnmuted", callback);
|
|
68
|
+
client.on("localTrackBandwidthSet", callback);
|
|
69
|
+
client.on("localTrackEncodingBandwidthSet", callback);
|
|
70
|
+
client.on("localTrackEncodingEnabled", callback);
|
|
71
|
+
client.on("localTrackEncodingDisabled", callback);
|
|
72
|
+
client.on("localPeerMetadataChanged", callback);
|
|
73
|
+
client.on("localTrackMetadataChanged", callback);
|
|
74
|
+
client.on("disconnectRequested", callback);
|
|
75
|
+
return () => {
|
|
76
|
+
client.removeListener("socketOpen", callback);
|
|
77
|
+
client.removeListener("socketError", callback);
|
|
78
|
+
client.removeListener("socketClose", callback);
|
|
79
|
+
client.removeListener("authSuccess", callback);
|
|
80
|
+
client.removeListener("authError", callback);
|
|
81
|
+
client.removeListener("disconnected", callback);
|
|
82
|
+
client.removeListener("joined", callback);
|
|
83
|
+
client.removeListener("joinError", callback);
|
|
84
|
+
client.removeListener("peerJoined", callback);
|
|
85
|
+
client.removeListener("peerUpdated", callback);
|
|
86
|
+
client.removeListener("peerLeft", callback);
|
|
87
|
+
client.removeListener("reconnected", callback);
|
|
88
|
+
client.removeListener("reconnectionRetriesLimitReached", callback);
|
|
89
|
+
client.removeListener("reconnectionStarted", callback);
|
|
90
|
+
client.removeListener("componentAdded", callback);
|
|
91
|
+
client.removeListener("componentUpdated", callback);
|
|
92
|
+
client.removeListener("componentRemoved", callback);
|
|
93
|
+
client.removeListener("trackReady", callback);
|
|
94
|
+
client.removeListener("trackAdded", callback);
|
|
95
|
+
client.removeListener("trackRemoved", callback);
|
|
96
|
+
client.removeListener("trackUpdated", callback);
|
|
97
|
+
client.removeListener("bandwidthEstimationChanged", callback);
|
|
98
|
+
client.removeListener("encodingChanged", callback);
|
|
99
|
+
client.removeListener("voiceActivityChanged", callback);
|
|
100
|
+
client.removeListener("deviceDisabled", callback);
|
|
101
|
+
client.removeListener("deviceEnabled", callback);
|
|
102
|
+
client.removeListener("managerInitialized", callback);
|
|
103
|
+
client.removeListener("managerStarted", callback);
|
|
104
|
+
client.removeListener("deviceStopped", callback);
|
|
105
|
+
client.removeListener("devicesStarted", callback);
|
|
106
|
+
client.removeListener("devicesReady", callback);
|
|
107
|
+
client.removeListener("error", callback);
|
|
108
|
+
client.removeListener("targetTrackEncodingRequested", callback);
|
|
109
|
+
client.removeListener("localTrackAdded", callback);
|
|
110
|
+
client.removeListener("localTrackRemoved", callback);
|
|
111
|
+
client.removeListener("localTrackReplaced", callback);
|
|
112
|
+
client.removeListener("localTrackMuted", callback);
|
|
113
|
+
client.removeListener("localTrackUnmuted", callback);
|
|
114
|
+
client.removeListener("localTrackBandwidthSet", callback);
|
|
115
|
+
client.removeListener("localTrackEncodingBandwidthSet", callback);
|
|
116
|
+
client.removeListener("localTrackEncodingEnabled", callback);
|
|
117
|
+
client.removeListener("localTrackEncodingDisabled", callback);
|
|
118
|
+
client.removeListener("localPeerMetadataChanged", callback);
|
|
119
|
+
client.removeListener("localTrackMetadataChanged", callback);
|
|
120
|
+
client.removeListener("disconnectRequested", callback);
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
const lastSnapshotRef = useRef(null);
|
|
124
|
+
const getSnapshot = useCallback(() => {
|
|
125
|
+
if (mutationRef.current || lastSnapshotRef.current === null) {
|
|
126
|
+
const state = {
|
|
127
|
+
remote: clientRef.current.peers,
|
|
128
|
+
screenShareManager: clientRef.current.screenShareManager,
|
|
129
|
+
media: clientRef.current.media,
|
|
130
|
+
bandwidthEstimation: clientRef.current.bandwidthEstimation,
|
|
131
|
+
tracks: clientRef.current.peersTracks,
|
|
132
|
+
local: clientRef.current.local,
|
|
133
|
+
status: clientRef.current.status,
|
|
134
|
+
devices: clientRef.current.devices,
|
|
135
|
+
videoTrackManager: clientRef.current.videoTrackManager,
|
|
136
|
+
audioTrackManager: clientRef.current.audioTrackManager,
|
|
137
|
+
client: clientRef.current,
|
|
138
|
+
reconnectionStatus: clientRef.current.reconnectionStatus,
|
|
139
|
+
};
|
|
140
|
+
lastSnapshotRef.current = state;
|
|
141
|
+
mutationRef.current = false;
|
|
142
|
+
}
|
|
143
|
+
return lastSnapshotRef.current;
|
|
144
|
+
}, []);
|
|
145
|
+
const state = useSyncExternalStore(subscribe, getSnapshot);
|
|
146
|
+
return _jsx(FishjamContext.Provider, { value: { state }, children: children });
|
|
147
|
+
};
|
|
148
|
+
const useFishjamContext = () => {
|
|
149
|
+
const context = useContext(FishjamContext);
|
|
150
|
+
if (!context)
|
|
151
|
+
throw new Error("useFishjamContext must be used within a FishjamContextProvider");
|
|
152
|
+
return context;
|
|
153
|
+
};
|
|
154
|
+
const useSelector = (selector) => {
|
|
155
|
+
const { state } = useFishjamContext();
|
|
156
|
+
return useMemo(() => selector(state), [selector, state]);
|
|
157
|
+
};
|
|
158
|
+
const useConnect = () => {
|
|
159
|
+
const { state } = useFishjamContext();
|
|
160
|
+
return useMemo(() => {
|
|
161
|
+
return (config) => {
|
|
162
|
+
state.client.connect(config);
|
|
163
|
+
return () => {
|
|
164
|
+
state.client.disconnect();
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
}, [state.client]);
|
|
168
|
+
};
|
|
169
|
+
const useDisconnect = () => {
|
|
170
|
+
const { state } = useFishjamContext();
|
|
171
|
+
return useCallback(() => {
|
|
172
|
+
state.client.disconnect();
|
|
173
|
+
}, [state.client]);
|
|
174
|
+
};
|
|
175
|
+
const useStatus = () => useSelector((s) => s.status);
|
|
176
|
+
const useTracks = () => useSelector((s) => s.tracks);
|
|
177
|
+
const useClient = () => useSelector((s) => s.client);
|
|
178
|
+
const useCamera = () => {
|
|
179
|
+
const { state } = useFishjamContext();
|
|
180
|
+
return { ...state.devices.camera, ...state.videoTrackManager };
|
|
181
|
+
};
|
|
182
|
+
const useMicrophone = () => {
|
|
183
|
+
const { state } = useFishjamContext();
|
|
184
|
+
return { ...state.devices.microphone, ...state.audioTrackManager };
|
|
185
|
+
};
|
|
186
|
+
const useScreenShare = () => {
|
|
187
|
+
const { state } = useFishjamContext();
|
|
188
|
+
return { ...state.devices.screenShare };
|
|
189
|
+
};
|
|
190
|
+
const useReconnection = () => {
|
|
191
|
+
const { state } = useFishjamContext();
|
|
192
|
+
return {
|
|
193
|
+
status: state.reconnectionStatus,
|
|
194
|
+
isReconnecting: state.reconnectionStatus === "reconnecting",
|
|
195
|
+
isError: state.reconnectionStatus === "error",
|
|
196
|
+
isIdle: state.reconnectionStatus === "idle",
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
const getPeerWithDistinguishedTracks = (peerState) => {
|
|
200
|
+
const localTracks = Object.values(peerState.tracks ?? {});
|
|
201
|
+
const videoTrack = localTracks.find(({ track }) => track?.kind === "video");
|
|
202
|
+
const audioTrack = localTracks.find(({ track }) => track?.kind === "audio");
|
|
203
|
+
return { ...peerState, videoTrack, audioTrack };
|
|
204
|
+
};
|
|
205
|
+
const useParticipants = () => {
|
|
206
|
+
const { state } = useFishjamContext();
|
|
207
|
+
const localParticipant = state.local
|
|
208
|
+
? getPeerWithDistinguishedTracks(state.local)
|
|
209
|
+
: null;
|
|
210
|
+
const participants = Object.values(state.remote).map(getPeerWithDistinguishedTracks);
|
|
211
|
+
return { localParticipant, participants };
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
FishjamContextProvider,
|
|
215
|
+
useSelector,
|
|
216
|
+
useConnect,
|
|
217
|
+
useDisconnect,
|
|
218
|
+
useStatus,
|
|
219
|
+
useTracks,
|
|
220
|
+
useSetupMedia: createUseSetupMediaHook(useFishjamContext),
|
|
221
|
+
useParticipants,
|
|
222
|
+
useCamera,
|
|
223
|
+
useMicrophone,
|
|
224
|
+
useScreenShare,
|
|
225
|
+
useClient,
|
|
226
|
+
useReconnection,
|
|
227
|
+
};
|
|
228
|
+
};
|