@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.
@@ -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
- Z: { code: "KeyZ", keyCode: 90 },
81
- X: { code: "KeyX", keyCode: 88 },
82
- C: { code: "KeyC", keyCode: 67 },
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 (playerController.playerPluginId !== "QuickBrickPlayerPlugin") {
173
- RNPlayerManager?.["QuickBrickPlayerPlugin"]?.stopBackgroundPlayback?.();
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: this.getPosition(),
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
- if (isTV()) {
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.2635505531",
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.2635505531",
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
- onPressStart(seekType: SEEK_TYPE): void;
5
- onPressEnd?(): void;
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
- log_debug,
6
- log_error,
7
- log_verbose,
8
- TVSeekController,
9
- } from "./TVSeekController";
10
- import { timer, interval, Subscription } from "rxjs";
11
- import { switchMap, tap } from "rxjs/operators";
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
- LONG_PRESS_THRESHOLD: number; // ms
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
- private pressStartTime = 0;
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 parent: TVSeekController,
20
+ private seekControllerDelegate: ISeekControllerDelegate,
21
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
32
22
  private config: LongPressConfig
33
23
  ) {}
34
24
 
35
- onPressStart(seekType: SEEK_TYPE) {
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.parent.currentSeekType = seekType;
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 = threshold$.subscribe({
79
- complete: () =>
80
- log_verbose(
81
- "LongPressSeekController: Long press subscription complete"
82
- ),
83
- error: (err) =>
84
- log_error(
85
- "LongPressSeekController: Long press subscription error",
86
- err
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
- onPressEnd() {
92
- const pressDuration = Date.now() - this.pressStartTime;
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.currentSkipAmount = 0;
65
+ this.seekIncrementalStrategy.reset();
115
66
  this.cleanUpSubscription();
116
67
  }
117
68