@raydiant/playlist-window-manager 0.29.2 → 0.30.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/build/IFrameRenderer.d.ts +1 -0
- package/build/IFrameRenderer.js +14 -0
- package/build/IFrameRenderer.test.d.ts +1 -0
- package/build/IFrameRenderer.test.js +70 -0
- package/build/PlaylistWindowManager.d.ts +7 -0
- package/build/PlaylistWindowManager.js +150 -39
- package/build/PlaylistWindowManager.test.d.ts +1 -0
- package/build/PlaylistWindowManager.test.js +1763 -0
- package/build/PresentationWindow.d.ts +31 -1
- package/build/PresentationWindow.js +538 -30
- package/build/PresentationWindow.test.js +323 -0
- package/build/SurfaceBroker.d.ts +30 -0
- package/build/SurfaceBroker.js +311 -0
- package/build/SurfaceBroker.test.d.ts +1 -0
- package/build/SurfaceBroker.test.js +432 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/surfaces.d.ts +52 -0
- package/build/surfaces.js +15 -0
- package/build/testUtils.d.ts +56 -0
- package/build/testUtils.js +327 -5
- package/build/types.d.ts +1 -0
- package/package.json +2 -1
- package/src/IFrameRenderer.test.ts +88 -0
- package/src/IFrameRenderer.ts +21 -0
- package/src/PlaylistWindowManager.test.ts +1466 -0
- package/src/PlaylistWindowManager.ts +134 -7
- package/src/PresentationWindow.test.ts +247 -0
- package/src/PresentationWindow.ts +644 -3
- package/src/SurfaceBroker.test.ts +278 -0
- package/src/SurfaceBroker.ts +281 -0
- package/src/__snapshots__/PlaylistWindowManager.test.ts.snap +26 -0
- package/src/index.ts +1 -0
- package/src/surfaces.ts +82 -0
- package/src/testUtils.ts +335 -1
- package/src/types.ts +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
35
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
36
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
37
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
38
|
+
function step(op) {
|
|
39
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
40
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
41
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
42
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
43
|
+
switch (op[0]) {
|
|
44
|
+
case 0: case 1: t = op; break;
|
|
45
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
46
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
47
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
48
|
+
default:
|
|
49
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
50
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
51
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
52
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
53
|
+
if (t[2]) _.ops.pop();
|
|
54
|
+
_.trys.pop(); continue;
|
|
55
|
+
}
|
|
56
|
+
op = body.call(thisArg, _);
|
|
57
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
58
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
+
var SurfaceBroker_1 = __importStar(require("./SurfaceBroker"));
|
|
63
|
+
var testUtils_1 = require("./testUtils");
|
|
64
|
+
jest.useFakeTimers();
|
|
65
|
+
// Lets promise callbacks run under fake timers.
|
|
66
|
+
var settle = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
67
|
+
var i;
|
|
68
|
+
return __generator(this, function (_a) {
|
|
69
|
+
switch (_a.label) {
|
|
70
|
+
case 0:
|
|
71
|
+
i = 0;
|
|
72
|
+
_a.label = 1;
|
|
73
|
+
case 1:
|
|
74
|
+
if (!(i < 20)) return [3 /*break*/, 4];
|
|
75
|
+
return [4 /*yield*/, Promise.resolve()];
|
|
76
|
+
case 2:
|
|
77
|
+
_a.sent();
|
|
78
|
+
_a.label = 3;
|
|
79
|
+
case 3:
|
|
80
|
+
i += 1;
|
|
81
|
+
return [3 /*break*/, 1];
|
|
82
|
+
case 4: return [2 /*return*/];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}); };
|
|
86
|
+
var options = function (label) { return ({
|
|
87
|
+
url: "https://cdn.test/".concat(label, ".mp4"),
|
|
88
|
+
bounds: { x: 0, y: 0, width: 1920, height: 1080 },
|
|
89
|
+
muted: true,
|
|
90
|
+
label: label,
|
|
91
|
+
}); };
|
|
92
|
+
function setup(hostOpts) {
|
|
93
|
+
var host = (0, testUtils_1.createFakeSurfaceHost)(hostOpts);
|
|
94
|
+
var warn = jest.fn();
|
|
95
|
+
var broker = new SurfaceBroker_1.default(host.surfaces, (0, testUtils_1.createLogger)({ warn: warn }));
|
|
96
|
+
var onEvent = jest.fn();
|
|
97
|
+
var onRefused = jest.fn();
|
|
98
|
+
var open = function (windowId) {
|
|
99
|
+
return broker.open(windowId, options(windowId), onEvent, onRefused);
|
|
100
|
+
};
|
|
101
|
+
var opens = function () { return host.calls.filter(function (c) { return c.method === 'openVideo'; }); };
|
|
102
|
+
var refusalsLogged = function (reason) {
|
|
103
|
+
return warn.mock.calls.filter(function (_a) {
|
|
104
|
+
var context = _a[1];
|
|
105
|
+
return context && context.reason === reason;
|
|
106
|
+
}).length;
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
host: host,
|
|
110
|
+
broker: broker,
|
|
111
|
+
warn: warn,
|
|
112
|
+
onEvent: onEvent,
|
|
113
|
+
onRefused: onRefused,
|
|
114
|
+
open: open,
|
|
115
|
+
opens: opens,
|
|
116
|
+
refusalsLogged: refusalsLogged,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Records the outcome of an open without letting a rejection go unhandled.
|
|
120
|
+
function track(promise) {
|
|
121
|
+
var result = {};
|
|
122
|
+
promise.then(function (surface) {
|
|
123
|
+
result.surface = surface;
|
|
124
|
+
}, function (err) {
|
|
125
|
+
result.reason = err.reason;
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
describe('a refusal is answered', function () {
|
|
130
|
+
test.each(['no_decoder', 'unsupported'])('the host refusing %s is a refusal with that reason, logged once', function (reason) { return __awaiter(void 0, void 0, void 0, function () {
|
|
131
|
+
var _a, open, refusalsLogged, result;
|
|
132
|
+
return __generator(this, function (_b) {
|
|
133
|
+
switch (_b.label) {
|
|
134
|
+
case 0:
|
|
135
|
+
_a = setup({ refuse: reason }), open = _a.open, refusalsLogged = _a.refusalsLogged;
|
|
136
|
+
result = track(open('w1'));
|
|
137
|
+
return [4 /*yield*/, settle()];
|
|
138
|
+
case 1:
|
|
139
|
+
_b.sent();
|
|
140
|
+
expect(result.reason).toBe(reason);
|
|
141
|
+
expect(refusalsLogged(reason)).toBe(1);
|
|
142
|
+
return [2 /*return*/];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}); });
|
|
146
|
+
test('an open past its deadline is refused, and a late handle is closed at once', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
147
|
+
var _a, host, open, refusalsLogged, result, lateId;
|
|
148
|
+
return __generator(this, function (_b) {
|
|
149
|
+
switch (_b.label) {
|
|
150
|
+
case 0:
|
|
151
|
+
_a = setup({ hang: true }), host = _a.host, open = _a.open, refusalsLogged = _a.refusalsLogged;
|
|
152
|
+
result = track(open('w1'));
|
|
153
|
+
return [4 /*yield*/, settle()];
|
|
154
|
+
case 1:
|
|
155
|
+
_b.sent();
|
|
156
|
+
expect(result.reason).toBeUndefined();
|
|
157
|
+
jest.advanceTimersByTime(SurfaceBroker_1.SURFACE_OPEN_DEADLINE_MS);
|
|
158
|
+
return [4 /*yield*/, settle()];
|
|
159
|
+
case 2:
|
|
160
|
+
_b.sent();
|
|
161
|
+
expect(result.reason).toBe('deadline');
|
|
162
|
+
expect(refusalsLogged('deadline')).toBe(1);
|
|
163
|
+
host.releaseHung();
|
|
164
|
+
return [4 /*yield*/, settle()];
|
|
165
|
+
case 3:
|
|
166
|
+
_b.sent();
|
|
167
|
+
lateId = host.lastId();
|
|
168
|
+
expect(host.calls.filter(function (c) { return c.id === lateId; }).map(function (c) { return c.method; })).toEqual(['close']);
|
|
169
|
+
expect(host.liveIds()).toEqual([]);
|
|
170
|
+
return [2 /*return*/];
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}); });
|
|
174
|
+
test('a first frame past its deadline closes the surface and is refused', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
175
|
+
var _a, host, open, onRefused, onEvent, refusalsLogged, result;
|
|
176
|
+
return __generator(this, function (_b) {
|
|
177
|
+
switch (_b.label) {
|
|
178
|
+
case 0:
|
|
179
|
+
_a = setup(), host = _a.host, open = _a.open, onRefused = _a.onRefused, onEvent = _a.onEvent, refusalsLogged = _a.refusalsLogged;
|
|
180
|
+
result = track(open('w1'));
|
|
181
|
+
return [4 /*yield*/, settle()];
|
|
182
|
+
case 1:
|
|
183
|
+
_b.sent();
|
|
184
|
+
expect(result.surface).toBeDefined();
|
|
185
|
+
jest.advanceTimersByTime(SurfaceBroker_1.SURFACE_FIRST_FRAME_DEADLINE_MS);
|
|
186
|
+
return [4 /*yield*/, settle()];
|
|
187
|
+
case 2:
|
|
188
|
+
_b.sent();
|
|
189
|
+
expect(onRefused).toHaveBeenCalledWith('deadline');
|
|
190
|
+
expect(refusalsLogged('deadline')).toBe(1);
|
|
191
|
+
expect(host.liveIds()).toEqual([]);
|
|
192
|
+
// A first frame after the deadline is not forwarded.
|
|
193
|
+
host.emit(host.lastId(), {
|
|
194
|
+
type: 'first_frame',
|
|
195
|
+
width: 1920,
|
|
196
|
+
height: 1080,
|
|
197
|
+
durationS: 10,
|
|
198
|
+
});
|
|
199
|
+
expect(onEvent).not.toHaveBeenCalled();
|
|
200
|
+
return [2 /*return*/];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}); });
|
|
204
|
+
test('a first frame in time is forwarded and stops the deadline', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
205
|
+
var _a, host, open, onRefused, onEvent, frame;
|
|
206
|
+
return __generator(this, function (_b) {
|
|
207
|
+
switch (_b.label) {
|
|
208
|
+
case 0:
|
|
209
|
+
_a = setup(), host = _a.host, open = _a.open, onRefused = _a.onRefused, onEvent = _a.onEvent;
|
|
210
|
+
track(open('w1'));
|
|
211
|
+
return [4 /*yield*/, settle()];
|
|
212
|
+
case 1:
|
|
213
|
+
_b.sent();
|
|
214
|
+
frame = {
|
|
215
|
+
type: 'first_frame',
|
|
216
|
+
width: 1920,
|
|
217
|
+
height: 1080,
|
|
218
|
+
durationS: 10,
|
|
219
|
+
};
|
|
220
|
+
host.emit(host.lastId(), frame);
|
|
221
|
+
jest.advanceTimersByTime(SurfaceBroker_1.SURFACE_FIRST_FRAME_DEADLINE_MS * 2);
|
|
222
|
+
return [4 /*yield*/, settle()];
|
|
223
|
+
case 2:
|
|
224
|
+
_b.sent();
|
|
225
|
+
expect(onEvent).toHaveBeenCalledWith(frame);
|
|
226
|
+
expect(onRefused).not.toHaveBeenCalled();
|
|
227
|
+
expect(host.liveIds()).toEqual([host.lastId()]);
|
|
228
|
+
return [2 /*return*/];
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}); });
|
|
232
|
+
test('no_decoder while a close is pending is tried once more after it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
233
|
+
var _a, host, broker, open, opens, refusalsLogged, first, surface, realClose, rejectOpen, realOpen, second;
|
|
234
|
+
return __generator(this, function (_b) {
|
|
235
|
+
switch (_b.label) {
|
|
236
|
+
case 0:
|
|
237
|
+
_a = setup(), host = _a.host, broker = _a.broker, open = _a.open, opens = _a.opens, refusalsLogged = _a.refusalsLogged;
|
|
238
|
+
first = track(open('w1'));
|
|
239
|
+
return [4 /*yield*/, settle()];
|
|
240
|
+
case 1:
|
|
241
|
+
_b.sent();
|
|
242
|
+
surface = first.surface;
|
|
243
|
+
realClose = surface.close.bind(surface);
|
|
244
|
+
surface.close = function () {
|
|
245
|
+
return new Promise(function (resolve) { return setTimeout(function () { return resolve(realClose()); }, 100); });
|
|
246
|
+
};
|
|
247
|
+
rejectOpen = function () { return undefined; };
|
|
248
|
+
realOpen = host.surfaces.openVideo;
|
|
249
|
+
host.surfaces.openVideo = function (opts) {
|
|
250
|
+
host.surfaces.openVideo = realOpen;
|
|
251
|
+
host.calls.push({ method: 'openVideo', args: [opts], seq: 0 });
|
|
252
|
+
return new Promise(function (_, reject) {
|
|
253
|
+
rejectOpen = reject;
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
second = track(open('w2'));
|
|
257
|
+
return [4 /*yield*/, settle()];
|
|
258
|
+
case 2:
|
|
259
|
+
_b.sent();
|
|
260
|
+
broker.close('w1');
|
|
261
|
+
rejectOpen(new Error('no_decoder'));
|
|
262
|
+
return [4 /*yield*/, settle()];
|
|
263
|
+
case 3:
|
|
264
|
+
_b.sent();
|
|
265
|
+
expect(opens()).toHaveLength(2);
|
|
266
|
+
jest.advanceTimersByTime(100);
|
|
267
|
+
return [4 /*yield*/, settle()];
|
|
268
|
+
case 4:
|
|
269
|
+
_b.sent();
|
|
270
|
+
expect(opens()).toHaveLength(3);
|
|
271
|
+
expect(second.surface).toBeDefined();
|
|
272
|
+
expect(refusalsLogged('no_decoder')).toBe(0);
|
|
273
|
+
return [2 /*return*/];
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}); });
|
|
277
|
+
test('no_decoder with no close pending is refused at once', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
278
|
+
var _a, open, opens, refusalsLogged, result;
|
|
279
|
+
return __generator(this, function (_b) {
|
|
280
|
+
switch (_b.label) {
|
|
281
|
+
case 0:
|
|
282
|
+
_a = setup({ refuse: 'no_decoder' }), open = _a.open, opens = _a.opens, refusalsLogged = _a.refusalsLogged;
|
|
283
|
+
result = track(open('w1'));
|
|
284
|
+
return [4 /*yield*/, settle()];
|
|
285
|
+
case 1:
|
|
286
|
+
_b.sent();
|
|
287
|
+
expect(result.reason).toBe('no_decoder');
|
|
288
|
+
expect(opens()).toHaveLength(1);
|
|
289
|
+
expect(refusalsLogged('no_decoder')).toBe(1);
|
|
290
|
+
return [2 /*return*/];
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}); });
|
|
294
|
+
});
|
|
295
|
+
describe('two at most; an open waits for a close', function () {
|
|
296
|
+
test('a third open with none closing or leaving is refused with no host call', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
297
|
+
var _a, host, open, opens, refusalsLogged, third;
|
|
298
|
+
return __generator(this, function (_b) {
|
|
299
|
+
switch (_b.label) {
|
|
300
|
+
case 0:
|
|
301
|
+
_a = setup(), host = _a.host, open = _a.open, opens = _a.opens, refusalsLogged = _a.refusalsLogged;
|
|
302
|
+
track(open('w1'));
|
|
303
|
+
track(open('w2'));
|
|
304
|
+
return [4 /*yield*/, settle()];
|
|
305
|
+
case 1:
|
|
306
|
+
_b.sent();
|
|
307
|
+
third = track(open('w3'));
|
|
308
|
+
return [4 /*yield*/, settle()];
|
|
309
|
+
case 2:
|
|
310
|
+
_b.sent();
|
|
311
|
+
expect(third.reason).toBe('no_decoder');
|
|
312
|
+
expect(opens()).toHaveLength(2);
|
|
313
|
+
expect(refusalsLogged('no_decoder')).toBe(1);
|
|
314
|
+
expect(host.violations).toEqual([]);
|
|
315
|
+
return [2 /*return*/];
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}); });
|
|
319
|
+
test('a third open with a surface leaving waits for its close', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
320
|
+
var _a, host, broker, open, opens, third;
|
|
321
|
+
return __generator(this, function (_b) {
|
|
322
|
+
switch (_b.label) {
|
|
323
|
+
case 0:
|
|
324
|
+
_a = setup(), host = _a.host, broker = _a.broker, open = _a.open, opens = _a.opens;
|
|
325
|
+
track(open('w1'));
|
|
326
|
+
track(open('w2'));
|
|
327
|
+
return [4 /*yield*/, settle()];
|
|
328
|
+
case 1:
|
|
329
|
+
_b.sent();
|
|
330
|
+
broker.leaving('w1');
|
|
331
|
+
third = track(open('w3'));
|
|
332
|
+
return [4 /*yield*/, settle()];
|
|
333
|
+
case 2:
|
|
334
|
+
_b.sent();
|
|
335
|
+
expect(opens()).toHaveLength(2);
|
|
336
|
+
expect(third.surface).toBeUndefined();
|
|
337
|
+
return [4 /*yield*/, broker.close('w1')];
|
|
338
|
+
case 3:
|
|
339
|
+
_b.sent();
|
|
340
|
+
return [4 /*yield*/, settle()];
|
|
341
|
+
case 4:
|
|
342
|
+
_b.sent();
|
|
343
|
+
expect(opens()).toHaveLength(3);
|
|
344
|
+
expect(third.surface).toBeDefined();
|
|
345
|
+
expect(host.violations).toEqual([]);
|
|
346
|
+
return [2 /*return*/];
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}); });
|
|
350
|
+
test('a third open with a close in flight waits for that close', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
351
|
+
var _a, host, broker, open, opens, first, surface, realClose, third;
|
|
352
|
+
return __generator(this, function (_b) {
|
|
353
|
+
switch (_b.label) {
|
|
354
|
+
case 0:
|
|
355
|
+
_a = setup(), host = _a.host, broker = _a.broker, open = _a.open, opens = _a.opens;
|
|
356
|
+
first = track(open('w1'));
|
|
357
|
+
track(open('w2'));
|
|
358
|
+
return [4 /*yield*/, settle()];
|
|
359
|
+
case 1:
|
|
360
|
+
_b.sent();
|
|
361
|
+
surface = first.surface;
|
|
362
|
+
realClose = surface.close.bind(surface);
|
|
363
|
+
surface.close = function () {
|
|
364
|
+
return new Promise(function (resolve) { return setTimeout(function () { return resolve(realClose()); }, 100); });
|
|
365
|
+
};
|
|
366
|
+
broker.close('w1');
|
|
367
|
+
third = track(open('w3'));
|
|
368
|
+
return [4 /*yield*/, settle()];
|
|
369
|
+
case 2:
|
|
370
|
+
_b.sent();
|
|
371
|
+
expect(opens()).toHaveLength(2);
|
|
372
|
+
jest.advanceTimersByTime(100);
|
|
373
|
+
return [4 /*yield*/, settle()];
|
|
374
|
+
case 3:
|
|
375
|
+
_b.sent();
|
|
376
|
+
expect(opens()).toHaveLength(3);
|
|
377
|
+
expect(third.surface).toBeDefined();
|
|
378
|
+
expect(host.violations).toEqual([]);
|
|
379
|
+
return [2 /*return*/];
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}); });
|
|
383
|
+
test('an open given up before the host answers closes the handle', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
384
|
+
var _a, host, broker, open, result;
|
|
385
|
+
return __generator(this, function (_b) {
|
|
386
|
+
switch (_b.label) {
|
|
387
|
+
case 0:
|
|
388
|
+
_a = setup({ hang: true }), host = _a.host, broker = _a.broker, open = _a.open;
|
|
389
|
+
result = track(open('w1'));
|
|
390
|
+
return [4 /*yield*/, settle()];
|
|
391
|
+
case 1:
|
|
392
|
+
_b.sent();
|
|
393
|
+
return [4 /*yield*/, broker.close('w1')];
|
|
394
|
+
case 2:
|
|
395
|
+
_b.sent();
|
|
396
|
+
host.releaseHung();
|
|
397
|
+
return [4 /*yield*/, settle()];
|
|
398
|
+
case 3:
|
|
399
|
+
_b.sent();
|
|
400
|
+
expect(result.reason).toBe('closed');
|
|
401
|
+
expect(host.liveIds()).toEqual([]);
|
|
402
|
+
return [2 /*return*/];
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}); });
|
|
406
|
+
test('close is idempotent and closeAll closes every surface', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
407
|
+
var _a, host, broker, open;
|
|
408
|
+
return __generator(this, function (_b) {
|
|
409
|
+
switch (_b.label) {
|
|
410
|
+
case 0:
|
|
411
|
+
_a = setup(), host = _a.host, broker = _a.broker, open = _a.open;
|
|
412
|
+
track(open('w1'));
|
|
413
|
+
track(open('w2'));
|
|
414
|
+
return [4 /*yield*/, settle()];
|
|
415
|
+
case 1:
|
|
416
|
+
_b.sent();
|
|
417
|
+
return [4 /*yield*/, broker.close('w1')];
|
|
418
|
+
case 2:
|
|
419
|
+
_b.sent();
|
|
420
|
+
return [4 /*yield*/, broker.close('w1')];
|
|
421
|
+
case 3:
|
|
422
|
+
_b.sent();
|
|
423
|
+
return [4 /*yield*/, broker.closeAll()];
|
|
424
|
+
case 4:
|
|
425
|
+
_b.sent();
|
|
426
|
+
expect(host.liveIds()).toEqual([]);
|
|
427
|
+
expect(host.calls.filter(function (c) { return c.method === 'close'; })).toHaveLength(2);
|
|
428
|
+
return [2 /*return*/];
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}); });
|
|
432
|
+
});
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
|
@@ -28,3 +28,4 @@ var PresentationWindow_1 = __importDefault(require("./PresentationWindow"));
|
|
|
28
28
|
exports.PresentationWindow = PresentationWindow_1.default;
|
|
29
29
|
__exportStar(require("./PresentationWindow"), exports);
|
|
30
30
|
__exportStar(require("./types"), exports);
|
|
31
|
+
__exportStar(require("./surfaces"), exports);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type VideoOptions = {
|
|
2
|
+
url: string;
|
|
3
|
+
bounds: Rect;
|
|
4
|
+
fit?: 'cover' | 'stretch';
|
|
5
|
+
loop?: boolean;
|
|
6
|
+
muted?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
};
|
|
9
|
+
export interface VideoSurface {
|
|
10
|
+
readonly id: number;
|
|
11
|
+
show(opts?: {
|
|
12
|
+
fadeMs?: number;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
hide(opts?: {
|
|
15
|
+
fadeMs?: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
play(): Promise<void>;
|
|
18
|
+
pause(): Promise<void>;
|
|
19
|
+
seek(seconds: number): Promise<void>;
|
|
20
|
+
setBounds(r: Rect): Promise<void>;
|
|
21
|
+
setMuted(m: boolean): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
onEvent(fn: (e: VideoEvent) => void): void;
|
|
24
|
+
}
|
|
25
|
+
export type Rect = {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
export type VideoEvent = {
|
|
32
|
+
type: 'first_frame';
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
durationS: number | null;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'playing';
|
|
38
|
+
} | {
|
|
39
|
+
type: 'stalled';
|
|
40
|
+
} | {
|
|
41
|
+
type: 'ended';
|
|
42
|
+
} | {
|
|
43
|
+
type: 'error';
|
|
44
|
+
reason: 'fetch' | 'decode' | 'unsupported' | 'closed';
|
|
45
|
+
};
|
|
46
|
+
export interface SurfaceHost {
|
|
47
|
+
openVideo(opts: VideoOptions): Promise<VideoSurface>;
|
|
48
|
+
onHole(open: boolean): void;
|
|
49
|
+
showAfterRevealMs?: number;
|
|
50
|
+
}
|
|
51
|
+
export declare const VIDEO_REQUESTS: string[];
|
|
52
|
+
export type VideoRefusalReason = 'no_decoder' | 'unsupported' | 'deadline' | 'not_offered';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The video contract's types, copied verbatim (exported, and kept out of
|
|
3
|
+
// prettier so the copy stays diffable against the source) from the device
|
|
4
|
+
// video contract that the Signage Stick's host builds to. A surface is a video
|
|
5
|
+
// the host draws outside the browser, under the page, in a hole the page
|
|
6
|
+
// clears.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.VIDEO_REQUESTS = void 0;
|
|
9
|
+
// The messages an app sends to ask for, size, reveal and give up a surface.
|
|
10
|
+
exports.VIDEO_REQUESTS = [
|
|
11
|
+
'open_video',
|
|
12
|
+
'video_bounds',
|
|
13
|
+
'video_hole_open',
|
|
14
|
+
'close_video',
|
|
15
|
+
];
|
package/build/testUtils.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as re from '@raydiant/playlist-rule-engine';
|
|
|
3
3
|
import PresentationWindow, { PresentationWindowOptions } from './PresentationWindow';
|
|
4
4
|
import PlaylistWindowManagerState, { PlaylistWindowManagerStateOptions } from './PlaylistWindowManagerState';
|
|
5
5
|
import { Presentation, ApplicationVersion, Logger, CreateWindowOptions } from './types';
|
|
6
|
+
import { SurfaceHost, VideoEvent } from './surfaces';
|
|
6
7
|
export declare function createPresentationWindow(overrides?: Partial<PresentationWindowOptions>): PresentationWindow;
|
|
7
8
|
export declare function createPresentation(overrides?: Partial<Presentation>): Presentation;
|
|
8
9
|
export declare function createPresentationRelationships(overrides?: Partial<Presentation['r']>): Presentation['r'];
|
|
@@ -41,3 +42,58 @@ export declare function createRenderer(): {
|
|
|
41
42
|
runReceiveMessageCallback(type: string, payload: any): void;
|
|
42
43
|
runErrorCallback(error: Error): void;
|
|
43
44
|
};
|
|
45
|
+
export interface RecordedCall {
|
|
46
|
+
method: string;
|
|
47
|
+
windowId: string;
|
|
48
|
+
args: unknown[];
|
|
49
|
+
seq: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function createRecordingRenderer(): {
|
|
52
|
+
renderer: {
|
|
53
|
+
createWindow(opts: CreateWindowOptions): void;
|
|
54
|
+
loadWindow(windowId: string): Promise<void>;
|
|
55
|
+
updateWindowBounds(windowId: string, bounds: unknown): void;
|
|
56
|
+
showWindow(windowId: string, isFocused: boolean): void;
|
|
57
|
+
hideWindow(windowId: string): void;
|
|
58
|
+
removeWindow(windowId: string): Promise<void>;
|
|
59
|
+
sendMessageString(windowId: string, message: string): void;
|
|
60
|
+
sendMessageObject(windowId: string, type: string, payload?: unknown): void;
|
|
61
|
+
muteWindow(windowId: string): void;
|
|
62
|
+
unMuteWindow(windowId: string): void;
|
|
63
|
+
setWindowBackground(windowId: string, color: string): void;
|
|
64
|
+
};
|
|
65
|
+
calls: RecordedCall[];
|
|
66
|
+
received: RecordedCall[];
|
|
67
|
+
hooks: {
|
|
68
|
+
onLoad?: ((windowId: string) => void) | undefined;
|
|
69
|
+
onMessage?: ((windowId: string, type: string, payload?: any) => void) | undefined;
|
|
70
|
+
};
|
|
71
|
+
receive(windowId: string, type: string, payload?: any): void;
|
|
72
|
+
fail(windowId: string, err: Error): void;
|
|
73
|
+
liveWindowIds(): string[];
|
|
74
|
+
};
|
|
75
|
+
export interface FakeSurfaceCall {
|
|
76
|
+
id?: number;
|
|
77
|
+
method: string;
|
|
78
|
+
args: unknown[];
|
|
79
|
+
seq: number;
|
|
80
|
+
}
|
|
81
|
+
export declare function createFakeSurfaceHost(opts?: {
|
|
82
|
+
decoders?: number;
|
|
83
|
+
refuse?: 'no_decoder' | 'unsupported';
|
|
84
|
+
hang?: boolean;
|
|
85
|
+
hangShow?: boolean;
|
|
86
|
+
playingAfterMs?: number;
|
|
87
|
+
}): {
|
|
88
|
+
surfaces: SurfaceHost;
|
|
89
|
+
calls: FakeSurfaceCall[];
|
|
90
|
+
violations: string[];
|
|
91
|
+
emit: (id: number, event: VideoEvent) => void;
|
|
92
|
+
releaseHung(): void;
|
|
93
|
+
liveIds(): number[];
|
|
94
|
+
lastId(): number;
|
|
95
|
+
refuseNext(reason: 'no_decoder' | 'unsupported'): void;
|
|
96
|
+
hangNextOpen(): void;
|
|
97
|
+
hangNextShow(): void;
|
|
98
|
+
};
|
|
99
|
+
export declare function flush(times?: number): Promise<void>;
|