@applicaster/zapp-react-dom-app 15.1.18 → 15.1.19-alpha.3927125430
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.
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { DISPLAY_STATES } from "@applicaster/zapp-react-native-ui-components/Contexts/DisplayStateContext";
|
|
2
2
|
import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
focusManager,
|
|
5
|
+
KEYS,
|
|
6
|
+
playerManager,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/appUtils";
|
|
8
|
+
import { KeyInputHandler } from "@applicaster/zapp-react-native-utils/appUtils/keyInputHandler/KeyInputHandler";
|
|
9
|
+
import { TVSeekController } from "@applicaster/zapp-react-native-utils/reactHooks/player/TVSeekControlller/TVSeekController";
|
|
10
|
+
import { SKIP_TIME_BASE } from "@applicaster/zapp-react-native-utils/reactHooks/player/const";
|
|
4
11
|
|
|
5
12
|
import { InteractionManagerClass } from "..";
|
|
6
13
|
|
|
14
|
+
// The web app runs with the web key map
|
|
15
|
+
jest.mock(
|
|
16
|
+
"@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys",
|
|
17
|
+
() =>
|
|
18
|
+
jest.requireActual(
|
|
19
|
+
"@applicaster/zapp-react-native-utils/appUtils/keyCodes/keys/keys.web"
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
|
|
7
23
|
jest.mock("@applicaster/zapp-react-native-utils/alertUtils", () => ({
|
|
8
24
|
showConfirmationDialog: jest.fn(),
|
|
9
25
|
}));
|
|
@@ -28,6 +44,8 @@ jest.mock("@applicaster/zapp-react-native-utils/logger", () => {
|
|
|
28
44
|
};
|
|
29
45
|
});
|
|
30
46
|
|
|
47
|
+
const globalAny: any = global;
|
|
48
|
+
|
|
31
49
|
const createBackEvent = () => ({
|
|
32
50
|
code: "Escape",
|
|
33
51
|
preventDefault: jest.fn(),
|
|
@@ -98,4 +116,155 @@ describe("InteractionManager (web)", () => {
|
|
|
98
116
|
expect(props.navigator.canGoBack).toHaveBeenCalled();
|
|
99
117
|
});
|
|
100
118
|
});
|
|
119
|
+
|
|
120
|
+
describe("Forward / Rewind keys", () => {
|
|
121
|
+
const POSITION = 100;
|
|
122
|
+
|
|
123
|
+
const createPlayer = (supportsNativeControls: boolean) => ({
|
|
124
|
+
supportsNativeControls: () => supportsNativeControls,
|
|
125
|
+
isPaused: () => false,
|
|
126
|
+
isLive: () => false,
|
|
127
|
+
getPosition: () => POSITION,
|
|
128
|
+
getSeekableDuration: () => 1000,
|
|
129
|
+
pause: jest.fn(),
|
|
130
|
+
play: jest.fn(),
|
|
131
|
+
seekTo: jest.fn(),
|
|
132
|
+
forward: jest.fn(),
|
|
133
|
+
rewind: jest.fn(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
let removeListener = () => {};
|
|
137
|
+
|
|
138
|
+
// Same wiring as usePlayerControllerSetup
|
|
139
|
+
const setup = ({ supportsNativeControls, ...props }: any) => {
|
|
140
|
+
const player = createPlayer(supportsNativeControls);
|
|
141
|
+
|
|
142
|
+
jest
|
|
143
|
+
.spyOn(playerManager, "getInstanceController")
|
|
144
|
+
.mockReturnValue(player as any);
|
|
145
|
+
|
|
146
|
+
removeListener = KeyInputHandler.getInstance().addListener(
|
|
147
|
+
new TVSeekController(player as any)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return { player, ...createManager(props) };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const press = (manager, key) => {
|
|
154
|
+
manager.onKeyDown(KEYS[key]);
|
|
155
|
+
manager.onKeyUp(KEYS[key]);
|
|
156
|
+
jest.runAllTimers();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
beforeEach(() => jest.useFakeTimers());
|
|
160
|
+
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
removeListener();
|
|
163
|
+
jest.useRealTimers();
|
|
164
|
+
jest.restoreAllMocks();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("with a player rendering its own controls", () => {
|
|
168
|
+
const expectUntouched = (player, props) => {
|
|
169
|
+
expect(player.seekTo).not.toHaveBeenCalled();
|
|
170
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
171
|
+
expect(player.rewind).not.toHaveBeenCalled();
|
|
172
|
+
expect(player.pause).not.toHaveBeenCalled();
|
|
173
|
+
expect(props.setDisplayState).not.toHaveBeenCalled();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// the deferral logs the focused element
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
globalAny.document = {};
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
afterEach(() => {
|
|
182
|
+
delete globalAny.document;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it.each(["Forward", "Rewind"])("%s is left to the player", (key) => {
|
|
186
|
+
const { player, manager, props } = setup({
|
|
187
|
+
supportsNativeControls: true,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const event = { ...KEYS[key], stopPropagation: jest.fn() };
|
|
191
|
+
|
|
192
|
+
manager.onKeyDown(event);
|
|
193
|
+
manager.onKeyUp(event);
|
|
194
|
+
jest.runAllTimers();
|
|
195
|
+
|
|
196
|
+
expectUntouched(player, props);
|
|
197
|
+
// the key has to reach the player's own handler
|
|
198
|
+
expect(event.stopPropagation).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("does not seek the player while the play-next overlay is visible", () => {
|
|
202
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
203
|
+
|
|
204
|
+
(manager.props.navigator as any).getPlayNextOverlay = () => ({});
|
|
205
|
+
|
|
206
|
+
press(manager, "Forward");
|
|
207
|
+
|
|
208
|
+
expect(player.seekTo).not.toHaveBeenCalled();
|
|
209
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("keeps the keys while a dialog is visible", () => {
|
|
213
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
214
|
+
|
|
215
|
+
confirmationDialogStore.getState().showDialog();
|
|
216
|
+
|
|
217
|
+
press(manager, "Forward");
|
|
218
|
+
|
|
219
|
+
expect(player.seekTo).toHaveBeenCalledWith(POSITION + SKIP_TIME_BASE);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("sends the key-up to the owner of the key-down when a dialog closes in between", () => {
|
|
223
|
+
const { player, manager } = setup({ supportsNativeControls: true });
|
|
224
|
+
|
|
225
|
+
confirmationDialogStore.getState().showDialog();
|
|
226
|
+
manager.onKeyDown(KEYS.Forward);
|
|
227
|
+
confirmationDialogStore.getState().hideDialog();
|
|
228
|
+
manager.onKeyUp(KEYS.Forward);
|
|
229
|
+
jest.runAllTimers();
|
|
230
|
+
|
|
231
|
+
// a single tap seek, not a long press left running
|
|
232
|
+
expect(player.seekTo).toHaveBeenCalledTimes(1);
|
|
233
|
+
expect(player.seekTo).toHaveBeenCalledWith(POSITION + SKIP_TIME_BASE);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("leaves the key-up to the player when a dialog opens after a deferred key-down", () => {
|
|
237
|
+
const { player, manager, props } = setup({
|
|
238
|
+
supportsNativeControls: true,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
manager.onKeyDown(KEYS.Forward);
|
|
242
|
+
confirmationDialogStore.getState().showDialog();
|
|
243
|
+
manager.onKeyUp(KEYS.Forward);
|
|
244
|
+
jest.runAllTimers();
|
|
245
|
+
|
|
246
|
+
expectUntouched(player, props);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("with QuickBrick's transport controls", () => {
|
|
251
|
+
it.each([
|
|
252
|
+
["Forward", POSITION + SKIP_TIME_BASE],
|
|
253
|
+
["Rewind", POSITION - SKIP_TIME_BASE],
|
|
254
|
+
])("%s keeps the delayed seek", (key, expectedPosition) => {
|
|
255
|
+
const { player, manager, props } = setup({
|
|
256
|
+
supportsNativeControls: false,
|
|
257
|
+
displayState: DISPLAY_STATES.PLAYER,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
press(manager, key);
|
|
261
|
+
|
|
262
|
+
expect(props.setDisplayState).toHaveBeenCalledWith(DISPLAY_STATES.HUD);
|
|
263
|
+
expect(player.seekTo).toHaveBeenCalledWith(expectedPosition);
|
|
264
|
+
expect(player.play).toHaveBeenCalled();
|
|
265
|
+
expect(player.forward).not.toHaveBeenCalled();
|
|
266
|
+
expect(player.rewind).not.toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
101
270
|
});
|
|
@@ -104,6 +104,8 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
104
104
|
private sequenceTimeout: NodeJS.Timeout | null;
|
|
105
105
|
private userSequence: number[] = [];
|
|
106
106
|
private isDismissing = false;
|
|
107
|
+
// Whether each key's key-down was left to the player's own controls
|
|
108
|
+
private deferredKeyDowns = new Map<number, boolean>();
|
|
107
109
|
private confirmDialog: ConfirmationDialogState =
|
|
108
110
|
confirmationDialogStore.getState();
|
|
109
111
|
|
|
@@ -369,6 +371,18 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
369
371
|
focusManager.pressOut();
|
|
370
372
|
}
|
|
371
373
|
|
|
374
|
+
// The key-up goes to whoever got the key-down, even if a dialog or the
|
|
375
|
+
// play-next overlay opened or closed in between.
|
|
376
|
+
const deferred =
|
|
377
|
+
this.deferredKeyDowns.get(event.keyCode) ??
|
|
378
|
+
this.isDeferredToNativeControls(event);
|
|
379
|
+
|
|
380
|
+
this.deferredKeyDowns.delete(event.keyCode);
|
|
381
|
+
|
|
382
|
+
if (deferred) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
372
386
|
if (
|
|
373
387
|
keyCode(event).matches(KEYS.Forward) ||
|
|
374
388
|
(__DEV__ && keyCode(event).matches(KEYS.C))
|
|
@@ -568,12 +582,22 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
568
582
|
return (
|
|
569
583
|
this.isNativePlayerControlsEnabled() &&
|
|
570
584
|
!this.isPlayNextOverlayVisible() &&
|
|
571
|
-
(keyCode(event).matchesAny(
|
|
572
|
-
|
|
573
|
-
|
|
585
|
+
(keyCode(event).matchesAny(
|
|
586
|
+
...ARROW_KEYS,
|
|
587
|
+
KEYS.Enter,
|
|
588
|
+
// The player seeks on its own with these keys
|
|
589
|
+
KEYS.Forward,
|
|
590
|
+
KEYS.Rewind
|
|
591
|
+
) ||
|
|
592
|
+
(__DEV__ && keyCode(event).matchesAny(KEYS.X, KEYS.C, KEYS.Z)))
|
|
574
593
|
);
|
|
575
594
|
};
|
|
576
595
|
|
|
596
|
+
// An open dialog owns the remote, whatever the player renders.
|
|
597
|
+
isDeferredToNativeControls = (event: KeyboardEvent) =>
|
|
598
|
+
!confirmationDialogStore.getState().isDialogVisible &&
|
|
599
|
+
this.shouldDeferPlaybackNavigationToNativeControls(event);
|
|
600
|
+
|
|
577
601
|
onKeyDown(event) {
|
|
578
602
|
const { setDisplayState, navigator, xray } = this.props;
|
|
579
603
|
const displayState = this.getEffectiveDisplayState();
|
|
@@ -612,11 +636,11 @@ export class InteractionManagerClass extends React.Component<Props, State> {
|
|
|
612
636
|
// Only handle repeat events for arrow keys
|
|
613
637
|
if (event.repeat && !keyCode(event).matchesAny(...ARROW_KEYS)) return;
|
|
614
638
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
) {
|
|
619
|
-
// The player's own
|
|
639
|
+
const deferred = this.isDeferredToNativeControls(event);
|
|
640
|
+
this.deferredKeyDowns.set(event.keyCode, deferred);
|
|
641
|
+
|
|
642
|
+
if (deferred) {
|
|
643
|
+
// The player's own key handling takes these keys.
|
|
620
644
|
// TEMP(native-controls): remove after device verification
|
|
621
645
|
log_debug("[NativeControls] key deferred to player", {
|
|
622
646
|
key: event.key,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-dom-app",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.19-alpha.3927125430",
|
|
4
4
|
"description": "Zapp App Component for Applicaster's Quick Brick React Native App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/applicaster/zapp-react-dom-app#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@applicaster/zapp-react-dom-ui-components": "15.1.
|
|
26
|
-
"@applicaster/zapp-react-native-bridge": "15.1.
|
|
27
|
-
"@applicaster/zapp-react-native-redux": "15.1.
|
|
28
|
-
"@applicaster/zapp-react-native-ui-components": "15.1.
|
|
29
|
-
"@applicaster/zapp-react-native-utils": "15.1.
|
|
25
|
+
"@applicaster/zapp-react-dom-ui-components": "15.1.19-alpha.3927125430",
|
|
26
|
+
"@applicaster/zapp-react-native-bridge": "15.1.19-alpha.3927125430",
|
|
27
|
+
"@applicaster/zapp-react-native-redux": "15.1.19-alpha.3927125430",
|
|
28
|
+
"@applicaster/zapp-react-native-ui-components": "15.1.19-alpha.3927125430",
|
|
29
|
+
"@applicaster/zapp-react-native-utils": "15.1.19-alpha.3927125430",
|
|
30
30
|
"abortcontroller-polyfill": "^1.7.5",
|
|
31
31
|
"typeface-montserrat": "^0.0.54",
|
|
32
32
|
"video.js": "7.14.3",
|