@playkit-js/transcript 3.0.1 → 3.1.0-canary.0-0df319e

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.
@@ -5,10 +5,19 @@ import * as styles from './transcript.scss';
5
5
  import {Spinner} from '../spinner';
6
6
  import {Search} from '../search';
7
7
  import {CaptionList} from '../caption-list';
8
- import {HighlightedMap, CuePointData, PluginPositions} from '../../types';
8
+ import {HighlightedMap, CuePointData} from '../../types';
9
9
  import {CloseButton} from '../close-button';
10
10
  import {ErrorIcon} from './error-icon';
11
- const {ENTER, Space, Tab, Esc} = KalturaPlayer.ui.utils.KeyMap;
11
+
12
+ const {ENTER, SPACE, TAB, ESC} = KalturaPlayer.ui.utils.KeyMap;
13
+ const {withText, Text} = KalturaPlayer.ui.preacti18n;
14
+
15
+ const translates = {
16
+ autoScrollLabel: <Text id="transcript.auto_scroll">Enable auto scroll</Text>,
17
+ errorTitle: <Text id="transcript.whoops">Whoops!</Text>,
18
+ errorDescripton: <Text id="transcript.load_failed">Failed to load transcript</Text>,
19
+ skipTranscript: <Text id="transcript.skip_transcript">Skip transcript</Text>
20
+ };
12
21
 
13
22
  export interface TranscriptProps {
14
23
  onSeek(time: number): void;
@@ -26,8 +35,11 @@ export interface TranscriptProps {
26
35
  kitchenSinkActive: boolean;
27
36
  toggledWithEnter: boolean;
28
37
  highlightedMap: HighlightedMap;
29
- pluginMode: PluginPositions;
30
38
  onItemClicked: (n: number) => void;
39
+ autoScrollLabel?: string;
40
+ errorTitle?: string;
41
+ errorDescripton?: string;
42
+ skipTranscript?: string;
31
43
  }
32
44
 
33
45
  interface TranscriptState {
@@ -50,7 +62,7 @@ const initialSearch = {
50
62
 
51
63
  const SEARCHBAR_HEIGHT = 38; // height of search bar with margins
52
64
 
53
- export class Transcript extends Component<TranscriptProps, TranscriptState> {
65
+ export class TranscriptComponent extends Component<TranscriptProps, TranscriptState> {
54
66
  private _transcriptListRef: HTMLElement | null = null;
55
67
  private _captionListRef: any = null;
56
68
  private _skipTranscriptButtonRef: HTMLDivElement | null = null;
@@ -105,7 +117,8 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
105
117
  <div
106
118
  role="button"
107
119
  className={`${styles.autoscrollButton} ${isAutoScrollEnabled ? '' : styles.autoscrollButtonVisible}`}
108
- tabIndex={isAutoScrollEnabled ? -1 : 1}
120
+ tabIndex={isAutoScrollEnabled ? -1 : 0}
121
+ aria-label={this.props.autoScrollLabel}
109
122
  ref={node => {
110
123
  this._autoscrollButtonRef = node;
111
124
  }}>
@@ -201,8 +214,13 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
201
214
  );
202
215
  };
203
216
 
217
+ private _handleClick = (event: MouseEvent | KeyboardEvent) => {
218
+ event.preventDefault();
219
+ this._autoscrollButtonRef?.focus();
220
+ };
221
+
204
222
  private _handleKeyDown = (event: KeyboardEvent) => {
205
- if (event.keyCode === Tab && !event.shiftKey) {
223
+ if (event.keyCode === TAB && !event.shiftKey) {
206
224
  this.setState({
207
225
  isAutoScrollEnabled: false
208
226
  });
@@ -211,9 +229,8 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
211
229
  event.preventDefault();
212
230
  captionRef.focus();
213
231
  }
214
- } else if (event.keyCode === ENTER || event.keyCode === Space) {
215
- event.preventDefault();
216
- this._autoscrollButtonRef?.focus();
232
+ } else if (event.keyCode === ENTER || event.keyCode === SPACE) {
233
+ this._handleClick(event);
217
234
  }
218
235
  };
219
236
 
@@ -226,8 +243,9 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
226
243
  }}
227
244
  className={styles.skipTranscriptButton}
228
245
  onKeyDown={this._handleKeyDown}
229
- tabIndex={1}>
230
- Skip transcript
246
+ onClick={this._handleClick}
247
+ tabIndex={0}>
248
+ {this.props.skipTranscript}
231
249
  </div>
232
250
  );
