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