@applicaster/zapp-react-native-utils 13.0.0-alpha.2635505531 → 13.0.0-alpha.3244448100
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/appUtils/keyCodes/keysByPlatform.js +4 -3
- package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
- package/appUtils/playerManager/index.ts +6 -2
- package/appUtils/playerManager/player.ts +12 -8
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -15
- package/package.json +2 -2
- package/reactHooks/player/TVSeekControlller/ISeekController.ts +28 -3
- package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +42 -91
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +205 -97
- package/reactHooks/player/TVSeekControlller/TapSeekController.ts +35 -55
- package/reactHooks/player/const.ts +1 -5
- package/reactHooks/player/index.ts +0 -6
- package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +0 -180
- package/reactHooks/player/__tests__/useAutoSeek.test.tsx +0 -123
- package/reactHooks/player/__tests__/useTapSeek.test.ts +0 -94
- package/reactHooks/player/useAutoSeek.tsx +0 -106
- package/reactHooks/player/useSeek.tsx +0 -17
- package/reactHooks/player/useTapSeek.tsx +0 -85
|
@@ -77,9 +77,10 @@ const web = {
|
|
|
77
77
|
Exit: { code: "Exit", keyCode: null },
|
|
78
78
|
Escape: { code: "Escape", keyCode: 27 },
|
|
79
79
|
EscapeDevice: { code: "Escape", keyCode: 27 },
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
// For Debugging
|
|
81
|
+
Z: { code: "KeyZ", keyCode: 90 }, // Hardware Backward Button
|
|
82
|
+
X: { code: "KeyX", keyCode: 88 }, // Hardware Enter Button
|
|
83
|
+
C: { code: "KeyC", keyCode: 67 }, // Hardware Forward Button
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
const android_tv = {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type SeekKeyHandlerListener = {
|
|
2
|
+
onRewindPress: () => boolean;
|
|
3
|
+
onForwardPress: () => boolean;
|
|
4
|
+
onRewindPressOut: () => void;
|
|
5
|
+
onForwardPressOut: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class KeyInputHandler {
|
|
9
|
+
private static instance: KeyInputHandler;
|
|
10
|
+
|
|
11
|
+
public static getInstance() {
|
|
12
|
+
if (!KeyInputHandler.instance) {
|
|
13
|
+
KeyInputHandler.instance = new KeyInputHandler();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return KeyInputHandler.instance;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.listeners = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private listeners: SeekKeyHandlerListener[] = [];
|
|
24
|
+
|
|
25
|
+
addListener = (listener: SeekKeyHandlerListener) => {
|
|
26
|
+
this.listeners.splice(0, 0, listener);
|
|
27
|
+
|
|
28
|
+
return () => this.removeListener(listener);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
removeListener = (listener: SeekKeyHandlerListener) => {
|
|
32
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
onRewindPress = () => {
|
|
36
|
+
for (const element of this.listeners) {
|
|
37
|
+
if (element.onRewindPress()) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
onForwardPress = () => {
|
|
44
|
+
for (const element of this.listeners) {
|
|
45
|
+
if (element.onForwardPress()) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// TODO: Event should be send to listeners who handled down event
|
|
52
|
+
onRewindPressOut = () => {
|
|
53
|
+
this.listeners.forEach((listener) => listener.onRewindPressOut());
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
onForwardPressOut = () => {
|
|
57
|
+
this.listeners.forEach((listener) => listener.onForwardPressOut());
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
getSeekMethods = () => {
|
|
61
|
+
return {
|
|
62
|
+
onForwardPress: this.onForwardPress,
|
|
63
|
+
onRewindPress: this.onRewindPress,
|
|
64
|
+
onForwardPressOut: this.onForwardPressOut,
|
|
65
|
+
onRewindPressOut: this.onRewindPressOut,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -169,8 +169,12 @@ export class PlayerManager {
|
|
|
169
169
|
this.addPlayer(id, playerController);
|
|
170
170
|
|
|
171
171
|
if (playerController.playerRole === PlayerRole.Primary) {
|
|
172
|
-
if (
|
|
173
|
-
RNPlayerManager
|
|
172
|
+
if (RNPlayerManager) {
|
|
173
|
+
Object.keys(RNPlayerManager).forEach((pluginId) => {
|
|
174
|
+
if (pluginId !== playerController.playerPluginId) {
|
|
175
|
+
RNPlayerManager[pluginId]?.stopBackgroundPlayback?.();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
playerController.addListener({
|
|
@@ -5,6 +5,7 @@ import { createLogger, utilsLogger } from "../../logger";
|
|
|
5
5
|
import { getBoolFromConfigValue } from "../../configurationUtils";
|
|
6
6
|
import { PlayerRole, SharedPlayerCallBacksKeys } from "./conts";
|
|
7
7
|
import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
|
|
8
|
+
import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
|
|
8
9
|
|
|
9
10
|
const logger = createLogger({
|
|
10
11
|
category: "PlayerController",
|
|
@@ -49,6 +50,8 @@ export class Player {
|
|
|
49
50
|
[key: string]: QuickBrickPlayer.SharedPlayerCallBacks;
|
|
50
51
|
} = {};
|
|
51
52
|
|
|
53
|
+
public seekController?: TVSeekController | null;
|
|
54
|
+
|
|
52
55
|
get entry(): ZappEntry | null {
|
|
53
56
|
return this._entry;
|
|
54
57
|
}
|
|
@@ -59,6 +62,8 @@ export class Player {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
constructor(props) {
|
|
65
|
+
this.seekController = null;
|
|
66
|
+
|
|
62
67
|
this.playerState = {
|
|
63
68
|
// We want isLive null by default to be able not to show controls if we do not know type
|
|
64
69
|
isLive: null,
|
|
@@ -338,7 +343,7 @@ export class Player {
|
|
|
338
343
|
});
|
|
339
344
|
};
|
|
340
345
|
|
|
341
|
-
onPlayerSeekComplete(event: QuickBrickPlayer.OnSeekCompleteEvent) {
|
|
346
|
+
onPlayerSeekComplete = (event: QuickBrickPlayer.OnSeekCompleteEvent) => {
|
|
342
347
|
const { isLive, seekableDuration, currentTime, duration } = event;
|
|
343
348
|
|
|
344
349
|
this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
|
|
@@ -353,7 +358,7 @@ export class Player {
|
|
|
353
358
|
callbackName: SharedPlayerCallBacksKeys.OnPlayerSeekComplete,
|
|
354
359
|
event,
|
|
355
360
|
});
|
|
356
|
-
}
|
|
361
|
+
};
|
|
357
362
|
|
|
358
363
|
onVideoEnd = (event) => {
|
|
359
364
|
this.invokeListenersUpdate({
|
|
@@ -623,14 +628,18 @@ export class Player {
|
|
|
623
628
|
selectTrack = (_track) => {};
|
|
624
629
|
|
|
625
630
|
protected notifyPlayHeadPositionUpdate = () => {
|
|
631
|
+
const currentTime = this.getPosition();
|
|
632
|
+
|
|
626
633
|
const event = {
|
|
627
|
-
currentTime
|
|
634
|
+
currentTime,
|
|
628
635
|
duration: this.getDuration(),
|
|
629
636
|
item: this.getEntry(),
|
|
630
637
|
seekableDuration: this.playerState.seekableDuration,
|
|
631
638
|
isLive: this.isLive(),
|
|
632
639
|
};
|
|
633
640
|
|
|
641
|
+
this.seekController?.onVideoProgress(currentTime);
|
|
642
|
+
|
|
634
643
|
this.invokeListenersUpdate({
|
|
635
644
|
callbackName: SharedPlayerCallBacksKeys.OnVideoProgress,
|
|
636
645
|
event,
|
|
@@ -736,10 +745,5 @@ export class Player {
|
|
|
736
745
|
|
|
737
746
|
public supportsNativeControls = (): boolean => false;
|
|
738
747
|
public supportNativeCast = (): boolean => false;
|
|
739
|
-
|
|
740
|
-
public isInDelayedSeek = (): boolean => false;
|
|
741
|
-
public enterDelayedSeek() {}
|
|
742
|
-
|
|
743
|
-
public exitDelayedSeek() {}
|
|
744
748
|
public destroy = () => {};
|
|
745
749
|
}
|
|
@@ -5,8 +5,6 @@ import { playerManager } from "./index";
|
|
|
5
5
|
import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
|
|
6
6
|
import { PlayerRole } from "./conts";
|
|
7
7
|
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
8
|
-
import { isTV } from "../../reactUtils";
|
|
9
|
-
import { DelayedSeekPlayerWrapper } from "./DelayedSeekPlayerWrapper";
|
|
10
8
|
|
|
11
9
|
// TODO: Rename to ControllerType
|
|
12
10
|
export const usePlayerControllerSetup = ({
|
|
@@ -51,19 +49,7 @@ export const usePlayerControllerSetup = ({
|
|
|
51
49
|
playerRole: PlayerRole.Primary,
|
|
52
50
|
};
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
const wrappedPlayerController = new ControllerType({
|
|
56
|
-
...combinedProps,
|
|
57
|
-
playerRole: PlayerRole.Unspecified,
|
|
58
|
-
playerId: playerId + "." + "wrapped",
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
setPlayerController(
|
|
62
|
-
new DelayedSeekPlayerWrapper(combinedProps, wrappedPlayerController)
|
|
63
|
-
);
|
|
64
|
-
} else {
|
|
65
|
-
setPlayerController(new ControllerType(combinedProps));
|
|
66
|
-
}
|
|
52
|
+
setPlayerController(new ControllerType(combinedProps));
|
|
67
53
|
}, [playerId, entry?.id]);
|
|
68
54
|
|
|
69
55
|
React.useEffect(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "13.0.0-alpha.
|
|
3
|
+
"version": "13.0.0-alpha.3244448100",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "13.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "13.0.0-alpha.3244448100",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
import { SEEK_TYPE } from "../const";
|
|
1
|
+
import { MAX_SEEK_TIME, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
|
|
2
|
+
|
|
3
|
+
export interface ISeekControllerDelegate {
|
|
4
|
+
handleDelayedSeek(offset: number): void;
|
|
5
|
+
handleSeek(offset: number): void;
|
|
6
|
+
handleSeekComplete(): void;
|
|
7
|
+
handlePlayerPause(): void;
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
export interface ISeekController {
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
title: string;
|
|
12
|
+
onStartSeek(seekType: SEEK_TYPE): void;
|
|
13
|
+
onSeekEnd(): void;
|
|
6
14
|
reset(): void;
|
|
7
15
|
}
|
|
16
|
+
|
|
17
|
+
export class SeekIncrementalBaseStrategy {
|
|
18
|
+
private skipMultiplier = 1;
|
|
19
|
+
|
|
20
|
+
getSkipTime = () =>
|
|
21
|
+
Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * this.skipMultiplier);
|
|
22
|
+
|
|
23
|
+
increment = () => {
|
|
24
|
+
this.skipMultiplier++;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
getSkipMultiplier = () => this.skipMultiplier;
|
|
28
|
+
|
|
29
|
+
reset = () => {
|
|
30
|
+
this.skipMultiplier = 1;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -1,117 +1,68 @@
|
|
|
1
|
-
// LongPressSeekController.ts
|
|
2
|
-
import { ISeekController } from "./ISeekController";
|
|
3
|
-
import { SEEK_TYPE } from "../const";
|
|
4
1
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
2
|
+
ISeekController,
|
|
3
|
+
ISeekControllerDelegate,
|
|
4
|
+
SeekIncrementalBaseStrategy,
|
|
5
|
+
} from "./ISeekController";
|
|
6
|
+
import { SEEK_TYPE } from "../const";
|
|
7
|
+
import { log_debug, log_error, log_verbose } from "./TVSeekController";
|
|
8
|
+
import { interval, Subscription } from "rxjs";
|
|
9
|
+
import { tap } from "rxjs/operators";
|
|
12
10
|
|
|
13
|
-
/**
|
|
14
|
-
* Example interface for the configuration needed for long-press seeking.
|
|
15
|
-
*/
|
|
16
11
|
interface LongPressConfig {
|
|
17
|
-
|
|
18
|
-
TIMER_INTERVAL_MS: number; // ms
|
|
19
|
-
SKIP_INCREMENT_STEP: number; // seconds
|
|
20
|
-
MAX_SKIP_PER_TICK: number; // seconds
|
|
21
|
-
INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
|
|
12
|
+
TIMER_INTERVAL_MS: number;
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
export class LongPressSeekController implements ISeekController {
|
|
25
|
-
|
|
26
|
-
private isLongPressActive = false;
|
|
27
|
-
private currentSkipAmount = 0;
|
|
16
|
+
isLongPressActive = false;
|
|
28
17
|
private subscription: Subscription | null = null;
|
|
29
|
-
|
|
18
|
+
readonly title = "LongPressSeek";
|
|
30
19
|
constructor(
|
|
31
|
-
private
|
|
20
|
+
private seekControllerDelegate: ISeekControllerDelegate,
|
|
21
|
+
private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
|
|
32
22
|
private config: LongPressConfig
|
|
33
23
|
) {}
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
onStartSeek(seekType: SEEK_TYPE) {
|
|
36
26
|
this.cleanUpSubscription();
|
|
37
27
|
|
|
38
|
-
log_debug(
|
|
39
|
-
`LongPressSeekController: start long press seek type:${seekType}.`
|
|
40
|
-
);
|
|
28
|
+
log_debug(`${this.title}: start long press seek type: ${seekType}.`);
|
|
41
29
|
|
|
42
|
-
this.
|
|
43
|
-
this.parent.pausePlayer();
|
|
30
|
+
this.seekControllerDelegate.handlePlayerPause();
|
|
44
31
|
|
|
45
|
-
this.pressStartTime = Date.now();
|
|
46
32
|
this.isLongPressActive = true;
|
|
47
|
-
this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* We wait LONG_PRESS_THRESHOLD ms to confirm it's a long press.
|
|
51
|
-
* If it still active, we switch to an interval that emits every TIMER_INTERVAL_MS.
|
|
52
|
-
*/
|
|
53
|
-
const threshold$ = timer(this.config.LONG_PRESS_THRESHOLD).pipe(
|
|
54
|
-
tap(() =>
|
|
55
|
-
log_verbose("LongPressSeekController: Crossed long-press threshold")
|
|
56
|
-
),
|
|
57
|
-
switchMap(() => {
|
|
58
|
-
return interval(this.config.TIMER_INTERVAL_MS).pipe(
|
|
59
|
-
tap(() => {
|
|
60
|
-
this.parent.handleDelayedSeek(
|
|
61
|
-
this.parent.currentSeekType,
|
|
62
|
-
this.currentSkipAmount
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
log_debug(
|
|
66
|
-
`LongPressSeekController: handle delayed seek type: ${seekType} - currentSkipAmount: ${this.currentSkipAmount}.`
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
this.currentSkipAmount = Math.min(
|
|
70
|
-
this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
|
|
71
|
-
this.config.MAX_SKIP_PER_TICK
|
|
72
|
-
);
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
})
|
|
76
|
-
);
|
|
77
33
|
|
|
78
|
-
this.subscription =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
34
|
+
this.subscription = interval(this.config.TIMER_INTERVAL_MS)
|
|
35
|
+
.pipe(
|
|
36
|
+
tap(() => {
|
|
37
|
+
this.seekControllerDelegate.handleDelayedSeek(
|
|
38
|
+
this.seekIncrementalStrategy.getSkipTime()
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
log_debug(
|
|
42
|
+
`${
|
|
43
|
+
this.title
|
|
44
|
+
}: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
this.seekIncrementalStrategy.increment();
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
.subscribe({
|
|
51
|
+
complete: () =>
|
|
52
|
+
log_verbose(`${this.title}: Long press subscription complete`),
|
|
53
|
+
error: (err) =>
|
|
54
|
+
log_error(`${this.title}: Long press subscription error`, err),
|
|
55
|
+
});
|
|
89
56
|
}
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
this.isLongPressActive &&
|
|
96
|
-
pressDuration > this.config.LONG_PRESS_THRESHOLD
|
|
97
|
-
) {
|
|
98
|
-
const offset = this.parent.delayedSeekOffset;
|
|
99
|
-
|
|
100
|
-
if (offset) {
|
|
101
|
-
log_debug(
|
|
102
|
-
`LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
this.parent.seekPlayer(offset);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.parent.resetAll();
|
|
58
|
+
onSeekEnd() {
|
|
59
|
+
this.cleanUpSubscription();
|
|
60
|
+
this.seekControllerDelegate.handleSeekComplete();
|
|
110
61
|
}
|
|
111
62
|
|
|
112
63
|
reset() {
|
|
113
64
|
this.isLongPressActive = false;
|
|
114
|
-
this.
|
|
65
|
+
this.seekIncrementalStrategy.reset();
|
|
115
66
|
this.cleanUpSubscription();
|
|
116
67
|
}
|
|
117
68
|
|