@vkontakte/media-streaming 1.0.1-dev.7c4e0d2.0 → 1.0.1-dev.e965d10.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/{esnext.cjs → media-streaming.cjs} +5 -5
- package/media-streaming.esm.js +668 -0
- package/package.json +9 -9
- package/types/MediaConstraints.d.ts +8 -0
- package/types/MediaError.d.ts +4 -0
- package/types/ScreenConstraints.d.ts +5 -0
- package/types/WebRTCStreamer.d.ts +51 -0
- package/types/constants.d.ts +31 -0
- package/types/env.d.ts +1 -0
- package/types/index.d.ts +4 -0
- package/types/types.d.ts +66 -0
- package/types/utils.d.ts +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @vkontakte/media-streaming v1.0.1-dev.
|
|
3
|
-
* Mon, 03 Aug 2026
|
|
2
|
+
* @vkontakte/media-streaming v1.0.1-dev.e965d10.0
|
|
3
|
+
* Mon, 03 Aug 2026 10:45:14 GMT
|
|
4
4
|
*/
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -61,7 +61,7 @@ __export(exports_src, {
|
|
|
61
61
|
module.exports = __toCommonJS(exports_src);
|
|
62
62
|
|
|
63
63
|
// src/env.ts
|
|
64
|
-
var VERSION = "1.0.1-dev.
|
|
64
|
+
var VERSION = "1.0.1-dev.e965d10.0";
|
|
65
65
|
// src/constants.ts
|
|
66
66
|
var DEFAULT_STUN_SERVERS = [{ urls: ["stun:videostun.mycdn.me:19302"] }];
|
|
67
67
|
var DEFAULT_WS_SERVER = "wss://ovsu.mycdn.me/publish";
|
|
@@ -704,6 +704,6 @@ __legacyDecorateClassTS([
|
|
|
704
704
|
WebRTCStreamer.notDestroying
|
|
705
705
|
], WebRTCStreamer.prototype, "destroy", null);
|
|
706
706
|
|
|
707
|
-
//# debugId=
|
|
707
|
+
//# debugId=710FC6A0B6D5946064756E2164756E21
|
|
708
708
|
|
|
709
|
-
//# sourceMappingURL=
|
|
709
|
+
//# sourceMappingURL=media-streaming.cjs.map
|
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vkontakte/media-streaming v1.0.1-dev.e965d10.0
|
|
3
|
+
* Mon, 03 Aug 2026 10:45:14 GMT
|
|
4
|
+
*/
|
|
5
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
6
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
7
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
8
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
9
|
+
else
|
|
10
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
11
|
+
if (d = decorators[i])
|
|
12
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
13
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/env.ts
|
|
17
|
+
var VERSION = "1.0.1-dev.e965d10.0";
|
|
18
|
+
// src/constants.ts
|
|
19
|
+
var DEFAULT_STUN_SERVERS = [{ urls: ["stun:videostun.mycdn.me:19302"] }];
|
|
20
|
+
var DEFAULT_WS_SERVER = "wss://ovsu.mycdn.me/publish";
|
|
21
|
+
var WS_CLOSE_CODE = 1000;
|
|
22
|
+
var DEFAULT_FRAME_RATE = 15;
|
|
23
|
+
var SCREEN_SHARE_CONTENT_TEXTHINT = "text";
|
|
24
|
+
var MediaErrorType;
|
|
25
|
+
((MediaErrorType2) => {
|
|
26
|
+
MediaErrorType2["UNKNOWN"] = "unknown";
|
|
27
|
+
MediaErrorType2["ACCESS"] = "access";
|
|
28
|
+
MediaErrorType2["PERMISSION"] = "permission";
|
|
29
|
+
MediaErrorType2["DEVICES_NOT_FOUND"] = "devices_not_found";
|
|
30
|
+
MediaErrorType2["DEVICES_NOT_SUPPORTED"] = "devices_not_supported";
|
|
31
|
+
})(MediaErrorType ||= {});
|
|
32
|
+
var StreamerError;
|
|
33
|
+
((StreamerError2) => {
|
|
34
|
+
StreamerError2["SOCKET"] = "socket";
|
|
35
|
+
StreamerError2["CONNECTION"] = "connection";
|
|
36
|
+
StreamerError2["NO_STREAM_DATA"] = "no_stream_data";
|
|
37
|
+
StreamerError2["PEERCONNECTION"] = "peerconnection";
|
|
38
|
+
StreamerError2["CONSTRAINTS"] = "constraints";
|
|
39
|
+
})(StreamerError ||= {});
|
|
40
|
+
// src/WebRTCStreamer.ts
|
|
41
|
+
import { flattenObject } from "@vkontakte/videoplayer-shared";
|
|
42
|
+
|
|
43
|
+
// src/MediaConstraints.ts
|
|
44
|
+
class MediaConstraints {
|
|
45
|
+
config;
|
|
46
|
+
audio;
|
|
47
|
+
video;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.audio = false;
|
|
51
|
+
this.video = false;
|
|
52
|
+
const { audio, video } = this.config;
|
|
53
|
+
if (audio) {
|
|
54
|
+
const deviceId = typeof audio === "boolean" ? null : audio.deviceId;
|
|
55
|
+
this.audio = deviceId ? { deviceId: { exact: deviceId } } : true;
|
|
56
|
+
}
|
|
57
|
+
if (video) {
|
|
58
|
+
const videoConfig = typeof video === "boolean" ? {} : video;
|
|
59
|
+
const { minWidth, maxWidth, minHeight, maxHeight, aspectRatio, deviceId, facingMode } = videoConfig;
|
|
60
|
+
this.video = {
|
|
61
|
+
width: { min: minWidth, max: maxWidth || 3840, ideal: 1920 },
|
|
62
|
+
height: { min: minHeight, max: maxHeight || 2160, ideal: 1080 },
|
|
63
|
+
aspectRatio: { ideal: aspectRatio || 16 / 9 }
|
|
64
|
+
};
|
|
65
|
+
if (facingMode) {
|
|
66
|
+
this.video.facingMode = { exact: facingMode };
|
|
67
|
+
} else if (deviceId) {
|
|
68
|
+
this.video.deviceId = { exact: deviceId };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
getNative() {
|
|
73
|
+
return Object.assign({}, { audio: this.audio, video: this.video });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/MediaError.ts
|
|
78
|
+
class MediaError2 extends Error {
|
|
79
|
+
constructor(name, message) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = name;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/utils.ts
|
|
86
|
+
var getWindow = () => {
|
|
87
|
+
return globalThis;
|
|
88
|
+
};
|
|
89
|
+
var getSafariVersion = (userAgent) => {
|
|
90
|
+
const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
91
|
+
if (!ua) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const isSafari = /webkit/i.test(ua) && /safari/i.test(ua) && !/chrome|crios|chromium|yabrowser|samsungbrowser|opr\/|edg/i.test(ua);
|
|
95
|
+
if (!isSafari) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const versionMatch = ua.match(/Version\/(\d+)/);
|
|
99
|
+
if (!versionMatch) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const version = parseInt(versionMatch[1], 10);
|
|
103
|
+
return isNaN(version) ? undefined : version;
|
|
104
|
+
};
|
|
105
|
+
var enumerateDevices = async () => await getWindow().navigator.mediaDevices.enumerateDevices();
|
|
106
|
+
var getUserMedia = (constraints) => getWindow().navigator.mediaDevices.getUserMedia(constraints).catch((e) => {
|
|
107
|
+
if (e instanceof Error) {
|
|
108
|
+
switch (e.name) {
|
|
109
|
+
case "PermissionDismissedError":
|
|
110
|
+
case "PermissionDeniedError":
|
|
111
|
+
case "SecurityError":
|
|
112
|
+
case "NotAllowedError":
|
|
113
|
+
case "DOMException":
|
|
114
|
+
throw new MediaError2(e.name, "permission" /* PERMISSION */);
|
|
115
|
+
case "OverconstrainedError":
|
|
116
|
+
case "NotFoundError":
|
|
117
|
+
case "TypeError":
|
|
118
|
+
throw new MediaError2(e.name, "unknown" /* UNKNOWN */);
|
|
119
|
+
case "AbortError":
|
|
120
|
+
case "NotReadableError":
|
|
121
|
+
throw new MediaError2(e.name, "access" /* ACCESS */);
|
|
122
|
+
default:
|
|
123
|
+
throw new MediaError2(e.name, "unknown" /* UNKNOWN */);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw new Error("unknown");
|
|
127
|
+
});
|
|
128
|
+
var getDisplayMedia = (constraints) => getWindow().navigator.mediaDevices.getDisplayMedia(constraints.getNative()).catch((e) => {
|
|
129
|
+
if (e instanceof Error) {
|
|
130
|
+
switch (e.name) {
|
|
131
|
+
case "PermissionDeniedError":
|
|
132
|
+
case "SecurityError":
|
|
133
|
+
case "NotAllowedError":
|
|
134
|
+
throw new Error("permission");
|
|
135
|
+
default:
|
|
136
|
+
throw new Error("access");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new Error("unknown");
|
|
140
|
+
});
|
|
141
|
+
var omitKeys = (data, keys) => {
|
|
142
|
+
return Object.fromEntries(Object.entries(data).filter(([k]) => !keys.includes(k)));
|
|
143
|
+
};
|
|
144
|
+
var serializeTrack = (track) => {
|
|
145
|
+
return {
|
|
146
|
+
on: track.enabled,
|
|
147
|
+
k: track.kind,
|
|
148
|
+
...track.contentHint && { h: track.contentHint }
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
var serializeStream = (stream) => {
|
|
152
|
+
return {
|
|
153
|
+
t: stream.getTracks().map(serializeTrack)
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
var serializeConfig = (cfg) => {
|
|
157
|
+
return {
|
|
158
|
+
key: cfg.streamKey,
|
|
159
|
+
...cfg.audioOnly && { audioOnly: true },
|
|
160
|
+
...cfg.lockOrientation && { lockOrientation: true },
|
|
161
|
+
...cfg.mediaConstraints && { mediaConstraints: omitKeys(cfg.mediaConstraints, ["getNative"]) },
|
|
162
|
+
...cfg.screenShare && { screenShare: omitKeys(cfg.screenShare, ["onEnded"]) }
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
var serializeMediaDevices = (devices) => {
|
|
166
|
+
return devices.map((d) => ({
|
|
167
|
+
id: d.deviceId.slice(0, 8),
|
|
168
|
+
kind: d.kind,
|
|
169
|
+
label: d.label
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/ScreenConstraints.ts
|
|
174
|
+
class ScreenConstraints extends MediaConstraints {
|
|
175
|
+
constructor({ width, height, frameRate = DEFAULT_FRAME_RATE }) {
|
|
176
|
+
super({
|
|
177
|
+
video: true,
|
|
178
|
+
audio: false
|
|
179
|
+
});
|
|
180
|
+
if (typeof this.video === "object") {
|
|
181
|
+
delete this.video.deviceId;
|
|
182
|
+
delete this.video.aspectRatio;
|
|
183
|
+
delete this.video.frameRate;
|
|
184
|
+
delete this.video.facingMode;
|
|
185
|
+
} else {
|
|
186
|
+
this.video = {};
|
|
187
|
+
}
|
|
188
|
+
this.video.cursor = "motion";
|
|
189
|
+
this.video.width = width;
|
|
190
|
+
this.video.height = height;
|
|
191
|
+
this.video.frameRate = frameRate;
|
|
192
|
+
if (getSafariVersion() === 16) {
|
|
193
|
+
this.video.width = { max: width };
|
|
194
|
+
this.video.height = { max: height };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/WebRTCStreamer.ts
|
|
200
|
+
class WebRTCStreamer {
|
|
201
|
+
config;
|
|
202
|
+
messagesQueue = [];
|
|
203
|
+
ws;
|
|
204
|
+
peerConnection;
|
|
205
|
+
stream;
|
|
206
|
+
screenShareTrack = null;
|
|
207
|
+
connected = false;
|
|
208
|
+
destroying = false;
|
|
209
|
+
cameraId;
|
|
210
|
+
microphoneId;
|
|
211
|
+
muted = false;
|
|
212
|
+
static notDestroying(_target, _propertyKey, descriptor) {
|
|
213
|
+
const original = descriptor.value;
|
|
214
|
+
descriptor.value = function(...args) {
|
|
215
|
+
if (this.destroying) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
return original.apply(this, args);
|
|
219
|
+
};
|
|
220
|
+
return descriptor;
|
|
221
|
+
}
|
|
222
|
+
constructor(config) {
|
|
223
|
+
this.config = config;
|
|
224
|
+
}
|
|
225
|
+
callback(name, ...args) {
|
|
226
|
+
const fn = this.config.callbacks[name];
|
|
227
|
+
if (fn && !this.destroying) {
|
|
228
|
+
fn?.(...args);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async init() {
|
|
232
|
+
this.tracerLog("init", {
|
|
233
|
+
cfg: serializeConfig(this.config),
|
|
234
|
+
d: serializeMediaDevices(await enumerateDevices())
|
|
235
|
+
});
|
|
236
|
+
let permissionName = "user_media" /* USER_MEDIA */;
|
|
237
|
+
const { audioOnly, mediaConstraints, screenShare } = this.config;
|
|
238
|
+
try {
|
|
239
|
+
const constraints = mediaConstraints || new MediaConstraints({
|
|
240
|
+
audio: true,
|
|
241
|
+
video: !screenShare && !audioOnly
|
|
242
|
+
});
|
|
243
|
+
this.stream = await getUserMedia(constraints.getNative());
|
|
244
|
+
if (!audioOnly && screenShare) {
|
|
245
|
+
permissionName = "display_media" /* DISPLAY_MEDIA */;
|
|
246
|
+
const screenShareTrack = await this.createScreenShareStream(screenShare);
|
|
247
|
+
if (screenShareTrack) {
|
|
248
|
+
const { frameRate = DEFAULT_FRAME_RATE } = screenShare;
|
|
249
|
+
try {
|
|
250
|
+
await screenShareTrack.applyConstraints({
|
|
251
|
+
frameRate: { exact: frameRate }
|
|
252
|
+
});
|
|
253
|
+
} catch (e) {
|
|
254
|
+
this.tracerLog("error", { type: "constraints" /* CONSTRAINTS */ });
|
|
255
|
+
this.callback("onError", "constraints" /* CONSTRAINTS */);
|
|
256
|
+
}
|
|
257
|
+
this.stream.addTrack(screenShareTrack);
|
|
258
|
+
}
|
|
259
|
+
this.screenShareTrack = screenShareTrack;
|
|
260
|
+
}
|
|
261
|
+
this.tracerLog("streamChanged", serializeStream(this.stream));
|
|
262
|
+
this.callback("onStreamChanged", this.stream);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
if (err instanceof MediaError) {
|
|
265
|
+
this.tracerLog("permissionFailed", { name: permissionName, msg: err.message, code: err.code });
|
|
266
|
+
this.callback("onPermissionFailed", permissionName, err);
|
|
267
|
+
} else {
|
|
268
|
+
this.tracerLog("permissionFailed", { name: permissionName });
|
|
269
|
+
this.callback("onPermissionFailed", permissionName);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async connect() {
|
|
274
|
+
this.tracerLog("connect");
|
|
275
|
+
this.openSocket();
|
|
276
|
+
if (!this.peerConnection) {
|
|
277
|
+
await this.createPeerConnection();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async switchCamera(deviceId) {
|
|
281
|
+
const { audioOnly } = this.config;
|
|
282
|
+
this.tracerLog("switchCamera", { id: deviceId, audioOnly });
|
|
283
|
+
if (audioOnly) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
this.cameraId = deviceId;
|
|
287
|
+
const constraints = new MediaConstraints({
|
|
288
|
+
audio: this.microphoneId ? {
|
|
289
|
+
deviceId: this.microphoneId
|
|
290
|
+
} : true,
|
|
291
|
+
video: {
|
|
292
|
+
deviceId: this.cameraId
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
const newStream = await getUserMedia(constraints.getNative());
|
|
296
|
+
this.replaceStream(newStream);
|
|
297
|
+
if (this.muted) {
|
|
298
|
+
this.setMicMuted(true);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async switchMicrophone(deviceId) {
|
|
302
|
+
this.tracerLog("switchMicrophone", { id: deviceId });
|
|
303
|
+
this.microphoneId = deviceId;
|
|
304
|
+
const constraints = new MediaConstraints({
|
|
305
|
+
audio: {
|
|
306
|
+
deviceId: this.microphoneId
|
|
307
|
+
},
|
|
308
|
+
video: this.screenShareTrack ? false : this.cameraId ? {
|
|
309
|
+
deviceId: this.cameraId
|
|
310
|
+
} : true
|
|
311
|
+
});
|
|
312
|
+
const newStream = await getUserMedia(constraints.getNative());
|
|
313
|
+
if (this.screenShareTrack) {
|
|
314
|
+
newStream.addTrack(this.screenShareTrack);
|
|
315
|
+
}
|
|
316
|
+
this.replaceStream(newStream, true);
|
|
317
|
+
if (this.muted) {
|
|
318
|
+
this.setMicMuted(true);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async enableScreenShare(config) {
|
|
322
|
+
this.tracerLog("enableScreenShare", omitKeys(config, ["onEnded"]));
|
|
323
|
+
const constraints = new MediaConstraints({
|
|
324
|
+
audio: this.microphoneId ? { deviceId: this.microphoneId } : true,
|
|
325
|
+
video: false
|
|
326
|
+
});
|
|
327
|
+
const newStream = await getUserMedia(constraints.getNative());
|
|
328
|
+
const screenShareTrack = await this.createScreenShareStream(config);
|
|
329
|
+
if (screenShareTrack) {
|
|
330
|
+
const { frameRate = DEFAULT_FRAME_RATE } = config;
|
|
331
|
+
await screenShareTrack.applyConstraints({
|
|
332
|
+
frameRate: { exact: frameRate }
|
|
333
|
+
});
|
|
334
|
+
newStream.addTrack(screenShareTrack);
|
|
335
|
+
}
|
|
336
|
+
this.screenShareTrack = screenShareTrack;
|
|
337
|
+
this.replaceStream(newStream);
|
|
338
|
+
if (this.muted) {
|
|
339
|
+
this.setMicMuted(true);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async disableScreenShare(cameraDeviceId) {
|
|
343
|
+
this.tracerLog("disableScreenShare", cameraDeviceId ? { cid: cameraDeviceId } : undefined);
|
|
344
|
+
if (cameraDeviceId) {
|
|
345
|
+
await this.switchCamera(cameraDeviceId);
|
|
346
|
+
} else {
|
|
347
|
+
const devices = await enumerateDevices();
|
|
348
|
+
const defaultCamera = devices.find((d) => d.kind === "videoinput");
|
|
349
|
+
if (defaultCamera) {
|
|
350
|
+
await this.switchCamera(defaultCamera.deviceId);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
setMicMuted(muted) {
|
|
355
|
+
this.tracerLog("setMicMuted", { m: muted });
|
|
356
|
+
this.muted = muted;
|
|
357
|
+
this.stream?.getAudioTracks().forEach((track) => {
|
|
358
|
+
track.enabled = !muted;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
setCameraEnabled(enabled) {
|
|
362
|
+
this.tracerLog("setCameraEnabled", { on: enabled });
|
|
363
|
+
this.stream?.getVideoTracks().forEach((track) => {
|
|
364
|
+
track.enabled = enabled;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
destroy() {
|
|
368
|
+
this.destroying = true;
|
|
369
|
+
this.tracerLog("destroy");
|
|
370
|
+
this.disconnect();
|
|
371
|
+
this.cameraId = undefined;
|
|
372
|
+
this.microphoneId = undefined;
|
|
373
|
+
this.muted = false;
|
|
374
|
+
if (this.stream) {
|
|
375
|
+
this.stream.getTracks().forEach((track) => track.stop());
|
|
376
|
+
this.stream = undefined;
|
|
377
|
+
this.screenShareTrack = null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
disconnect() {
|
|
381
|
+
this.tracerLog("disconnect");
|
|
382
|
+
this.closePeerConnection();
|
|
383
|
+
if (this.socketOpened) {
|
|
384
|
+
this.send({ type: "BYE" });
|
|
385
|
+
this.closeWSConnection();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
replaceStream(stream, keepScreenShare = false) {
|
|
389
|
+
this.stream?.getTracks().forEach((track) => {
|
|
390
|
+
const isScreenShare = track.contentHint === SCREEN_SHARE_CONTENT_TEXTHINT;
|
|
391
|
+
if (!isScreenShare || !keepScreenShare) {
|
|
392
|
+
track.stop();
|
|
393
|
+
if (isScreenShare) {
|
|
394
|
+
this.screenShareTrack = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.stream?.removeTrack(track);
|
|
398
|
+
});
|
|
399
|
+
this.stream = stream;
|
|
400
|
+
this.tracerLog("streamChanged", serializeStream(stream));
|
|
401
|
+
this.callback("onStreamChanged", this.stream);
|
|
402
|
+
this.replaceTracksInPeerConnection(this.stream);
|
|
403
|
+
}
|
|
404
|
+
replaceTracksInPeerConnection(stream, keepScreenShare = false) {
|
|
405
|
+
if (!this.peerConnection) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.peerConnection.getTransceivers().forEach(({ sender }) => {
|
|
409
|
+
const { track } = sender;
|
|
410
|
+
if (track && (track.contentHint !== SCREEN_SHARE_CONTENT_TEXTHINT || !keepScreenShare)) {
|
|
411
|
+
track.stop();
|
|
412
|
+
}
|
|
413
|
+
stream.getTracks().forEach((newTrack) => {
|
|
414
|
+
if (track?.kind === newTrack.kind) {
|
|
415
|
+
sender.replaceTrack(newTrack).then(() => {}, () => {});
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
get socketOpened() {
|
|
421
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
422
|
+
}
|
|
423
|
+
async createScreenShareStream(screenShare) {
|
|
424
|
+
const { width, height, onEnded = null } = screenShare;
|
|
425
|
+
const displayStream = await getDisplayMedia(new ScreenConstraints({ width, height }));
|
|
426
|
+
const screenShareTrack = displayStream ? displayStream.getVideoTracks()[0] : null;
|
|
427
|
+
if (screenShareTrack) {
|
|
428
|
+
screenShareTrack.contentHint = SCREEN_SHARE_CONTENT_TEXTHINT;
|
|
429
|
+
screenShareTrack.onended = () => {
|
|
430
|
+
this.screenShareTrack = null;
|
|
431
|
+
onEnded?.();
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return screenShareTrack;
|
|
435
|
+
}
|
|
436
|
+
openSocket() {
|
|
437
|
+
if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
|
|
438
|
+
this.ws = new WebSocket(this.config.serverUrl ?? DEFAULT_WS_SERVER);
|
|
439
|
+
this.ws.onopen = this.handleWSOpen;
|
|
440
|
+
this.ws.onmessage = this.handleWSMessage;
|
|
441
|
+
this.ws.onclose = this.handleWSClose;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
handleWSOpen = () => {
|
|
445
|
+
this.sendMessageFromQueue();
|
|
446
|
+
};
|
|
447
|
+
handleWSMessage = (event) => {
|
|
448
|
+
if (this.destroying || !this.ws || this.ws.readyState >= WebSocket.CLOSING) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const message = this.parseMessage(event.data);
|
|
452
|
+
switch (message.type) {
|
|
453
|
+
case "INVITE":
|
|
454
|
+
this.handleInviteMessage(message);
|
|
455
|
+
break;
|
|
456
|
+
case "ERROR":
|
|
457
|
+
this.handleError("socket" /* SOCKET */, message);
|
|
458
|
+
break;
|
|
459
|
+
case "STATUS":
|
|
460
|
+
this.handleStatusMessage(message);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
handleWSClose = (event) => {
|
|
465
|
+
this.closePeerConnection();
|
|
466
|
+
if (!this.ws) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
this.messagesQueue = [];
|
|
470
|
+
this.connected = false;
|
|
471
|
+
this.ws = undefined;
|
|
472
|
+
this.tracerLog("disconnected", { code: event.code });
|
|
473
|
+
this.callback("onDisconnected", event);
|
|
474
|
+
};
|
|
475
|
+
send(message) {
|
|
476
|
+
this.messagesQueue.push(message);
|
|
477
|
+
if (this.socketOpened) {
|
|
478
|
+
this.sendMessageFromQueue();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
sendMessageFromQueue() {
|
|
482
|
+
while (this.messagesQueue.length > 0) {
|
|
483
|
+
if (this.socketOpened) {
|
|
484
|
+
const message = this.messagesQueue.shift();
|
|
485
|
+
this.ws?.send(JSON.stringify(message));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
handleStatusMessage(msg) {
|
|
490
|
+
const { status, message } = msg;
|
|
491
|
+
if (status === "INFO" && message === "no_stream_data") {
|
|
492
|
+
this.handleError("no_stream_data" /* NO_STREAM_DATA */, msg);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
handleInviteMessage(message) {
|
|
496
|
+
switch (message.inviteType) {
|
|
497
|
+
case "ANSWER":
|
|
498
|
+
this.handleAnswer(message.sdp);
|
|
499
|
+
break;
|
|
500
|
+
case "CANDIDATE":
|
|
501
|
+
if (message.candidate) {
|
|
502
|
+
this.tracerLog("gotRTCCandidate", { v: message.candidate });
|
|
503
|
+
this.handleCandidate(message.candidate);
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
default:
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
handleAnswer(sdp) {
|
|
511
|
+
this.peerConnection?.setRemoteDescription(new RTCSessionDescription({ type: "answer", sdp }));
|
|
512
|
+
}
|
|
513
|
+
handleCandidate(candidate) {
|
|
514
|
+
this.peerConnection?.addIceCandidate(new RTCIceCandidate(candidate));
|
|
515
|
+
}
|
|
516
|
+
handleError(err, message) {
|
|
517
|
+
if (["no_stream_data" /* NO_STREAM_DATA */, "peerconnection" /* PEERCONNECTION */].includes(err)) {
|
|
518
|
+
this.closePeerConnection();
|
|
519
|
+
this.closeWSConnection();
|
|
520
|
+
}
|
|
521
|
+
const reason = message ? typeof message === "string" ? message : message.text ?? message.message : undefined;
|
|
522
|
+
this.tracerLog("error", { type: err, ...reason && { r: reason } });
|
|
523
|
+
this.callback("onError", err, reason);
|
|
524
|
+
}
|
|
525
|
+
async createPeerConnection() {
|
|
526
|
+
this.peerConnection = new RTCPeerConnection({
|
|
527
|
+
iceServers: this.config.stunServers ?? DEFAULT_STUN_SERVERS,
|
|
528
|
+
rtcpMuxPolicy: "require",
|
|
529
|
+
bundlePolicy: "max-compat"
|
|
530
|
+
});
|
|
531
|
+
this.peerConnection.onicecandidate = this.handleRTCIceCandidate;
|
|
532
|
+
this.peerConnection.onsignalingstatechange = this.handleRTCSignalingStateChange;
|
|
533
|
+
this.peerConnection.oniceconnectionstatechange = this.handleRTCIceConnectionStateChange;
|
|
534
|
+
this.stream?.getTracks().forEach((track) => {
|
|
535
|
+
if (track.kind === "video") {
|
|
536
|
+
track.contentHint = "text";
|
|
537
|
+
}
|
|
538
|
+
this.stream && this.peerConnection?.addTrack(track, this.stream);
|
|
539
|
+
});
|
|
540
|
+
try {
|
|
541
|
+
const offer = await this.peerConnection.createOffer();
|
|
542
|
+
if (this.config.lockOrientation) {
|
|
543
|
+
offer.sdp = offer.sdp?.replace(/a=extmap:[0-9]+ urn:3gpp:video-orientation[\r\n]+/g, "");
|
|
544
|
+
}
|
|
545
|
+
await this.setDescription(offer);
|
|
546
|
+
} catch (e) {
|
|
547
|
+
this.handleError("peerconnection" /* PEERCONNECTION */, "set offer description failed");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
handleRTCIceCandidate = ({ candidate }) => {
|
|
551
|
+
if (candidate) {
|
|
552
|
+
this.tracerLog("sendRTCCandidate", { v: candidate.candidate });
|
|
553
|
+
this.send({
|
|
554
|
+
type: "INVITE",
|
|
555
|
+
promoCall: false,
|
|
556
|
+
inviteType: "CANDIDATE",
|
|
557
|
+
candidate
|
|
558
|
+
});
|
|
559
|
+
} else {
|
|
560
|
+
this.send({ type: "INVITE", promoCall: false, inviteType: "CANDIDATE" });
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
handleRTCSignalingStateChange = () => {
|
|
564
|
+
if (!this.peerConnection) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
switch (this.peerConnection.signalingState) {
|
|
568
|
+
case "have-local-offer":
|
|
569
|
+
const sdp = this.peerConnection.localDescription?.sdp;
|
|
570
|
+
this.send({
|
|
571
|
+
type: "INVITE",
|
|
572
|
+
inviteType: "OFFER",
|
|
573
|
+
promoCall: false,
|
|
574
|
+
streamKey: this.config.streamKey,
|
|
575
|
+
sdp
|
|
576
|
+
});
|
|
577
|
+
break;
|
|
578
|
+
case "closed":
|
|
579
|
+
this.handleError("peerconnection" /* PEERCONNECTION */, "signalling closed");
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
handleRTCIceConnectionStateChange = () => {
|
|
584
|
+
if (!this.peerConnection) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
switch (this.peerConnection.iceConnectionState) {
|
|
588
|
+
case "failed":
|
|
589
|
+
this.handleError("peerconnection" /* PEERCONNECTION */, "ice connection failed");
|
|
590
|
+
break;
|
|
591
|
+
case "disconnected":
|
|
592
|
+
this.disconnect();
|
|
593
|
+
break;
|
|
594
|
+
case "connected":
|
|
595
|
+
this.connected = true;
|
|
596
|
+
this.tracerLog("connected");
|
|
597
|
+
this.callback("onConnected");
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
async setDescription(description) {
|
|
602
|
+
try {
|
|
603
|
+
await this.peerConnection?.setLocalDescription(description);
|
|
604
|
+
} catch (e) {
|
|
605
|
+
this.handleError("peerconnection" /* PEERCONNECTION */, "set local description failed");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
closePeerConnection() {
|
|
609
|
+
if (this.peerConnection) {
|
|
610
|
+
if (this.peerConnection.signalingState !== "closed") {
|
|
611
|
+
this.peerConnection.close();
|
|
612
|
+
}
|
|
613
|
+
this.peerConnection = undefined;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
closeWSConnection() {
|
|
617
|
+
if (this.ws && this.ws.readyState < WebSocket.CLOSING) {
|
|
618
|
+
this.ws.close(WS_CLOSE_CODE);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
parseMessage(data) {
|
|
622
|
+
try {
|
|
623
|
+
return JSON.parse(data);
|
|
624
|
+
} catch (e) {
|
|
625
|
+
return { type: "EMPTY" };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
tracerLog(event, data) {
|
|
629
|
+
return this.config.tracer?.log(event, flattenObject(data));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
__legacyDecorateClassTS([
|
|
633
|
+
WebRTCStreamer.notDestroying
|
|
634
|
+
], WebRTCStreamer.prototype, "init", null);
|
|
635
|
+
__legacyDecorateClassTS([
|
|
636
|
+
WebRTCStreamer.notDestroying
|
|
637
|
+
], WebRTCStreamer.prototype, "connect", null);
|
|
638
|
+
__legacyDecorateClassTS([
|
|
639
|
+
WebRTCStreamer.notDestroying
|
|
640
|
+
], WebRTCStreamer.prototype, "switchCamera", null);
|
|
641
|
+
__legacyDecorateClassTS([
|
|
642
|
+
WebRTCStreamer.notDestroying
|
|
643
|
+
], WebRTCStreamer.prototype, "switchMicrophone", null);
|
|
644
|
+
__legacyDecorateClassTS([
|
|
645
|
+
WebRTCStreamer.notDestroying
|
|
646
|
+
], WebRTCStreamer.prototype, "enableScreenShare", null);
|
|
647
|
+
__legacyDecorateClassTS([
|
|
648
|
+
WebRTCStreamer.notDestroying
|
|
649
|
+
], WebRTCStreamer.prototype, "disableScreenShare", null);
|
|
650
|
+
__legacyDecorateClassTS([
|
|
651
|
+
WebRTCStreamer.notDestroying
|
|
652
|
+
], WebRTCStreamer.prototype, "setMicMuted", null);
|
|
653
|
+
__legacyDecorateClassTS([
|
|
654
|
+
WebRTCStreamer.notDestroying
|
|
655
|
+
], WebRTCStreamer.prototype, "setCameraEnabled", null);
|
|
656
|
+
__legacyDecorateClassTS([
|
|
657
|
+
WebRTCStreamer.notDestroying
|
|
658
|
+
], WebRTCStreamer.prototype, "destroy", null);
|
|
659
|
+
export {
|
|
660
|
+
WebRTCStreamer,
|
|
661
|
+
VERSION,
|
|
662
|
+
StreamerError,
|
|
663
|
+
MediaErrorType
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
//# debugId=281C5760B351F83D64756E2164756E21
|
|
667
|
+
|
|
668
|
+
//# sourceMappingURL=media-streaming.esm.js.map
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vkontakte/media-streaming",
|
|
3
|
-
"version": "1.0.1-dev.
|
|
3
|
+
"version": "1.0.1-dev.e965d10.0",
|
|
4
4
|
"author": "vk.com",
|
|
5
5
|
"description": "VK WebRTC media streaming module",
|
|
6
6
|
"homepage": "https://vk.com",
|
|
7
7
|
"license": "GPL-3.0",
|
|
8
|
-
"main": "
|
|
9
|
-
"module": "
|
|
8
|
+
"main": "media-streaming.cjs",
|
|
9
|
+
"module": "media-streaming.esm.js",
|
|
10
10
|
"types": "types/index.d.ts",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"types": "./
|
|
15
|
-
"import": "./
|
|
16
|
-
"require": "./
|
|
14
|
+
"types": "./types/index.d.ts",
|
|
15
|
+
"import": "./media-streaming.esm.js",
|
|
16
|
+
"require": "./media-streaming.cjs"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
20
|
+
"media-streaming.esm.js",
|
|
21
|
+
"media-streaming.cjs",
|
|
22
|
+
"types/**/*.d.ts"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@vkontakte/videoplayer-shared": "^1.0.97"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IMediaConstraints, MediaConstraintsConfig } from "./types";
|
|
2
|
+
export declare class MediaConstraints implements IMediaConstraints {
|
|
3
|
+
protected readonly config: MediaConstraintsConfig;
|
|
4
|
+
audio: boolean | MediaTrackConstraints;
|
|
5
|
+
video: boolean | MediaTrackConstraints;
|
|
6
|
+
constructor(config: MediaConstraintsConfig);
|
|
7
|
+
getNative(): MediaStreamConstraints;
|
|
8
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ScreenShareConfig, WebRTCStreamerConfig } from "./types";
|
|
2
|
+
export declare class WebRTCStreamer {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private messagesQueue;
|
|
5
|
+
private ws;
|
|
6
|
+
private peerConnection;
|
|
7
|
+
private stream;
|
|
8
|
+
private screenShareTrack;
|
|
9
|
+
private connected;
|
|
10
|
+
private destroying;
|
|
11
|
+
private cameraId;
|
|
12
|
+
private microphoneId;
|
|
13
|
+
private muted;
|
|
14
|
+
private static notDestroying;
|
|
15
|
+
constructor(config: WebRTCStreamerConfig);
|
|
16
|
+
private callback;
|
|
17
|
+
init(): Promise<void>;
|
|
18
|
+
connect(): Promise<void>;
|
|
19
|
+
switchCamera(deviceId: string): Promise<void>;
|
|
20
|
+
switchMicrophone(deviceId: string): Promise<void>;
|
|
21
|
+
enableScreenShare(config: ScreenShareConfig): Promise<void>;
|
|
22
|
+
disableScreenShare(cameraDeviceId?: string): Promise<void>;
|
|
23
|
+
setMicMuted(muted: boolean): void;
|
|
24
|
+
setCameraEnabled(enabled: boolean): void;
|
|
25
|
+
destroy(): void;
|
|
26
|
+
private disconnect;
|
|
27
|
+
private replaceStream;
|
|
28
|
+
private replaceTracksInPeerConnection;
|
|
29
|
+
private get socketOpened();
|
|
30
|
+
private createScreenShareStream;
|
|
31
|
+
private openSocket;
|
|
32
|
+
private readonly handleWSOpen;
|
|
33
|
+
private readonly handleWSMessage;
|
|
34
|
+
private readonly handleWSClose;
|
|
35
|
+
private send;
|
|
36
|
+
private sendMessageFromQueue;
|
|
37
|
+
private handleStatusMessage;
|
|
38
|
+
private handleInviteMessage;
|
|
39
|
+
private handleAnswer;
|
|
40
|
+
private handleCandidate;
|
|
41
|
+
private handleError;
|
|
42
|
+
private createPeerConnection;
|
|
43
|
+
private readonly handleRTCIceCandidate;
|
|
44
|
+
private readonly handleRTCSignalingStateChange;
|
|
45
|
+
private readonly handleRTCIceConnectionStateChange;
|
|
46
|
+
private setDescription;
|
|
47
|
+
private closePeerConnection;
|
|
48
|
+
private closeWSConnection;
|
|
49
|
+
private parseMessage;
|
|
50
|
+
private tracerLog;
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const DEFAULT_STUN_SERVERS: RTCIceServer[];
|
|
2
|
+
export declare const DEFAULT_WS_SERVER = "wss://ovsu.mycdn.me/publish";
|
|
3
|
+
export declare const WS_CLOSE_CODE = 1e3;
|
|
4
|
+
export declare const DEFAULT_FRAME_RATE = 15;
|
|
5
|
+
export declare const SCREEN_SHARE_CONTENT_TEXTHINT = "text";
|
|
6
|
+
export declare enum PermissionName {
|
|
7
|
+
DISPLAY_MEDIA = "display_media",
|
|
8
|
+
USER_MEDIA = "user_media"
|
|
9
|
+
}
|
|
10
|
+
export declare enum MediaErrorType {
|
|
11
|
+
UNKNOWN = "unknown",
|
|
12
|
+
ACCESS = "access",
|
|
13
|
+
PERMISSION = "permission",
|
|
14
|
+
DEVICES_NOT_FOUND = "devices_not_found",
|
|
15
|
+
DEVICES_NOT_SUPPORTED = "devices_not_supported"
|
|
16
|
+
}
|
|
17
|
+
export declare enum StreamerError {
|
|
18
|
+
SOCKET = "socket",
|
|
19
|
+
CONNECTION = "connection",
|
|
20
|
+
NO_STREAM_DATA = "no_stream_data",
|
|
21
|
+
PEERCONNECTION = "peerconnection",
|
|
22
|
+
CONSTRAINTS = "constraints"
|
|
23
|
+
}
|
|
24
|
+
export declare enum StreamerEvent {
|
|
25
|
+
ERROR = "error",
|
|
26
|
+
PERMISSION = "permission",
|
|
27
|
+
CONNECT = "connect",
|
|
28
|
+
DISCONNECT = "disconnect",
|
|
29
|
+
CONNECTING = "connecting",
|
|
30
|
+
DESTROY = "destroy"
|
|
31
|
+
}
|
package/types/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION: string;
|
package/types/index.d.ts
ADDED
package/types/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ITracer } from "@vkontakte/videoplayer-shared";
|
|
2
|
+
import type { PermissionName, StreamerError } from "./constants";
|
|
3
|
+
export type SafeAny = any;
|
|
4
|
+
export type RTCMessageType = "INVITE" | "STATUS" | "BYE" | "ERROR" | "EMPTY";
|
|
5
|
+
export type RTCMessageInviteType = "OFFER" | "CANDIDATE" | "ANSWER" | "BYE";
|
|
6
|
+
export interface RTCMessage {
|
|
7
|
+
type: RTCMessageType;
|
|
8
|
+
inviteType?: RTCMessageInviteType;
|
|
9
|
+
streamKey?: string;
|
|
10
|
+
sdp?: string;
|
|
11
|
+
status?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
candidate?: RTCIceCandidateInit;
|
|
15
|
+
promoCall?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface WSIncomingMessage {
|
|
18
|
+
data: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MediaDeviceConfig {
|
|
21
|
+
deviceId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface VideoMediaConfig extends MediaDeviceConfig {
|
|
24
|
+
minWidth?: number;
|
|
25
|
+
maxWidth?: number;
|
|
26
|
+
minHeight?: number;
|
|
27
|
+
maxHeight?: number;
|
|
28
|
+
aspectRatio?: number;
|
|
29
|
+
facingMode?: "user" | "environment";
|
|
30
|
+
}
|
|
31
|
+
export interface MediaConstraintsConfig {
|
|
32
|
+
audio: boolean | MediaDeviceConfig;
|
|
33
|
+
video: boolean | VideoMediaConfig;
|
|
34
|
+
}
|
|
35
|
+
export interface ScreenConstraintsConfig {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
frameRate?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface IMediaConstraints {
|
|
41
|
+
audio: boolean | MediaTrackConstraints;
|
|
42
|
+
video: boolean | MediaTrackConstraints;
|
|
43
|
+
getNative(): MediaStreamConstraints;
|
|
44
|
+
}
|
|
45
|
+
export interface WebRTCStreamerCallbacks {
|
|
46
|
+
onConnected?: VoidFunction;
|
|
47
|
+
onDisconnected?: (event: CloseEvent) => void;
|
|
48
|
+
onError?: (err: StreamerError, reason?: string) => void;
|
|
49
|
+
onPermissionFailed?: (permissionName: PermissionName, err?: MediaError) => void;
|
|
50
|
+
onStreamChanged?: (stream: MediaStream | undefined) => void;
|
|
51
|
+
}
|
|
52
|
+
export type CallbackArgs<N extends keyof WebRTCStreamerCallbacks> = Parameters<NonNullable<WebRTCStreamerCallbacks[N]>>;
|
|
53
|
+
export type ScreenShareConfig = ScreenConstraintsConfig & {
|
|
54
|
+
onEnded?: VoidFunction;
|
|
55
|
+
};
|
|
56
|
+
export interface WebRTCStreamerConfig {
|
|
57
|
+
streamKey: string;
|
|
58
|
+
callbacks: WebRTCStreamerCallbacks;
|
|
59
|
+
audioOnly?: boolean;
|
|
60
|
+
lockOrientation?: boolean;
|
|
61
|
+
mediaConstraints?: IMediaConstraints;
|
|
62
|
+
screenShare?: ScreenShareConfig;
|
|
63
|
+
serverUrl?: string;
|
|
64
|
+
stunServers?: RTCIceServer[];
|
|
65
|
+
tracer?: ITracer;
|
|
66
|
+
}
|
package/types/utils.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SafeAny } from "./types";
|
|
2
|
+
import type { ScreenConstraints } from "./ScreenConstraints";
|
|
3
|
+
import type { WebRTCStreamerConfig } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Определяет версию Safari по userAgent.
|
|
6
|
+
* Возвращает major-версию как число, либо undefined если браузер не Safari
|
|
7
|
+
* или версию не удалось определить.
|
|
8
|
+
*
|
|
9
|
+
* @param userAgent — строка user-agent (по умолчанию navigator.userAgent)
|
|
10
|
+
* @returns номер major-версии Safari или undefined
|
|
11
|
+
*/
|
|
12
|
+
export declare const getSafariVersion: (userAgent?: string) => number | undefined;
|
|
13
|
+
export declare const enumerateDevices: () => Promise<MediaDeviceInfo[]>;
|
|
14
|
+
export declare const getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
15
|
+
export declare const getDisplayMedia: (constraints: ScreenConstraints) => Promise<MediaStream>;
|
|
16
|
+
export declare const omitKeys: <T extends Record<string, SafeAny>>(data: T, keys: (keyof T)[]) => Record<string, SafeAny>;
|
|
17
|
+
export declare const serializeStream: (stream: MediaStream) => Record<string, SafeAny>;
|
|
18
|
+
export declare const serializeConfig: (cfg: WebRTCStreamerConfig) => Record<string, SafeAny>;
|
|
19
|
+
export declare const serializeMediaDevices: (devices: MediaDeviceInfo[]) => Record<string, SafeAny>[];
|