@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.8-alpha.2196",
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-alpha.965",
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');
@@ -384,3 +384,7 @@ export const GAMEPAD_A = new Key({
384
384
  export const GAMEPAD_B = new Key({
385
385
  id: 'GAMEPAD_B',
386
386
  });
387
+
388
+ export const SUBTITLES = new Key({
389
+ id: 'SUBTITLES',
390
+ });
@@ -38,4 +38,5 @@ export const KEYMAP = {
38
38
  KP_9: 105,
39
39
  CHANNEL_UP: 33,
40
40
  CHANNEL_DOWN: 34,
41
+ SUBTITLES: 83,
41
42
  };
@@ -82,4 +82,6 @@ export type KeycodeKeyMap = {
82
82
 
83
83
  GAMEPAD_A: number,
84
84
  GAMEPAD_B: number,
85
+
86
+ SUBTITLES: number,
85
87
  };
@@ -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 - 1;
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 { addClass } from '@24i/bigscreen-sdk/utils/addClass';
3
- import { removeClass } from '@24i/bigscreen-sdk/utils/removeClass';
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={`icon ${icon ? this.getClass(icon) : ''}`}
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
- this.seeking.stopAutomaticSeek();
142
- return true;
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
- this.seeking.stopAutomaticSeek();
154
- return true;
155
- }
156
- if (!this.seeking.isAutomaticallySeekingBackward) {
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 = 20000;
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
- leftConsecutives = 0;
29
+ leftConsecutiveness = 0;
30
30
 
31
- rightConsecutives = 0;
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.leftConsecutives += 1;
119
+ this.leftConsecutiveness += 1;
118
120
  if (ui.seeking.isAutomaticallySeeking()) {
119
121
  ui.seeking.stopAutomaticSeek();
120
122
  }
121
- ui.seeking.seekBackward(this.leftConsecutives >= FAST_TRESHOLD ? FAST_TIME : SLOW_TIME);
123
+ ui.seeking.seekBackward(this.leftConsecutiveness >= FAST_TRESHOLD ? FAST_TIME : SLOW_TIME);
122
124
  this.leftConsecutiveTimeout.set(() => {
123
- this.leftConsecutives = 0;
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.rightConsecutives += 1;
134
+ this.rightConsecutiveness += 1;
133
135
  if (ui.seeking.isAutomaticallySeeking()) {
134
136
  ui.seeking.stopAutomaticSeek();
135
137
  }
136
- const isFastSeeking = this.rightConsecutives >= FAST_TRESHOLD;
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.rightConsecutives = 0;
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 = 20000;
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
- startAutomaticSeekForward() {
80
- this.stopAutomaticSeek();
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
- startAutomaticSeekBackward() {
89
- this.stopAutomaticSeek();
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
- startAutomaticSeekForward: jest.fn(),
21
- startAutomaticSeekBackward: jest.fn(),
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
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ Options,
3
+ ParamOptions,
4
+ ServiceDataResponse,
5
+ getTime,
6
+ } from './24iMediaTimeApi';
@@ -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 = (