@24i/bigscreen-sdk 1.0.8-alpha.2196 → 1.0.8
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 +3 -2
- 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/grid/src/FirstOnlyGrid/FirstOnlyGrid.tsx +4 -2
- package/packages/icon/src/Icon.tsx +28 -5
- package/packages/player-ui/src/PlayerUI.tsx +21 -20
- package/packages/player-ui/src/Seekbar.tsx +13 -9
- package/packages/player-ui/src/Seeking.ts +48 -12
- package/packages/player-ui/src/mocks.ts +6 -2
- 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/utils/src/textUtils.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@24i/bigscreen-sdk",
|
|
3
|
-
"version": "1.0.8
|
|
3
|
+
"version": "1.0.8",
|
|
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",
|
|
@@ -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",
|
|
@@ -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
|
};
|
|
@@ -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,14 @@ 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
|
|
66
|
+
? className : ''} icon ${icon
|
|
67
|
+
? this.getClass(icon) : ''} ${initialIsHidden
|
|
68
|
+
? DISPLAY_NONE : ''}`}
|
|
46
69
|
/>
|
|
47
70
|
);
|
|
48
71
|
}
|
|
@@ -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 = () => {
|
|
@@ -137,27 +147,18 @@ export class PlayerUI extends Component<Props>
|
|
|
137
147
|
};
|
|
138
148
|
|
|
139
149
|
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;
|
|
150
|
+
if (this.seeking.isAutomaticallySeekingBackward) this.seeking.stopAutomaticSeek();
|
|
151
|
+
this.disableHiding();
|
|
152
|
+
this.seeking.automaticSeekForward();
|
|
153
|
+
return true;
|
|
149
154
|
};
|
|
150
155
|
|
|
151
156
|
onKeyRewind = () => {
|
|
152
|
-
if (this.seeking.isAutomaticallySeekingForward)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.seeking.startAutomaticSeekBackward();
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
157
|
+
if (this.seeking.isAutomaticallySeekingForward) this.seeking.stopAutomaticSeek();
|
|
158
|
+
this.disableHiding();
|
|
159
|
+
this.seeking.automaticSeekBackward();
|
|
160
|
+
return true;
|
|
161
|
+
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
getPlayer = () => {
|
|
@@ -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,11 +131,11 @@ 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
140
|
(seekTime != null &&
|
|
139
141
|
(seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
|
|
@@ -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,8 @@ export class Seekbar extends Component<Props> implements IFocusable {
|
|
|
163
165
|
this.updateSeekBar();
|
|
164
166
|
};
|
|
165
167
|
|
|
168
|
+
onStopAutomaticSeek = () => this.props.ui.enableHiding();
|
|
169
|
+
|
|
166
170
|
onTimeUpdate = ({ currentTime }: ITimeUpdateEvent) => {
|
|
167
171
|
const { ui } = this.props;
|
|
168
172
|
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(),
|
|
@@ -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
|
|
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
|
+
}
|
|
@@ -9,6 +9,7 @@ const trimWithEllipsis = (
|
|
|
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,7 +41,7 @@ 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 = (
|