@4players/odin-plugin-web 1.0.0-alpha.2 → 1.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/lib/common/msgpack.d.ts +2 -0
  2. package/lib/common/msgpack.js +117 -0
  3. package/{backend/lib → lib/plugin}/config.d.ts +5 -4
  4. package/lib/plugin/config.js +22 -0
  5. package/{backend/lib → lib/plugin}/connectionPool.d.ts +3 -3
  6. package/{backend/lib → lib/plugin}/connectionPool.js +32 -13
  7. package/lib/plugin/deviceManager.d.ts +12 -0
  8. package/lib/plugin/deviceManager.js +120 -0
  9. package/lib/plugin/h3/mainConnection.d.ts +31 -0
  10. package/lib/plugin/h3/mainConnection.js +195 -0
  11. package/{backend/lib → lib/plugin}/index.d.ts +30 -13
  12. package/lib/plugin/index.js +404 -0
  13. package/lib/plugin/mainConnection.d.ts +30 -0
  14. package/lib/plugin/mainConnection.js +21 -0
  15. package/{backend/lib/audioController.d.ts → lib/plugin/mediaController.d.ts} +12 -48
  16. package/lib/plugin/mediaController.js +215 -0
  17. package/lib/plugin/medias/audio-capture.d.ts +30 -0
  18. package/lib/plugin/medias/audio-capture.js +224 -0
  19. package/lib/plugin/medias/audio-playback.d.ts +13 -0
  20. package/lib/plugin/medias/audio-playback.js +40 -0
  21. package/lib/plugin/medias/video-capture.d.ts +15 -0
  22. package/lib/plugin/medias/video-capture.js +69 -0
  23. package/lib/plugin/medias/video-playback.d.ts +13 -0
  24. package/lib/plugin/medias/video-playback.js +20 -0
  25. package/{backend/lib → lib/plugin}/roomConnection.d.ts +9 -6
  26. package/{backend/lib → lib/plugin}/roomConnection.js +74 -53
  27. package/lib/plugin/rpcConnection.d.ts +57 -0
  28. package/{backend/lib → lib/plugin}/rpcConnection.js +129 -83
  29. package/lib/plugin/token.d.ts +13 -0
  30. package/{backend/lib → lib/plugin}/token.js +8 -9
  31. package/lib/plugin/webrtc/mainConnection.d.ts +53 -0
  32. package/lib/plugin/webrtc/mainConnection.js +455 -0
  33. package/lib/plugin/webrtc/websocket.d.ts +3 -0
  34. package/lib/plugin/webrtc/websocket.js +56 -0
  35. package/lib/worker/buffers.d.ts +42 -0
  36. package/lib/worker/buffers.js +79 -0
  37. package/{worker/lib/index.d.ts → lib/worker/common.d.ts} +7 -8
  38. package/lib/worker/common.js +7 -0
  39. package/lib/worker/config.d.ts +8 -0
  40. package/lib/worker/config.js +11 -0
  41. package/lib/worker/index.d.ts +3 -0
  42. package/lib/worker/index.js +1 -0
  43. package/lib/worker/memory.d.ts +9 -0
  44. package/lib/worker/memory.js +29 -0
  45. package/lib/worker/messages.d.ts +14 -0
  46. package/lib/worker/messages.js +15 -0
  47. package/lib/worker/packet.d.ts +11 -0
  48. package/lib/worker/packet.js +59 -0
  49. package/lib/worker/shared/capture.d.ts +27 -0
  50. package/lib/worker/shared/capture.js +63 -0
  51. package/lib/worker/shared/index.d.ts +3 -0
  52. package/lib/worker/shared/index.js +147 -0
  53. package/lib/worker/shared/messages.d.ts +67 -0
  54. package/lib/worker/shared/messages.js +64 -0
  55. package/lib/worker/shared/playback.d.ts +20 -0
  56. package/lib/worker/shared/playback.js +78 -0
  57. package/lib/worker/unshared/capture.d.ts +27 -0
  58. package/lib/worker/unshared/capture.js +66 -0
  59. package/lib/worker/unshared/index.d.ts +3 -0
  60. package/lib/worker/unshared/index.js +170 -0
  61. package/{worker/lib → lib/worker/unshared}/messages.d.ts +3 -15
  62. package/lib/worker/unshared/messages.js +80 -0
  63. package/lib/worker/unshared/playback.d.ts +22 -0
  64. package/lib/worker/unshared/playback.js +83 -0
  65. package/package.json +29 -26
  66. package/backend/lib/audioController.d.ts.map +0 -1
  67. package/backend/lib/audioController.js +0 -442
  68. package/backend/lib/audioController.js.map +0 -1
  69. package/backend/lib/config.d.ts.map +0 -1
  70. package/backend/lib/config.js +0 -11
  71. package/backend/lib/config.js.map +0 -1
  72. package/backend/lib/connectionPool.d.ts.map +0 -1
  73. package/backend/lib/connectionPool.js.map +0 -1
  74. package/backend/lib/index.d.ts.map +0 -1
  75. package/backend/lib/index.js +0 -293
  76. package/backend/lib/index.js.map +0 -1
  77. package/backend/lib/mainConnection.d.ts +0 -40
  78. package/backend/lib/mainConnection.d.ts.map +0 -1
  79. package/backend/lib/mainConnection.js +0 -294
  80. package/backend/lib/mainConnection.js.map +0 -1
  81. package/backend/lib/roomConnection.d.ts.map +0 -1
  82. package/backend/lib/roomConnection.js.map +0 -1
  83. package/backend/lib/rpcConnection.d.ts +0 -49
  84. package/backend/lib/rpcConnection.d.ts.map +0 -1
  85. package/backend/lib/rpcConnection.js.map +0 -1
  86. package/backend/lib/token.d.ts +0 -16
  87. package/backend/lib/token.d.ts.map +0 -1
  88. package/backend/lib/token.js.map +0 -1
  89. package/backend/package.json +0 -13
  90. package/wasm/lib/index.js +0 -19
  91. package/wasm/package.json +0 -14
  92. package/worker/lib/common.d.ts +0 -27
  93. package/worker/lib/index.js +0 -2
  94. package/worker/package.json +0 -13
  95. /package/{wasm/lib → lib/wasm}/index.d.ts +0 -0