233
251
  };
@@ -235,9 +253,6 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
235
253
  private _renderTranscript = () => {
236
254
  const {captions, hasError, onRetryLoad, showTime, videoDuration, highlightedMap} = this.props;
237
255
  const {isAutoScrollEnabled, searchMap, activeSearchIndex, searchLength} = this.state;
238
- if (!captions || !captions.length) {
239
- return null;
240
- }
241
256
 
242
257
  if (hasError) {
243
258
  return (
@@ -245,17 +260,16 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
245
260
  <div className={styles.errorIcon}>
246
261
  <ErrorIcon />
247
262
  </div>
248
- <p className={styles.errorMainText}>Whoops!</p>
249
- <p className={styles.errorDescriptionText}>
250
- Failed to get transcript, please try again
251
- <button className={styles.retryButton} onClick={onRetryLoad}>
252
- Retry
253
- </button>
254
- </p>
263
+ <p className={styles.errorMainText}>{this.props.errorTitle}</p>
264
+ <p className={styles.errorDescriptionText}>{this.props.errorDescripton}</p>
255
265
  </div>
256
266
  );
257
267
  }
258
268
 
269
+ if (!captions || !captions.length) {
270
+ return null;
271
+ }
272
+
259
273
  const captionProps = {
260
274
  showTime,
261
275
  searchLength,
@@ -351,14 +365,14 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
351
365
  };
352
366
 
353
367
  private _handleEsc = (event: KeyboardEvent) => {
354
- if (event.keyCode === Esc) {
368
+ if (event.keyCode === ESC) {
355
369
  this.props.onClose();
356
370
  }
357
371
  };
358
372
 
359
373
  render(props: TranscriptProps) {
360
- const {isLoading, kitchenSinkActive} = props;
361
-
374
+ const {isLoading, kitchenSinkActive, hasError} = props;
375
+ const renderTranscriptButtons = !(isLoading || hasError);
362
376
  return (
363
377
  <div
364
378
  className={`${styles.root} ${kitchenSinkActive ? '' : styles.hidden}`}
@@ -371,7 +385,7 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
371
385
 
372
386
  <CloseButton onClick={this.props.onClose} />
373
387
 
374
- {!isLoading && this._renderSkipTranscriptButton()}
388
+ {renderTranscriptButtons && this._renderSkipTranscriptButton()}
375
389
  <div
376
390
  className={styles.body}
377
391
  onScroll={this._onScroll}
@@ -380,9 +394,11 @@ export class Transcript extends Component<TranscriptProps, TranscriptState> {
380
394
  }}>
381
395
  {isLoading ? this._renderLoading() : this._renderTranscript()}
382
396
  </div>
383
- {this._renderScrollToButton()}
397
+ {renderTranscriptButtons && this._renderScrollToButton()}
384
398
  </div>
385
399
  </div>
386
400
  );
387
401
  }
388
402
  }
403
+
404
+ export const Transcript = withText(translates)(TranscriptComponent);
@@ -1,23 +1,19 @@
1
1
  import {h} from 'preact';
2
2
  import {OnClickEvent} from '@playkit-js/common';
3
3
  import {ui} from 'kaltura-player-js';
4
- import {ObjectUtils} from './utils';
4
+ import {UpperBarManager, SidePanelsManager} from '@playkit-js/ui-managers';
5
+ import {ObjectUtils, downloadContent, printContent} from './utils';
6
+ import {icons} from './components/icons';
5
7
  import {PluginButton} from './components/plugin-button/plugin-button';
6
8
  import {Transcript} from './components/transcript';
7
9
  import {getConfigValue, isBoolean, makePlainText, prepareCuePoint} from './utils';
8
- import {TranscriptConfig, PluginPositions, PluginStates, HighlightedMap, CuePointData, ItemTypes, CuePoint} from './types';
9
- import {DownloadPrintMenu, downloadContent, printContent} from './components/download-print-menu';
10
+ import {TranscriptConfig, PluginStates, HighlightedMap, CuePointData, ItemTypes, CuePoint} from './types';
10
11
 
11
12
  const {SidePanelModes, SidePanelPositions, ReservedPresetNames, ReservedPresetAreas} = ui;
