@sprucelabs/heartwood-view-controllers 119.6.9 → 119.7.1
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/build/.spruce/schemas/heartwoodViewControllers/v2021_02_11/webRtcPlayer.schema.js +5 -0
- package/build/.spruce/schemas/schemas.types.d.ts +8 -0
- package/build/esm/.spruce/schemas/heartwoodViewControllers/v2021_02_11/webRtcPlayer.schema.js +5 -0
- package/build/esm/.spruce/schemas/schemas.types.d.ts +8 -0
- package/build/esm/schemas/v2021_02_11/webRtcPlayer.builder.d.ts +6 -0
- package/build/esm/schemas/v2021_02_11/webRtcPlayer.builder.js +6 -0
- package/build/esm/tests/StubRtcStatsReport.d.ts +3 -0
- package/build/esm/tests/StubRtcStatsReport.js +34 -0
- package/build/esm/tests/utilities/webRtcInteractor.d.ts +2 -1
- package/build/esm/tests/utilities/webRtcInteractor.js +12 -0
- package/build/esm/viewControllers/webRtcStreaming/WebRtcPlayer.vc.js +16 -1
- package/build/esm/webRtcStreaming/WebRtcConnection.d.ts +15 -4
- package/build/esm/webRtcStreaming/WebRtcConnection.js +38 -12
- package/build/esm/webRtcStreaming/WebRtcStreamer.d.ts +2 -2
- package/build/esm/webRtcStreaming/WebRtcStreamer.js +4 -4
- package/build/schemas/v2021_02_11/webRtcPlayer.builder.d.ts +6 -0
- package/build/schemas/v2021_02_11/webRtcPlayer.builder.js +6 -0
- package/build/tests/StubRtcStatsReport.d.ts +3 -0
- package/build/tests/StubRtcStatsReport.js +37 -0
- package/build/tests/utilities/webRtcInteractor.d.ts +2 -1
- package/build/tests/utilities/webRtcInteractor.js +10 -0
- package/build/viewControllers/webRtcStreaming/WebRtcPlayer.vc.js +6 -1
- package/build/webRtcStreaming/WebRtcConnection.d.ts +15 -4
- package/build/webRtcStreaming/WebRtcConnection.js +34 -12
- package/build/webRtcStreaming/WebRtcStreamer.d.ts +2 -2
- package/build/webRtcStreaming/WebRtcStreamer.js +4 -4
- package/package.json +1 -1
|
@@ -33,6 +33,11 @@ const webRtcPlayerSchema = {
|
|
|
33
33
|
options: { valueType: `HeartwoodTypes.WebRtcPlayerCropHandler`, }
|
|
34
34
|
},
|
|
35
35
|
/** . */
|
|
36
|
+
'onStateChange': {
|
|
37
|
+
type: 'raw',
|
|
38
|
+
options: { valueType: `HeartwoodTypes.WebRtcStateChangeHandler`, }
|
|
39
|
+
},
|
|
40
|
+
/** . */
|
|
36
41
|
'crop': {
|
|
37
42
|
type: 'schema',
|
|
38
43
|
options: { schema: webRtcCropPoint_schema_1.default, }
|
|
@@ -753,6 +753,7 @@ declare module '@sprucelabs/spruce-core-schemas/build/.spruce/schemas/core.schem
|
|
|
753
753
|
'controller'?: (HeartwoodTypes.ViewController<HeartwoodTypes.WebRtcPlayer>) | undefined | null;
|
|
754
754
|
'shouldAllowCropping'?: boolean | undefined | null;
|
|
755
755
|
'onCrop'?: (HeartwoodTypes.WebRtcPlayerCropHandler) | undefined | null;
|
|
756
|
+
'onStateChange'?: (HeartwoodTypes.WebRtcStateChangeHandler) | undefined | null;
|
|
756
757
|
'crop'?: SpruceSchemas.HeartwoodViewControllers.v2021_02_11.WebRtcCropPoint | undefined | null;
|
|
757
758
|
'connection'?: (HeartwoodTypes.WebRtcConnection) | undefined | null;
|
|
758
759
|
'streamer'?: (HeartwoodTypes.WebRtcStreamer) | undefined | null;
|
|
@@ -789,6 +790,13 @@ declare module '@sprucelabs/spruce-core-schemas/build/.spruce/schemas/core.schem
|
|
|
789
790
|
};
|
|
790
791
|
};
|
|
791
792
|
/** . */
|
|
793
|
+
'onStateChange': {
|
|
794
|
+
type: 'raw';
|
|
795
|
+
options: {
|
|
796
|
+
valueType: `HeartwoodTypes.WebRtcStateChangeHandler`;
|
|
797
|
+
};
|
|
798
|
+
};
|
|
799
|
+
/** . */
|
|
792
800
|
'crop': {
|
|
793
801
|
type: 'schema';
|
|
794
802
|
options: {
|
package/build/esm/.spruce/schemas/heartwoodViewControllers/v2021_02_11/webRtcPlayer.schema.js
CHANGED
|
@@ -28,6 +28,11 @@ const webRtcPlayerSchema = {
|
|
|
28
28
|
options: { valueType: `HeartwoodTypes.WebRtcPlayerCropHandler`, }
|
|
29
29
|
},
|
|
30
30
|
/** . */
|
|
31
|
+
'onStateChange': {
|
|
32
|
+
type: 'raw',
|
|
33
|
+
options: { valueType: `HeartwoodTypes.WebRtcStateChangeHandler`, }
|
|
34
|
+
},
|
|
35
|
+
/** . */
|
|
31
36
|
'crop': {
|
|
32
37
|
type: 'schema',
|
|
33
38
|
options: { schema: webRtcCropPointSchema_v2021_02_11, }
|
|
@@ -753,6 +753,7 @@ declare module '@sprucelabs/spruce-core-schemas/build/.spruce/schemas/core.schem
|
|
|
753
753
|
'controller'?: (HeartwoodTypes.ViewController<HeartwoodTypes.WebRtcPlayer>) | undefined | null;
|
|
754
754
|
'shouldAllowCropping'?: boolean | undefined | null;
|
|
755
755
|
'onCrop'?: (HeartwoodTypes.WebRtcPlayerCropHandler) | undefined | null;
|
|
756
|
+
'onStateChange'?: (HeartwoodTypes.WebRtcStateChangeHandler) | undefined | null;
|
|
756
757
|
'crop'?: SpruceSchemas.HeartwoodViewControllers.v2021_02_11.WebRtcCropPoint | undefined | null;
|
|
757
758
|
'connection'?: (HeartwoodTypes.WebRtcConnection) | undefined | null;
|
|
758
759
|
'streamer'?: (HeartwoodTypes.WebRtcStreamer) | undefined | null;
|
|
@@ -789,6 +790,13 @@ declare module '@sprucelabs/spruce-core-schemas/build/.spruce/schemas/core.schem
|
|
|
789
790
|
};
|
|
790
791
|
};
|
|
791
792
|
/** . */
|
|
793
|
+
'onStateChange': {
|
|
794
|
+
type: 'raw';
|
|
795
|
+
options: {
|
|
796
|
+
valueType: `HeartwoodTypes.WebRtcStateChangeHandler`;
|
|
797
|
+
};
|
|
798
|
+
};
|
|
799
|
+
/** . */
|
|
792
800
|
'crop': {
|
|
793
801
|
type: 'schema';
|
|
794
802
|
options: {
|
|
@@ -20,6 +20,12 @@ export default buildSchema({
|
|
|
20
20
|
valueType: 'HeartwoodTypes.WebRtcPlayerCropHandler',
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
+
onStateChange: {
|
|
24
|
+
type: 'raw',
|
|
25
|
+
options: {
|
|
26
|
+
valueType: 'HeartwoodTypes.WebRtcStateChangeHandler',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
23
29
|
crop: {
|
|
24
30
|
type: 'schema',
|
|
25
31
|
options: {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default class StubRtcStatsReport extends Map {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
const baseTime = Date.now();
|
|
5
|
+
this.set('inbound-rtp', {
|
|
6
|
+
id: 'inbound-rtp',
|
|
7
|
+
type: 'inbound-rtp',
|
|
8
|
+
timestamp: baseTime,
|
|
9
|
+
kind: 'video',
|
|
10
|
+
ssrc: 1234,
|
|
11
|
+
bytesReceived: 500000,
|
|
12
|
+
packetsReceived: 1000,
|
|
13
|
+
packetsLost: 10,
|
|
14
|
+
jitter: 0.005,
|
|
15
|
+
});
|
|
16
|
+
this.set('outbound-rtp', {
|
|
17
|
+
id: 'outbound-rtp',
|
|
18
|
+
type: 'outbound-rtp',
|
|
19
|
+
timestamp: baseTime,
|
|
20
|
+
kind: 'video',
|
|
21
|
+
ssrc: 5678,
|
|
22
|
+
bytesSent: 700000,
|
|
23
|
+
packetsSent: 1200,
|
|
24
|
+
});
|
|
25
|
+
this.set('candidate-pair', {
|
|
26
|
+
id: 'candidate-pair',
|
|
27
|
+
type: 'candidate-pair',
|
|
28
|
+
timestamp: baseTime,
|
|
29
|
+
state: 'succeeded',
|
|
30
|
+
currentRoundTripTime: 0.03,
|
|
31
|
+
availableOutgoingBitrate: 3000000,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { WebRtcCropPoint } from '../../types/heartwood.types';
|
|
1
|
+
import { WebRtcConnectionState, WebRtcCropPoint, WebRtcStateChangeEvent } from '../../types/heartwood.types';
|
|
2
2
|
import WebRtcPlayerViewController from '../../viewControllers/webRtcStreaming/WebRtcPlayer.vc';
|
|
3
3
|
declare const webRtcInteractor: {
|
|
4
4
|
simulateCrop: (vc: WebRtcPlayerViewController, cropPoint?: WebRtcCropPoint) => Promise<void>;
|
|
5
|
+
simulateStateChange<State extends WebRtcConnectionState>(vc: WebRtcPlayerViewController, state: State, event?: WebRtcStateChangeEvent<State>): Promise<void>;
|
|
5
6
|
};
|
|
6
7
|
export default webRtcInteractor;
|
|
@@ -8,7 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { assertOptions } from '@sprucelabs/schema';
|
|
11
|
+
import { assert } from '@sprucelabs/test-utils';
|
|
11
12
|
import renderUtil from '../../utilities/render.utility.js';
|
|
13
|
+
import WebRtcConnectionImpl from '../../webRtcStreaming/WebRtcConnection.js';
|
|
14
|
+
import MockRtcPeerConnection from '../MockRtcPeerConnection.js';
|
|
12
15
|
const webRtcInteractor = {
|
|
13
16
|
simulateCrop: (vc, cropPoint) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
17
|
var _a;
|
|
@@ -16,5 +19,14 @@ const webRtcInteractor = {
|
|
|
16
19
|
const model = renderUtil.render(vc);
|
|
17
20
|
yield ((_a = model.onCrop) === null || _a === void 0 ? void 0 : _a.call(model, cropPoint));
|
|
18
21
|
}),
|
|
22
|
+
simulateStateChange(vc, state, event) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const model = renderUtil.render(vc);
|
|
25
|
+
const { onStateChange } = model;
|
|
26
|
+
assert.isFunction(onStateChange, `You must pass onStateChange to the view model during construction: this.Controller('web-rtc-player', { onStateChange: () => {} }).`);
|
|
27
|
+
assert.isEqual(WebRtcConnectionImpl.RTCPeerConnection, MockRtcPeerConnection, `You must setup the MockRtcPeerConnection in your test to test interactions. Try putting WebRtcConnectionImpl.RTCPeerConnection = MockRtcPeerConnection in your beforeEach().`);
|
|
28
|
+
yield onStateChange(state, event);
|
|
29
|
+
});
|
|
30
|
+
},
|
|
19
31
|
};
|
|
20
32
|
export default webRtcInteractor;
|
|
@@ -7,6 +7,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
10
21
|
import { assertOptions } from '@sprucelabs/schema';
|
|
11
22
|
import SpruceError from '../../errors/SpruceError.js';
|
|
12
23
|
import removeUniversalViewOptions from '../../utilities/removeUniversalViewOptions.js';
|
|
@@ -15,8 +26,12 @@ import AbstractViewController from '../Abstract.vc.js';
|
|
|
15
26
|
class WebRtcPlayerViewController extends AbstractViewController {
|
|
16
27
|
constructor(options) {
|
|
17
28
|
super(options);
|
|
29
|
+
const _a = removeUniversalViewOptions(options), { onStateChange } = _a, model = __rest(_a, ["onStateChange"]);
|
|
18
30
|
this.connection = WebRtcConnectionImpl.Connection();
|
|
19
|
-
|
|
31
|
+
if (onStateChange) {
|
|
32
|
+
this.connection.onStateChange(onStateChange);
|
|
33
|
+
}
|
|
34
|
+
this.model = Object.assign(Object.assign({ connection: this.connection, onStateChange }, model), { controller: this });
|
|
20
35
|
}
|
|
21
36
|
setStreamer(streamer) {
|
|
22
37
|
assertOptions({ streamer }, ['streamer']);
|
|
@@ -10,6 +10,9 @@ export default class WebRtcConnectionImpl implements WebRtcConnection {
|
|
|
10
10
|
static Connection(): WebRtcConnection;
|
|
11
11
|
getRtcPeerConnection(): RTCPeerConnection;
|
|
12
12
|
createOffer(options: WebRtcVcPluginCreateOfferOptions): Promise<WebRtcCreateOfferResponse>;
|
|
13
|
+
private addStateChangeListener;
|
|
14
|
+
private handleStatusFailed;
|
|
15
|
+
private logStatsReport;
|
|
13
16
|
private emitStateChange;
|
|
14
17
|
onStateChange(cb: WebRtcStateChangeHandler): void;
|
|
15
18
|
offStateChange(listener: WebRtcStateChangeHandler): void;
|
|
@@ -21,16 +24,23 @@ export interface WebRtcVcPluginCreateOfferOptions {
|
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
export interface WebRtcStateEventMap {
|
|
24
|
-
createdOffer:
|
|
25
|
-
suppliedAnswer:
|
|
27
|
+
createdOffer: undefined;
|
|
28
|
+
suppliedAnswer: undefined;
|
|
29
|
+
connecting: WebRtcEvent;
|
|
30
|
+
connected: WebRtcEvent;
|
|
31
|
+
disconnected: WebRtcEvent;
|
|
26
32
|
trackAdded: RTCTrackEvent;
|
|
27
33
|
error: WebRtcErrorEvent;
|
|
28
34
|
}
|
|
35
|
+
interface WebRtcEvent {
|
|
36
|
+
connection: RTCPeerConnection;
|
|
37
|
+
}
|
|
29
38
|
export type WebRtcConnectionState = keyof WebRtcStateEventMap;
|
|
30
|
-
export type WebRtcStateChangeEvent =
|
|
31
|
-
export type WebRtcStateChangeHandler = (state:
|
|
39
|
+
export type WebRtcStateChangeEvent<State extends WebRtcConnectionState = WebRtcConnectionState> = WebRtcStateEventMap[State];
|
|
40
|
+
export type WebRtcStateChangeHandler<State extends WebRtcConnectionState = WebRtcConnectionState> = (state: State, event?: WebRtcStateChangeEvent<State>) => void | Promise<void>;
|
|
32
41
|
export interface WebRtcErrorEvent {
|
|
33
42
|
stats: RTCStatsReport;
|
|
43
|
+
connection: RTCPeerConnection;
|
|
34
44
|
}
|
|
35
45
|
export interface WebRtcConnection {
|
|
36
46
|
offStateChange(listener: WebRtcStateChangeHandler): void;
|
|
@@ -43,3 +53,4 @@ export interface WebRtcCreateOfferResponse {
|
|
|
43
53
|
streamer: WebRtcStreamer;
|
|
44
54
|
rtcPeerConnection: RTCPeerConnection;
|
|
45
55
|
}
|
|
56
|
+
export {};
|
|
@@ -44,18 +44,7 @@ export default class WebRtcConnectionImpl {
|
|
|
44
44
|
iceServers: [],
|
|
45
45
|
});
|
|
46
46
|
this.rtcPeerConnection = connection;
|
|
47
|
-
|
|
48
|
-
this.log.info(`RTCPeerConnection state changed to ${connection.connectionState}`);
|
|
49
|
-
if (connection.connectionState === 'failed') {
|
|
50
|
-
const stats = yield connection.getStats();
|
|
51
|
-
const messages = [];
|
|
52
|
-
stats.forEach((report) => {
|
|
53
|
-
messages.push(`[${report.type}] ID: ${report.id}`, report);
|
|
54
|
-
});
|
|
55
|
-
this.log.error(`RTCPeerConnection failed with stats:\n\n`, messages.join('\n'));
|
|
56
|
-
yield this.emitStateChange('error', { stats });
|
|
57
|
-
}
|
|
58
|
-
}));
|
|
47
|
+
this.addStateChangeListener();
|
|
59
48
|
const { offerToReceiveAudio, offerToReceiveVideo } = offerOptions;
|
|
60
49
|
if (offerToReceiveAudio) {
|
|
61
50
|
connection.addTransceiver('audio', { direction: 'recvonly' });
|
|
@@ -81,6 +70,43 @@ export default class WebRtcConnectionImpl {
|
|
|
81
70
|
};
|
|
82
71
|
});
|
|
83
72
|
}
|
|
73
|
+
addStateChangeListener() {
|
|
74
|
+
var _a;
|
|
75
|
+
(_a = this.rtcPeerConnection) === null || _a === void 0 ? void 0 : _a.addEventListener('connectionstatechange', () => __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
var _a, _b;
|
|
77
|
+
this.log.info(`RTCPeerConnection state changed to ${(_a = this.rtcPeerConnection) === null || _a === void 0 ? void 0 : _a.connectionState}`);
|
|
78
|
+
const state = (_b = this.rtcPeerConnection) === null || _b === void 0 ? void 0 : _b.connectionState;
|
|
79
|
+
if (state === 'failed') {
|
|
80
|
+
yield this.handleStatusFailed();
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
yield this.emitStateChange(state, {
|
|
84
|
+
connection: this.rtcPeerConnection,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
handleStatusFailed() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
const connection = this.rtcPeerConnection;
|
|
92
|
+
if (!connection) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const stats = yield connection.getStats();
|
|
96
|
+
this.logStatsReport(stats);
|
|
97
|
+
yield this.emitStateChange('error', {
|
|
98
|
+
stats,
|
|
99
|
+
connection,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
logStatsReport(stats) {
|
|
104
|
+
const messages = [];
|
|
105
|
+
stats.forEach((report) => {
|
|
106
|
+
messages.push(`[${report.type}] ID: ${report.id}`, JSON.stringify(report, null, 2));
|
|
107
|
+
});
|
|
108
|
+
this.log.error(`RTCPeerConnection failed with stats:\n\n`, messages.join('\n'));
|
|
109
|
+
}
|
|
84
110
|
emitStateChange(state, event) {
|
|
85
111
|
return __awaiter(this, void 0, void 0, function* () {
|
|
86
112
|
for (const handler of this.stateChangeListeners) {
|
|
@@ -3,8 +3,8 @@ export default class WebRtcStreamerImpl implements WebRtcStreamer {
|
|
|
3
3
|
private connection;
|
|
4
4
|
static Class?: new (connection: RTCPeerConnection, stateChangeHandlers?: WebRtcStateChangeListener) => WebRtcStreamer;
|
|
5
5
|
private setAnwserHandler?;
|
|
6
|
-
protected constructor(connection: RTCPeerConnection,
|
|
7
|
-
static Streamer(connection: RTCPeerConnection,
|
|
6
|
+
protected constructor(connection: RTCPeerConnection, onSetAnswer?: WebRtcStateChangeListener);
|
|
7
|
+
static Streamer(connection: RTCPeerConnection, onSetAnswer?: WebRtcStateChangeListener): WebRtcStreamer;
|
|
8
8
|
setAnswer(answerSdp: string): Promise<void>;
|
|
9
9
|
onTrack(cb: (event: RTCTrackEvent) => void): void;
|
|
10
10
|
}
|
|
@@ -9,13 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { assertOptions } from '@sprucelabs/schema';
|
|
11
11
|
export default class WebRtcStreamerImpl {
|
|
12
|
-
constructor(connection,
|
|
12
|
+
constructor(connection, onSetAnswer) {
|
|
13
13
|
this.connection = connection;
|
|
14
|
-
this.setAnwserHandler =
|
|
14
|
+
this.setAnwserHandler = onSetAnswer;
|
|
15
15
|
}
|
|
16
|
-
static Streamer(connection,
|
|
16
|
+
static Streamer(connection, onSetAnswer) {
|
|
17
17
|
var _a;
|
|
18
|
-
return new ((_a = this.Class) !== null && _a !== void 0 ? _a : this)(connection,
|
|
18
|
+
return new ((_a = this.Class) !== null && _a !== void 0 ? _a : this)(connection, onSetAnswer);
|
|
19
19
|
}
|
|
20
20
|
setAnswer(answerSdp) {
|
|
21
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -22,6 +22,12 @@ exports.default = (0, schema_1.buildSchema)({
|
|
|
22
22
|
valueType: 'HeartwoodTypes.WebRtcPlayerCropHandler',
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
|
+
onStateChange: {
|
|
26
|
+
type: 'raw',
|
|
27
|
+
options: {
|
|
28
|
+
valueType: 'HeartwoodTypes.WebRtcStateChangeHandler',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
25
31
|
crop: {
|
|
26
32
|
type: 'schema',
|
|
27
33
|
options: {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class StubRtcStatsReport extends Map {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
const baseTime = Date.now();
|
|
7
|
+
this.set('inbound-rtp', {
|
|
8
|
+
id: 'inbound-rtp',
|
|
9
|
+
type: 'inbound-rtp',
|
|
10
|
+
timestamp: baseTime,
|
|
11
|
+
kind: 'video',
|
|
12
|
+
ssrc: 1234,
|
|
13
|
+
bytesReceived: 500000,
|
|
14
|
+
packetsReceived: 1000,
|
|
15
|
+
packetsLost: 10,
|
|
16
|
+
jitter: 0.005,
|
|
17
|
+
});
|
|
18
|
+
this.set('outbound-rtp', {
|
|
19
|
+
id: 'outbound-rtp',
|
|
20
|
+
type: 'outbound-rtp',
|
|
21
|
+
timestamp: baseTime,
|
|
22
|
+
kind: 'video',
|
|
23
|
+
ssrc: 5678,
|
|
24
|
+
bytesSent: 700000,
|
|
25
|
+
packetsSent: 1200,
|
|
26
|
+
});
|
|
27
|
+
this.set('candidate-pair', {
|
|
28
|
+
id: 'candidate-pair',
|
|
29
|
+
type: 'candidate-pair',
|
|
30
|
+
timestamp: baseTime,
|
|
31
|
+
state: 'succeeded',
|
|
32
|
+
currentRoundTripTime: 0.03,
|
|
33
|
+
availableOutgoingBitrate: 3000000,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = StubRtcStatsReport;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { WebRtcCropPoint } from '../../types/heartwood.types';
|
|
1
|
+
import { WebRtcConnectionState, WebRtcCropPoint, WebRtcStateChangeEvent } from '../../types/heartwood.types';
|
|
2
2
|
import WebRtcPlayerViewController from '../../viewControllers/webRtcStreaming/WebRtcPlayer.vc';
|
|
3
3
|
declare const webRtcInteractor: {
|
|
4
4
|
simulateCrop: (vc: WebRtcPlayerViewController, cropPoint?: WebRtcCropPoint) => Promise<void>;
|
|
5
|
+
simulateStateChange<State extends WebRtcConnectionState>(vc: WebRtcPlayerViewController, state: State, event?: WebRtcStateChangeEvent<State>): Promise<void>;
|
|
5
6
|
};
|
|
6
7
|
export default webRtcInteractor;
|
|
@@ -4,12 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const schema_1 = require("@sprucelabs/schema");
|
|
7
|
+
const test_utils_1 = require("@sprucelabs/test-utils");
|
|
7
8
|
const render_utility_1 = __importDefault(require("../../utilities/render.utility"));
|
|
9
|
+
const WebRtcConnection_1 = __importDefault(require("../../webRtcStreaming/WebRtcConnection"));
|
|
10
|
+
const MockRtcPeerConnection_1 = __importDefault(require("../MockRtcPeerConnection"));
|
|
8
11
|
const webRtcInteractor = {
|
|
9
12
|
simulateCrop: async (vc, cropPoint) => {
|
|
10
13
|
(0, schema_1.assertOptions)({ vc }, ['vc']);
|
|
11
14
|
const model = render_utility_1.default.render(vc);
|
|
12
15
|
await model.onCrop?.(cropPoint);
|
|
13
16
|
},
|
|
17
|
+
async simulateStateChange(vc, state, event) {
|
|
18
|
+
const model = render_utility_1.default.render(vc);
|
|
19
|
+
const { onStateChange } = model;
|
|
20
|
+
test_utils_1.assert.isFunction(onStateChange, `You must pass onStateChange to the view model during construction: this.Controller('web-rtc-player', { onStateChange: () => {} }).`);
|
|
21
|
+
test_utils_1.assert.isEqual(WebRtcConnection_1.default.RTCPeerConnection, MockRtcPeerConnection_1.default, `You must setup the MockRtcPeerConnection in your test to test interactions. Try putting WebRtcConnectionImpl.RTCPeerConnection = MockRtcPeerConnection in your beforeEach().`);
|
|
22
|
+
await onStateChange(state, event);
|
|
23
|
+
},
|
|
14
24
|
};
|
|
15
25
|
exports.default = webRtcInteractor;
|
|
@@ -11,10 +11,15 @@ const Abstract_vc_1 = __importDefault(require("../Abstract.vc"));
|
|
|
11
11
|
class WebRtcPlayerViewController extends Abstract_vc_1.default {
|
|
12
12
|
constructor(options) {
|
|
13
13
|
super(options);
|
|
14
|
+
const { onStateChange, ...model } = (0, removeUniversalViewOptions_1.default)(options);
|
|
14
15
|
this.connection = WebRtcConnection_1.default.Connection();
|
|
16
|
+
if (onStateChange) {
|
|
17
|
+
this.connection.onStateChange(onStateChange);
|
|
18
|
+
}
|
|
15
19
|
this.model = {
|
|
16
20
|
connection: this.connection,
|
|
17
|
-
|
|
21
|
+
onStateChange,
|
|
22
|
+
...model,
|
|
18
23
|
controller: this,
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -10,6 +10,9 @@ export default class WebRtcConnectionImpl implements WebRtcConnection {
|
|
|
10
10
|
static Connection(): WebRtcConnection;
|
|
11
11
|
getRtcPeerConnection(): RTCPeerConnection;
|
|
12
12
|
createOffer(options: WebRtcVcPluginCreateOfferOptions): Promise<WebRtcCreateOfferResponse>;
|
|
13
|
+
private addStateChangeListener;
|
|
14
|
+
private handleStatusFailed;
|
|
15
|
+
private logStatsReport;
|
|
13
16
|
private emitStateChange;
|
|
14
17
|
onStateChange(cb: WebRtcStateChangeHandler): void;
|
|
15
18
|
offStateChange(listener: WebRtcStateChangeHandler): void;
|
|
@@ -21,16 +24,23 @@ export interface WebRtcVcPluginCreateOfferOptions {
|
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
export interface WebRtcStateEventMap {
|
|
24
|
-
createdOffer:
|
|
25
|
-
suppliedAnswer:
|
|
27
|
+
createdOffer: undefined;
|
|
28
|
+
suppliedAnswer: undefined;
|
|
29
|
+
connecting: WebRtcEvent;
|
|
30
|
+
connected: WebRtcEvent;
|
|
31
|
+
disconnected: WebRtcEvent;
|
|
26
32
|
trackAdded: RTCTrackEvent;
|
|
27
33
|
error: WebRtcErrorEvent;
|
|
28
34
|
}
|
|
35
|
+
interface WebRtcEvent {
|
|
36
|
+
connection: RTCPeerConnection;
|
|
37
|
+
}
|
|
29
38
|
export type WebRtcConnectionState = keyof WebRtcStateEventMap;
|
|
30
|
-
export type WebRtcStateChangeEvent =
|
|
31
|
-
export type WebRtcStateChangeHandler = (state:
|
|
39
|
+
export type WebRtcStateChangeEvent<State extends WebRtcConnectionState = WebRtcConnectionState> = WebRtcStateEventMap[State];
|
|
40
|
+
export type WebRtcStateChangeHandler<State extends WebRtcConnectionState = WebRtcConnectionState> = (state: State, event?: WebRtcStateChangeEvent<State>) => void | Promise<void>;
|
|
32
41
|
export interface WebRtcErrorEvent {
|
|
33
42
|
stats: RTCStatsReport;
|
|
43
|
+
connection: RTCPeerConnection;
|
|
34
44
|
}
|
|
35
45
|
export interface WebRtcConnection {
|
|
36
46
|
offStateChange(listener: WebRtcStateChangeHandler): void;
|
|
@@ -43,3 +53,4 @@ export interface WebRtcCreateOfferResponse {
|
|
|
43
53
|
streamer: WebRtcStreamer;
|
|
44
54
|
rtcPeerConnection: RTCPeerConnection;
|
|
45
55
|
}
|
|
56
|
+
export {};
|
|
@@ -38,18 +38,7 @@ class WebRtcConnectionImpl {
|
|
|
38
38
|
iceServers: [],
|
|
39
39
|
});
|
|
40
40
|
this.rtcPeerConnection = connection;
|
|
41
|
-
|
|
42
|
-
this.log.info(`RTCPeerConnection state changed to ${connection.connectionState}`);
|
|
43
|
-
if (connection.connectionState === 'failed') {
|
|
44
|
-
const stats = await connection.getStats();
|
|
45
|
-
const messages = [];
|
|
46
|
-
stats.forEach((report) => {
|
|
47
|
-
messages.push(`[${report.type}] ID: ${report.id}`, report);
|
|
48
|
-
});
|
|
49
|
-
this.log.error(`RTCPeerConnection failed with stats:\n\n`, messages.join('\n'));
|
|
50
|
-
await this.emitStateChange('error', { stats });
|
|
51
|
-
}
|
|
52
|
-
});
|
|
41
|
+
this.addStateChangeListener();
|
|
53
42
|
const { offerToReceiveAudio, offerToReceiveVideo } = offerOptions;
|
|
54
43
|
if (offerToReceiveAudio) {
|
|
55
44
|
connection.addTransceiver('audio', { direction: 'recvonly' });
|
|
@@ -74,6 +63,39 @@ class WebRtcConnectionImpl {
|
|
|
74
63
|
}),
|
|
75
64
|
};
|
|
76
65
|
}
|
|
66
|
+
addStateChangeListener() {
|
|
67
|
+
this.rtcPeerConnection?.addEventListener('connectionstatechange', async () => {
|
|
68
|
+
this.log.info(`RTCPeerConnection state changed to ${this.rtcPeerConnection?.connectionState}`);
|
|
69
|
+
const state = this.rtcPeerConnection?.connectionState;
|
|
70
|
+
if (state === 'failed') {
|
|
71
|
+
await this.handleStatusFailed();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await this.emitStateChange(state, {
|
|
75
|
+
connection: this.rtcPeerConnection,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async handleStatusFailed() {
|
|
81
|
+
const connection = this.rtcPeerConnection;
|
|
82
|
+
if (!connection) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const stats = await connection.getStats();
|
|
86
|
+
this.logStatsReport(stats);
|
|
87
|
+
await this.emitStateChange('error', {
|
|
88
|
+
stats,
|
|
89
|
+
connection,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
logStatsReport(stats) {
|
|
93
|
+
const messages = [];
|
|
94
|
+
stats.forEach((report) => {
|
|
95
|
+
messages.push(`[${report.type}] ID: ${report.id}`, JSON.stringify(report, null, 2));
|
|
96
|
+
});
|
|
97
|
+
this.log.error(`RTCPeerConnection failed with stats:\n\n`, messages.join('\n'));
|
|
98
|
+
}
|
|
77
99
|
async emitStateChange(state, event) {
|
|
78
100
|
for (const handler of this.stateChangeListeners) {
|
|
79
101
|
await handler(state, event);
|
|
@@ -3,8 +3,8 @@ export default class WebRtcStreamerImpl implements WebRtcStreamer {
|
|
|
3
3
|
private connection;
|
|
4
4
|
static Class?: new (connection: RTCPeerConnection, stateChangeHandlers?: WebRtcStateChangeListener) => WebRtcStreamer;
|
|
5
5
|
private setAnwserHandler?;
|
|
6
|
-
protected constructor(connection: RTCPeerConnection,
|
|
7
|
-
static Streamer(connection: RTCPeerConnection,
|
|
6
|
+
protected constructor(connection: RTCPeerConnection, onSetAnswer?: WebRtcStateChangeListener);
|
|
7
|
+
static Streamer(connection: RTCPeerConnection, onSetAnswer?: WebRtcStateChangeListener): WebRtcStreamer;
|
|
8
8
|
setAnswer(answerSdp: string): Promise<void>;
|
|
9
9
|
onTrack(cb: (event: RTCTrackEvent) => void): void;
|
|
10
10
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const schema_1 = require("@sprucelabs/schema");
|
|
4
4
|
class WebRtcStreamerImpl {
|
|
5
|
-
constructor(connection,
|
|
5
|
+
constructor(connection, onSetAnswer) {
|
|
6
6
|
this.connection = connection;
|
|
7
|
-
this.setAnwserHandler =
|
|
7
|
+
this.setAnwserHandler = onSetAnswer;
|
|
8
8
|
}
|
|
9
|
-
static Streamer(connection,
|
|
10
|
-
return new (this.Class ?? this)(connection,
|
|
9
|
+
static Streamer(connection, onSetAnswer) {
|
|
10
|
+
return new (this.Class ?? this)(connection, onSetAnswer);
|
|
11
11
|
}
|
|
12
12
|
async setAnswer(answerSdp) {
|
|
13
13
|
(0, schema_1.assertOptions)({ answerSdp }, ['answerSdp']);
|
package/package.json
CHANGED