@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.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @vkontakte/media-streaming v1.0.1-dev.7c4e0d2.0
3
- * Mon, 03 Aug 2026 09:18:14 GMT
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.7c4e0d2.0";
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=F692A4354B910EA964756E2164756E21
707
+ //# debugId=710FC6A0B6D5946064756E2164756E21
708
708
 
709
- //# sourceMappingURL=esnext.cjs.map
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.7c4e0d2.0",
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": "esnext.cjs",
9
- "module": "esnext.esm.js",
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": "./out/types/index.d.ts",
15
- "import": "./out/esnext.esm.js",
16
- "require": "./out/esnext.cjs"
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
- "out/esnext.esm.js",
21
- "out/esnext.cjs",
22
- "out/types/**/*.d.ts"
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,4 @@
1
+ import type { MediaErrorType } from "./constants";
2
+ export declare class MediaError extends Error {
3
+ constructor(name: string, message: MediaErrorType);
4
+ }
@@ -0,0 +1,5 @@
1
+ import { MediaConstraints } from "./MediaConstraints";
2
+ import type { ScreenConstraintsConfig } from "./types";
3
+ export declare class ScreenConstraints extends MediaConstraints {
4
+ constructor({ width, height, frameRate }: ScreenConstraintsConfig);
5
+ }
@@ -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;
@@ -0,0 +1,4 @@
1
+ export { VERSION } from "./env";
2
+ export type { IMediaConstraints, ScreenShareConfig, WebRTCStreamerConfig, WebRTCStreamerCallbacks } from "./types";
3
+ export { MediaErrorType, StreamerError } from "./constants";
4
+ export { WebRTCStreamer } from "./WebRTCStreamer";
@@ -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
+ }
@@ -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>[];