@@ -0,0 +1,2 @@
1
+ export declare function getByteSize(buffer: Uint8Array, offset: number): number | undefined;
2
+ export declare function getNextOffset(buffer: Uint8Array, offset: number): number | undefined;
@@ -0,0 +1,117 @@
1
+ export function getByteSize(buffer, offset) {
2
+ const nextOffset = getNextOffset(buffer, offset);
3
+ return nextOffset !== undefined ? nextOffset - offset : undefined;
4
+ }
5
+ export function getNextOffset(buffer, offset) {
6
+ if (offset >= buffer.length)
7
+ return undefined;
8
+ const tag = buffer[offset++];
9
+ switch (tag) {
10
+ case 0xc0:
11
+ case 0xc2:
12
+ case 0xc3:
13
+ return offset;
14
+ case 0xc4:
15
+ return skipBytes(buffer, offset + 1, read8bit(buffer, offset));
16
+ case 0xc5:
17
+ return skipBytes(buffer, offset + 2, read16bit(buffer, offset));
18
+ case 0xc6:
19
+ return skipBytes(buffer, offset + 4, read32bit(buffer, offset));
20
+ case 0xc7:
21
+ return skipBytes(buffer, offset + 2, read8bit(buffer, offset));
22
+ case 0xc8:
23
+ return skipBytes(buffer, offset + 3, read16bit(buffer, offset));
24
+ case 0xc9:
25
+ return skipBytes(buffer, offset + 5, read32bit(buffer, offset));
26
+ case 0xca:
27
+ return skipBytes(buffer, offset, 4);
28
+ case 0xcb:
29
+ return skipBytes(buffer, offset, 8);
30
+ case 0xcc:
31
+ return skipBytes(buffer, offset, 1);
32
+ case 0xcd:
33
+ return skipBytes(buffer, offset, 2);
34
+ case 0xce:
35
+ return skipBytes(buffer, offset, 4);
36
+ case 0xcf:
37
+ return skipBytes(buffer, offset, 8);
38
+ case 0xd0:
39
+ return skipBytes(buffer, offset, 1);
40
+ case 0xd1:
41
+ return skipBytes(buffer, offset, 2);
42
+ case 0xd2:
43
+ return skipBytes(buffer, offset, 4);
44
+ case 0xd3:
45
+ return skipBytes(buffer, offset, 8);
46
+ case 0xd4:
47
+ return skipBytes(buffer, offset, 2);
48
+ case 0xd5:
49
+ return skipBytes(buffer, offset, 3);
50
+ case 0xd6:
51
+ return skipBytes(buffer, offset, 5);
52
+ case 0xd7:
53
+ return skipBytes(buffer, offset, 9);
54
+ case 0xd8:
55
+ return skipBytes(buffer, offset, 17);
56
+ case 0xd9:
57
+ return skipBytes(buffer, offset + 1, read8bit(buffer, offset));
58
+ case 0xda:
59
+ return skipBytes(buffer, offset + 2, read16bit(buffer, offset));
60
+ case 0xdb:
61
+ return skipBytes(buffer, offset + 4, read32bit(buffer, offset));
62
+ case 0xdc:
63
+ return skipObjects(buffer, offset + 2, read16bit(buffer, offset));
64
+ case 0xdd:
65
+ return skipObjects(buffer, offset + 4, read32bit(buffer, offset));
66
+ case 0xde:
67
+ return skipObjects(buffer, offset + 2, read16bit(buffer, offset) * 2);
68
+ case 0xdf:
69
+ return skipObjects(buffer, offset + 4, read32bit(buffer, offset) * 2);
70
+ default:
71
+ if ((tag & 0x80) === 0x00)
72
+ return offset;
73
+ if ((tag & 0xf0) === 0x80) {
74
+ return skipObjects(buffer, offset, (tag & 0x0f) * 2);
75
+ }
76
+ if ((tag & 0xf0) === 0x90)
77
+ return skipObjects(buffer, offset, tag & 0x0f);
78
+ if ((tag & 0xe0) === 0xa0)
79
+ return skipBytes(buffer, offset, tag & 0x1f);
80
+ if ((tag & 0xe0) === 0xe0)
81
+ return offset;
82
+ throw new Error('invalid msgpack');
83
+ }
84
+ }
85
+ function skipBytes(buffer, offset, bytes) {
86
+ const newOffset = offset + bytes;
87
+ return isFinite(newOffset) ? newOffset : undefined;
88
+ }
89
+ function skipObjects(buffer, offset, objects) {
90
+ if (!isFinite(objects))
91
+ return undefined;
92
+ for (let i = 0; i < objects; i++) {
93
+ const newOffset = getNextOffset(buffer, offset);
94
+ if (newOffset === undefined)
95
+ return undefined;
96
+ offset = newOffset;
97
+ }
98
+ return offset;
99
+ }
100
+ function read8bit(bytes, offset) {
101
+ if (offset > bytes.length)
102
+ return NaN;
103
+ return bytes[offset];
104
+ }
105
+ function read16bit(bytes, offset) {
106
+ if (offset + 2 > bytes.length)
107
+ return NaN;
108
+ return (bytes[offset] << 8) | bytes[offset + 1];
109
+ }
110
+ function read32bit(bytes, offset) {
111
+ if (offset + 4 > bytes.length)
112
+ return NaN;
113
+ return ((bytes[offset] << 24) |
114
+ (bytes[offset + 1] << 16) |
115
+ (bytes[offset + 2] << 8) |
116
+ bytes[offset + 3]);
117
+ }
@@ -1,11 +1,12 @@
1
+ import { Logger } from '@4players/odin-common';
1
2
  export declare const HealthCheckTimeout = 1000;
