@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
|
@@ -80,8 +80,47 @@ var embeddedApps_1 = __importDefault(require("./embeddedApps"));
|
|
|
80
80
|
var PlaylistWindowManager_1 = __importDefault(require("./PlaylistWindowManager"));
|
|
81
81
|
var WindowTimeoutError_1 = __importDefault(require("./WindowTimeoutError"));
|
|
82
82
|
var WindowLoadError_1 = __importDefault(require("./WindowLoadError"));
|
|
83
|
+
var SurfaceBroker_1 = require("./SurfaceBroker");
|
|
83
84
|
// TODO: Automatically inject latest version when building in CI.
|
|
84
85
|
var defaultAppRendererUrl = 'https://apps.raydiant.com/3.1.1/index.html';
|
|
86
|
+
// The urls a window may open a surface for besides its content url: the
|
|
87
|
+
// Videos app probes for these renditions beside the original.
|
|
88
|
+
var RENDITION_SUFFIXES = ['_4K', '_720p', '_480p'];
|
|
89
|
+
var isValidRect = function (value) {
|
|
90
|
+
return value &&
|
|
91
|
+
typeof value === 'object' &&
|
|
92
|
+
isFiniteNumber(value.x) &&
|
|
93
|
+
isFiniteNumber(value.y) &&
|
|
94
|
+
isFiniteNumber(value.width) &&
|
|
95
|
+
isFiniteNumber(value.height);
|
|
96
|
+
};
|
|
97
|
+
var isFiniteNumber = function (value) {
|
|
98
|
+
return typeof value === 'number' && isFinite(value);
|
|
99
|
+
};
|
|
100
|
+
var roundRect = function (rect) { return ({
|
|
101
|
+
x: Math.round(rect.x),
|
|
102
|
+
y: Math.round(rect.y),
|
|
103
|
+
width: Math.round(rect.width),
|
|
104
|
+
height: Math.round(rect.height),
|
|
105
|
+
}); };
|
|
106
|
+
// Every string held under a `url` key, at any depth.
|
|
107
|
+
var collectUrls = function (value, urls) {
|
|
108
|
+
if (urls === void 0) { urls = []; }
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
value.forEach(function (item) { return collectUrls(item, urls); });
|
|
111
|
+
}
|
|
112
|
+
else if (value && typeof value === 'object') {
|
|
113
|
+
Object.keys(value).forEach(function (key) {
|
|
114
|
+
if (key === 'url' && typeof value[key] === 'string') {
|
|
115
|
+
urls.push(value[key]);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
collectUrls(value[key], urls);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return urls;
|
|
123
|
+
};
|
|
85
124
|
var isValidNumber = function (value) {
|
|
86
125
|
return typeof value === 'number' && !isNaN(value);
|
|
87
126
|
};
|
|
@@ -154,6 +193,17 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
154
193
|
this.createObjectURLTimeouts = {};
|
|
155
194
|
this.interruptLocks = {};
|
|
156
195
|
this.previouslyLocked = false;
|
|
196
|
+
this.surface = null;
|
|
197
|
+
// Once a window has been shown without a ready surface, or has given one
|
|
198
|
+
// up, it is offered none from then on and its app keeps <video>.
|
|
199
|
+
this.surfaceWithdrawn = false;
|
|
200
|
+
this.surfaceShow = null;
|
|
201
|
+
this.wakeSurfaceShow = [];
|
|
202
|
+
this.holeOpenWaiter = null;
|
|
203
|
+
this.playingWaiter = null;
|
|
204
|
+
this.isPlayHeld = false;
|
|
205
|
+
this.isRemoved = false;
|
|
206
|
+
this.frameBackground = '#000000';
|
|
157
207
|
this.handleEmbeddedMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
|
|
158
208
|
var windowId, workerState, lock, lock, lock;
|
|
159
209
|
return __generator(this, function (_a) {
|
|
@@ -361,6 +411,11 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
361
411
|
}
|
|
362
412
|
break;
|
|
363
413
|
}
|
|
414
|
+
case 'open_video': {
|
|
415
|
+
// An embedded app is never offered a video surface.
|
|
416
|
+
this.refuseVideo('not_offered');
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
364
419
|
default: {
|
|
365
420
|
this.logWarning("Received unknown embedded message type: ".concat(type));
|
|
366
421
|
}
|
|
@@ -369,7 +424,7 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
369
424
|
});
|
|
370
425
|
}); };
|
|
371
426
|
this.handleLegacyMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
|
|
372
|
-
var _a, windowId, onPresentationProperties;
|
|
427
|
+
var _a, windowId, onPresentationProperties, surface;
|
|
373
428
|
return __generator(this, function (_b) {
|
|
374
429
|
_a = this.options, windowId = _a.windowId, onPresentationProperties = _a.onPresentationProperties;
|
|
375
430
|
if (type === 'loaded') {
|
|
@@ -411,6 +466,33 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
411
466
|
this.logWarning("Invalid payload received for 'open_child_window', ignoring");
|
|
412
467
|
}
|
|
413
468
|
}
|
|
469
|
+
if (type === 'open_video') {
|
|
470
|
+
this.handleOpenVideo(payload);
|
|
471
|
+
}
|
|
472
|
+
if (type === 'video_bounds') {
|
|
473
|
+
this.handleVideoBounds(payload);
|
|
474
|
+
}
|
|
475
|
+
if (type === 'video_hole_open') {
|
|
476
|
+
this.logInfo("Received 'video_hole_open' from window '".concat(windowId, "'"));
|
|
477
|
+
surface = this.surface;
|
|
478
|
+
if (this.holeOpenWaiter) {
|
|
479
|
+
this.holeOpenWaiter();
|
|
480
|
+
}
|
|
481
|
+
else if (surface &&
|
|
482
|
+
surface.firstFrame &&
|
|
483
|
+
!surface.holeOpen &&
|
|
484
|
+
!this.surfaceShow) {
|
|
485
|
+
// Opened before the window is shown, so a swap may play and reveal
|
|
486
|
+
// it before showing its surface.
|
|
487
|
+
surface.isHolePreopened = true;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (type === 'close_video') {
|
|
491
|
+
this.logInfo("Received 'close_video' from window '".concat(windowId, "'"));
|
|
492
|
+
if (this.surface) {
|
|
493
|
+
this.dropSurface(this.surface);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
414
496
|
if (type === 'close_child_window') {
|
|
415
497
|
// TODO: Validate close_child_window message payload.
|
|
416
498
|
this.logInfo("Received 'close_child_window', windowId=".concat(payload.windowId));
|
|
@@ -456,9 +538,40 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
456
538
|
}
|
|
457
539
|
return this.loadLegacyApp();
|
|
458
540
|
};
|
|
459
|
-
|
|
541
|
+
// Shows the window. A window holding a surface with its first frame and
|
|
542
|
+
// bounds shows the surface first and reveals its frame only once the app
|
|
543
|
+
// has opened its hole; onReveal then runs in the reveal's task. Given
|
|
544
|
+
// showAfterRevealMs by a swap, a surface whose app opened its hole while
|
|
545
|
+
// hidden is played, then revealed, then shown instead. The promise
|
|
546
|
+
// resolves once the frame is revealed and any such show has settled, or
|
|
547
|
+
// the window is removed.
|
|
548
|
+
PresentationWindow.prototype.show = function (isFocused, onReveal, showAfterRevealMs) {
|
|
460
549
|
var _this = this;
|
|
461
550
|
if (isFocused === void 0) { isFocused = true; }
|
|
551
|
+
if (this.surfaceShow) {
|
|
552
|
+
return this.surfaceShow;
|
|
553
|
+
}
|
|
554
|
+
var surface = this.surface;
|
|
555
|
+
if (surface && surface.holeOpen) {
|
|
556
|
+
this.showFrame(isFocused);
|
|
557
|
+
return Promise.resolve();
|
|
558
|
+
}
|
|
559
|
+
if (surface && this.isSurfaceReady(surface)) {
|
|
560
|
+
var showing = showAfterRevealMs !== undefined && surface.isHolePreopened
|
|
561
|
+
? this.showSurfaceAfterReveal(surface, isFocused, showAfterRevealMs, onReveal)
|
|
562
|
+
: this.showSurface(surface, isFocused, onReveal);
|
|
563
|
+
this.surfaceShow = showing.then(function () {
|
|
564
|
+
_this.surfaceShow = null;
|
|
565
|
+
});
|
|
566
|
+
return this.surfaceShow;
|
|
567
|
+
}
|
|
568
|
+
// Shown before its surface was ready: the app keeps <video>.
|
|
569
|
+
this.withdrawSurface();
|
|
570
|
+
this.showFrame(isFocused);
|
|
571
|
+
return Promise.resolve();
|
|
572
|
+
};
|
|
573
|
+
PresentationWindow.prototype.showFrame = function (isFocused) {
|
|
574
|
+
var _this = this;
|
|
462
575
|
var _a = this.options, windowId = _a.windowId, renderer = _a.renderer;
|
|
463
576
|
// Show the main window and focus it before any child windows. Showing a child
|
|
464
577
|
// window that requires focus will move focus from the main window to the child.
|
|
@@ -495,7 +608,7 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
495
608
|
};
|
|
496
609
|
PresentationWindow.prototype.remove = function () {
|
|
497
610
|
return __awaiter(this, void 0, void 0, function () {
|
|
498
|
-
var _a, windowId, renderer, err_1;
|
|
611
|
+
var _a, windowId, renderer, surface, surfaceBroker, err_1;
|
|
499
612
|
return __generator(this, function (_b) {
|
|
500
613
|
switch (_b.label) {
|
|
501
614
|
case 0:
|
|
@@ -520,23 +633,40 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
520
633
|
// Release anything waiting on a paint signal so callers never hang on a
|
|
521
634
|
// window that is going away.
|
|
522
635
|
this.resolveFirstPaintWaiters();
|
|
636
|
+
// Release a surface show in progress; a removed window is never revealed.
|
|
637
|
+
this.isRemoved = true;
|
|
638
|
+
this.isPlayHeld = false;
|
|
639
|
+
this.wakeSurfaceShowWaiters();
|
|
523
640
|
// Remove any child window managers.
|
|
524
641
|
Object.values(this.windowManagers).forEach(function (wm) {
|
|
525
642
|
wm.destroy();
|
|
526
643
|
});
|
|
527
|
-
|
|
644
|
+
surface = this.surface;
|
|
645
|
+
surfaceBroker = this.options.surfaceBroker;
|
|
646
|
+
if (!(surface && surfaceBroker)) return [3 /*break*/, 2];
|
|
647
|
+
this.surface = null;
|
|
648
|
+
if (surface.handle) {
|
|
649
|
+
this.sendVideoMessage('video_closing');
|
|
650
|
+
}
|
|
651
|
+
return [4 /*yield*/, surfaceBroker.close(windowId)];
|
|
528
652
|
case 1:
|
|
529
|
-
_b.
|
|
530
|
-
|
|
653
|
+
_b.sent();
|
|
654
|
+
if (surface.holeOpen) {
|
|
655
|
+
surfaceBroker.onHole(false);
|
|
656
|
+
}
|
|
657
|
+
_b.label = 2;
|
|
531
658
|
case 2:
|
|
659
|
+
_b.trys.push([2, 4, , 5]);
|
|
660
|
+
return [4 /*yield*/, renderer.removeWindow(windowId)];
|
|
661
|
+
case 3:
|
|
532
662
|
_b.sent();
|
|
533
663
|
this.logInfo("Removed window '".concat(windowId, "'"));
|
|
534
|
-
return [3 /*break*/,
|
|
535
|
-
case
|
|
664
|
+
return [3 /*break*/, 5];
|
|
665
|
+
case 4:
|
|
536
666
|
err_1 = _b.sent();
|
|
537
667
|
this.logError("Failed to remove window '".concat(windowId, "' with '").concat(err_1.message, "'"));
|
|
538
|
-
return [3 /*break*/,
|
|
539
|
-
case
|
|
668
|
+
return [3 /*break*/, 5];
|
|
669
|
+
case 5: return [2 /*return*/];
|
|
540
670
|
}
|
|
541
671
|
});
|
|
542
672
|
});
|
|
@@ -550,6 +680,14 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
550
680
|
PresentationWindow.prototype.play = function () {
|
|
551
681
|
var _this = this;
|
|
552
682
|
var _a = this.options, applicationVersion = _a.applicationVersion, presentation = _a.presentation;
|
|
683
|
+
// A window holding a surface plays only once its frame is revealed over
|
|
684
|
+
// an open hole, so its first paint follows the hole opening.
|
|
685
|
+
var surface = this.surface;
|
|
686
|
+
if (surface && !surface.holeOpen) {
|
|
687
|
+
this.isPlayHeld = true;
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.isPlayHeld = false;
|
|
553
691
|
// Each play starts a new paint cycle; 'first_painted' arrives once the
|
|
554
692
|
// window has presented a frame after this play.
|
|
555
693
|
this.didPaintSinceLastPlay = false;
|
|
@@ -566,6 +704,20 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
566
704
|
}
|
|
567
705
|
if (!this.isEmbeddedApp()) {
|
|
568
706
|
this.sendMessageString('play');
|
|
707
|
+
// A replay seeks to the start rather than reopening, which would cost
|
|
708
|
+
// a new first frame with nothing in the hole meanwhile.
|
|
709
|
+
if (surface && surface.handle) {
|
|
710
|
+
if (surface.isPlayingAhead) {
|
|
711
|
+
surface.isPlayingAhead = false;
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
if (surface.plays > 0) {
|
|
715
|
+
surface.handle.seek(0);
|
|
716
|
+
}
|
|
717
|
+
surface.handle.play();
|
|
718
|
+
surface.plays += 1;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
569
721
|
// Play any child window managers.
|
|
570
722
|
Object.values(this.windowManagers).forEach(function (wm) {
|
|
571
723
|
// We only want to trigger play on child windows the first play of the main window or if
|
|
@@ -636,6 +788,26 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
636
788
|
var _a = this.options, windowId = _a.windowId, renderer = _a.renderer;
|
|
637
789
|
renderer.muteWindow(windowId);
|
|
638
790
|
};
|
|
791
|
+
PresentationWindow.prototype.wasRemoved = function () {
|
|
792
|
+
return this.isRemoved;
|
|
793
|
+
};
|
|
794
|
+
// The window holds a video surface, opened or still opening.
|
|
795
|
+
PresentationWindow.prototype.holdsSurface = function () {
|
|
796
|
+
return this.surface !== null;
|
|
797
|
+
};
|
|
798
|
+
// A swap is taking this window off screen: mute its surface and tell the
|
|
799
|
+
// broker its close follows, or mute the window as today when it has none.
|
|
800
|
+
PresentationWindow.prototype.muteSurfaceOrFrame = function () {
|
|
801
|
+
var _a = this.options, windowId = _a.windowId, surfaceBroker = _a.surfaceBroker;
|
|
802
|
+
var surface = this.surface;
|
|
803
|
+
if (surface && surface.handle && surfaceBroker) {
|
|
804
|
+
surface.handle.setMuted(true);
|
|
805
|
+
surfaceBroker.leaving(windowId);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
this.mute();
|
|
809
|
+
}
|
|
810
|
+
};
|
|
639
811
|
PresentationWindow.prototype.resolveFirstPaintWaiters = function () {
|
|
640
812
|
var resolvers = this.firstPaintResolvers;
|
|
641
813
|
this.firstPaintResolvers = [];
|
|
@@ -666,6 +838,12 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
666
838
|
if (this.isEmbeddedApp()) {
|
|
667
839
|
return this.didEmbeddedAppLoad;
|
|
668
840
|
}
|
|
841
|
+
// A window holding a surface is ready only once its first frame has
|
|
842
|
+
// arrived and the app has sized its hole from it.
|
|
843
|
+
var surface = this.surface;
|
|
844
|
+
if (surface && !this.isSurfaceReady(surface)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
669
847
|
return this.didReceiveReady && this.allChildWindowManagersReady();
|
|
670
848
|
};
|
|
671
849
|
PresentationWindow.prototype.isComplete = function () {
|
|
@@ -906,12 +1084,12 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
906
1084
|
case 0:
|
|
907
1085
|
_a = this.options, windowId = _a.windowId, _b = _a.appRendererUrl, appRendererUrl = _b === void 0 ? defaultAppRendererUrl : _b, appRendererOfflineSupport = _a.appRendererOfflineSupport, backgroundColor = _a.backgroundColor;
|
|
908
1086
|
this.logInfo("Create window '".concat(windowId, "'"));
|
|
1087
|
+
this.frameBackground = backgroundColor || '#000000'; // Default to black bg for Raydiant apps
|
|
909
1088
|
return [4 /*yield*/, this.createAndLoadWindow(this.getFullAppRendererUrl({
|
|
910
1089
|
appRendererUrl: appRendererUrl,
|
|
911
1090
|
windowId: windowId,
|
|
912
1091
|
appRendererOfflineSupport: appRendererOfflineSupport,
|
|
913
|
-
}),
|
|
914
|
-
this.handleLegacyMessage)];
|
|
1092
|
+
}), this.frameBackground, this.handleLegacyMessage)];
|
|
915
1093
|
case 1:
|
|
916
1094
|
_c.sent();
|
|
917
1095
|
return [4 /*yield*/, this.waitForPesentationReady()];
|
|
@@ -1038,31 +1216,20 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1038
1216
|
return nextTheme;
|
|
1039
1217
|
};
|
|
1040
1218
|
PresentationWindow.prototype.sendRenderMessage = function () {
|
|
1041
|
-
var _a = this.options, applicationVersion = _a.applicationVersion, isPreview = _a.isPreview, device = _a.device;
|
|
1219
|
+
var _a = this.options, applicationVersion = _a.applicationVersion, isPreview = _a.isPreview, device = _a.device, surfaceBroker = _a.surfaceBroker;
|
|
1220
|
+
// The offer of a video surface; app renderer 3.2.0 gives the app its
|
|
1221
|
+
// miraVideo prop only when this is set.
|
|
1222
|
+
var surfaceOffer = surfaceBroker ? { nativeVideo: true } : {};
|
|
1042
1223
|
// IFrames do not have a mute/unmute API so we are overriding any mute properties
|
|
1043
1224
|
// to support muting MZ zones when using the IFrameRenderer (SecondScreen).
|
|
1044
1225
|
if (this.forceMute) {
|
|
1045
|
-
this.sendMessageString('render', {
|
|
1046
|
-
builderState: this.builderState,
|
|
1047
|
-
presentation: __assign(__assign({}, this.presentation), { applicationVariables: __assign(__assign({}, this.presentation.applicationVariables), {
|
|
1226
|
+
this.sendMessageString('render', __assign({ builderState: this.builderState, presentation: __assign(__assign({}, this.presentation), { applicationVariables: __assign(__assign({}, this.presentation.applicationVariables), {
|
|
1048
1227
|
// TODO: We need a better way to identify mute properties (ie. PropType.mute())
|
|
1049
1228
|
// Force mute the presentation, existing apps use either mute or muted.
|
|
1050
|
-
mute: true, muted: true }) }),
|
|
1051
|
-
theme: this.buildTheme(),
|
|
1052
|
-
applicationVersion: applicationVersion,
|
|
1053
|
-
device: device,
|
|
1054
|
-
isPreview: isPreview,
|
|
1055
|
-
});
|
|
1229
|
+
mute: true, muted: true }) }), theme: this.buildTheme(), applicationVersion: applicationVersion, device: device, isPreview: isPreview }, surfaceOffer));
|
|
1056
1230
|
}
|
|
1057
1231
|
else {
|
|
1058
|
-
this.sendMessageString('render', {
|
|
1059
|
-
builderState: this.builderState,
|
|
1060
|
-
presentation: this.presentation,
|
|
1061
|
-
theme: this.buildTheme(),
|
|
1062
|
-
applicationVersion: applicationVersion,
|
|
1063
|
-
device: device,
|
|
1064
|
-
isPreview: isPreview,
|
|
1065
|
-
});
|
|
1232
|
+
this.sendMessageString('render', __assign({ builderState: this.builderState, presentation: this.presentation, theme: this.buildTheme(), applicationVersion: applicationVersion, device: device, isPreview: isPreview }, surfaceOffer));
|
|
1066
1233
|
}
|
|
1067
1234
|
};
|
|
1068
1235
|
PresentationWindow.prototype.openChildWindow = function (windowName, playlistId, bounds, forceMute, isFocused, themeOverride) {
|
|
@@ -1247,6 +1414,347 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1247
1414
|
appRendererOfflineSupport: appRendererOfflineSupport,
|
|
1248
1415
|
});
|
|
1249
1416
|
};
|
|
1417
|
+
PresentationWindow.prototype.handleOpenVideo = function (payload) {
|
|
1418
|
+
var _this = this;
|
|
1419
|
+
var _a = this.options, windowId = _a.windowId, surfaceBroker = _a.surfaceBroker, presentation = _a.presentation;
|
|
1420
|
+
this.logInfo("Received 'open_video' from window '".concat(windowId, "', url=").concat(payload && payload.url));
|
|
1421
|
+
if (!surfaceBroker || this.surfaceWithdrawn || this.isEmbeddedApp()) {
|
|
1422
|
+
this.refuseVideo('not_offered');
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.surface) {
|
|
1426
|
+
this.logWarning("Ignoring 'open_video' from window '".concat(windowId, "', it already holds a surface"));
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (!payload ||
|
|
1430
|
+
typeof payload !== 'object' ||
|
|
1431
|
+
typeof payload.url !== 'string' ||
|
|
1432
|
+
!isValidRect(payload.bounds) ||
|
|
1433
|
+
!this.isOfferedVideoUrl(payload.url)) {
|
|
1434
|
+
this.refuseVideo('not_offered');
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
var surface = {
|
|
1438
|
+
handle: null,
|
|
1439
|
+
wantsSound: payload.muted === false,
|
|
1440
|
+
firstFrame: false,
|
|
1441
|
+
bounds: null,
|
|
1442
|
+
holeOpen: false,
|
|
1443
|
+
isHolePreopened: false,
|
|
1444
|
+
isPlayingAhead: false,
|
|
1445
|
+
plays: 0,
|
|
1446
|
+
};
|
|
1447
|
+
this.surface = surface;
|
|
1448
|
+
surfaceBroker
|
|
1449
|
+
.open(windowId, {
|
|
1450
|
+
url: payload.url,
|
|
1451
|
+
bounds: roundRect(payload.bounds),
|
|
1452
|
+
// Always opened muted; sound waits for the show.
|
|
1453
|
+
muted: true,
|
|
1454
|
+
label: presentation.id,
|
|
1455
|
+
}, function (event) { return _this.handleSurfaceEvent(surface, event); }, function (reason) {
|
|
1456
|
+
if (_this.surface !== surface)
|
|
1457
|
+
return;
|
|
1458
|
+
_this.sendVideoMessage('video_refused', { reason: reason });
|
|
1459
|
+
_this.dropSurface(surface);
|
|
1460
|
+
})
|
|
1461
|
+
.then(function (handle) {
|
|
1462
|
+
if (_this.surface !== surface)
|
|
1463
|
+
return;
|
|
1464
|
+
surface.handle = handle;
|
|
1465
|
+
_this.sendVideoMessage('video_opened');
|
|
1466
|
+
}, function (err) {
|
|
1467
|
+
if (_this.surface !== surface)
|
|
1468
|
+
return;
|
|
1469
|
+
var reason = err instanceof SurfaceBroker_1.SurfaceRefusal ? err.reason : 'unsupported';
|
|
1470
|
+
// The broker has logged its refusal.
|
|
1471
|
+
if (reason !== 'closed') {
|
|
1472
|
+
_this.sendVideoMessage('video_refused', { reason: reason });
|
|
1473
|
+
}
|
|
1474
|
+
_this.dropSurface(surface);
|
|
1475
|
+
});
|
|
1476
|
+
};
|
|
1477
|
+
PresentationWindow.prototype.handleVideoBounds = function (payload) {
|
|
1478
|
+
var surface = this.surface;
|
|
1479
|
+
if (!surface || !surface.handle)
|
|
1480
|
+
return;
|
|
1481
|
+
if (!payload || !isValidRect(payload.bounds)) {
|
|
1482
|
+
this.logWarning("Invalid payload received for 'video_bounds', ignoring");
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
var bounds = roundRect(payload.bounds);
|
|
1486
|
+
surface.handle.setBounds(bounds);
|
|
1487
|
+
if (surface.firstFrame) {
|
|
1488
|
+
var wasSized = surface.bounds !== null;
|
|
1489
|
+
surface.bounds = bounds;
|
|
1490
|
+
if (!wasSized) {
|
|
1491
|
+
this.checkIsReadyCallback();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
PresentationWindow.prototype.handleSurfaceEvent = function (surface, event) {
|
|
1496
|
+
if (this.surface !== surface)
|
|
1497
|
+
return;
|
|
1498
|
+
// A stall is the host's to ride out, but it reaches the app: an app that
|
|
1499
|
+
// times its own complete holds that timer until the next 'playing'.
|
|
1500
|
+
if (event.type === 'first_frame') {
|
|
1501
|
+
surface.firstFrame = true;
|
|
1502
|
+
}
|
|
1503
|
+
this.sendVideoMessage('video_event', event);
|
|
1504
|
+
if (event.type === 'playing' && this.playingWaiter) {
|
|
1505
|
+
this.playingWaiter();
|
|
1506
|
+
}
|
|
1507
|
+
if (event.type === 'error') {
|
|
1508
|
+
this.dropSurface(surface);
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
PresentationWindow.prototype.isSurfaceReady = function (surface) {
|
|
1512
|
+
return !!surface.handle && surface.firstFrame && surface.bounds !== null;
|
|
1513
|
+
};
|
|
1514
|
+
// Only an http(s) url the presentation holds, or one of its renditions,
|
|
1515
|
+
// opens a surface: it is the url the app would give <video>.
|
|
1516
|
+
PresentationWindow.prototype.isOfferedVideoUrl = function (url) {
|
|
1517
|
+
if (!/^https?:\/\//i.test(url))
|
|
1518
|
+
return false;
|
|
1519
|
+
return collectUrls(this.presentation.applicationVariables).some(function (offered) {
|
|
1520
|
+
return url === offered ||
|
|
1521
|
+
RENDITION_SUFFIXES.some(function (suffix) { return url === "".concat(offered).concat(suffix); });
|
|
1522
|
+
});
|
|
1523
|
+
};
|
|
1524
|
+
// Shows a ready surface: the host's show() is awaited, the app is told to
|
|
1525
|
+
// open its hole, and the frame is revealed once it has. One deadline covers
|
|
1526
|
+
// both waits; past it the app keeps <video> and the frame shows as today.
|
|
1527
|
+
PresentationWindow.prototype.showSurface = function (surface, isFocused, onReveal) {
|
|
1528
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1529
|
+
var _a, surfaceShowDeadlineMs, handle, passDeadline, deadline, timer, wake, woken, holeOpen, finish, outcome;
|
|
1530
|
+
var _this = this;
|
|
1531
|
+
return __generator(this, function (_b) {
|
|
1532
|
+
switch (_b.label) {
|
|
1533
|
+
case 0:
|
|
1534
|
+
_a = this.options.surfaceShowDeadlineMs, surfaceShowDeadlineMs = _a === void 0 ? SurfaceBroker_1.DEFAULT_SURFACE_SWAP_DELAY_MS : _a;
|
|
1535
|
+
handle = surface.handle;
|
|
1536
|
+
passDeadline = function () { return undefined; };
|
|
1537
|
+
deadline = new Promise(function (resolve) {
|
|
1538
|
+
passDeadline = resolve;
|
|
1539
|
+
});
|
|
1540
|
+
timer = setTimeout(function () { return passDeadline('deadline'); }, surfaceShowDeadlineMs);
|
|
1541
|
+
wake = function () { return undefined; };
|
|
1542
|
+
woken = new Promise(function (resolve) {
|
|
1543
|
+
wake = function () { return resolve('woken'); };
|
|
1544
|
+
});
|
|
1545
|
+
this.wakeSurfaceShow.push(wake);
|
|
1546
|
+
holeOpen = new Promise(function (resolve) {
|
|
1547
|
+
_this.holeOpenWaiter = function () { return resolve('hole_open'); };
|
|
1548
|
+
});
|
|
1549
|
+
finish = function () {
|
|
1550
|
+
clearTimeout(timer);
|
|
1551
|
+
_this.holeOpenWaiter = null;
|
|
1552
|
+
_this.wakeSurfaceShow = _this.wakeSurfaceShow.filter(function (w) { return w !== wake; });
|
|
1553
|
+
};
|
|
1554
|
+
return [4 /*yield*/, Promise.race([
|
|
1555
|
+
handle.show().then(function () { return 'shown'; }, function () { return 'failed'; }),
|
|
1556
|
+
deadline,
|
|
1557
|
+
woken,
|
|
1558
|
+
])];
|
|
1559
|
+
case 1:
|
|
1560
|
+
outcome = _b.sent();
|
|
1561
|
+
if (!(outcome === 'shown' && this.surface === surface && !this.isRemoved)) return [3 /*break*/, 3];
|
|
1562
|
+
if (surface.wantsSound && !this.forceMute) {
|
|
1563
|
+
handle.setMuted(false);
|
|
1564
|
+
}
|
|
1565
|
+
this.sendVideoMessage('video_shown');
|
|
1566
|
+
return [4 /*yield*/, Promise.race([holeOpen, deadline, woken])];
|
|
1567
|
+
case 2:
|
|
1568
|
+
outcome = _b.sent();
|
|
1569
|
+
_b.label = 3;
|
|
1570
|
+
case 3:
|
|
1571
|
+
finish();
|
|
1572
|
+
if (this.isRemoved) {
|
|
1573
|
+
return [2 /*return*/];
|
|
1574
|
+
}
|
|
1575
|
+
if (outcome === 'hole_open' && this.surface === surface) {
|
|
1576
|
+
this.revealOverHole(surface, isFocused, onReveal);
|
|
1577
|
+
return [2 /*return*/];
|
|
1578
|
+
}
|
|
1579
|
+
// A show the host rejected falls back as a missed deadline does, under
|
|
1580
|
+
// the host's own answer.
|
|
1581
|
+
if ((outcome === 'deadline' || outcome === 'failed') &&
|
|
1582
|
+
this.surface === surface) {
|
|
1583
|
+
this.refuseVideo(outcome === 'failed' ? 'unsupported' : 'deadline');
|
|
1584
|
+
this.dropSurface(surface);
|
|
1585
|
+
}
|
|
1586
|
+
// The surface is gone: the frame shows and plays as today.
|
|
1587
|
+
this.showFrame(isFocused);
|
|
1588
|
+
this.releaseHeldPlay();
|
|
1589
|
+
return [2 /*return*/];
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
};
|
|
1594
|
+
// A swap's incoming surface whose app opened its hole while hidden. It
|
|
1595
|
+
// plays first, still hidden and muted, and the frame is revealed over the
|
|
1596
|
+
// outgoing surface once it reports playing, so the picture is moving when
|
|
1597
|
+
// it appears and no first frame is held. The surface is shown
|
|
1598
|
+
// showAfterRevealMs later: it reaches the screen sooner than the frame
|
|
1599
|
+
// does, so the two land together. Sound waits for the show, so it never
|
|
1600
|
+
// overlaps the outgoing surface's. A surface that never reports playing is
|
|
1601
|
+
// revealed at the show deadline; past a second deadline on the show the
|
|
1602
|
+
// app keeps <video> in the frame already revealed.
|
|
1603
|
+
PresentationWindow.prototype.showSurfaceAfterReveal = function (surface, isFocused, showAfterRevealMs, onReveal) {
|
|
1604
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1605
|
+
var _a, surfaceShowDeadlineMs, handle, timers, after, wake, woken, isHeld, playing, isRevealed, outcome;
|
|
1606
|
+
var _this = this;
|
|
1607
|
+
return __generator(this, function (_b) {
|
|
1608
|
+
switch (_b.label) {
|
|
1609
|
+
case 0:
|
|
1610
|
+
_a = this.options.surfaceShowDeadlineMs, surfaceShowDeadlineMs = _a === void 0 ? SurfaceBroker_1.DEFAULT_SURFACE_SWAP_DELAY_MS : _a;
|
|
1611
|
+
handle = surface.handle;
|
|
1612
|
+
timers = [];
|
|
1613
|
+
after = function (ms, value) {
|
|
1614
|
+
return new Promise(function (resolve) {
|
|
1615
|
+
timers.push(setTimeout(function () { return resolve(value); }, ms));
|
|
1616
|
+
});
|
|
1617
|
+
};
|
|
1618
|
+
wake = function () { return undefined; };
|
|
1619
|
+
woken = new Promise(function (resolve) {
|
|
1620
|
+
wake = function () { return resolve('woken'); };
|
|
1621
|
+
});
|
|
1622
|
+
this.wakeSurfaceShow.push(wake);
|
|
1623
|
+
isHeld = function () { return !_this.isRemoved && _this.surface === surface; };
|
|
1624
|
+
playing = new Promise(function (resolve) {
|
|
1625
|
+
_this.playingWaiter = function () { return resolve('playing'); };
|
|
1626
|
+
});
|
|
1627
|
+
handle.play();
|
|
1628
|
+
surface.plays += 1;
|
|
1629
|
+
surface.isPlayingAhead = true;
|
|
1630
|
+
isRevealed = false;
|
|
1631
|
+
return [4 /*yield*/, Promise.race([
|
|
1632
|
+
playing,
|
|
1633
|
+
after(surfaceShowDeadlineMs, 'no_playing'),
|
|
1634
|
+
woken,
|
|
1635
|
+
])];
|
|
1636
|
+
case 1:
|
|
1637
|
+
outcome = _b.sent();
|
|
1638
|
+
this.playingWaiter = null;
|
|
1639
|
+
if (outcome === 'no_playing') {
|
|
1640
|
+
this.logWarning("No 'playing' from the surface of window '".concat(this.options.windowId, "', revealing it anyway"));
|
|
1641
|
+
}
|
|
1642
|
+
if (!((outcome === 'playing' || outcome === 'no_playing') && isHeld())) return [3 /*break*/, 3];
|
|
1643
|
+
isRevealed = true;
|
|
1644
|
+
this.revealOverHole(surface, isFocused, onReveal);
|
|
1645
|
+
return [4 /*yield*/, Promise.race([
|
|
1646
|
+
after(showAfterRevealMs, 'show'),
|
|
1647
|
+
woken,
|
|
1648
|
+
])];
|
|
1649
|
+
case 2:
|
|
1650
|
+
outcome = _b.sent();
|
|
1651
|
+
_b.label = 3;
|
|
1652
|
+
case 3:
|
|
1653
|
+
if (!(outcome === 'show' && isHeld())) return [3 /*break*/, 5];
|
|
1654
|
+
return [4 /*yield*/, Promise.race([
|
|
1655
|
+
handle.show().then(function () { return 'shown'; }, function () { return 'failed'; }),
|
|
1656
|
+
after(surfaceShowDeadlineMs, 'deadline'),
|
|
1657
|
+
woken,
|
|
1658
|
+
])];
|
|
1659
|
+
case 4:
|
|
1660
|
+
outcome = _b.sent();
|
|
1661
|
+
_b.label = 5;
|
|
1662
|
+
case 5:
|
|
1663
|
+
timers.forEach(function (timer) { return clearTimeout(timer); });
|
|
1664
|
+
this.wakeSurfaceShow = this.wakeSurfaceShow.filter(function (w) { return w !== wake; });
|
|
1665
|
+
if (this.isRemoved) {
|
|
1666
|
+
return [2 /*return*/];
|
|
1667
|
+
}
|
|
1668
|
+
if (outcome === 'shown' && this.surface === surface) {
|
|
1669
|
+
if (surface.wantsSound && !this.forceMute) {
|
|
1670
|
+
handle.setMuted(false);
|
|
1671
|
+
}
|
|
1672
|
+
this.sendVideoMessage('video_shown');
|
|
1673
|
+
return [2 /*return*/];
|
|
1674
|
+
}
|
|
1675
|
+
if ((outcome === 'deadline' || outcome === 'failed') &&
|
|
1676
|
+
this.surface === surface) {
|
|
1677
|
+
this.refuseVideo(outcome === 'failed' ? 'unsupported' : 'deadline');
|
|
1678
|
+
this.dropSurface(surface);
|
|
1679
|
+
}
|
|
1680
|
+
// The surface went before its reveal: the frame shows and plays as today.
|
|
1681
|
+
if (!isRevealed) {
|
|
1682
|
+
this.showFrame(isFocused);
|
|
1683
|
+
this.releaseHeldPlay();
|
|
1684
|
+
}
|
|
1685
|
+
return [2 /*return*/];
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
});
|
|
1689
|
+
};
|
|
1690
|
+
// Reveals the frame over its surface's open hole, and runs the caller's
|
|
1691
|
+
// onReveal and any held play in that same task.
|
|
1692
|
+
PresentationWindow.prototype.revealOverHole = function (surface, isFocused, onReveal) {
|
|
1693
|
+
var _a = this.options, windowId = _a.windowId, renderer = _a.renderer, surfaceBroker = _a.surfaceBroker;
|
|
1694
|
+
surface.holeOpen = true;
|
|
1695
|
+
surfaceBroker.onHole(true);
|
|
1696
|
+
if (renderer.setWindowBackground) {
|
|
1697
|
+
renderer.setWindowBackground(windowId, 'transparent');
|
|
1698
|
+
}
|
|
1699
|
+
this.showFrame(isFocused);
|
|
1700
|
+
if (onReveal)
|
|
1701
|
+
onReveal();
|
|
1702
|
+
this.releaseHeldPlay();
|
|
1703
|
+
};
|
|
1704
|
+
// Shown before its surface was ready: any surface it holds closes and the
|
|
1705
|
+
// window is offered none from then on.
|
|
1706
|
+
PresentationWindow.prototype.withdrawSurface = function () {
|
|
1707
|
+
this.surfaceWithdrawn = true;
|
|
1708
|
+
var surface = this.surface;
|
|
1709
|
+
if (surface) {
|
|
1710
|
+
this.refuseVideo('not_offered');
|
|
1711
|
+
this.dropSurface(surface);
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
// The window gives its surface up: closed through the broker, its hole
|
|
1715
|
+
// reported closed and its frame background restored. It holds none from
|
|
1716
|
+
// then on, and counts ready and swaps as any other window.
|
|
1717
|
+
PresentationWindow.prototype.dropSurface = function (surface) {
|
|
1718
|
+
var _a = this.options, windowId = _a.windowId, renderer = _a.renderer, surfaceBroker = _a.surfaceBroker;
|
|
1719
|
+
if (this.surface !== surface || !surfaceBroker)
|
|
1720
|
+
return;
|
|
1721
|
+
this.surface = null;
|
|
1722
|
+
this.surfaceWithdrawn = true;
|
|
1723
|
+
surfaceBroker.close(windowId);
|
|
1724
|
+
if (surface.holeOpen) {
|
|
1725
|
+
surface.holeOpen = false;
|
|
1726
|
+
surfaceBroker.onHole(false);
|
|
1727
|
+
if (renderer.setWindowBackground) {
|
|
1728
|
+
renderer.setWindowBackground(windowId, this.frameBackground);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
this.wakeSurfaceShowWaiters();
|
|
1732
|
+
this.checkIsReadyCallback();
|
|
1733
|
+
};
|
|
1734
|
+
PresentationWindow.prototype.wakeSurfaceShowWaiters = function () {
|
|
1735
|
+
var waiters = this.wakeSurfaceShow;
|
|
1736
|
+
this.wakeSurfaceShow = [];
|
|
1737
|
+
waiters.forEach(function (wake) { return wake(); });
|
|
1738
|
+
};
|
|
1739
|
+
PresentationWindow.prototype.releaseHeldPlay = function () {
|
|
1740
|
+
if (this.isPlayHeld) {
|
|
1741
|
+
this.isPlayHeld = false;
|
|
1742
|
+
this.play();
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
PresentationWindow.prototype.refuseVideo = function (reason) {
|
|
1746
|
+
this.logWarning("Sending 'video_refused' to window '".concat(this.options.windowId, "', reason=").concat(reason));
|
|
1747
|
+
this.sendVideoMessage('video_refused', { reason: reason });
|
|
1748
|
+
};
|
|
1749
|
+
// Every video message rides the string channel the app renderer reads.
|
|
1750
|
+
PresentationWindow.prototype.sendVideoMessage = function (type, payload) {
|
|
1751
|
+
try {
|
|
1752
|
+
this.sendMessageString(type, payload);
|
|
1753
|
+
}
|
|
1754
|
+
catch (err) {
|
|
1755
|
+
this.logWarning("Failed to send '".concat(type, "' with '").concat(err.message, "'"));
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1250
1758
|
PresentationWindow.prototype.handleReady = function () {
|
|
1251
1759
|
this.didReceiveReady = true;
|
|
1252
1760
|
this.checkIsReadyCallback();
|