12
- const {get} = ObjectUtils;
13
- const {Tooltip} = KalturaPlayer.ui.components;
14
13
  const {withText, Text} = KalturaPlayer.ui.preacti18n;
14
+ const {get} = ObjectUtils;
15
15
 
16
- const translates = () => ({
17
- printDownloadAreaLabel: <Text id="transcript.print_download_area_label">Download or print current transcript</Text>,
18
- printTranscript: <Text id="transcript.print_transcript">Print current transcript</Text>,
19
- downloadTranscript: <Text id="transcript.download_transcript">Download current transcript</Text>
20
- });
16
+ const LOADING_TIMEOUT = 10000;
21
17
 
22
18
  interface TimedMetadataEvent {
23
19
  payload: {
@@ -41,12 +37,14 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
41
37
  private _activeCuePointsMap: HighlightedMap = {};
42
38
  private _captionMap: Map<string, Array<CuePointData>> = new Map();
43
39
  private _isLoading = false;
40
+ private _loadingTimeoutId?: ReturnType<typeof setTimeout>;
44
41
  private _hasError = false;
45
42
  private _triggeredByKeyboard = false;
46
43
 
47
- private _removePopoverIcon: null | Function = null;
48
-
49
- private _transcriptPanel = null;
44
+ private _transcriptPanel = -1;
45
+ private _transcriptIcon = -1;
46
+ private _printIcon = -1;
47
+ private _downloadIcon = -1;
50
48
  private _pluginState: PluginStates | null = null;
51
49
 
52
50
  constructor(name: string, player: KalturaPlayerTypes.Player, config: TranscriptConfig) {
@@ -54,7 +52,11 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
54
52
  }
55
53
 
56
54
  get sidePanelsManager() {
57
- return this.player.getService('sidePanelsManager') as any;
55
+ return this.player.getService('sidePanelsManager') as SidePanelsManager | undefined;
56
+ }
57
+
58
+ get upperBarManager() {
59
+ return this.player.getService('upperBarManager') as UpperBarManager | undefined;
58
60
  }
59
61
 
60
62
  get cuePointManager() {
@@ -66,8 +68,8 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
66
68
  }
67
69
 
68
70
  loadMedia(): void {
69
- if (!this.cuePointManager || !this.sidePanelsManager) {
70
- this.logger.warn("kalturaCuepoints or sidePanelsManager haven't registered");
71
+ if (!this.cuePointManager || !this.sidePanelsManager || !this.upperBarManager) {
72
+ this.logger.warn("kalturaCuepoints, sidePanelsManager or upperBarManager haven't registered");
71
73
  return;
72
74
  }
73
75
  if (this.player.isLive()) {
@@ -79,31 +81,44 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
79
81
  }
80
82
 
81
83
  private _initListeners(): void {
82
- this.eventManager.listen(this.player, this.player.Event.FIRST_PLAYING, () => {
83
- if ((this.player.getTracks(this.player.Track.TEXT) || []).length) {
84
- // check if captions already added in TextTrack
85
- if (!this._captionMap.size) {
86
- // turn on loading animation till captions added to TextTrack
87
- this._isLoading = true;
88
- }
89
- this._addPopoverIcon();
84
+ this.eventManager.listenOnce(this.player, this.player.Event.TRACKS_CHANGED, () => {
85
+ if (this._getTextTracks().length) {
86
+ this.eventManager.listen(this.player, this.player.Event.TIMED_METADATA_CHANGE, this._onTimedMetadataChange);
87
+ this.eventManager.listen(this.player, this.player.Event.TIMED_METADATA_ADDED, this._onTimedMetadataAdded);
88
+ this.eventManager.listen(this.player, this.player.Event.TEXT_TRACK_CHANGED, this._handleLanguageChange);
89
+ this._addDownloadIcon();
90
+ this._addPrintIcon();
90
91
  this._addTranscriptItem();
92
+ this._initLoading();
91
93
  }
92
94
  });
93
- this.eventManager.listen(this.player, this.player.Event.TIMED_METADATA_CHANGE, this._onTimedMetadataChange);
94
- this.eventManager.listen(this.player, this.player.Event.TIMED_METADATA_ADDED, this._onTimedMetadataAdded);
95
- this.eventManager.listen(this.player, this.player.Event.TEXT_TRACK_CHANGED, this._handleLanguageChange);
96
95
  }
97
96
 
97
+ private _initLoading = () => {
98
+ clearTimeout(this._loadingTimeoutId);
99
+ this._isLoading = false;
100
+ this._hasError = false;
101
+ if (!this._captionMap.has(this._activeCaptionMapId)) {
102
+ // turn on loading animation till captions added to TextTrack
103
+ this._isLoading = true;
104
+ this._loadingTimeoutId = setTimeout(() => {
105
+ // display error slate
106
+ this._isLoading = false;
107
+ this._hasError = true;
108
+ this._updateTranscriptPanel();
109
+ }, LOADING_TIMEOUT);
110
+ }
111
+ this._updateTranscriptPanel();
112
+ };
113
+
98
114
  private _handleLanguageChange = () => {
99
115
  this._activeCaptionMapId = this._getCaptionMapId();
100
- this._isLoading = !this._captionMap.has(this._activeCaptionMapId);
101
- this._updateTranscriptPanel();
116
+ this._initLoading();
102
117
  };
103
118
 
104
119
  private _updateTranscriptPanel() {
105
120
  if (this._transcriptPanel) {
106
- this.sidePanelsManager.update(this._transcriptPanel);
121
+ this.sidePanelsManager?.update(this._transcriptPanel);
107
122
  }
108
123
  }
109
124
 
@@ -120,14 +135,15 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
120
135
  };
121
136
 
122
137
  private _onTimedMetadataChange = ({payload}: TimedMetadataEvent) => {
123
- const transcriptCuePoints: Array<CuePoint> = payload.cues.filter((cue: CuePoint) => {
124
- return cue.metadata.cuePointType === ItemTypes.Caption;
138
+ const transcriptCuePoints: Array<CuePoint> = payload.cues
139
+ .filter((cue: CuePoint) => {
140
+ return cue.metadata.cuePointType === ItemTypes.Caption;
125
141
  })
126
142
  .filter((cue, index, array) => {
127
143
  // filter out captions that has endTime eq to next caption startTime
128
144
  const nextCue = array[index + 1];
129
145
  return !nextCue || cue.endTime !== nextCue.startTime;
130
- });
146
+ });
131
147
  this._activeCuePointsMap = {};
132
148
  transcriptCuePoints.forEach(cue => {
133
149
  this._activeCuePointsMap[cue.id] = true;
@@ -139,11 +155,16 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
139
155
  this._activeCaptionMapId = this._getCaptionMapId();
140
156
  this._captionMap.set(this._activeCaptionMapId, newData);
141
157
  this._isLoading = false;
158
+ clearTimeout(this._loadingTimeoutId);
142
159
  this._updateTranscriptPanel();
143
160
  };
144
161
 
162
+ private _getTextTracks = () => {
163
+ return this.player.getTracks(this.player.Track.TEXT) || [];
164
+ };
165
+
145
166
  private _getCaptionMapId = (): string => {
146
- const allTextTracks = this.player.getTracks(this.player.Track.TEXT) || [];
167
+ const allTextTracks = this._getTextTracks();
147
168
  const activeTextTrack = allTextTracks.find(track => track.active);
148
169
  if (activeTextTrack?.language === 'off') {
149
170
  // use 1st captions from text-track list
@@ -152,42 +173,78 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
152
173
  return `${activeTextTrack?.language}-${activeTextTrack?.label}`;
153
174
  };
154
175
 
155
- private _handleCloseClick = () => {
156
- this.sidePanelsManager.deactivateItem(this._transcriptPanel);
157
- this._pluginState = PluginStates.CLOSED;
176
+ private _activatePlugin = () => {
177
+ this.ready.then(() => {
178
+ this.sidePanelsManager?.activateItem(this._transcriptPanel);
179
+ this._pluginState === PluginStates.OPENED;
180
+ this.upperBarManager?.update(this._transcriptIcon);
181
+ });
158
182
  };
159
183
 
160
- private _addPopoverIcon(): void {
161
- const {downloadDisabled, printDisabled} = this.config;
162
- if (this._removePopoverIcon) {
184
+ private _deactivatePlugin = () => {
185
+ this.ready.then(() => {
186
+ this.sidePanelsManager?.deactivateItem(this._transcriptPanel);
187
+ this._pluginState = PluginStates.CLOSED;
188
+ this.upperBarManager?.update(this._transcriptIcon);
189
+ });
190
+ };
191
+
192
+ private _isPluginActive = () => {
193
+ return this.sidePanelsManager!.isItemActive(this._transcriptPanel);
194
+ };
195
+
196
+ private _handleClickOnPluginIcon = (e: OnClickEvent, byKeyboard?: boolean) => {
197
+ if (this._isPluginActive()) {
198
+ this._triggeredByKeyboard = false;
199
+ this._deactivatePlugin();
200
+ } else {
201
+ this._triggeredByKeyboard = Boolean(byKeyboard);
202
+ this._activatePlugin();
203
+ }
204
+ };
205
+
206
+ private _addDownloadIcon(): void {
207
+ const {downloadDisabled} = this.config;
208
+ if (this._downloadIcon > 0 || downloadDisabled) {
163
209
  return;
164
210
  }
165
- this._removePopoverIcon = this.player.ui.addComponent({
166
- label: 'Download or print transcript',
167
- area: ReservedPresetAreas.TopBarRightControls,
168
- presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live],
169
- get: withText(translates)(({printDownloadAreaLabel, printTranscript, downloadTranscript}: Record<string, string>) => (
170
- <DownloadPrintMenu
171
- onDownload={this._handleDownload}
172
- onPrint={this._handlePrint}
173
- downloadDisabled={getConfigValue(downloadDisabled, isBoolean, false)}
174
- printDisabled={getConfigValue(printDisabled, isBoolean, false)}
175
- dropdownAriaLabel={printDownloadAreaLabel}
176
- printButtonAriaLabel={printTranscript}
177
- downloadButtonAriaLabel={downloadTranscript}
178
- />
211
+ const translate = {
212
+ label: <Text id="transcript.download_transcript">Download current transcript</Text>
213
+ };
214
+ this._downloadIcon = this.upperBarManager!.add({
215
+ label: translate.label as any,
216
+ svgIcon: {path: icons.DOWNLOAD_ICON, viewBox: `0 0 ${icons.BigSize} ${icons.BigSize}`},
217
+ onClick: this._handleDownload,
218
+ component: withText(translate)((props: {label: string}) => (
219
+ <PluginButton isActive={false} onClick={this._handleDownload} id={'download-transcript'} icon={icons.DOWNLOAD_ICON} label={props.label} />
179
220
  ))
180
- });
221
+ }) as number;
222
+ }
223
+ private _addPrintIcon(): void {
224
+ const {printDisabled} = this.config;
225
+ if (this._printIcon > 0 || printDisabled) {
226
+ return;
227
+ }
228
+ const translate = {
229
+ label: <Text id="transcript.print_transcript">Print current transcript</Text>
230
+ };
231
+ this._printIcon = this.upperBarManager!.add({
232
+ label: translate.label as any,
233
+ svgIcon: {path: icons.PRINT_ICON, viewBox: `0 0 ${icons.BigSize} ${icons.BigSize}`},
234
+ onClick: this._handlePrint,
235
+ component: withText(translate)((props: {label: string}) => (
236
+ <PluginButton isActive={false} onClick={this._handlePrint} id={'print-transcript'} icon={icons.PRINT_ICON} label={props.label} />
237
+ ))
238
+ }) as number;
181
239
  }
182
240
 
183
241
  private _addTranscriptItem(): void {
184
- const buttonLabel = 'Transcript';
185
- const {expandMode, position, expandOnFirstPlay} = this.config;
186
- const pluginMode: PluginPositions = [SidePanelPositions.RIGHT, SidePanelPositions.LEFT].includes(position)
187
- ? PluginPositions.VERTICAL
188
- : PluginPositions.HORIZONTAL;
189
- const {showTime, scrollOffset, searchDebounceTimeout, searchNextPrevDebounceTimeout} = this.config;
190
- this._transcriptPanel = this.sidePanelsManager.addItem({
242
+ if (this._transcriptPanel > 0) {
243
+ return;
244
+ }
245
+
246
+ const {expandMode, position, expandOnFirstPlay, showTime, scrollOffset, searchDebounceTimeout, searchNextPrevDebounceTimeout} = this.config;
247
+ this._transcriptPanel = this.sidePanelsManager!.add({
191
248
  label: 'Transcript',
192
249
  panelComponent: () => {
193
250
  return (
@@ -198,7 +255,6 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
198
255
  searchNextPrevDebounceTimeout={getConfigValue(searchNextPrevDebounceTimeout, Number.isInteger, 100)}
199
256
  highlightedMap={this._activeCuePointsMap}
200
257
  onSeek={this._seekTo}
201
- pluginMode={pluginMode}
202
258
  onItemClicked={this._seekTo}
203
259
  captions={this._data}
204
260
  isLoading={this._isLoading}
@@ -206,43 +262,36 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
206
262
  onRetryLoad={this._updateTranscriptPanel}
207
263
  currentTime={this.player.currentTime}
208
264
  videoDuration={this.player.duration}
209
- kitchenSinkActive={!!this.sidePanelsManager.isItemActive(this._transcriptPanel)}
265
+ kitchenSinkActive={this._isPluginActive()}
210
266
  toggledWithEnter={this._triggeredByKeyboard}
211
- onClose={this._handleCloseClick}
267
+ onClose={this._deactivatePlugin}
212
268
  />
213
269
  );
214
270
  },
215
- iconComponent: ({isActive}: {isActive: boolean}) => {
216
- return (
217
- <Tooltip label={buttonLabel} type="bottom">
218
- <PluginButton
219
- isActive={isActive}
220
- label={buttonLabel}
221
- onClick={(e: OnClickEvent, byKeyboard?: boolean) => {
222
- if (this.sidePanelsManager.isItemActive(this._transcriptPanel)) {
223
- this._triggeredByKeyboard = false;
224
- this._handleCloseClick();
225
- } else {
226
- this._triggeredByKeyboard = Boolean(byKeyboard);
227
- this.sidePanelsManager.activateItem(this._transcriptPanel);
228
- }
229
- }}
230
- />
231
- </Tooltip>
232
- );
233
- },
234
271
  presets: [ReservedPresetNames.Playback, ReservedPresetNames.Live, ReservedPresetNames.Ads],
235
272
  position: position,
236
273
  expandMode: expandMode === SidePanelModes.ALONGSIDE ? SidePanelModes.ALONGSIDE : SidePanelModes.OVER,
237
- onActivate: () => {
238
- this._pluginState = PluginStates.OPENED;
239
- }
240
- });
274
+ onDeactivate: this._deactivatePlugin
275
+ }) as number;
276
+ const translates = {
277
+ showTranscript: <Text id="transcript.show_plugin">Show Transcript</Text>,
278
+ hideTranscript: <Text id="transcript.hide_plugin">Hide Transcript</Text>
279
+ };
280
+ this._transcriptIcon = this.upperBarManager!.add({
281
+ label: 'Transcript',
282
+ svgIcon: {path: icons.PLUGIN_ICON, viewBox: `0 0 ${icons.BigSize} ${icons.BigSize}`},
283
+ onClick: this._handleClickOnPluginIcon as () => void,
284
+ component: withText(translates)((props: {showTranscript: string; hideTranscript: string}) => {
285
+ const isActive = this._isPluginActive();
286
+ const label = isActive ? props.hideTranscript : props.showTranscript;
287
+ return (
288
+ <PluginButton isActive={isActive} onClick={this._handleClickOnPluginIcon} id="transcript-icon" label={label} icon={icons.PLUGIN_ICON} />
289
+ );
290
+ })
291
+ }) as number;
241
292
 
242
293
  if ((expandOnFirstPlay && !this._pluginState) || this._pluginState === PluginStates.OPENED) {
243
- this.player.ready().then(() => {
244
- this.sidePanelsManager.activateItem(this._transcriptPanel);
245
- });
294
+ this._activatePlugin();
246
295
  }
247
296
  }
248
297
 
@@ -276,17 +325,24 @@ export class TranscriptPlugin extends KalturaPlayer.core.BasePlugin {
276
325
 
277
326
  reset(): void {
278
327
  this.eventManager.removeAll();
279
- if (this._removePopoverIcon) {
280
- this._removePopoverIcon();
281
- this._removePopoverIcon = null;
328
+ if (Math.max(this._transcriptPanel, this._transcriptIcon) > 0) {
329
+ this.sidePanelsManager?.remove(this._transcriptPanel);
330
+ this.upperBarManager!.remove(this._transcriptIcon);
331
+ this._transcriptPanel = -1;
332
+ this._transcriptIcon = -1;
282
333
  }
283
- if (this._transcriptPanel) {
284
- this.sidePanelsManager.removeItem(this._transcriptPanel);
285
- this._transcriptPanel = null;
334
+ if (this._printIcon > 0) {
335
+ this.upperBarManager!.remove(this._printIcon);
336
+ this._printIcon = -1;
337
+ }
338
+ if (this._downloadIcon > 0) {
339
+ this.upperBarManager!.remove(this._downloadIcon);
340
+ this._downloadIcon = -1;
286
341
  }
287
342
  this._captionMap = new Map();
288
343
  this._activeCaptionMapId = '';
289
344
  this._isLoading = false;
345
+ clearTimeout(this._loadingTimeoutId);
290
346
  this._hasError = false;
291
347
  this._triggeredByKeyboard = false;
292
348
  }
@@ -1,10 +1,3 @@
1
- export type OnClick = (e: KeyboardEvent | MouseEvent, byKeyboard?: boolean) => void;
2
-
3
- export enum PluginPositions {
4
- HORIZONTAL = 'horizontal',
5
- VERTICAL = 'vertical'
6
- }
7
-
8
1
  export enum PluginStates {
9
2
  OPENED = 'opened',
10
3
  CLOSED = 'closed'
@@ -1,4 +1,3 @@
1
1
  export * from './utils';
2
2
  export * from './object-utils';
3
3
  export * from './debounce';
4
- export * from './popover/popover';
@@ -84,3 +84,37 @@ export const prepareCuePoint = (cuePoint: CuePoint): CuePointData => {
84
84
 
85
85
  return itemData;
86
86
  };
87
+
88
+ export function downloadContent(content: string, name: string): void {
89
+ const blob = new Blob([content], {type: 'text/plain;charset=utf-8;'});
90
+ const anchor = document.createElement('a');
91
+ const {navigator} = window as any;
92
+
93
+ if (navigator.msSaveBlob) {
94
+ // IE
95
+ navigator.msSaveOrOpenBlob(blob, name);
96
+ return;
97
+ }
98
+ if (navigator.userAgent.search('Firefox') !== -1) {
99
+ // Firefox
100
+ anchor.style.display = 'none';
101
+ anchor.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
102
+ } else {
103
+ // Chrome
104
+ anchor.setAttribute('href', URL.createObjectURL(blob));
105
+ }
106
+ anchor.setAttribute('target', '_blank');
107
+ anchor.setAttribute('download', name);
108
+ anchor.click();
109
+ anchor.remove();
110
+ }
111
+ export function printContent(content: string): void {
112
+ const printWindow = window.open('', '', 'width=400,height=600');
113
+ if (printWindow) {
114
+ printWindow.document.write(content);
115
+ printWindow.document.close();
116
+ printWindow.focus();
117
+ printWindow.print();
118
+ printWindow.close();
119
+ }
120
+ }
@@ -1,23 +0,0 @@
1
- import {h} from 'preact';
2
-
3
- export const PrintIcon = () => (
4
- <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
5
- <g id="Icons/32/print" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
6
- <path
7
- d="M21,3 C22.0543618,3 22.9181651,3.81587779 22.9945143,4.85073766 L23,5 L23,9 L26,9 C27.1045695,9 28,9.8954305 28,11 L28,21 C28,22.1045695 27.1045695,23 26,23 L23,23 L23,28 C23,29.1045695 22.1045695,30 21,30 L11,30 C9.8954305,30 9,29.1045695 9,28 L9,23 L6,23 C4.8954305,23 4,22.1045695 4,21 L4,11 C4,9.8954305 4.8954305,9 6,9 L9,9 L9,5 C9,3.9456382 9.81587779,3.08183488 10.8507377,3.00548574 L11,3 L21,3 Z M21,19 L11,19 L11,28 L21,28 L21,19 Z M26,11 L6,11 L6,21 L9,21 L9,19 C8.44771525,19 8,18.5522847 8,18 C8,17.4477153 8.44771525,17 9,17 L23,17 C23.5522847,17 24,17.4477153 24,18 C24,18.5522847 23.5522847,19 23,19 L23,21 L26,21 L26,11 Z M21,5 L11,5 L11,9 L21,9 L21,5 Z"
8
- id="Combined-Shape"
9
- fill="#fff"></path>
10
- </g>
11
- </svg>
12
- );
13
-
14
- export const DownloadIcon = () => (
15
- <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
16
- <g id="Icons/32/Download" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
17
- <path
18
- d="M26,25 C26.5522847,25 27,25.4477153 27,26 C27,26.5522847 26.5522847,27 26,27 L26,27 L7,27 C6.44771525,27 6,26.5522847 6,26 C6,25.4477153 6.44771525,25 7,25 L7,25 Z M15.897,20.797 L15.817,20.73 L15.8163834,20.7298413 L8.34305882,13.7298413 C7.93997861,13.3522902 7.91928313,12.7194636 8.29683417,12.3163834 C8.67438521,11.9133032 9.30721188,11.8926077 9.71029209,12.2701587 L15.4996721,17.693 L15.5,6 C15.5,5.48716416 15.8860402,5.06449284 16.3833789,5.00672773 L16.5,5 C17.0522847,5 17.5,5.44771525 17.5,6 L17.4996721,17.694 L23.2951711,12.2699211 C23.6673663,11.9215418 24.2352038,11.9125649 24.6172049,12.230382 L24.7086128,12.3166371 C25.0860237,12.7198486 25.0651082,13.352668 24.6618968,13.7300789 L17.1833629,20.7300789 L17.1610165,20.7503813 L17.1610165,20.7503813 C17.1421868,20.7669999 17.1224361,20.7831339 17.102079,20.7985075 C17.0891381,20.8082894 17.0764369,20.8174134 17.0635772,20.826204 C17.0434306,20.8399634 17.0223437,20.8532674 17.0007451,20.8657864 C16.9872099,20.8736423 16.9734873,20.8811624 16.959633,20.8883367 L16.8877511,20.9220455 L16.8877511,20.9220455 C16.8756318,20.927087 16.8632234,20.9320132 16.8507409,20.9366814 C16.83028,20.9444208 16.8097352,20.9513578 16.7889039,20.9576336 C16.7705976,20.9630349 16.752126,20.968019 16.7335525,20.9724647 C16.6585039,20.9905214 16.5803589,21 16.5,21 C16.4170842,21 16.3365254,20.9899086 16.2594848,20.9708871 C16.2500284,20.9684434 16.2399293,20.9657886 16.2298654,20.9629733 C16.2028024,20.9554899 16.1769173,20.947049 16.1515197,20.9376057 C16.1370523,20.9321598 16.1223107,20.9262914 16.1076867,20.9200585 C16.0832011,20.9096448 16.0596143,20.8984375 16.036557,20.886357 C16.025923,20.8807972 16.0148138,20.8747205 16.0037984,20.8684173 C15.9792921,20.8543502 15.955966,20.8396537 15.9333153,20.8240474 L15.898,20.798 L15.897,20.797 Z M15.867,20.774 L15.888,20.79 L15.8735171,20.7794831 L15.8735171,20.7794831 L15.867,20.774 Z M15.817,20.73 L15.9035191,20.8027045 C15.8784859,20.7840722 15.8543541,20.7642966 15.831201,20.7434548 L15.817,20.73 Z"
19
- id="Combined-Shape"
20
- fill="#ffffff"></path>
21
- </g>
22
- </svg>
23
- );
@@ -1,44 +0,0 @@
1
- @import '../../variables.scss';
2
-
3
- .download-print-button {
4
- display: inline-block;
5
- width: 36px;
6
- height: 36px;
7
- color: $white-color;
8
- border: none;
9
- cursor: pointer;
10
- padding: 0;
11
- background: none;
12
- border-radius: 4px;
13
- outline-offset: 0 -1px;
14
- &:hover,
15
- &:active {
16
- border-radius: 4px;
17
- background-color: rgba(0, 0, 0, 0.8);
18
- }
19
- &:focus {
20
- outline: 1px solid $focus-color;
21
- }
22
- .icon {
23
- width: 100%;
24
- height: 100%;
25
- background-position: center;
26
- background-size: cover;
27
- }
28
- }
29
- .popover-menu-item {
30
- display: flex;
31
- align-items: center;
32
- padding: 9px 24px 9px 16px;
33
- white-space: nowrap;
34
- min-height: 30px;
35
- line-height: 18px;
36
- font-size: 15px;
37
- &:hover {
38
- cursor: pointer;
39
- background-color: $semigray-color;
40
- }
41
- &:focus {
42
- outline: 1px solid $focus-color;
43
- }
44
- }