@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
|
@@ -1,4 +1,27 @@
|
|
|
1
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
|
+
};
|
|
2
25
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
26
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
27
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -35,8 +58,13 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
58
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
59
|
}
|
|
37
60
|
};
|
|
61
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
62
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
|
+
};
|
|
38
64
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
65
|
var testUtils_1 = require("./testUtils");
|
|
66
|
+
var re = __importStar(require("@raydiant/playlist-rule-engine"));
|
|
67
|
+
var SurfaceBroker_1 = __importDefault(require("./SurfaceBroker"));
|
|
40
68
|
var delay = function (ms) { return new Promise(function (resolve) { return setTimeout(resolve, ms); }); };
|
|
41
69
|
describe('embedded apps', function () {
|
|
42
70
|
test('loads embedded url format', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -617,3 +645,298 @@ describe('detachCallbacks', function () {
|
|
|
617
645
|
});
|
|
618
646
|
}); });
|
|
619
647
|
});
|
|
648
|
+
describe('video surfaces', function () {
|
|
649
|
+
var CONTENT_URL = 'https://cdn.test/clip.mp4';
|
|
650
|
+
function setupVideoWindow(opts) {
|
|
651
|
+
var _a;
|
|
652
|
+
if (opts === void 0) { opts = {}; }
|
|
653
|
+
var testRenderer = (0, testUtils_1.createRenderer)();
|
|
654
|
+
var host = (0, testUtils_1.createFakeSurfaceHost)();
|
|
655
|
+
var warn = jest.fn();
|
|
656
|
+
var logger = (0, testUtils_1.createLogger)({ warn: warn });
|
|
657
|
+
var window = (0, testUtils_1.createPresentationWindow)({
|
|
658
|
+
windowId: 'video-window',
|
|
659
|
+
presentation: (0, testUtils_1.createPresentation)({
|
|
660
|
+
id: 'p1',
|
|
661
|
+
applicationVariables: (_a = opts.applicationVariables) !== null && _a !== void 0 ? _a : {
|
|
662
|
+
content: { url: CONTENT_URL, filename: 'clip.mp4' },
|
|
663
|
+
},
|
|
664
|
+
}),
|
|
665
|
+
applicationVersion: (0, testUtils_1.createApplicationVersion)(opts.applicationVersion),
|
|
666
|
+
renderer: testRenderer,
|
|
667
|
+
logger: logger,
|
|
668
|
+
surfaceBroker: opts.withHost === false
|
|
669
|
+
? undefined
|
|
670
|
+
: new SurfaceBroker_1.default(host.surfaces, logger),
|
|
671
|
+
});
|
|
672
|
+
var messages = function () {
|
|
673
|
+
return testRenderer.sendMessageString.mock.calls.map(function (_a) {
|
|
674
|
+
var message = _a[1];
|
|
675
|
+
return JSON.parse(message);
|
|
676
|
+
});
|
|
677
|
+
};
|
|
678
|
+
var refusals = function () {
|
|
679
|
+
return messages()
|
|
680
|
+
.filter(function (m) { return m.type === 'video_refused'; })
|
|
681
|
+
.map(function (m) { return m.payload.reason; });
|
|
682
|
+
};
|
|
683
|
+
var refusalsLogged = function () {
|
|
684
|
+
return warn.mock.calls.filter(function (_a) {
|
|
685
|
+
var message = _a[0];
|
|
686
|
+
return /video_refused/.test(message);
|
|
687
|
+
})
|
|
688
|
+
.length;
|
|
689
|
+
};
|
|
690
|
+
var openVideo = function (url) {
|
|
691
|
+
return testRenderer.runReceiveMessageCallback('open_video', {
|
|
692
|
+
url: url,
|
|
693
|
+
bounds: { x: 0.4, y: 10.6, width: 1919.6, height: 1080 },
|
|
694
|
+
muted: false,
|
|
695
|
+
});
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
testRenderer: testRenderer,
|
|
699
|
+
host: host,
|
|
700
|
+
window: window,
|
|
701
|
+
messages: messages,
|
|
702
|
+
refusals: refusals,
|
|
703
|
+
refusalsLogged: refusalsLogged,
|
|
704
|
+
openVideo: openVideo,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
test('open_video reaches the host, and render carries nativeVideo, only with a host', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
708
|
+
var offered, unoffered;
|
|
709
|
+
return __generator(this, function (_a) {
|
|
710
|
+
switch (_a.label) {
|
|
711
|
+
case 0:
|
|
712
|
+
offered = setupVideoWindow();
|
|
713
|
+
offered.window.load();
|
|
714
|
+
return [4 /*yield*/, delay(0)];
|
|
715
|
+
case 1:
|
|
716
|
+
_a.sent();
|
|
717
|
+
offered.testRenderer.runReceiveMessageCallback('loaded', undefined);
|
|
718
|
+
expect(offered.messages().find(function (m) { return m.type === 'render'; }).payload.nativeVideo).toBe(true);
|
|
719
|
+
offered.openVideo(CONTENT_URL);
|
|
720
|
+
return [4 /*yield*/, delay(0)];
|
|
721
|
+
case 2:
|
|
722
|
+
_a.sent();
|
|
723
|
+
expect(offered.host.calls.filter(function (c) { return c.method === 'openVideo'; })[0].args[0]).toEqual({
|
|
724
|
+
url: CONTENT_URL,
|
|
725
|
+
bounds: { x: 0, y: 11, width: 1920, height: 1080 },
|
|
726
|
+
muted: true,
|
|
727
|
+
label: 'p1',
|
|
728
|
+
});
|
|
729
|
+
expect(offered.messages().map(function (m) { return m.type; })).toContain('video_opened');
|
|
730
|
+
return [4 /*yield*/, offered.window.remove()];
|
|
731
|
+
case 3:
|
|
732
|
+
_a.sent();
|
|
733
|
+
unoffered = setupVideoWindow({ withHost: false });
|
|
734
|
+
unoffered.window.load();
|
|
735
|
+
return [4 /*yield*/, delay(0)];
|
|
736
|
+
case 4:
|
|
737
|
+
_a.sent();
|
|
738
|
+
unoffered.testRenderer.runReceiveMessageCallback('loaded', undefined);
|
|
739
|
+
expect(unoffered.messages().find(function (m) { return m.type === 'render'; }).payload).not.toHaveProperty('nativeVideo');
|
|
740
|
+
unoffered.openVideo(CONTENT_URL);
|
|
741
|
+
expect(unoffered.refusals()).toEqual(['not_offered']);
|
|
742
|
+
expect(unoffered.refusalsLogged()).toBe(1);
|
|
743
|
+
return [4 /*yield*/, unoffered.window.remove()];
|
|
744
|
+
case 5:
|
|
745
|
+
_a.sent();
|
|
746
|
+
return [2 /*return*/];
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
}); });
|
|
750
|
+
test('a rendition beside the presentation url opens a surface', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
751
|
+
var _a, window, host, openVideo, refusals;
|
|
752
|
+
return __generator(this, function (_b) {
|
|
753
|
+
switch (_b.label) {
|
|
754
|
+
case 0:
|
|
755
|
+
_a = setupVideoWindow(), window = _a.window, host = _a.host, openVideo = _a.openVideo, refusals = _a.refusals;
|
|
756
|
+
window.load();
|
|
757
|
+
return [4 /*yield*/, delay(0)];
|
|
758
|
+
case 1:
|
|
759
|
+
_b.sent();
|
|
760
|
+
openVideo("".concat(CONTENT_URL, "_720p"));
|
|
761
|
+
return [4 /*yield*/, delay(0)];
|
|
762
|
+
case 2:
|
|
763
|
+
_b.sent();
|
|
764
|
+
expect(host.calls.filter(function (c) { return c.method === 'openVideo'; })).toHaveLength(1);
|
|
765
|
+
expect(refusals()).toEqual([]);
|
|
766
|
+
return [4 /*yield*/, window.remove()];
|
|
767
|
+
case 3:
|
|
768
|
+
_b.sent();
|
|
769
|
+
return [2 /*return*/];
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}); });
|
|
773
|
+
test.each([
|
|
774
|
+
['a url the presentation does not hold', 'https://cdn.test/other.mp4'],
|
|
775
|
+
['a url beside it with another suffix', "".concat(CONTENT_URL, "_1080p")],
|
|
776
|
+
])('%s is refused not_offered', function (_, url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
777
|
+
var _a, window, host, openVideo, refusals, refusalsLogged;
|
|
778
|
+
return __generator(this, function (_b) {
|
|
779
|
+
switch (_b.label) {
|
|
780
|
+
case 0:
|
|
781
|
+
_a = setupVideoWindow(), window = _a.window, host = _a.host, openVideo = _a.openVideo, refusals = _a.refusals, refusalsLogged = _a.refusalsLogged;
|
|
782
|
+
window.load();
|
|
783
|
+
return [4 /*yield*/, delay(0)];
|
|
784
|
+
case 1:
|
|
785
|
+
_b.sent();
|
|
786
|
+
openVideo(url);
|
|
787
|
+
return [4 /*yield*/, delay(0)];
|
|
788
|
+
case 2:
|
|
789
|
+
_b.sent();
|
|
790
|
+
expect(refusals()).toEqual(['not_offered']);
|
|
791
|
+
expect(refusalsLogged()).toBe(1);
|
|
792
|
+
expect(host.calls).toEqual([]);
|
|
793
|
+
return [4 /*yield*/, window.remove()];
|
|
794
|
+
case 3:
|
|
795
|
+
_b.sent();
|
|
796
|
+
return [2 /*return*/];
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}); });
|
|
800
|
+
test('a url that is not http(s) is refused not_offered', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
801
|
+
var _a, window, host, openVideo, refusals;
|
|
802
|
+
return __generator(this, function (_b) {
|
|
803
|
+
switch (_b.label) {
|
|
804
|
+
case 0:
|
|
805
|
+
_a = setupVideoWindow({
|
|
806
|
+
applicationVariables: { content: { url: 'blob:https://cdn.test/1' } },
|
|
807
|
+
}), window = _a.window, host = _a.host, openVideo = _a.openVideo, refusals = _a.refusals;
|
|
808
|
+
window.load();
|
|
809
|
+
return [4 /*yield*/, delay(0)];
|
|
810
|
+
case 1:
|
|
811
|
+
_b.sent();
|
|
812
|
+
openVideo('blob:https://cdn.test/1');
|
|
813
|
+
return [4 /*yield*/, delay(0)];
|
|
814
|
+
case 2:
|
|
815
|
+
_b.sent();
|
|
816
|
+
expect(refusals()).toEqual(['not_offered']);
|
|
817
|
+
expect(host.calls).toEqual([]);
|
|
818
|
+
return [4 /*yield*/, window.remove()];
|
|
819
|
+
case 3:
|
|
820
|
+
_b.sent();
|
|
821
|
+
return [2 /*return*/];
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}); });
|
|
825
|
+
test('an embedded app is refused not_offered', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
826
|
+
var _a, window, host, testRenderer, refusals, refusalsLogged;
|
|
827
|
+
return __generator(this, function (_b) {
|
|
828
|
+
switch (_b.label) {
|
|
829
|
+
case 0:
|
|
830
|
+
_a = setupVideoWindow({
|
|
831
|
+
applicationVersion: { embeddedUrlFormat: 'https://embedded.test/' },
|
|
832
|
+
}), window = _a.window, host = _a.host, testRenderer = _a.testRenderer, refusals = _a.refusals, refusalsLogged = _a.refusalsLogged;
|
|
833
|
+
return [4 /*yield*/, window.load()];
|
|
834
|
+
case 1:
|
|
835
|
+
_b.sent();
|
|
836
|
+
testRenderer.runReceiveMessageCallback('open_video', {
|
|
837
|
+
url: CONTENT_URL,
|
|
838
|
+
bounds: { x: 0, y: 0, width: 1920, height: 1080 },
|
|
839
|
+
});
|
|
840
|
+
return [4 /*yield*/, delay(0)];
|
|
841
|
+
case 2:
|
|
842
|
+
_b.sent();
|
|
843
|
+
expect(refusals()).toEqual(['not_offered']);
|
|
844
|
+
expect(refusalsLogged()).toBe(1);
|
|
845
|
+
expect(host.calls).toEqual([]);
|
|
846
|
+
return [4 /*yield*/, window.remove()];
|
|
847
|
+
case 3:
|
|
848
|
+
_b.sent();
|
|
849
|
+
return [2 /*return*/];
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}); });
|
|
853
|
+
test("a zone's windows are offered no surface and refused not_offered", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
854
|
+
var testRenderer, host, logger, zonePresentation, window, zoneWindowId, zoneMessages;
|
|
855
|
+
return __generator(this, function (_a) {
|
|
856
|
+
switch (_a.label) {
|
|
857
|
+
case 0:
|
|
858
|
+
testRenderer = (0, testUtils_1.createRenderer)();
|
|
859
|
+
host = (0, testUtils_1.createFakeSurfaceHost)();
|
|
860
|
+
logger = (0, testUtils_1.createLogger)();
|
|
861
|
+
zonePresentation = (0, testUtils_1.createPresentation)({
|
|
862
|
+
id: 'z1',
|
|
863
|
+
applicationDeploymentId: 'zone-app',
|
|
864
|
+
applicationVariables: { content: { url: CONTENT_URL } },
|
|
865
|
+
});
|
|
866
|
+
window = (0, testUtils_1.createPresentationWindow)({
|
|
867
|
+
windowId: 'multizone',
|
|
868
|
+
presentation: (0, testUtils_1.createPresentation)({
|
|
869
|
+
id: 'mz',
|
|
870
|
+
r: {
|
|
871
|
+
resource: { updatedAt: 'updatedAt', createdAt: 'createdAt' },
|
|
872
|
+
presentationsPlaylists: [{ playlistId: 'zone-playlist' }],
|
|
873
|
+
},
|
|
874
|
+
}),
|
|
875
|
+
playlistWindowManagerState: (0, testUtils_1.createPlaylistWindowManagerState)({
|
|
876
|
+
presentations: { z1: zonePresentation },
|
|
877
|
+
applicationVersions: {
|
|
878
|
+
'zone-app': (0, testUtils_1.createApplicationVersion)({ id: 'zone-app' }),
|
|
879
|
+
},
|
|
880
|
+
}),
|
|
881
|
+
ruleEngineState: (0, testUtils_1.createRuleEngineState)({
|
|
882
|
+
'zone-playlist': {
|
|
883
|
+
id: 'zone-playlist',
|
|
884
|
+
mode: re.PlaylistMode.Sequential,
|
|
885
|
+
items: [{ presentationId: 'z1' }],
|
|
886
|
+
},
|
|
887
|
+
}, { z1: { id: 'z1', applicationName: 'videos' } }),
|
|
888
|
+
renderer: testRenderer,
|
|
889
|
+
logger: logger,
|
|
890
|
+
surfaceBroker: new SurfaceBroker_1.default(host.surfaces, logger),
|
|
891
|
+
});
|
|
892
|
+
window.load();
|
|
893
|
+
return [4 /*yield*/, delay(0)];
|
|
894
|
+
case 1:
|
|
895
|
+
_a.sent();
|
|
896
|
+
testRenderer.runReceiveMessageCallback('open_child_window', {
|
|
897
|
+
windowId: 'zone1',
|
|
898
|
+
playlistId: 'zone-playlist',
|
|
899
|
+
x: 0,
|
|
900
|
+
y: 0,
|
|
901
|
+
width: 960,
|
|
902
|
+
height: 1080,
|
|
903
|
+
isMuted: false,
|
|
904
|
+
isFocused: false,
|
|
905
|
+
});
|
|
906
|
+
return [4 /*yield*/, delay(0)];
|
|
907
|
+
case 2:
|
|
908
|
+
_a.sent();
|
|
909
|
+
zoneWindowId = testRenderer.createWindow.mock.calls[1][0].windowId;
|
|
910
|
+
testRenderer.runReceiveMessageCallback('loaded', undefined);
|
|
911
|
+
zoneMessages = function () {
|
|
912
|
+
return testRenderer.sendMessageString.mock.calls
|
|
913
|
+
.filter(function (_a) {
|
|
914
|
+
var windowId = _a[0];
|
|
915
|
+
return windowId === zoneWindowId;
|
|
916
|
+
})
|
|
917
|
+
.map(function (_a) {
|
|
918
|
+
var message = _a[1];
|
|
919
|
+
return JSON.parse(message);
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
expect(zoneMessages().find(function (m) { return m.type === 'render'; }).payload).not.toHaveProperty('nativeVideo');
|
|
923
|
+
testRenderer.runReceiveMessageCallback('open_video', {
|
|
924
|
+
url: CONTENT_URL,
|
|
925
|
+
bounds: { x: 0, y: 0, width: 960, height: 1080 },
|
|
926
|
+
muted: false,
|
|
927
|
+
});
|
|
928
|
+
return [4 /*yield*/, delay(0)];
|
|
929
|
+
case 3:
|
|
930
|
+
_a.sent();
|
|
931
|
+
expect(zoneMessages()
|
|
932
|
+
.filter(function (m) { return m.type === 'video_refused'; })
|
|
933
|
+
.map(function (m) { return m.payload.reason; })).toEqual(['not_offered']);
|
|
934
|
+
expect(host.calls).toEqual([]);
|
|
935
|
+
return [4 /*yield*/, window.remove()];
|
|
936
|
+
case 4:
|
|
937
|
+
_a.sent();
|
|
938
|
+
return [2 /*return*/];
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}); });
|
|
942
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Logger } from './types';
|
|
2
|
+
import { SurfaceHost, VideoEvent, VideoOptions, VideoSurface, VideoRefusalReason } from './surfaces';
|
|
3
|
+
export declare const SURFACE_OPEN_DEADLINE_MS = 2000;
|
|
4
|
+
export declare const SURFACE_FIRST_FRAME_DEADLINE_MS = 10000;
|
|
5
|
+
export declare const MAX_LIVE_SURFACES = 2;
|
|
6
|
+
export declare const DEFAULT_SURFACE_SWAP_DELAY_MS = 1000;
|
|
7
|
+
export declare const DEFAULT_SHOW_AFTER_REVEAL_MS = 80;
|
|
8
|
+
export type SurfaceRefusalReason = Exclude<VideoRefusalReason, 'not_offered'>;
|
|
9
|
+
export declare class SurfaceRefusal extends Error {
|
|
10
|
+
reason: SurfaceRefusalReason | 'closed';
|
|
11
|
+
constructor(reason: SurfaceRefusalReason | 'closed');
|
|
12
|
+
}
|
|
13
|
+
export default class SurfaceBroker {
|
|
14
|
+
private host;
|
|
15
|
+
private logger;
|
|
16
|
+
private entries;
|
|
17
|
+
constructor(host: SurfaceHost, logger: Logger);
|
|
18
|
+
open(windowId: string, opts: VideoOptions, onEvent: (e: VideoEvent) => void, onRefused: (reason: SurfaceRefusalReason) => void): Promise<VideoSurface>;
|
|
19
|
+
leaving(windowId: string): void;
|
|
20
|
+
close(windowId: string): Promise<void>;
|
|
21
|
+
closeAll(): Promise<void>;
|
|
22
|
+
onHole(open: boolean): void;
|
|
23
|
+
private openWithDeadline;
|
|
24
|
+
private throwIfGivenUp;
|
|
25
|
+
private liveOthers;
|
|
26
|
+
private pendingCloses;
|
|
27
|
+
private refuse;
|
|
28
|
+
private warnRefusal;
|
|
29
|
+
private forget;
|
|
30
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
28
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
+
function step(op) {
|
|
31
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
+
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;
|
|
34
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
+
switch (op[0]) {
|
|
36
|
+
case 0: case 1: t = op; break;
|
|
37
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
+
default:
|
|
41
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
+
if (t[2]) _.ops.pop();
|
|
46
|
+
_.trys.pop(); continue;
|
|
47
|
+
}
|
|
48
|
+
op = body.call(thisArg, _);
|
|
49
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.SurfaceRefusal = exports.DEFAULT_SHOW_AFTER_REVEAL_MS = exports.DEFAULT_SURFACE_SWAP_DELAY_MS = exports.MAX_LIVE_SURFACES = exports.SURFACE_FIRST_FRAME_DEADLINE_MS = exports.SURFACE_OPEN_DEADLINE_MS = void 0;
|
|
55
|
+
// How long the host's openVideo may take to resolve.
|
|
56
|
+
exports.SURFACE_OPEN_DEADLINE_MS = 2000;
|
|
57
|
+
// From openVideo resolving to first_frame. Inside the 30-second ready timeout,
|
|
58
|
+
// with time left for the app to fall back to <video> and still be ready.
|
|
59
|
+
exports.SURFACE_FIRST_FRAME_DEADLINE_MS = 10000;
|
|
60
|
+
// One playing and one prepared.
|
|
61
|
+
exports.MAX_LIVE_SURFACES = 2;
|
|
62
|
+
// How long a surface swap waits for the incoming window's first paint, and
|
|
63
|
+
// how long showing a surface may take, when closeWindowDelayMs is not set:
|
|
64
|
+
// 1000 ms, the closeWindowDelayMs the player passes.
|
|
65
|
+
exports.DEFAULT_SURFACE_SWAP_DELAY_MS = 1000;
|
|
66
|
+
// SurfaceHost.showAfterRevealMs when the player sets none. On the Signage
|
|
67
|
+
// Stick (Fire OS 8.4, Amazon WebView 136, portrait) the WebView takes about
|
|
68
|
+
// 80-120 ms longer than a surface to put a reveal on screen: 60 ms left the
|
|
69
|
+
// theme trailing the picture by up to 35 ms, and 100 ms put it ahead by up
|
|
70
|
+
// to 54 ms.
|
|
71
|
+
exports.DEFAULT_SHOW_AFTER_REVEAL_MS = 80;
|
|
72
|
+
// Rejects an open. `closed` means the window gave the surface up before the
|
|
73
|
+
// open finished, which needs no answer to the app.
|
|
74
|
+
var SurfaceRefusal = /** @class */ (function (_super) {
|
|
75
|
+
__extends(SurfaceRefusal, _super);
|
|
76
|
+
function SurfaceRefusal(reason) {
|
|
77
|
+
var _this = _super.call(this, "Video surface refused: ".concat(reason)) || this;
|
|
78
|
+
_this.reason = reason;
|
|
79
|
+
_this.name = 'SurfaceRefusal';
|
|
80
|
+
Object.setPrototypeOf(_this, SurfaceRefusal.prototype);
|
|
81
|
+
return _this;
|
|
82
|
+
}
|
|
83
|
+
return SurfaceRefusal;
|
|
84
|
+
}(Error));
|
|
85
|
+
exports.SurfaceRefusal = SurfaceRefusal;
|
|
86
|
+
// The host rejects with an Error whose message is a short machine code.
|
|
87
|
+
var reasonOf = function (err) {
|
|
88
|
+
if (err instanceof SurfaceRefusal && err.reason !== 'closed') {
|
|
89
|
+
return err.reason;
|
|
90
|
+
}
|
|
91
|
+
var code = typeof err === 'string'
|
|
92
|
+
? err
|
|
93
|
+
: err && typeof err === 'object'
|
|
94
|
+
? err.message
|
|
95
|
+
: undefined;
|
|
96
|
+
return code === 'no_decoder' ? 'no_decoder' : 'unsupported';
|
|
97
|
+
};
|
|
98
|
+
// Holds every video surface one window manager has open, so the manager never
|
|
99
|
+
// asks the host for more than two at once and opens the next only after the
|
|
100
|
+
// outgoing one has closed, leaving the host an idle decoder to reuse.
|
|
101
|
+
var SurfaceBroker = /** @class */ (function () {
|
|
102
|
+
function SurfaceBroker(host, logger) {
|
|
103
|
+
this.host = host;
|
|
104
|
+
this.logger = logger;
|
|
105
|
+
this.entries = {};
|
|
106
|
+
}
|
|
107
|
+
// Resolves once the host has opened a hidden surface. Rejects with a
|
|
108
|
+
// SurfaceRefusal. A first frame later than its deadline closes the surface
|
|
109
|
+
// and reports it through onRefused instead.
|
|
110
|
+
SurfaceBroker.prototype.open = function (windowId, opts, onEvent, onRefused) {
|
|
111
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
112
|
+
var markClosed, closed, entry, pending_1, surface, retried, err_1, reason, pending_2;
|
|
113
|
+
var _this = this;
|
|
114
|
+
return __generator(this, function (_a) {
|
|
115
|
+
switch (_a.label) {
|
|
116
|
+
case 0:
|
|
117
|
+
markClosed = function () { return undefined; };
|
|
118
|
+
closed = new Promise(function (resolve) {
|
|
119
|
+
markClosed = resolve;
|
|
120
|
+
});
|
|
121
|
+
entry = {
|
|
122
|
+
windowId: windowId,
|
|
123
|
+
label: opts.label,
|
|
124
|
+
surface: null,
|
|
125
|
+
state: 'opening',
|
|
126
|
+
leaving: false,
|
|
127
|
+
firstFrameTimer: null,
|
|
128
|
+
closePromise: null,
|
|
129
|
+
closed: closed,
|
|
130
|
+
markClosed: markClosed,
|
|
131
|
+
};
|
|
132
|
+
this.entries[windowId] = entry;
|
|
133
|
+
_a.label = 1;
|
|
134
|
+
case 1:
|
|
135
|
+
this.throwIfGivenUp(entry);
|
|
136
|
+
pending_1 = this.pendingCloses(entry);
|
|
137
|
+
if (!(pending_1.length > 0)) return [3 /*break*/, 3];
|
|
138
|
+
return [4 /*yield*/, Promise.all(pending_1)];
|
|
139
|
+
case 2:
|
|
140
|
+
_a.sent();
|
|
141
|
+
return [3 /*break*/, 4];
|
|
142
|
+
case 3:
|
|
143
|
+
if (this.liveOthers(entry).length >= exports.MAX_LIVE_SURFACES) {
|
|
144
|
+
throw this.refuse(entry, 'no_decoder');
|
|
145
|
+
}
|
|
146
|
+
return [3 /*break*/, 5];
|
|
147
|
+
case 4: return [3 /*break*/, 1];
|
|
148
|
+
case 5:
|
|
149
|
+
retried = false;
|
|
150
|
+
_a.label = 6;
|
|
151
|
+
case 6:
|
|
152
|
+
this.throwIfGivenUp(entry);
|
|
153
|
+
_a.label = 7;
|
|
154
|
+
case 7:
|
|
155
|
+
_a.trys.push([7, 9, , 12]);
|
|
156
|
+
return [4 /*yield*/, this.openWithDeadline(opts)];
|
|
157
|
+
case 8:
|
|
158
|
+
surface = _a.sent();
|
|
159
|
+
return [3 /*break*/, 13];
|
|
160
|
+
case 9:
|
|
161
|
+
err_1 = _a.sent();
|
|
162
|
+
this.throwIfGivenUp(entry);
|
|
163
|
+
reason = reasonOf(err_1);
|
|
164
|
+
pending_2 = this.pendingCloses(entry);
|
|
165
|
+
if (!(reason === 'no_decoder' && !retried && pending_2.length > 0)) return [3 /*break*/, 11];
|
|
166
|
+
retried = true;
|
|
167
|
+
return [4 /*yield*/, Promise.all(pending_2)];
|
|
168
|
+
case 10:
|
|
169
|
+
_a.sent();
|
|
170
|
+
return [3 /*break*/, 12];
|
|
171
|
+
case 11: throw this.refuse(entry, reason);
|
|
172
|
+
case 12: return [3 /*break*/, 6];
|
|
173
|
+
case 13:
|
|
174
|
+
if (entry.state === 'closed') {
|
|
175
|
+
surface.close();
|
|
176
|
+
throw new SurfaceRefusal('closed');
|
|
177
|
+
}
|
|
178
|
+
entry.surface = surface;
|
|
179
|
+
entry.state = 'open';
|
|
180
|
+
entry.firstFrameTimer = setTimeout(function () {
|
|
181
|
+
entry.firstFrameTimer = null;
|
|
182
|
+
_this.warnRefusal(entry, 'deadline');
|
|
183
|
+
_this.close(windowId);
|
|
184
|
+
onRefused('deadline');
|
|
185
|
+
}, exports.SURFACE_FIRST_FRAME_DEADLINE_MS);
|
|
186
|
+
surface.onEvent(function (e) {
|
|
187
|
+
if (entry.state !== 'open')
|
|
188
|
+
return;
|
|
189
|
+
if (e.type === 'first_frame') {
|
|
190
|
+
if (!entry.firstFrameTimer)
|
|
191
|
+
return;
|
|
192
|
+
clearTimeout(entry.firstFrameTimer);
|
|
193
|
+
entry.firstFrameTimer = null;
|
|
194
|
+
}
|
|
195
|
+
onEvent(e);
|
|
196
|
+
});
|
|
197
|
+
return [2 /*return*/, surface];
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
// A swap has started for this window's surface; its close follows.
|
|
203
|
+
SurfaceBroker.prototype.leaving = function (windowId) {
|
|
204
|
+
var entry = this.entries[windowId];
|
|
205
|
+
if (entry)
|
|
206
|
+
entry.leaving = true;
|
|
207
|
+
};
|
|
208
|
+
// Closes the window's surface, or gives up its open. Idempotent.
|
|
209
|
+
SurfaceBroker.prototype.close = function (windowId) {
|
|
210
|
+
var _this = this;
|
|
211
|
+
var entry = this.entries[windowId];
|
|
212
|
+
if (!entry)
|
|
213
|
+
return Promise.resolve();
|
|
214
|
+
if (entry.closePromise)
|
|
215
|
+
return entry.closePromise;
|
|
216
|
+
if (entry.firstFrameTimer) {
|
|
217
|
+
clearTimeout(entry.firstFrameTimer);
|
|
218
|
+
entry.firstFrameTimer = null;
|
|
219
|
+
}
|
|
220
|
+
var surface = entry.surface;
|
|
221
|
+
if (!surface) {
|
|
222
|
+
// Still opening: open() closes a handle that resolves from here on.
|
|
223
|
+
this.forget(entry);
|
|
224
|
+
return Promise.resolve();
|
|
225
|
+
}
|
|
226
|
+
entry.state = 'closing';
|
|
227
|
+
entry.closePromise = surface
|
|
228
|
+
.close()
|
|
229
|
+
.catch(function (err) {
|
|
230
|
+
_this.logger.warn("Failed to close video surface for window '".concat(windowId, "' with '").concat(err && err.message, "'"), { windowId: windowId, presentationId: entry.label });
|
|
231
|
+
})
|
|
232
|
+
.then(function () { return _this.forget(entry); });
|
|
233
|
+
return entry.closePromise;
|
|
234
|
+
};
|
|
235
|
+
SurfaceBroker.prototype.closeAll = function () {
|
|
236
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
237
|
+
var _this = this;
|
|
238
|
+
return __generator(this, function (_a) {
|
|
239
|
+
switch (_a.label) {
|
|
240
|
+
case 0: return [4 /*yield*/, Promise.all(Object.keys(this.entries).map(function (id) { return _this.close(id); }))];
|
|
241
|
+
case 1:
|
|
242
|
+
_a.sent();
|
|
243
|
+
return [2 /*return*/];
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
// A window's hole opened or closed.
|
|
249
|
+
SurfaceBroker.prototype.onHole = function (open) {
|
|
250
|
+
this.host.onHole(open);
|
|
251
|
+
};
|
|
252
|
+
SurfaceBroker.prototype.openWithDeadline = function (opts) {
|
|
253
|
+
var _this = this;
|
|
254
|
+
return new Promise(function (resolve, reject) {
|
|
255
|
+
var settled = false;
|
|
256
|
+
var timer = setTimeout(function () {
|
|
257
|
+
settled = true;
|
|
258
|
+
reject(new SurfaceRefusal('deadline'));
|
|
259
|
+
}, exports.SURFACE_OPEN_DEADLINE_MS);
|
|
260
|
+
new Promise(function (r) { return r(_this.host.openVideo(opts)); }).then(function (surface) {
|
|
261
|
+
if (settled) {
|
|
262
|
+
// A late handle never holds a decoder nobody uses.
|
|
263
|
+
surface.close();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
settled = true;
|
|
267
|
+
clearTimeout(timer);
|
|
268
|
+
resolve(surface);
|
|
269
|
+
}, function (err) {
|
|
270
|
+
if (settled)
|
|
271
|
+
return;
|
|
272
|
+
settled = true;
|
|
273
|
+
clearTimeout(timer);
|
|
274
|
+
reject(err);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
SurfaceBroker.prototype.throwIfGivenUp = function (entry) {
|
|
279
|
+
if (entry.state === 'closed') {
|
|
280
|
+
throw new SurfaceRefusal('closed');
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
SurfaceBroker.prototype.liveOthers = function (entry) {
|
|
284
|
+
var _this = this;
|
|
285
|
+
return Object.keys(this.entries)
|
|
286
|
+
.map(function (id) { return _this.entries[id]; })
|
|
287
|
+
.filter(function (e) { return e !== entry && e.state !== 'closed'; });
|
|
288
|
+
};
|
|
289
|
+
SurfaceBroker.prototype.pendingCloses = function (entry) {
|
|
290
|
+
return this.liveOthers(entry)
|
|
291
|
+
.filter(function (e) { return e.state === 'closing' || e.leaving; })
|
|
292
|
+
.map(function (e) { return e.closed; });
|
|
293
|
+
};
|
|
294
|
+
SurfaceBroker.prototype.refuse = function (entry, reason) {
|
|
295
|
+
this.forget(entry);
|
|
296
|
+
this.warnRefusal(entry, reason);
|
|
297
|
+
return new SurfaceRefusal(reason);
|
|
298
|
+
};
|
|
299
|
+
SurfaceBroker.prototype.warnRefusal = function (entry, reason) {
|
|
300
|
+
this.logger.warn("Sending 'video_refused' to window '".concat(entry.windowId, "', reason=").concat(reason), { windowId: entry.windowId, presentationId: entry.label, reason: reason });
|
|
301
|
+
};
|
|
302
|
+
SurfaceBroker.prototype.forget = function (entry) {
|
|
303
|
+
entry.state = 'closed';
|
|
304
|
+
if (this.entries[entry.windowId] === entry) {
|
|
305
|
+
delete this.entries[entry.windowId];
|
|
306
|
+
}
|
|
307
|
+
entry.markClosed();
|
|
308
|
+
};
|
|
309
|
+
return SurfaceBroker;
|
|
310
|
+
}());
|
|
311
|
+
exports.default = SurfaceBroker;
|