@24i/bigscreen-sdk 1.0.8-alpha.2196 → 1.0.8-alpha.2201
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/package.json +9 -8
- package/packages/analytics/README.md +2 -2
- package/packages/analytics/src/A.ts +2 -2
- package/packages/analytics/src/Analytics.ts +1 -1
- package/packages/analytics/src/clients/GoogleAnalytics/GoogleAnalytics.ts +1 -1
- package/packages/analytics/src/clients/GoogleAnalytics/constants.ts +17 -17
- package/packages/analytics/src/constants.ts +2 -2
- package/packages/analytics/src/interface.ts +1 -1
- package/packages/device/src/resolver/resolver.ts +5 -5
- package/packages/driver-base/src/KeyMap/KeyMap.ts +4 -0
- package/packages/driver-base/src/__mocks__/keyMap.ts +1 -0
- package/packages/driver-base/src/types/KeycodeKeyMap.ts +2 -0
- package/packages/driver-browser/src/keymap.ts +8 -0
- package/packages/epg/src/v2/DataManager.ts +1 -0
- package/packages/epg/src/v2/DatePicker.tsx +1 -1
- package/packages/epg/src/v2/Epg.tsx +2 -4
- package/packages/focus/src/Layout/Matrix.tsx +18 -18
- package/packages/grid/src/Base/Base.tsx +10 -10
- package/packages/grid/src/FirstOnlyGrid/FirstOnlyGrid.tsx +4 -2
- package/packages/icon/src/Icon.tsx +27 -5
- package/packages/keyboard/src/WhitelabelKeyboard/Keyboard.tsx +20 -18
- package/packages/l10n/src/date/de.ts +6 -6
- package/packages/l10n/src/date/en-common.ts +1 -1
- package/packages/l10n/src/date/fr.ts +5 -4
- package/packages/l10n/src/date/he.ts +6 -6
- package/packages/l10n/src/date/types.ts +1 -1
- package/packages/list/src/Base/Base.tsx +8 -8
- package/packages/player-ui/src/PlayerUI.tsx +26 -25
- package/packages/player-ui/src/Seekbar.tsx +18 -11
- package/packages/player-ui/src/Seeking.ts +48 -12
- package/packages/player-ui/src/mocks.ts +8 -4
- package/packages/player-ui/src/timeUtils.ts +3 -3
- package/packages/time/src/adapters/24iMediaTimeApi.ts +47 -0
- package/packages/time/src/services/24iMediaTimeApi/24iMediaTimeApi.ts +43 -0
- package/packages/time/src/services/24iMediaTimeApi/index.ts +6 -0
- package/packages/types-bigscreen-jsx/index.d.ts +9 -6
- package/packages/utils/src/textUtils.ts +6 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@24i/bigscreen-sdk",
|
|
3
|
-
"version": "1.0.8-alpha.
|
|
3
|
+
"version": "1.0.8-alpha.2201",
|
|
4
4
|
"description": "SmartApps BIGscreen SDK monorepo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"homepage": "https://github.com/24i/smartapps-bigscreen-sdk#readme",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@24i/player-base": "6.10.0",
|
|
41
|
-
"@24i/smartapps-datalayer": "3.0.5
|
|
41
|
+
"@24i/smartapps-datalayer": "3.0.5",
|
|
42
42
|
"@sentry/browser": "^7.12.1",
|
|
43
43
|
"@types/gtag.js": "^0.0.12",
|
|
44
44
|
"@types/prop-types": "^15.7.5",
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"@babel/core": "^7.19.0",
|
|
55
55
|
"@babel/preset-env": "^7.19.0",
|
|
56
56
|
"@types/fs-extra": "^9.0.13",
|
|
57
|
-
"@types/inquirer": "^9.0.
|
|
58
|
-
"@types/jest": "^29.
|
|
57
|
+
"@types/inquirer": "^9.0.3",
|
|
58
|
+
"@types/jest": "^29.2.3",
|
|
59
59
|
"@types/minimist": "^1.2.2",
|
|
60
60
|
"@types/node": "^18.7.18",
|
|
61
|
-
"@types/react": "^18.0.
|
|
61
|
+
"@types/react": "^18.0.25",
|
|
62
62
|
"babel-jest": "^29.0.3",
|
|
63
63
|
"chalk": "^5.0.1",
|
|
64
64
|
"coveralls": "^3.1.1",
|
|
@@ -66,15 +66,15 @@
|
|
|
66
66
|
"fs-extra": "^10.1.0",
|
|
67
67
|
"full-icu": "^1.5.0",
|
|
68
68
|
"identity-obj-proxy": "^3.0.0",
|
|
69
|
-
"inquirer": "^9.1.
|
|
70
|
-
"jest": "^29.
|
|
69
|
+
"inquirer": "^9.1.4",
|
|
70
|
+
"jest": "^29.3.1",
|
|
71
71
|
"jest-canvas-mock": "^2.4.0",
|
|
72
72
|
"jest-environment-jsdom": "^29.0.3",
|
|
73
73
|
"minimist": "^1.2.6",
|
|
74
74
|
"patch-package": "^6.4.7",
|
|
75
75
|
"sass": "^1.54.9",
|
|
76
76
|
"sass-true": "^6.1.0",
|
|
77
|
-
"ts-jest": "^29.0.
|
|
77
|
+
"ts-jest": "^29.0.3",
|
|
78
78
|
"ts-node": "^10.9.1",
|
|
79
79
|
"tsconfig-paths-jest": "0.0.1",
|
|
80
80
|
"typescript": "^4.8.3"
|
|
@@ -250,6 +250,7 @@
|
|
|
250
250
|
"./storage": "./packages/storage/src/index.ts",
|
|
251
251
|
"./throbber": "./packages/throbber/src/index.ts",
|
|
252
252
|
"./time/adapters/worldTimeApi": "./packages/time/src/adapters/worldTimeApi.ts",
|
|
253
|
+
"./time/adapters/24iMediaTimeApi": "./packages/time/src/adapters/24iMediaTimeApi.ts",
|
|
253
254
|
"./time": "./packages/time/src/index.ts",
|
|
254
255
|
"./toast/styles": "./packages/toast/src/Toast/styles.ts",
|
|
255
256
|
"./toast": "./packages/toast/src/index.ts",
|
|
@@ -44,7 +44,7 @@ class DetailScene {
|
|
|
44
44
|
});
|
|
45
45
|
this.a('buffering_stop');
|
|
46
46
|
// render
|
|
47
|
-
this.a('
|
|
47
|
+
this.a('scene_view');
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
```
|
|
@@ -60,6 +60,6 @@ Clients have to opt-in to events they wish to listen to. This can be done by lis
|
|
|
60
60
|
using a regular expression.
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
|
-
analytics.addClient(new GoogleAnalytics(), ['
|
|
63
|
+
analytics.addClient(new GoogleAnalytics(), ['scene_view', 'buffering_start', 'buffering_stop']);
|
|
64
64
|
analytics.addClient(new ConsoleAnalytics(), /.*/);
|
|
65
65
|
```
|
|
@@ -16,8 +16,8 @@ export class A {
|
|
|
16
16
|
* // Creates a function and sets the basic payload.
|
|
17
17
|
* const a = A.of({ sceneType: 'HOME', sceneId: 'home' });
|
|
18
18
|
*
|
|
19
|
-
* // Sends '
|
|
20
|
-
* a('
|
|
19
|
+
* // Sends 'scene_view' event with the basic payload.
|
|
20
|
+
* a('scene_view');
|
|
21
21
|
*
|
|
22
22
|
* // Sends 'buffering_stop' with shallow merge of the basic payload and { asset } object.
|
|
23
23
|
* a('buffering_stop', { asset });
|
|
@@ -80,7 +80,7 @@ export class GoogleAnalytics implements AnalyticsClient {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async startSession() {
|
|
83
|
-
const sessionNumber = parseInt(await Storage.getItem(GA_SESSION_COUNTER) ?? '0') + 1;
|
|
83
|
+
const sessionNumber = parseInt(await Storage.getItem(GA_SESSION_COUNTER) ?? '0', 10) + 1;
|
|
84
84
|
this.constantUrlParams.sct = sessionNumber;
|
|
85
85
|
this.constantUrlParams.sid = generateSessionId();
|
|
86
86
|
await Storage.setItem(GA_SESSION_COUNTER, sessionNumber.toString());
|
|
@@ -12,7 +12,7 @@ export const GOOGLE_ANALYTICS_TRIGGERS = [
|
|
|
12
12
|
'playback_stop',
|
|
13
13
|
'player_close',
|
|
14
14
|
'player_open',
|
|
15
|
-
'
|
|
15
|
+
'scene_view',
|
|
16
16
|
'scroll_25',
|
|
17
17
|
'scroll_50',
|
|
18
18
|
'scroll_75',
|
|
@@ -23,31 +23,31 @@ export const TRIGGER_NAME_TO_GA_EVENTS: Record<
|
|
|
23
23
|
typeof GOOGLE_ANALYTICS_TRIGGERS[number], GAEventsNames[]
|
|
24
24
|
> = {
|
|
25
25
|
// App
|
|
26
|
-
|
|
26
|
+
app_close: ['app_close', 'video_progress'],
|
|
27
27
|
|
|
28
28
|
// Playback
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
playback_10: ['video_progress'],
|
|
30
|
+
playback_25: ['video_progress'],
|
|
31
|
+
playback_50: ['video_progress'],
|
|
32
|
+
playback_75: ['video_progress'],
|
|
33
|
+
playback_90: ['video_progress'],
|
|
34
|
+
playback_pause: ['video_progress'],
|
|
35
|
+
playback_start: ['video_start'],
|
|
36
|
+
playback_stop: ['video_progress'],
|
|
37
37
|
|
|
38
38
|
// Player
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
player_close: ['a24i_content', 'video_progress'],
|
|
40
|
+
player_open: ['a24i_content'],
|
|
41
41
|
|
|
42
42
|
// Scenes
|
|
43
|
-
|
|
43
|
+
scene_view: [
|
|
44
44
|
'page_view',
|
|
45
45
|
'a24i_operational',
|
|
46
46
|
],
|
|
47
47
|
|
|
48
48
|
// Scrolling in scene
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
scroll_25: ['scroll'],
|
|
50
|
+
scroll_50: ['scroll'],
|
|
51
|
+
scroll_75: ['scroll'],
|
|
52
|
+
scroll_90: ['scroll'],
|
|
53
53
|
};
|
|
@@ -26,7 +26,7 @@ export const EVENT_NAMES = [
|
|
|
26
26
|
'player_open',
|
|
27
27
|
|
|
28
28
|
// Scenes
|
|
29
|
-
'
|
|
29
|
+
'scene_view',
|
|
30
30
|
|
|
31
31
|
// Scrolling in scene
|
|
32
32
|
'scroll_25',
|
|
@@ -52,5 +52,5 @@ export const ONLY_ONCE_EVENTS: AnalyticsEventsNames[] = [
|
|
|
52
52
|
|
|
53
53
|
export const UNREPEATABLE_EVENTS: AnalyticsEventsNames[] = [
|
|
54
54
|
// Scenes
|
|
55
|
-
'
|
|
55
|
+
'scene_view',
|
|
56
56
|
];
|
|
@@ -47,7 +47,7 @@ export type AnalyticsEventsMap = {
|
|
|
47
47
|
'player_open': (payload: void) => void,
|
|
48
48
|
|
|
49
49
|
// Scenes
|
|
50
|
-
'
|
|
50
|
+
'scene_view': (payload: SceneInfo) => void,
|
|
51
51
|
|
|
52
52
|
// Scrolling in scene
|
|
53
53
|
'scroll_25': (payload: SceneInfo) => void,
|
|
@@ -20,6 +20,11 @@ const getDevice = () => {
|
|
|
20
20
|
info('HbbTV');
|
|
21
21
|
return new DeviceHbbTV();
|
|
22
22
|
}
|
|
23
|
+
// any because JavaScriptBridge property is defined on Android TV Bridge platform
|
|
24
|
+
if (typeof (window as any).JavaScriptBridge !== 'undefined') {
|
|
25
|
+
info('AndroidTV');
|
|
26
|
+
return new DeviceAndroidTV();
|
|
27
|
+
}
|
|
23
28
|
if (userAgent.match(/Tizen/)) {
|
|
24
29
|
info('Tizen');
|
|
25
30
|
return new DeviceTizen();
|
|
@@ -40,11 +45,6 @@ const getDevice = () => {
|
|
|
40
45
|
info('KreaTV');
|
|
41
46
|
return new DeviceKreaTV();
|
|
42
47
|
}
|
|
43
|
-
// any because JavaScriptBridge property is defined on Android TV Bridge platform
|
|
44
|
-
if (typeof (window as any).JavaScriptBridge !== 'undefined') {
|
|
45
|
-
info('AndroidTV');
|
|
46
|
-
return new DeviceAndroidTV();
|
|
47
|
-
}
|
|
48
48
|
// any because ENTONE property is defined on Entone platform
|
|
49
49
|
if (typeof (window as any).ENTONE !== 'undefined') {
|
|
50
50
|
info('Entone');
|
|
@@ -9,6 +9,14 @@ export const getKeyMap = () => {
|
|
|
9
9
|
VOL_UP: { code: 'KeyU', keyCode: 85 },
|
|
10
10
|
VOL_DOWN: { code: 'KeyJ', keyCode: 74 },
|
|
11
11
|
MUTE: { code: 'KeyM', keyCode: 77 },
|
|
12
|
+
PLAY: { code: 'KeyO', keyCode: 79 },
|
|
13
|
+
PAUSE: { code: 'KeyP', keyCode: 80 },
|
|
14
|
+
PLAYPAUSE: { code: 'Space', keyCode: 32 },
|
|
15
|
+
FF: { code: 'Period', keyCode: 190 },
|
|
16
|
+
REW: { code: 'Comma', keyCode: 188 },
|
|
17
|
+
STOP: { code: 'Escape', keyCode: 27 },
|
|
18
|
+
SUBTITLES: { code: 'KeyS', keyCode: 83 },
|
|
19
|
+
REC: { code: 'KeyI', keyCode: 73 },
|
|
12
20
|
});
|
|
13
21
|
return generateKeyCodeKeyMap();
|
|
14
22
|
};
|
|
@@ -47,7 +47,7 @@ const prepareDateData = ({
|
|
|
47
47
|
const defaultFormat = (date: Date) => {
|
|
48
48
|
const locale = i18n.getLanguage();
|
|
49
49
|
return l10n.date(date, locale).toLocaleDateString(
|
|
50
|
-
{ weekday: 'short', day: 'numeric', month: 'short' }
|
|
50
|
+
{ weekday: 'short', day: 'numeric', month: 'short' },
|
|
51
51
|
);
|
|
52
52
|
};
|
|
53
53
|
|
|
@@ -297,6 +297,8 @@ export class Epg<
|
|
|
297
297
|
return ref;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
isFocusInRows = () => this.focused === 'epg';
|
|
301
|
+
|
|
300
302
|
focus(options?: FocusOptions) {
|
|
301
303
|
if (this.focused === 'datepicker') {
|
|
302
304
|
this.datePickerComponent.current!.focus(options);
|
|
@@ -376,10 +378,6 @@ export class Epg<
|
|
|
376
378
|
return this.inputEvents.isVerticallyFastScrolling();
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
isFocusInRows = () => {
|
|
380
|
-
return this.focused === 'epg';
|
|
381
|
-
};
|
|
382
|
-
|
|
383
381
|
render() {
|
|
384
382
|
const {
|
|
385
383
|
datePickerProps, timelineWidthPx, channelHeaderWidthPx, scrollStepMs, rowHeightPx,
|
|
@@ -144,24 +144,6 @@ export class Matrix extends Component<Props> implements IFocusable {
|
|
|
144
144
|
return false;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
isDirectionLeft(event: KeyboardEvent) {
|
|
148
|
-
const { dir } = this.props;
|
|
149
|
-
const rtl = isRtl(dir);
|
|
150
|
-
return (
|
|
151
|
-
(!rtl && LEFT.is(event))
|
|
152
|
-
|| (rtl && RIGHT.is(event))
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
isDirectionRight(event: KeyboardEvent) {
|
|
157
|
-
const { dir } = this.props;
|
|
158
|
-
const rtl = isRtl(dir);
|
|
159
|
-
return (
|
|
160
|
-
(!rtl && RIGHT.is(event))
|
|
161
|
-
|| (rtl && LEFT.is(event))
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
147
|
moveForwards = (isCircular: boolean) => (x: number, length: number) => {
|
|
166
148
|
if (x === length - 1) {
|
|
167
149
|
return isCircular ? 0 : length - 1;
|
|
@@ -204,6 +186,24 @@ export class Matrix extends Component<Props> implements IFocusable {
|
|
|
204
186
|
return this.searchVertical(column, fromY, this.moveForwards(!!isCircular));
|
|
205
187
|
};
|
|
206
188
|
|
|
189
|
+
isDirectionLeft(event: KeyboardEvent) {
|
|
190
|
+
const { dir } = this.props;
|
|
191
|
+
const rtl = isRtl(dir);
|
|
192
|
+
return (
|
|
193
|
+
(!rtl && LEFT.is(event))
|
|
194
|
+
|| (rtl && RIGHT.is(event))
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
isDirectionRight(event: KeyboardEvent) {
|
|
199
|
+
const { dir } = this.props;
|
|
200
|
+
const rtl = isRtl(dir);
|
|
201
|
+
return (
|
|
202
|
+
(!rtl && RIGHT.is(event))
|
|
203
|
+
|| (rtl && LEFT.is(event))
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
207
|
isDifferentReferenceFromCurrent(index: Index2D): boolean {
|
|
208
208
|
const { current } = this.getRefAt(this.index);
|
|
209
209
|
const { current: searched } = this.getRefAt(index);
|
|
@@ -178,16 +178,6 @@ export class GridBase<T extends Item<U>, U>
|
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
-
shouldPreventBlur(event: KeyboardEvent) {
|
|
182
|
-
const { horizontal, preventBlurOnLongPress } = this.props;
|
|
183
|
-
return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
|
|
184
|
-
(preventBlurOnLongPress.forward && isForward(event, horizontal))
|
|
185
|
-
|| (preventBlurOnLongPress.backward && isBackward(event, horizontal))
|
|
186
|
-
|| (preventBlurOnLongPress.forwardGroup && isFwGroup(event, horizontal))
|
|
187
|
-
|| (preventBlurOnLongPress.backwardGroup && isBwGroup(event, horizontal))
|
|
188
|
-
));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
181
|
getRefAtIndex(i: number): Reference<T> {
|
|
192
182
|
const ref = this.itemRefs[i];
|
|
193
183
|
if (!ref) {
|
|
@@ -253,6 +243,16 @@ export class GridBase<T extends Item<U>, U>
|
|
|
253
243
|
this.fastScrollingCounter.reset();
|
|
254
244
|
};
|
|
255
245
|
|
|
246
|
+
shouldPreventBlur(event: KeyboardEvent) {
|
|
247
|
+
const { horizontal, preventBlurOnLongPress } = this.props;
|
|
248
|
+
return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
|
|
249
|
+
(preventBlurOnLongPress.forward && isForward(event, horizontal))
|
|
250
|
+
|| (preventBlurOnLongPress.backward && isBackward(event, horizontal))
|
|
251
|
+
|| (preventBlurOnLongPress.forwardGroup && isFwGroup(event, horizontal))
|
|
252
|
+
|| (preventBlurOnLongPress.backwardGroup && isBwGroup(event, horizontal))
|
|
253
|
+
));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
256
|
animatedScrollStarted() {
|
|
257
257
|
this.isAnimatedScrollRunning = true;
|
|
258
258
|
}
|
|
@@ -61,6 +61,8 @@ export class FirstOnlyGrid<T extends Item<U>, U>
|
|
|
61
61
|
const linesDiff = Math.floor(grid.index / itemsInGroup) - oldLine;
|
|
62
62
|
if (linesDiff > 0) {
|
|
63
63
|
this.scrollForward(linesDiff);
|
|
64
|
+
} else if (linesDiff < 0) {
|
|
65
|
+
this.scrollBackward(Math.abs(linesDiff));
|
|
64
66
|
}
|
|
65
67
|
};
|
|
66
68
|
|
|
@@ -77,8 +79,8 @@ export class FirstOnlyGrid<T extends Item<U>, U>
|
|
|
77
79
|
this.controller.scroll(targetScrollIndex);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
scrollBackward() {
|
|
81
|
-
const targetScrollIndex = this.controller.scrollIndex -
|
|
82
|
+
scrollBackward(diff = 1) {
|
|
83
|
+
const targetScrollIndex = this.controller.scrollIndex - diff;
|
|
82
84
|
this.controller.scroll(targetScrollIndex);
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { createRef, Reference, Component } from '@24i/bigscreen-sdk/jsx';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
getDisplayToggler,
|
|
4
|
+
IDisplayToggler,
|
|
5
|
+
removeClass,
|
|
6
|
+
addClass,
|
|
7
|
+
DISPLAY_NONE,
|
|
8
|
+
} from '@24i/bigscreen-sdk/utils';
|
|
4
9
|
|
|
5
10
|
const ICON_PREFIX = 'icon-';
|
|
6
11
|
|
|
7
12
|
type Props<SpritesJSON> = {
|
|
8
13
|
icon?: keyof SpritesJSON,
|
|
9
|
-
domRef?: Reference<HTMLDivElement
|
|
14
|
+
domRef?: Reference<HTMLDivElement>,
|
|
15
|
+
className?: string,
|
|
16
|
+
initialIsHidden?: boolean,
|
|
10
17
|
};
|
|
11
18
|
|
|
12
19
|
export class Icon<T> extends Component<Props<T>> {
|
|
@@ -14,10 +21,13 @@ export class Icon<T> extends Component<Props<T>> {
|
|
|
14
21
|
|
|
15
22
|
currentIcon: keyof T | undefined;
|
|
16
23
|
|
|
24
|
+
iconDisplay: IDisplayToggler;
|
|
25
|
+
|
|
17
26
|
constructor(props: Props<T>) {
|
|
18
27
|
super(props);
|
|
19
28
|
this.ref = props.domRef || createRef();
|
|
20
29
|
this.currentIcon = props.icon;
|
|
30
|
+
this.iconDisplay = getDisplayToggler(this.ref, props.initialIsHidden);
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
getClass(icon: keyof T): string {
|
|
@@ -31,6 +41,16 @@ export class Icon<T> extends Component<Props<T>> {
|
|
|
31
41
|
this.currentIcon = icon;
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
show() {
|
|
45
|
+
if (!this.currentIcon) return;
|
|
46
|
+
this.iconDisplay.show();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
hide() {
|
|
50
|
+
if (!this.currentIcon) return;
|
|
51
|
+
this.iconDisplay.hide();
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
clearIcon() {
|
|
35
55
|
if (!this.currentIcon) return;
|
|
36
56
|
removeClass(this.ref, this.getClass(this.currentIcon));
|
|
@@ -38,11 +58,13 @@ export class Icon<T> extends Component<Props<T>> {
|
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
render() {
|
|
41
|
-
const { icon } = this.props;
|
|
61
|
+
const { icon, className, initialIsHidden } = this.props;
|
|
42
62
|
return (
|
|
43
63
|
<div
|
|
44
64
|
ref={this.ref}
|
|
45
|
-
className={
|
|
65
|
+
className={`${className || ''} icon ${icon
|
|
66
|
+
? this.getClass(icon) : ''} ${initialIsHidden
|
|
67
|
+
? DISPLAY_NONE : ''}`}
|
|
46
68
|
/>
|
|
47
69
|
);
|
|
48
70
|
}
|
|
@@ -56,7 +56,7 @@ export class Keyboard extends KeyboardBase<Layouts, Props> {
|
|
|
56
56
|
createLanguageFocusMatrix() {
|
|
57
57
|
const { languageLayout } = this.props;
|
|
58
58
|
this.languageFocusMatrix = generateFocusMatrixFromStringMatrix(
|
|
59
|
-
this.keyRefs, languageLayout!.makeFocusStrings(languageLayout!.layout, true)
|
|
59
|
+
this.keyRefs, languageLayout!.makeFocusStrings(languageLayout!.layout, true),
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -257,23 +257,25 @@ export class Keyboard extends KeyboardBase<Layouts, Props> {
|
|
|
257
257
|
<div className="key-row">
|
|
258
258
|
<div className="key key-wide key-empty"> </div>
|
|
259
259
|
</div>
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<div className="key-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
260
|
+
{languageLayout
|
|
261
|
+
? (
|
|
262
|
+
<div className="key-row rounded-border-container">
|
|
263
|
+
<Interactable
|
|
264
|
+
ref={this.keyRefs.language}
|
|
265
|
+
className="key key-wide key-with-icon key-language"
|
|
266
|
+
onPress={() => this.onLanguage()}
|
|
267
|
+
>
|
|
268
|
+
<div className="key-background">
|
|
269
|
+
<div className="key-mask-icon" />
|
|
270
|
+
</div>
|
|
271
|
+
</Interactable>
|
|
272
|
+
</div>
|
|
273
|
+
)
|
|
274
|
+
: (
|
|
275
|
+
<div className="key-row">
|
|
276
|
+
<div className="key key-wide key-empty"> </div>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
277
279
|
</div>
|
|
278
280
|
<div className="keys-container key-layout">
|
|
279
281
|
{layout}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { processYear, dateFormatter, toTwoDigitLocaleTimeString } from './common';
|
|
2
2
|
import { DateTimeFormatOptions, Day, Month, Weekday, Year } from './types';
|
|
3
3
|
|
|
4
|
-
//https://web.library.yale.edu/cataloging/months
|
|
4
|
+
// https://web.library.yale.edu/cataloging/months
|
|
5
5
|
const DE_MONTHS = {
|
|
6
6
|
narrow: [
|
|
7
7
|
'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D',
|
|
@@ -40,7 +40,7 @@ const processMonth = (
|
|
|
40
40
|
) => {
|
|
41
41
|
if (typeof month === 'undefined') return;
|
|
42
42
|
if (month === '2-digit' || month === 'numeric') {
|
|
43
|
-
const strMonth = (date.getMonth() + 1).toString()
|
|
43
|
+
const strMonth = `${(date.getMonth() + 1).toString()}.`;
|
|
44
44
|
dateParts.push(strMonth);
|
|
45
45
|
} else {
|
|
46
46
|
const finalMonth = DE_MONTHS[month][date.getMonth()];
|
|
@@ -80,10 +80,10 @@ const toLocaleDateString = (date: Date) => (
|
|
|
80
80
|
const dateConnection = '';
|
|
81
81
|
const dateParts: string[] = [];
|
|
82
82
|
processDay(dateParts, day, date);
|
|
83
|
-
if ((month === 'narrow'
|
|
84
|
-
month === 'short'
|
|
85
|
-
month === 'long')
|
|
86
|
-
day != null
|
|
83
|
+
if ((month === 'narrow'
|
|
84
|
+
|| month === 'short'
|
|
85
|
+
|| month === 'long')
|
|
86
|
+
&& day != null
|
|
87
87
|
) {
|
|
88
88
|
dateParts.push(' ');
|
|
89
89
|
}
|
|
@@ -32,7 +32,7 @@ export const processWeekday = (
|
|
|
32
32
|
day: Day | undefined,
|
|
33
33
|
weekday: Weekday | undefined,
|
|
34
34
|
date: Date,
|
|
35
|
-
weekdaysLocaleData: LocaleData = EN_WEEKDAYS
|
|
35
|
+
weekdaysLocaleData: LocaleData = EN_WEEKDAYS,
|
|
36
36
|
) => {
|
|
37
37
|
if (typeof weekday === 'undefined') return;
|
|
38
38
|
let weekdayString = weekdaysLocaleData[weekday][date.getDay()];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dateFormatter, processYear, toTwoDigitLocaleTimeString } from './common';
|
|
2
2
|
import { DateTimeFormatOptions, Day, Month, Weekday, Year } from './types';
|
|
3
3
|
|
|
4
|
-
//https://web.library.yale.edu/cataloging/months
|
|
4
|
+
// https://web.library.yale.edu/cataloging/months
|
|
5
5
|
const FR_MONTHS = {
|
|
6
6
|
narrow: [
|
|
7
7
|
'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D',
|
|
@@ -38,7 +38,7 @@ const processWeekday = (
|
|
|
38
38
|
if (typeof weekday === 'undefined') return;
|
|
39
39
|
let weekdayString = FR_WEEKDAYS[weekday][date.getDay()];
|
|
40
40
|
if (
|
|
41
|
-
(
|
|
41
|
+
(typeof year !== 'undefined'
|
|
42
42
|
|| typeof month !== 'undefined'
|
|
43
43
|
|| typeof day !== 'undefined') && weekday !== 'long'
|
|
44
44
|
) {
|
|
@@ -48,11 +48,12 @@ const processWeekday = (
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const processDay = (
|
|
51
|
-
dateParts: string[], day: Day | undefined, month: Month | undefined, date: Date
|
|
51
|
+
dateParts: string[], day: Day | undefined, month: Month | undefined, date: Date,
|
|
52
|
+
) => {
|
|
52
53
|
if (typeof day === 'undefined') return;
|
|
53
54
|
const strDate = date.getDate().toString();
|
|
54
55
|
dateParts.push(
|
|
55
|
-
strDate.length === 1 && month !== 'short' && month !== 'long'
|
|
56
|
+
strDate.length === 1 && month !== 'short' && month !== 'long' ? `0${strDate}` : strDate,
|
|
56
57
|
);
|
|
57
58
|
};
|
|
58
59
|
|
|
@@ -81,14 +81,14 @@ const toLocaleDateString = (date: Date) => (
|
|
|
81
81
|
},
|
|
82
82
|
) => {
|
|
83
83
|
const dateStringParts: string[] = [];
|
|
84
|
-
processWeekday(dateStringParts,
|
|
85
|
-
const dateConnection = month === 'numeric' || month === '2-digit' ?
|
|
84
|
+
processWeekday(dateStringParts, weekday, date);
|
|
85
|
+
const dateConnection = month === 'numeric' || month === '2-digit' ? '.' : '';
|
|
86
86
|
const dateParts: string[] = [];
|
|
87
87
|
processDay(dateParts, day, date);
|
|
88
|
-
if ((month === 'narrow'
|
|
89
|
-
month === 'short'
|
|
90
|
-
month === 'long')
|
|
91
|
-
day != null
|
|
88
|
+
if ((month === 'narrow'
|
|
89
|
+
|| month === 'short'
|
|
90
|
+
|| month === 'long')
|
|
91
|
+
&& day != null
|
|
92
92
|
) {
|
|
93
93
|
dateParts.push(' ');
|
|
94
94
|
}
|
|
@@ -152,14 +152,6 @@ export class ListBase<T extends Item<U>, U>
|
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
-
shouldPreventBlur(event: KeyboardEvent) {
|
|
156
|
-
const { horizontal, preventBlurOnLongPress } = this.props;
|
|
157
|
-
return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
|
|
158
|
-
(preventBlurOnLongPress.forward && isForward(event, horizontal))
|
|
159
|
-
|| (preventBlurOnLongPress.backward && isBackward(event, horizontal))
|
|
160
|
-
));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
155
|
getRefAtIndex(i: number): Reference<T> {
|
|
164
156
|
const ref = this.itemRefs[i];
|
|
165
157
|
if (!ref) {
|
|
@@ -201,6 +193,14 @@ export class ListBase<T extends Item<U>, U>
|
|
|
201
193
|
this.fastScrollingCounter.reset();
|
|
202
194
|
}
|
|
203
195
|
|
|
196
|
+
shouldPreventBlur(event: KeyboardEvent) {
|
|
197
|
+
const { horizontal, preventBlurOnLongPress } = this.props;
|
|
198
|
+
return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
|
|
199
|
+
(preventBlurOnLongPress.forward && isForward(event, horizontal))
|
|
200
|
+
|| (preventBlurOnLongPress.backward && isBackward(event, horizontal))
|
|
201
|
+
));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
204
|
animatedScrollStarted() {
|
|
205
205
|
this.isAnimatedScrollRunning = true;
|
|
206
206
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, createRef, DeclareProps } from '@24i/bigscreen-sdk/jsx';
|
|
2
2
|
import { PlayerBase } from '@24i/player-base';
|
|
3
|
-
import { switchByKey } from '@24i/bigscreen-sdk/device/keymap';
|
|
3
|
+
import { FF, REW, switchByKey } from '@24i/bigscreen-sdk/device/keymap';
|
|
4
4
|
import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
|
|
5
5
|
import {
|
|
6
6
|
IDisplayToggler, getDisplayToggler, DISPLAY_NONE,
|
|
@@ -23,6 +23,7 @@ type Props = {
|
|
|
23
23
|
onClose?: () => void,
|
|
24
24
|
onHide?: () => void,
|
|
25
25
|
isPauseAllowed?: () => boolean,
|
|
26
|
+
showSubtitlesPicker?: () => void,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export class PlayerUI extends Component<Props>
|
|
@@ -84,8 +85,17 @@ export class PlayerUI extends Component<Props>
|
|
|
84
85
|
STOP: this.onKeyStop,
|
|
85
86
|
FF: this.onKeyForward,
|
|
86
87
|
REW: this.onKeyRewind,
|
|
88
|
+
SUBTITLES: this.onKeySubtitles,
|
|
87
89
|
});
|
|
88
|
-
if (processed) stopEvent(event);
|
|
90
|
+
if (processed && !FF.is(event) && !REW.is(event)) stopEvent(event);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
onKeySubtitles = () => {
|
|
94
|
+
const { showSubtitlesPicker } = this.props;
|
|
95
|
+
if (showSubtitlesPicker) {
|
|
96
|
+
showSubtitlesPicker();
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
89
99
|
};
|
|
90
100
|
|
|
91
101
|
onKeyPlay = () => {
|
|
@@ -103,7 +113,8 @@ export class PlayerUI extends Component<Props>
|
|
|
103
113
|
};
|
|
104
114
|
|
|
105
115
|
onKeyPause = () => {
|
|
106
|
-
|
|
116
|
+
const { isPauseAllowed } = this.props;
|
|
117
|
+
if (!isPauseAllowed()) return false;
|
|
107
118
|
if (this.seeking.isAutomaticallySeeking()) {
|
|
108
119
|
this.seeking.stopAutomaticSeek();
|
|
109
120
|
if (this.isPlaying()) this.trigger(PlayerAction.PAUSE);
|
|
@@ -118,7 +129,8 @@ export class PlayerUI extends Component<Props>
|
|
|
118
129
|
};
|
|
119
130
|
|
|
120
131
|
onKeyPlayPause = () => {
|
|
121
|
-
|
|
132
|
+
const { isPauseAllowed } = this.props;
|
|
133
|
+
if (!isPauseAllowed()) return false;
|
|
122
134
|
if (this.seeking.isSeeking()) {
|
|
123
135
|
this.seeking.confirmSeek();
|
|
124
136
|
if (!this.isPlaying()) this.trigger(PlayerAction.PLAY);
|
|
@@ -137,33 +149,22 @@ export class PlayerUI extends Component<Props>
|
|
|
137
149
|
};
|
|
138
150
|
|
|
139
151
|
onKeyForward = () => {
|
|
140
|
-
if (this.seeking.isAutomaticallySeekingBackward)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!this.seeking.isAutomaticallySeekingForward) {
|
|
145
|
-
this.seeking.startAutomaticSeekForward();
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
return false;
|
|
152
|
+
if (this.seeking.isAutomaticallySeekingBackward) this.seeking.stopAutomaticSeek();
|
|
153
|
+
this.disableHiding();
|
|
154
|
+
this.seeking.automaticSeekForward();
|
|
155
|
+
return true;
|
|
149
156
|
};
|
|
150
157
|
|
|
151
158
|
onKeyRewind = () => {
|
|
152
|
-
if (this.seeking.isAutomaticallySeekingForward)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!this.seeking.isAutomaticallySeekingBackward) {
|
|
157
|
-
this.seeking.startAutomaticSeekBackward();
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
};
|
|
159
|
+
if (this.seeking.isAutomaticallySeekingForward) this.seeking.stopAutomaticSeek();
|
|
160
|
+
this.disableHiding();
|
|
161
|
+
this.seeking.automaticSeekBackward();
|
|
162
|
+
return true;
|
|
162
163
|
|
|
163
|
-
getPlayer = () => {
|
|
164
|
-
return this.player!;
|
|
165
164
|
};
|
|
166
165
|
|
|
166
|
+
getPlayer = () => this.player!;
|
|
167
|
+
|
|
167
168
|
setPlayer(player: PlayerBase<any>) {
|
|
168
169
|
this.player = player;
|
|
169
170
|
this.seeking.setPlayer(player);
|
|
@@ -11,7 +11,7 @@ import { PlayerUICommonProps } from './types';
|
|
|
11
11
|
const CONSECUTIVE_TIMEOUT = 500;
|
|
12
12
|
const FAST_TRESHOLD = 5;
|
|
13
13
|
const SLOW_TIME = 10000;
|
|
14
|
-
const FAST_TIME =
|
|
14
|
+
const FAST_TIME = 30000;
|
|
15
15
|
const SAFE_END_MARGIN = 10000;
|
|
16
16
|
const TO_PERCENT = 100;
|
|
17
17
|
|
|
@@ -26,9 +26,9 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
26
26
|
|
|
27
27
|
seekTime = createRef<PlayerTime>();
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
leftConsecutiveness = 0;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
rightConsecutiveness = 0;
|
|
32
32
|
|
|
33
33
|
leftConsecutiveTimeout = createTimeout();
|
|
34
34
|
|
|
@@ -51,6 +51,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
51
51
|
document.addEventListener('mouseup', this.onMouseUp);
|
|
52
52
|
document.addEventListener('mousemove', this.onMouseMove);
|
|
53
53
|
ui.seeking.addEventListener('seektimeupdate', this.onSeekTimeUpdate);
|
|
54
|
+
ui.seeking.addEventListener('stopautomaticseek', this.onStopAutomaticSeek);
|
|
54
55
|
ui.getPlayer().addEventListener('timeupdate', this.onTimeUpdate);
|
|
55
56
|
ui.getPlayer().addEventListener('durationchange', this.onDurationUpdate);
|
|
56
57
|
}
|
|
@@ -60,6 +61,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
60
61
|
document.removeEventListener('mouseup', this.onMouseUp);
|
|
61
62
|
document.removeEventListener('mousemove', this.onMouseMove);
|
|
62
63
|
ui.seeking.removeEventListener('seektimeupdate', this.onSeekTimeUpdate);
|
|
64
|
+
ui.seeking.removeEventListener('stopautomaticseek', this.onStopAutomaticSeek);
|
|
63
65
|
ui.getPlayer().removeEventListener('timeupdate', this.onTimeUpdate);
|
|
64
66
|
ui.getPlayer().removeEventListener('durationchange', this.onDurationUpdate);
|
|
65
67
|
}
|
|
@@ -114,13 +116,13 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
114
116
|
|
|
115
117
|
onLeft = () => {
|
|
116
118
|
const { ui } = this.props;
|
|
117
|
-
this.
|
|
119
|
+
this.leftConsecutiveness += 1;
|
|
118
120
|
if (ui.seeking.isAutomaticallySeeking()) {
|
|
119
121
|
ui.seeking.stopAutomaticSeek();
|
|
120
122
|
}
|
|
121
|
-
ui.seeking.seekBackward(this.
|
|
123
|
+
ui.seeking.seekBackward(this.leftConsecutiveness >= FAST_TRESHOLD ? FAST_TIME : SLOW_TIME);
|
|
122
124
|
this.leftConsecutiveTimeout.set(() => {
|
|
123
|
-
this.
|
|
125
|
+
this.leftConsecutiveness = 0;
|
|
124
126
|
}, CONSECUTIVE_TIMEOUT);
|
|
125
127
|
return true;
|
|
126
128
|
};
|
|
@@ -129,14 +131,14 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
129
131
|
const { ui } = this.props;
|
|
130
132
|
const { seekTime } = ui.seeking;
|
|
131
133
|
if (seekTime == null && this.currentTime + SLOW_TIME >= this.duration) return true;
|
|
132
|
-
this.
|
|
134
|
+
this.rightConsecutiveness += 1;
|
|
133
135
|
if (ui.seeking.isAutomaticallySeeking()) {
|
|
134
136
|
ui.seeking.stopAutomaticSeek();
|
|
135
137
|
}
|
|
136
|
-
const isFastSeeking = this.
|
|
138
|
+
const isFastSeeking = this.rightConsecutiveness >= FAST_TRESHOLD;
|
|
137
139
|
if (
|
|
138
|
-
(seekTime != null
|
|
139
|
-
(seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
|
|
140
|
+
(seekTime != null
|
|
141
|
+
&& (seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
|
|
140
142
|
|| (isFastSeeking && seekTime + FAST_TIME >= this.duration - SAFE_END_MARGIN)))
|
|
141
143
|
|| (seekTime == null && this.currentTime >= this.duration - SAFE_END_MARGIN)
|
|
142
144
|
) {
|
|
@@ -145,7 +147,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
145
147
|
ui.seeking.seekForward(isFastSeeking ? FAST_TIME : SLOW_TIME);
|
|
146
148
|
}
|
|
147
149
|
this.rightConsecutiveTimeout.set(() => {
|
|
148
|
-
this.
|
|
150
|
+
this.rightConsecutiveness = 0;
|
|
149
151
|
}, CONSECUTIVE_TIMEOUT);
|
|
150
152
|
return true;
|
|
151
153
|
};
|
|
@@ -163,6 +165,11 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
163
165
|
this.updateSeekBar();
|
|
164
166
|
};
|
|
165
167
|
|
|
168
|
+
onStopAutomaticSeek = () => {
|
|
169
|
+
const { ui } = this.props;
|
|
170
|
+
ui.enableHiding();
|
|
171
|
+
};
|
|
172
|
+
|
|
166
173
|
onTimeUpdate = ({ currentTime }: ITimeUpdateEvent) => {
|
|
167
174
|
const { ui } = this.props;
|
|
168
175
|
this.currentTime = currentTime;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { IEvents, EventMapType, EventsManager } from '@24i/bigscreen-sdk/events-manager';
|
|
2
|
-
import { createInterval } from '@24i/bigscreen-sdk/utils/timers';
|
|
2
|
+
import { createInterval, createTimeout } from '@24i/bigscreen-sdk/utils/timers';
|
|
3
3
|
import { PlayerBase } from '@24i/player-base';
|
|
4
4
|
|
|
5
|
-
const DEFAULT_SEEK_TIME =
|
|
5
|
+
const DEFAULT_SEEK_TIME = 10000;
|
|
6
|
+
const FAST_SEEK_TIME = 30000;
|
|
6
7
|
const AUTOMATIC_SEEK_INTERVAL = 300;
|
|
8
|
+
const FAST_THRESHOLD = 5;
|
|
9
|
+
const CONSECUTIVE_TIMEOUT = 500;
|
|
7
10
|
|
|
8
11
|
interface SeekingEventsMap extends EventMapType {
|
|
9
12
|
'seektimeupdate': (payload: number) => void,
|
|
@@ -29,6 +32,14 @@ export class Seeking implements IEvents<SeekingEventsMap> {
|
|
|
29
32
|
|
|
30
33
|
isAutomaticallySeekingBackward = false;
|
|
31
34
|
|
|
35
|
+
ffConsecutiveness = 0;
|
|
36
|
+
|
|
37
|
+
rewConsecutiveness = 0;
|
|
38
|
+
|
|
39
|
+
ffConsecutiveTimeout = createTimeout();
|
|
40
|
+
|
|
41
|
+
rewConsecutiveTimeout = createTimeout();
|
|
42
|
+
|
|
32
43
|
constructor(player: PlayerBase<any> | null = null) {
|
|
33
44
|
this.player = player;
|
|
34
45
|
}
|
|
@@ -76,28 +87,53 @@ export class Seeking implements IEvents<SeekingEventsMap> {
|
|
|
76
87
|
this.triggerEvent('seektimeupdate', this.seekTime!);
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
this.
|
|
90
|
+
automaticSeekForward() {
|
|
91
|
+
if (this.isAutomaticallySeekingForward) {
|
|
92
|
+
this.ffConsecutiveTimeout.set(() => {
|
|
93
|
+
this.ffConsecutiveness = 0;
|
|
94
|
+
}, CONSECUTIVE_TIMEOUT);
|
|
95
|
+
this.ffConsecutiveness += 1;
|
|
96
|
+
if (this.ffConsecutiveness === FAST_THRESHOLD) {
|
|
97
|
+
this.automaticSeekInterval.set(
|
|
98
|
+
() => this.seekForward(FAST_SEEK_TIME),
|
|
99
|
+
AUTOMATIC_SEEK_INTERVAL,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
81
104
|
this.isAutomaticallySeekingForward = true;
|
|
105
|
+
this.ffConsecutiveness += 1;
|
|
82
106
|
this.seekForward();
|
|
83
|
-
this.automaticSeekInterval.set(() =>
|
|
84
|
-
this.seekForward();
|
|
85
|
-
}, AUTOMATIC_SEEK_INTERVAL);
|
|
107
|
+
this.automaticSeekInterval.set(() => this.seekForward(), AUTOMATIC_SEEK_INTERVAL);
|
|
86
108
|
}
|
|
87
109
|
|
|
88
|
-
|
|
89
|
-
this.
|
|
110
|
+
automaticSeekBackward() {
|
|
111
|
+
if (this.isAutomaticallySeekingBackward) {
|
|
112
|
+
this.rewConsecutiveTimeout.set(() => {
|
|
113
|
+
this.rewConsecutiveness = 0;
|
|
114
|
+
}, CONSECUTIVE_TIMEOUT);
|
|
115
|
+
this.rewConsecutiveness += 1;
|
|
116
|
+
if (this.rewConsecutiveness === FAST_THRESHOLD) {
|
|
117
|
+
this.automaticSeekInterval.set(
|
|
118
|
+
() => this.seekBackward(FAST_SEEK_TIME),
|
|
119
|
+
AUTOMATIC_SEEK_INTERVAL,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
90
124
|
this.isAutomaticallySeekingBackward = true;
|
|
125
|
+
this.rewConsecutiveness += 1;
|
|
91
126
|
this.seekBackward();
|
|
92
|
-
this.automaticSeekInterval.set(() =>
|
|
93
|
-
this.seekBackward();
|
|
94
|
-
}, AUTOMATIC_SEEK_INTERVAL);
|
|
127
|
+
this.automaticSeekInterval.set(() => this.seekBackward(), AUTOMATIC_SEEK_INTERVAL);
|
|
95
128
|
}
|
|
96
129
|
|
|
97
130
|
stopAutomaticSeek() {
|
|
131
|
+
if (this.isAutomaticallySeeking()) this.triggerEvent('stopautomaticseek');
|
|
98
132
|
this.automaticSeekInterval.clear();
|
|
99
133
|
this.isAutomaticallySeekingForward = false;
|
|
100
134
|
this.isAutomaticallySeekingBackward = false;
|
|
135
|
+
this.ffConsecutiveness = 0;
|
|
136
|
+
this.rewConsecutiveness = 0;
|
|
101
137
|
}
|
|
102
138
|
|
|
103
139
|
cancelSeek() {
|
|
@@ -13,12 +13,16 @@ const getSeekingMock = (): jest.Mocked<IPlayerUI['seeking']> => ({
|
|
|
13
13
|
isAutomaticallySeeking: jest.fn(),
|
|
14
14
|
isAutomaticallySeekingForward: false,
|
|
15
15
|
isAutomaticallySeekingBackward: false,
|
|
16
|
+
ffConsecutiveness: 0,
|
|
17
|
+
rewConsecutiveness: 0,
|
|
18
|
+
ffConsecutiveTimeout: null!,
|
|
19
|
+
rewConsecutiveTimeout: null!,
|
|
16
20
|
isSeeking: jest.fn(),
|
|
17
21
|
startSeeking: jest.fn(),
|
|
18
22
|
seekForward: jest.fn(),
|
|
19
23
|
seekBackward: jest.fn(),
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
automaticSeekForward: jest.fn(),
|
|
25
|
+
automaticSeekBackward: jest.fn(),
|
|
22
26
|
stopAutomaticSeek: jest.fn(),
|
|
23
27
|
cancelSeek: jest.fn(),
|
|
24
28
|
confirmSeek: jest.fn(),
|
|
@@ -31,8 +35,8 @@ const getSeekingMock = (): jest.Mocked<IPlayerUI['seeking']> => ({
|
|
|
31
35
|
|
|
32
36
|
const PLAYER_MOCK_DURATION = 60000;
|
|
33
37
|
|
|
34
|
-
export const getPlayerMock = (): PlayerBase<any> =>{
|
|
35
|
-
const playerMock =
|
|
38
|
+
export const getPlayerMock = (): PlayerBase<any> => {
|
|
39
|
+
const playerMock = new PlayerBaseMock();
|
|
36
40
|
jest.spyOn(playerMock, 'duration', 'get').mockReturnValue(PLAYER_MOCK_DURATION);
|
|
37
41
|
|
|
38
42
|
return playerMock;
|
|
@@ -63,9 +63,9 @@ export const formatTime = (duration: number, pattern: string) => {
|
|
|
63
63
|
const unitValues = calculateUnitValues(duration, units);
|
|
64
64
|
let result: string = pattern;
|
|
65
65
|
forEach(matches, (match) => {
|
|
66
|
-
const formatedValue = Number.isNaN(unitValues[match.unit])
|
|
67
|
-
'--'
|
|
68
|
-
unitValues[match.unit]!.toString().padStart(match.count, '0');
|
|
66
|
+
const formatedValue = Number.isNaN(unitValues[match.unit])
|
|
67
|
+
? '--'
|
|
68
|
+
: unitValues[match.unit]!.toString().padStart(match.count, '0');
|
|
69
69
|
result = result.replace(
|
|
70
70
|
match.completeMatch,
|
|
71
71
|
formatedValue,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HOUR_IN_MS, MINUTE_IN_MS } from '@24i/bigscreen-sdk/utils';
|
|
2
|
+
import * as TwentyFourIMediaTimeApi from '../services/24iMediaTimeApi';
|
|
3
|
+
|
|
4
|
+
type ParamOptions = TwentyFourIMediaTimeApi.ParamOptions;
|
|
5
|
+
|
|
6
|
+
const defaultOptions: ParamOptions = {
|
|
7
|
+
timeoutInMs: 30000,
|
|
8
|
+
retryConfig: {
|
|
9
|
+
retry: 2,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns a tring in ISO format followed by +/-hh:mm suffix for timezone offset.
|
|
15
|
+
* @param resp Time api response.
|
|
16
|
+
* @returns ISO string with timezone data.
|
|
17
|
+
*/
|
|
18
|
+
const getIsoDateString = (resp: TwentyFourIMediaTimeApi.ServiceDataResponse): string => {
|
|
19
|
+
let datetime = new Date(resp.timestamp + resp.timezone_offset).toISOString();
|
|
20
|
+
const offset = resp.timezone_offset / HOUR_IN_MS;
|
|
21
|
+
if (offset) {
|
|
22
|
+
const sign = offset > 0 ? '+' : '-';
|
|
23
|
+
const hours = Math.abs(offset);
|
|
24
|
+
const minutes = Math.floor((resp.timezone_offset % HOUR_IN_MS) / MINUTE_IN_MS);
|
|
25
|
+
// eslint-disable-next-line no-magic-numbers, sonarjs/no-nested-template-literals
|
|
26
|
+
const timeString = `${sign + `0${hours}`.slice(-2)}:${`0${minutes}`.slice(-2)}`;
|
|
27
|
+
datetime = datetime.replace('Z', timeString);
|
|
28
|
+
}
|
|
29
|
+
return datetime;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wrapper over "TwentyFourIMediaTimeApi.getTime" method requesting the current time
|
|
34
|
+
* from timekeeping server. Transforms the result to ISO string with timezone offset data
|
|
35
|
+
* included.
|
|
36
|
+
*
|
|
37
|
+
* @param options Options object
|
|
38
|
+
*/
|
|
39
|
+
export const getTime = async (options: ParamOptions = {}) => {
|
|
40
|
+
const opts = { ...defaultOptions, ...options };
|
|
41
|
+
const resp = await TwentyFourIMediaTimeApi.getTime(opts);
|
|
42
|
+
|
|
43
|
+
if ('timestamp' in resp && 'timezone_offset' in resp && 'in_dst' in resp) {
|
|
44
|
+
return { datetime: getIsoDateString(resp) };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package "24iMediaTimeApi" provides a single function to obtain the current time
|
|
3
|
+
* adjusted by local timezone and daylight savings time for use in time package,
|
|
4
|
+
* from 24i time API at https://time.24imedia.tv/
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
xhrSendRetry, type RetryConfig, type XhrResponse,
|
|
8
|
+
} from '@24i/bigscreen-sdk/utils/xhr';
|
|
9
|
+
|
|
10
|
+
const API_URL = 'https://time.24imedia.tv/';
|
|
11
|
+
|
|
12
|
+
export type Options = {
|
|
13
|
+
/** The request timeout in milliseconds. The value 0 for no timeout */
|
|
14
|
+
timeoutInMs: number,
|
|
15
|
+
/** Retry configuration */
|
|
16
|
+
retryConfig: RetryConfig,
|
|
17
|
+
};
|
|
18
|
+
export type ParamOptions = Partial<Options>;
|
|
19
|
+
|
|
20
|
+
export type ServiceDataResponse = {
|
|
21
|
+
/** Is dailight savings time */
|
|
22
|
+
in_dst: boolean,
|
|
23
|
+
/** Current time in GMT, in milliseconds */
|
|
24
|
+
timestamp: number,
|
|
25
|
+
/** Timezeone offset in milliseconds */
|
|
26
|
+
timezone_offset: number,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Request the current time based on public IP of request.
|
|
31
|
+
*
|
|
32
|
+
* @param options Options object
|
|
33
|
+
*/
|
|
34
|
+
export async function getTime(options?: ParamOptions): Promise<ServiceDataResponse> {
|
|
35
|
+
try {
|
|
36
|
+
const resp = (await xhrSendRetry(API_URL, options)) as XhrResponse;
|
|
37
|
+
const data: any = resp.dataJson || {};
|
|
38
|
+
return data as ServiceDataResponse;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error('[24iMediaTimeApi] Failed to get time', e);
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
// Victor Magalhães <https://github.com/vhfmag>
|
|
28
28
|
// Dale Tan <https://github.com/hellatan>
|
|
29
29
|
// Priyanshu Rav <https://github.com/priyanshurav>
|
|
30
|
+
// Dmitry Semigradsky <https://github.com/Semigradsky>
|
|
30
31
|
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
31
32
|
// TypeScript Version: 2.8
|
|
32
33
|
|
|
@@ -1250,6 +1251,8 @@ declare namespace React {
|
|
|
1250
1251
|
target: EventTarget & T;
|
|
1251
1252
|
}
|
|
1252
1253
|
|
|
1254
|
+
export type ModifierKey = "Alt" | "AltGraph" | "CapsLock" | "Control" | "Fn" | "FnLock" | "Hyper" | "Meta" | "NumLock" | "ScrollLock" | "Shift" | "Super" | "Symbol" | "SymbolLock";
|
|
1255
|
+
|
|
1253
1256
|
interface KeyboardEvent<T = Element> extends UIEvent<T, NativeKeyboardEvent> {
|
|
1254
1257
|
altKey: boolean;
|
|
1255
1258
|
/** @deprecated */
|
|
@@ -1259,7 +1262,7 @@ declare namespace React {
|
|
|
1259
1262
|
/**
|
|
1260
1263
|
* See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
|
|
1261
1264
|
*/
|
|
1262
|
-
getModifierState(key:
|
|
1265
|
+
getModifierState(key: ModifierKey): boolean;
|
|
1263
1266
|
/**
|
|
1264
1267
|
* See the [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#named-key-attribute-values). for possible values
|
|
1265
1268
|
*/
|
|
@@ -1285,7 +1288,7 @@ declare namespace React {
|
|
|
1285
1288
|
/**
|
|
1286
1289
|
* See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
|
|
1287
1290
|
*/
|
|
1288
|
-
getModifierState(key:
|
|
1291
|
+
getModifierState(key: ModifierKey): boolean;
|
|
1289
1292
|
metaKey: boolean;
|
|
1290
1293
|
movementX: number;
|
|
1291
1294
|
movementY: number;
|
|
@@ -1304,7 +1307,7 @@ declare namespace React {
|
|
|
1304
1307
|
/**
|
|
1305
1308
|
* See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
|
|
1306
1309
|
*/
|
|
1307
|
-
getModifierState(key:
|
|
1310
|
+
getModifierState(key: ModifierKey): boolean;
|
|
1308
1311
|
metaKey: boolean;
|
|
1309
1312
|
shiftKey: boolean;
|
|
1310
1313
|
targetTouches: TouchList;
|
|
@@ -1459,6 +1462,8 @@ declare namespace React {
|
|
|
1459
1462
|
onProgressCapture?: ReactEventHandler<T> | undefined;
|
|
1460
1463
|
onRateChange?: ReactEventHandler<T> | undefined;
|
|
1461
1464
|
onRateChangeCapture?: ReactEventHandler<T> | undefined;
|
|
1465
|
+
onResize?: ReactEventHandler<T> | undefined;
|
|
1466
|
+
onResizeCapture?: ReactEventHandler<T> | undefined;
|
|
1462
1467
|
onSeeked?: ReactEventHandler<T> | undefined;
|
|
1463
1468
|
onSeekedCapture?: ReactEventHandler<T> | undefined;
|
|
1464
1469
|
onSeeking?: ReactEventHandler<T> | undefined;
|
|
@@ -1858,6 +1863,7 @@ declare namespace React {
|
|
|
1858
1863
|
hidden?: boolean | undefined;
|
|
1859
1864
|
id?: string | undefined;
|
|
1860
1865
|
lang?: string | undefined;
|
|
1866
|
+
nonce?: string | undefined;
|
|
1861
1867
|
placeholder?: string | undefined;
|
|
1862
1868
|
slot?: string | undefined;
|
|
1863
1869
|
spellCheck?: Booleanish | undefined;
|
|
@@ -1978,7 +1984,6 @@ declare namespace React {
|
|
|
1978
1984
|
multiple?: boolean | undefined;
|
|
1979
1985
|
muted?: boolean | undefined;
|
|
1980
1986
|
name?: string | undefined;
|
|
1981
|
-
nonce?: string | undefined;
|
|
1982
1987
|
noValidate?: boolean | undefined;
|
|
1983
1988
|
open?: boolean | undefined;
|
|
1984
1989
|
optimum?: number | undefined;
|
|
@@ -2391,7 +2396,6 @@ declare namespace React {
|
|
|
2391
2396
|
defer?: boolean | undefined;
|
|
2392
2397
|
integrity?: string | undefined;
|
|
2393
2398
|
noModule?: boolean | undefined;
|
|
2394
|
-
nonce?: string | undefined;
|
|
2395
2399
|
referrerPolicy?: HTMLAttributeReferrerPolicy | undefined;
|
|
2396
2400
|
src?: string | undefined;
|
|
2397
2401
|
type?: string | undefined;
|
|
@@ -2422,7 +2426,6 @@ declare namespace React {
|
|
|
2422
2426
|
|
|
2423
2427
|
interface StyleHTMLAttributes<T> extends HTMLAttributes<T> {
|
|
2424
2428
|
media?: string | undefined;
|
|
2425
|
-
nonce?: string | undefined;
|
|
2426
2429
|
scoped?: boolean | undefined;
|
|
2427
2430
|
type?: string | undefined;
|
|
2428
2431
|
}
|
|
@@ -4,11 +4,12 @@ const trimWithEllipsis = (
|
|
|
4
4
|
row: HTMLSpanElement | HTMLDivElement,
|
|
5
5
|
requestedNumberOfLines: number,
|
|
6
6
|
ellipsis = ELLIPSIS,
|
|
7
|
-
ending = false
|
|
7
|
+
ending = false,
|
|
8
8
|
) : void => {
|
|
9
9
|
if (!row.textContent) {
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
|
+
const { visibility } = row.style;
|
|
12
13
|
row.style.visibility = 'hidden';
|
|
13
14
|
const lineHeightString = window.getComputedStyle(row, null).getPropertyValue('line-height');
|
|
14
15
|
const lineHeight = parseInt(lineHeightString, 10);
|
|
@@ -27,6 +28,7 @@ const trimWithEllipsis = (
|
|
|
27
28
|
} while (numberOfLines <= requestedNumberOfLines
|
|
28
29
|
&& currentPosition < originalTextContent.length);
|
|
29
30
|
row.textContent = stringBuffer.substring(0, stringBuffer.length - 1) + ellipsis;
|
|
31
|
+
|
|
30
32
|
} else {
|
|
31
33
|
let currentPosition = originalTextContent.length - 1;
|
|
32
34
|
do {
|
|
@@ -39,21 +41,17 @@ const trimWithEllipsis = (
|
|
|
39
41
|
row.textContent = `${ellipsis}${stringBuffer.substring(1)}`;
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
|
-
row.style.visibility =
|
|
44
|
+
row.style.visibility = visibility;
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
export const trimWithLeadingEllipsis = (
|
|
46
48
|
row: HTMLSpanElement | HTMLDivElement,
|
|
47
49
|
requestedNumberOfLines: number,
|
|
48
50
|
ellipsis = ELLIPSIS,
|
|
49
|
-
) =>
|
|
50
|
-
return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
|
|
51
|
-
};
|
|
51
|
+
) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
|
|
52
52
|
|
|
53
53
|
export const trimWithEndingEllipsis = (
|
|
54
54
|
row: HTMLSpanElement | HTMLDivElement,
|
|
55
55
|
requestedNumberOfLines: number,
|
|
56
56
|
ellipsis = ELLIPSIS,
|
|
57
|
-
) =>
|
|
58
|
-
return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);
|
|
59
|
-
};
|
|
57
|
+
) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);
|