2
3
  export declare const ClockJumpCheckInterval = 1000;
3
4
  export declare const ClockJumpThreshold = 10000;
4
- export declare const MainConnectionPingInterval = 10000;
5
- export declare const WebRTCIceTimeout = 3000;
6
- export declare const WebRTCTimeout = 10000;
5
+ export declare const MainConnectionPingInterval: undefined;
6
+ export declare const WebRtcIceTimeout = 3000;
7
+ export declare const WebRtcTimeout = 10000;
7
8
  export declare const ConnectTimeout = 5000;
8
9
  export declare const MinTokenLifetime = 60000;
9
10
  export declare const MinTokenRefreshInterval = 5000;
10
11
  export declare const ConnectInterval: number[];
11
- //# sourceMappingURL=config.d.ts.map
12
+ export declare const logger: Logger;
@@ -0,0 +1,22 @@
1
+ import { Logger } from '@4players/odin-common';
2
+ export const HealthCheckTimeout = 1000; /* ms */
3
+ export const ClockJumpCheckInterval = 1000; /* ms */
4
+ export const ClockJumpThreshold = 10000; /* ms */
5
+ export const MainConnectionPingInterval = undefined; /* disabled, was 10000 ms */
6
+ export const WebRtcIceTimeout = 3000; /* ms */
7
+ export const WebRtcTimeout = 10000; /* ms */
8
+ export const ConnectTimeout = 5000; /* ms */
9
+ export const MinTokenLifetime = 60000; /* 1min */
10
+ export const MinTokenRefreshInterval = 5000; /* 5sec */
11
+ export const ConnectInterval = [0, 100, 200, 200, 500, 1000, 2000]; /* ms */
12
+ let loggerInstance;
13
+ const projectName = 'odin-plugin-web';
14
+ function createLogger() {
15
+ return new Logger(projectName, {
16
+ tags: [],
17
+ });
18
+ }
19
+ if (!loggerInstance) {
20
+ loggerInstance = createLogger();
21
+ }
22
+ export const logger = loggerInstance;
@@ -1,5 +1,6 @@
1
- import { DatagramHandler, MainConnection } from './mainConnection';
2
1
  import { Result } from '@4players/odin-common';
2
+ import { DatagramHandler, MainConnection } from './mainConnection';
3
+ import { Transport } from './index';
3
4
  export declare class ConnectionPool {
4
5
  readonly onDatagram: DatagramHandler;
5
6
  private _LastConnectionId;
@@ -9,9 +10,8 @@ export declare class ConnectionPool {
9
10
  get closed(): boolean;
10
11
  constructor(onDatagram: DatagramHandler);
11
12
  sendDatagram(connectionId: number, packet: Uint8Array): void;
12
- acquireConnection(url: URL, signal: AbortSignal): Promise<Result<MainConnection> | 'closed'>;
13
+ acquireConnection(transport: Transport, address: string, signal: AbortSignal): Promise<Result<MainConnection> | 'closed'>;
13
14
  releaseConnection(connection: MainConnection): void;
14
15
  checkHealth(): Promise<void>;
15
16
  close(): void;
16
17
  }
17
- //# sourceMappingURL=connectionPool.d.ts.map
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { ClockJumpCheckInterval, ClockJumpThreshold } from './config';
11
- import { MainConnection } from './mainConnection';
12
10
  import { failure, nextTick, success } from '@4players/odin-common';
11
+ import { ClockJumpCheckInterval, ClockJumpThreshold, logger } from './config';
12
+ import { createMainConnection, } from './mainConnection';
13
13
  export class ConnectionPool {
14
14
  get closed() {
15
15
  return this._Close.signal.aborted;
@@ -47,55 +47,72 @@ export class ConnectionPool {
47
47
  connection.connection.sendDatagram(packet);
48
48
  }
49
49
  }
50
- acquireConnection(url, signal) {
50
+ acquireConnection(transport, address, signal) {
51
51
  return __awaiter(this, void 0, void 0, function* () {
52
+ logger.log(['Connection'], 'ConnectionPool.acquireConnection(): transport, address', transport, address);
52
53
  yield nextTick(); // slow down acquireConnection
53
54
  if (this.closed)
54
55
  return 'closed';
55
- let entry = this._ConnectionsByUrl.get(url);
56
+ const key = create_connection_key(transport, address);
57
+ let entry = this._ConnectionsByUrl.get(key);
56
58
  if (entry === undefined ||
57
59
  (entry.status === 'ready' && entry.connection.closed)) {
60
+ // Create a new Connection
58
61
  const id = this._LastConnectionId++;
59
- const promise = MainConnection.create(id, url, this.onDatagram, signal);
60
- entry = { status: 'init', id, promise };
61
- this._ConnectionsByUrl.set(url, entry);
62
+ const promise = createMainConnection(id, transport, address, this.onDatagram, signal);
63
+ entry = { status: 'init', id, promise, references: 1 };
64
+ this._ConnectionsByUrl.set(key, entry);
62
65
  this._ConnectionsById.set(id, entry);
63
66
  const connection = yield promise;
67
+ logger.log(['Connection'], 'ConnectionPool.acquireConnection(): MainConnection resolved.', connection);
64
68
  if (connection.type !== 'Success') {
65
- this._ConnectionsByUrl.delete(url);
69
+ this._ConnectionsByUrl.delete(key);
66
70
  this._ConnectionsById.delete(id);
67
71
  return connection;
68
72
  }
69
- entry = { status: 'ready', connection: connection.value, references: 1 };
70
- this._ConnectionsByUrl.set(url, entry);
73
+ entry = {
74
+ status: 'ready',
75
+ connection: connection.value,
76
+ references: entry.references,
77
+ };
78
+ this._ConnectionsByUrl.set(key, entry);
71
79
  this._ConnectionsById.set(id, entry);
72
80
  return success(entry.connection);
73
81
  }
74
82
  else if (entry.status === 'init') {
83
+ logger.log(['Connection'], 'ConnectionPool.acquireConnection(): Found a MainConnection but it is still in init state.');
84
+ // Wait for the connection to connect
85
+ entry.references += 1;
75
86
  const onAbort = new Promise((accept) => signal.addEventListener('abort', () => accept(), { once: true }));
76
87
  const result = yield Promise.race([entry.promise, onAbort]);
88
+ logger.log(['Connection'], 'ConnectionPool.acquireConnection(): MainConnection was in init state but is now resolved', result);
77
89
  if (result === undefined)
78
90
  return failure('aborted');
79
91
  return result;
80
92
  }
81
93
  else {
94
+ // Return the connection
95
+ logger.log(['Connection'], 'ConnectionPool.acquireConnection(): Found a MainConnection and return it');
82
96
  entry.references += 1;
83
97
  return success(entry.connection);
84
98
  }
85
99
  });
86
100
  }
87
101
  releaseConnection(connection) {
102
+ logger.log(['Connection'], 'ConnectionPool.releaseConnection()');
88
103
  const entry = this._ConnectionsById.get(connection.id);
104
+ logger.log(['Connection'], 'ConnectionPool.releaseConnection() entry refs', entry === null || entry === void 0 ? void 0 : entry.references);
89
105
  if (entry === undefined)
90
106
  return;
91
107
  if (entry.status === 'init') {
92
- console.error('released connection before it was acquired!');
108
+ logger.log(['Connection'], 'ConnectionPool.releaseConnection(): MainConnection state is init, return.');
93
109
  return;
94
110
  }
95
111
  entry.references -= 1;
96
112
  if (entry.references === 0) {
113
+ logger.log(['Connection'], 'ConnectionPool.releaseConnection(): References is 0, close the MainConnection.');
97
114
  entry.connection.close();
98
- this._ConnectionsByUrl.delete(connection.url);
115
+ this._ConnectionsByUrl.delete(create_connection_key(connection.transport, connection.address));
99
116
  this._ConnectionsById.delete(connection.id);
100
117
  }
101
118
  }
@@ -108,4 +125,6 @@ export class ConnectionPool {
108
125
  this._Close.abort();
109
126
  }
110
127
  }
111
- //# sourceMappingURL=connectionPool.js.map
128
+ function create_connection_key(transport, address) {
129
+ return `${transport} ${address}`;
130
+ }
@@ -0,0 +1,12 @@
1
+ import { MediaController, Device } from './mediaController';
2
+ import { DeviceParameters } from './index';
3
+ import { Backend, Result } from '@4players/odin-common';
4
+ export declare function openCaptureDevice(controller: MediaController, parameters: DeviceParameters, apm: Backend.ApmConfig): Promise<Result<MediaStreamAudioSourceNode>>;
5
+ export declare function stopMediaStream(ms: MediaStream): void;
6
+ export declare function enumerateDevices(): Promise<Device[]>;
7
+ /**
8
+ * Requests audio and video browser permissions. Optionally, requests either audio or video permissions.
9
+ * @private
10
+ * @throws
11
+ */
12
+ export declare function requestMediaPermissions(): Promise<boolean>;
@@ -0,0 +1,120 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Device } from './mediaController';
11
+ import { logger } from './index';
12
+ import { failure, sleep, success, } from '@4players/odin-common';
13
+ let hasAudioPermissions = false;
14
+ export function openCaptureDevice(controller, parameters, apm) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ const deviceId = parameters.device
17
+ ? { exact: parameters.device.device.deviceId }
18
+ : undefined;
19
+ let stream;
20
+ try {
21
+ const result = yield Promise.race([
22
+ navigator.mediaDevices.getUserMedia({
23
+ audio: {
24
+ echoCancellation: apm.echoCanceller,
25
+ noiseSuppression: apm.noiseSuppression !== 'None',
26
+ autoGainControl: apm.gainController,
27
+ // @ts-ignore chrome fix
28
+ echoCancellationType: 'system',
29
+ // sampleRate: 48000,
30
+ deviceId,
31
+ },
32
+ }),
33
+ sleep(30000, 'timeout'),
34
+ ]);
35
+ if (result === 'timeout')
36
+ return failure('getUserMedia; timeout');
37
+ hasAudioPermissions = true;
38
+ stream = result;
39
+ }
40
+ catch (reason) {
41
+ return failure(`getUserMedia; ${reason}`);
42
+ }
43
+ const sampleRate = stream.getAudioTracks()[0].getSettings().sampleRate;
44
+ const context = yield controller.audioContexts.acquire(sampleRate);
45
+ return success(context.createMediaStreamSource(stream));
46
+ });
47
+ }
48
+ export function stopMediaStream(ms) {
49
+ ms.getTracks().forEach((track) => {
50
+ track.stop();
51
+ ms.removeTrack(track);
52
+ });
53
+ }
54
+ export function enumerateDevices() {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ hasAudioPermissions = yield requestMediaPermissions();
57
+ const mediaDeviceInfos = yield navigator.mediaDevices.enumerateDevices();
58
+ const devices = [];
59
+ let outputDeviceIndex = 0;
60
+ let inputDeviceIndex = 0;
61
+ for (const mediaDeviceInfo of mediaDeviceInfos) {
62
+ if (mediaDeviceInfo.kind === 'audioinput') {
63
+ if (inputDeviceIndex === 0) {
64
+ devices.push(new Device(mediaDeviceInfo, true));
65
+ }
66
+ else {
67
+ devices.push(new Device(mediaDeviceInfo, false));
68
+ }
69
+ inputDeviceIndex++;
70
+ }
71
+ if (mediaDeviceInfo.kind === 'audiooutput') {
72
+ if (outputDeviceIndex === 0) {
73
+ devices.push(new Device(mediaDeviceInfo, true));
74
+ }
75
+ else {
76
+ devices.push(new Device(mediaDeviceInfo, false));
77
+ }
78
+ outputDeviceIndex++;
79
+ }
80
+ }
81
+ return devices;
82
+ });
83
+ }
84
+ /**
85
+ * Requests audio and video browser permissions. Optionally, requests either audio or video permissions.
86
+ * @private
87
+ * @throws
88
+ */
89
+ export function requestMediaPermissions() {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ if (hasAudioPermissions)
92
+ return true;
93
+ const constraints = {};
94
+ constraints.audio = {
95
+ sampleRate: undefined,
96
+ deviceId: '',
97
+ autoGainControl: false,
98
+ noiseSuppression: false,
99
+ advanced: undefined,
100
+ aspectRatio: undefined,
101
+ echoCancellation: false,
102
+ channelCount: 0,
103
+ facingMode: undefined,
104
+ frameRate: 0,
105
+ };
106
+ logger.debug(['Device'], 'requestMediaPermissions(): constraints', constraints);
107
+ try {
108
+ const ms = yield navigator.mediaDevices.getUserMedia(constraints);
109
+ ms === null || ms === void 0 ? void 0 : ms.getTracks().forEach((track) => {
110
+ track.stop();
111
+ ms.removeTrack(track);
112
+ });
113
+ }
114
+ catch (e) {
115
+ logger.error(['Device'], `requestMediaPermissions(): Requesting permissions for ${'audio'} failed.`, e);
116
+ return false;
117
+ }
118
+ return true;
119
+ });
120
+ }
@@ -0,0 +1,31 @@
1
+ import { ConnectionStatus, type DatagramHandler, type MainConnection } from '../mainConnection';
2
+ import { MainCommands, MainNotifications, PeerId, Result } from '@4players/odin-common';
3
+ import { Transport } from '..';
4
+ import { Token } from '../token';
5
+ import { RpcConnection, TypedRpcConnection } from '../rpcConnection';
6
+ export declare class H3MainConnection implements MainConnection {
7
+ readonly id: number;
8
+ readonly address: string;
9
+ readonly connection: WebTransport;
10
+ readonly rpc: TypedRpcConnection<MainCommands, MainNotifications>;
11
+ readonly closeController: AbortController;
12
+ status: ConnectionStatus;
13
+ readonly closePromise: Promise<'closed'>;
14
+ readonly datagramWriter: WritableStreamDefaultWriter<Uint8Array>;
15
+ get closeSignal(): AbortSignal;
16
+ get transport(): Transport;
17
+ get closed(): boolean;
18
+ static create(id: number, address: string, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
19
+ private constructor();
20
+ waitForConnected(): Promise<void>;
21
+ close(): void;
22
+ joinRoom(token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]): Promise<'closed' | Result<RpcConnection>>;
23
+ getReconnectToken(peerId: PeerId): Promise<Result<Token>>;
24
+ healthCheck(): Promise<boolean>;
25
+ sendDatagram(datagram: Uint8Array): void;
26
+ startLocalVideo(mediaId: number, track: MediaStreamTrack): void;
27
+ stopLocalVideo(mediaId: number): void;
28
+ startRemoteVideo(mediaId: number): Promise<MediaStreamTrack>;
29
+ stopRemoteVideo(mediaId: number): void;
30
+ private _Request;
31
+ }
@@ -0,0 +1,195 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { ConnectionStatus, } from '../mainConnection';
11
+ import { MainCommandsRpc, MainNotificationsRpc, RoomCommandsRpc, RoomNotificationsRpc, failure, success, } from '@4players/odin-common';
12
+ import { logger, Transport } from '..';
13
+ import { Token } from '../token';
14
+ import { RpcConnection, TypedRpcConnection } from '../rpcConnection';
15
+ import { HealthCheckTimeout } from '../config';
16
+ import * as pkg from '../../../package.json';
17
+ export class H3MainConnection {
18
+ get closeSignal() {
19
+ return this.closeController.signal;
20
+ }
21
+ get transport() {
22
+ return Transport.H3;
23
+ }
24
+ get closed() {
25
+ return this.closeSignal.aborted;
26
+ }
27
+ static create(id, address, onDatagram, signal) {
28
+ return __awaiter(this, void 0, void 0, function* () {
29
+ try {
30
+ const url = new URL(`https://${address}?version=${pkg.version}`);
31
+ const closeController = new AbortController();
32
+ signal.addEventListener('abort', () => closeController.abort(), {
33
+ once: true,
34
+ });
35
+ const connection = new WebTransport(url);
36
+ closeController.signal.addEventListener('abort', () => __awaiter(this, void 0, void 0, function* () {
37
+ try {
38
+ yield connection.close();
39
+ }
40
+ catch (_a) {
41
+ /* drop silently */
42
+ }
43
+ }), { once: true });
44
+ yield connection.ready;
45
+ const stream = yield connection.createBidirectionalStream();
46
+ const rpc = new TypedRpcConnection(new RpcConnection(stream), MainCommandsRpc, MainNotificationsRpc);
47
+ closeController.signal.addEventListener('abort', () => rpc.close(), {
48
+ once: true,
49
+ });
50
+ rpc.notify('Hello', { stream: 'main' });
51
+ return success(new H3MainConnection(id, address, onDatagram, connection, rpc, closeController));
52
+ }
53
+ catch (error) {
54
+ return failure(`connecting to ${address} failed with ${error}`);
55
+ }
56
+ });
57
+ }
58
+ constructor(id, address, onDatagram, connection, rpc, closeController) {
59
+ this.id = id;
60
+ this.address = address;
61
+ this.connection = connection;
62
+ this.rpc = rpc;
63
+ this.closeController = closeController;
64
+ this.status = ConnectionStatus.Connecting;
65
+ logger.info(['Connection', 'H3'], 'H3MainConnection.constructor()');
66
+ this.closePromise = (() => __awaiter(this, void 0, void 0, function* () {
67
+ try {
68
+ yield this.connection.closed;
69
+ }
70
+ catch (_a) {
71
+ /* drop silently */
72
+ }
73
+ return 'closed';
74
+ }))();
75
+ this.datagramWriter = connection.datagrams.writable.getWriter();
76
+ this.connection.closed
77
+ .then(() => {
78
+ this.status = ConnectionStatus.Closed;
79
+ })
80
+ .catch(() => {
81
+ /* drop silently */
82
+ });
83
+ this.connection.ready
84
+ .then(() => {
85
+ this.status = ConnectionStatus.Connected;
86
+ })
87
+ .catch(() => {
88
+ /* drop silently */
89
+ });
90
+ readDatagrams(this.connection, this.id, onDatagram);
91
+ }
92
+ waitForConnected() {
93
+ return Promise.race([
94
+ this.connection.ready,
95
+ this.connection.closed.then(() => {
96
+ throw new Error('connection closed');
97
+ }),
98
+ ]);
99
+ }
100
+ close() {
101
+ this.closeController.abort('request');
102
+ }
103
+ joinRoom(token, roomId, userData, position) {
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ try {
106
+ if (this.closed)
107
+ return 'closed';
108
+ const stream = yield this.connection
109
+ .createBidirectionalStream()
110
+ .catch(() => 'closed');
111
+ if (stream === 'closed')
112
+ return 'closed';
113
+ const rpc = new TypedRpcConnection(new RpcConnection(stream), RoomCommandsRpc, RoomNotificationsRpc);
114
+ const response = yield rpc.request('Hello', {
115
+ stream: 'room',
116
+ token: String(token),
117
+ room_id: roomId,
118
+ user_data: userData,
119
+ position,
120
+ });
121
+ if (response === 'closed' || response.type === 'Failure')
122
+ return response;
123
+ return success(rpc.rpcConnection);
124
+ }
125
+ catch (error) {
126
+ return failure(`JoinRoom failed; ${error}`);
127
+ }
128
+ });
129
+ }
130
+ getReconnectToken(peerId) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ const response = yield this._Request('RequestReconnectToken', {
133
+ peer_id: peerId,
134
+ });
135
+ if (response.type === 'Failure')
136
+ return response;
137
+ return Token.parse(response.value);
138
+ });
139
+ }
140
+ healthCheck() {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ const response = yield this._Request('Ping', {}, HealthCheckTimeout);
143
+ const success = response !== 'timeout' && response.type === 'Success';
144
+ if (!success)
145
+ this.close();
146
+ return success;
147
+ });
148
+ }
149
+ sendDatagram(datagram) {
150
+ try {
151
+ this.datagramWriter.write(datagram);
152
+ }
153
+ catch (_a) {
154
+ /* drop silently */
155
+ }
156
+ }
157
+ startLocalVideo(mediaId, track) {
158
+ throw new Error('not supported.');
159
+ }
160
+ stopLocalVideo(mediaId) {
161
+ throw new Error('not supported.');
162
+ }
163
+ startRemoteVideo(mediaId) {
164
+ throw new Error('not supported.');
165
+ }
166
+ stopRemoteVideo(mediaId) {
167
+ throw new Error('not supported.');
168
+ }
169
+ _Request(name, properties, timeout) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ const result = yield (timeout === undefined
172
+ ? this.rpc.request(name, properties)
173
+ : this.rpc.request(name, properties, timeout));
174
+ if (result === 'closed')
175
+ return failure('connection lost');
176
+ return result;
177
+ });
178
+ }
179
+ }
180
+ function readDatagrams(connection, connectionId, onDatagram) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ try {
183
+ const reader = connection.datagrams.readable.getReader();
184
+ for (;;) {
185
+ const read = yield reader.read();
186
+ if (read.done)
187
+ break;
188
+ onDatagram(connectionId, read.value);
189
+ }
190
+ }
191
+ catch (_a) {
192
+ /* drop silently */
193
+ }
194
+ });
195
+ }