@playkit-js/transcript 2.1.4 → 2.1.5-canary.16-1ac3e09

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +5 -5
  3. package/README.md +122 -24
  4. package/dist/1501adfdf5c835667ce7.svg +9 -0
  5. package/dist/301e7a199b2cd06c2edf.svg +9 -0
  6. package/dist/33bce27c0f546e80478c.svg +36 -0
  7. package/dist/6a4867d3d9170cc2a24d.svg +9 -0
  8. package/dist/73bab0af28a1c7aed29f.svg +9 -0
  9. package/dist/84087eb1cff72e5e6bd3.svg +9 -0
  10. package/dist/95d7192dc427afb678d0.svg +9 -0
  11. package/dist/cea5d6a7f050cbd199a1.svg +9 -0
  12. package/dist/d93f06ff32cdfcd016df.svg +9 -0
  13. package/dist/e5496f4c01207db44ffc.svg +9 -0
  14. package/dist/playkit-transcript.js +1 -31
  15. package/dist/playkit-transcript.js.map +1 -1
  16. package/package.json +53 -50
  17. package/src/components/a11y-wrapper/a11y-wrapper.ts +26 -0
  18. package/src/components/a11y-wrapper/index.ts +1 -0
  19. package/src/components/caption/caption.scss +1 -1
  20. package/src/components/caption/caption.tsx +118 -140
  21. package/src/components/caption/index.ts +1 -1
  22. package/src/components/caption-list/captionList.scss +8 -8
  23. package/src/components/caption-list/captionList.tsx +115 -117
  24. package/src/components/caption-list/index.ts +1 -1
  25. package/src/components/close-button/close-button.scss +11 -0
  26. package/src/components/close-button/index.tsx +23 -0
  27. package/src/components/download-print-menu/download-print-menu.scss +49 -48
  28. package/src/components/download-print-menu/download-print-menu.tsx +147 -125
  29. package/src/components/download-print-menu/index.ts +1 -1
  30. package/src/components/icons/index.ts +11 -0
  31. package/src/components/plugin-button/plugin-button.scss +26 -0
  32. package/src/components/plugin-button/plugin-button.tsx +29 -0
  33. package/src/components/popover-menu/index.ts +1 -1
  34. package/src/components/popover-menu/popover-menu.scss +3 -3
  35. package/src/components/popover-menu/popover-menu.tsx +25 -25
  36. package/src/components/search/index.ts +1 -1
  37. package/src/components/search/search.scss +1 -1
  38. package/src/components/search/search.tsx +137 -144
  39. package/src/components/spinner/index.ts +1 -1
  40. package/src/components/spinner/spinner.scss +58 -50
  41. package/src/components/spinner/spinner.tsx +9 -9
  42. package/src/components/transcript/index.ts +1 -1
  43. package/src/components/transcript/transcript.scss +9 -33
  44. package/src/components/transcript/transcript.tsx +333 -454
  45. package/src/global.d.ts +6 -6
  46. package/src/index.ts +13 -1
  47. package/src/transcript-plugin.scss +3 -3
  48. package/src/transcript-plugin.tsx +210 -391
  49. package/src/types/index.ts +3 -0
  50. package/src/types/transcript-config.ts +11 -0
  51. package/src/types/transcript-item-data.ts +29 -0
  52. package/src/types/types-ui.ts +11 -0
  53. package/src/utils/debounce.ts +36 -0
  54. package/src/utils/index.ts +4 -0
  55. package/src/utils/object-utils.ts +34 -0
  56. package/src/utils/popover/popover.scss +30 -0
  57. package/src/utils/popover/popover.tsx +178 -0
  58. package/src/utils/utils.ts +86 -0
  59. package/src/variables.scss +14 -0
  60. package/src/assets/close.svg +0 -10
  61. package/src/utils.ts +0 -192
@@ -0,0 +1,11 @@
1
+ export interface TranscriptConfig {
2
+ expandOnFirstPlay: boolean;
3
+ showTime: boolean;
4
+ position: string;
5
+ scrollOffset: number; // distance between top border of transcript container and active caption on auto-scroll
6
+ searchDebounceTimeout: number; // debounce on search
7
+ searchNextPrevDebounceTimeout: number; // debounce on jump between prev/next search result
8
+ downloadDisabled: boolean; // disable download menu
9
+ printDisabled: boolean; // disable print menu
10
+ expandMode: string; // over or pushing the player
11
+ }
@@ -0,0 +1,29 @@
1
+ export type HighlightedMap = Record<string, boolean>;
2
+
3
+ export interface RawItemData {
4
+ content?: Array<{text: string}>;
5
+ cuePointType?: string;
6
+ label?: string;
7
+ language?: string;
8
+ text?: string;
9
+ }
10
+
11
+ export interface CuePointData {
12
+ id: string;
13
+ startTime: number;
14
+ displayTime?: number;
15
+ text: string;
16
+ }
17
+
18
+ export enum ItemTypes {
19
+ Caption = 'kalturaCaption'
20
+ }
21
+
22
+ export interface CuePoint {
23
+ startTime: number;
24
+ endTime?: number;
25
+ id: string;
26
+ type: string;
27
+ metadata: RawItemData;
28
+ text?: string;
29
+ }
@@ -0,0 +1,11 @@
1
+ export type OnClick = (e: KeyboardEvent | MouseEvent, byKeyboard?: boolean) => void;
2
+
3
+ export enum PluginPositions {
4
+ HORIZONTAL = 'horizontal',
5
+ VERTICAL = 'vertical'
6
+ }
7
+
8
+ export enum PluginStates {
9
+ OPENED = 'opened',
10
+ CLOSED = 'closed'
11
+ }
@@ -0,0 +1,36 @@
1
+ type Procedure = (...args: any[]) => void;
2
+
3
+ type Options = {
4
+ isImmediate: boolean;
5
+ };
6
+
7
+ export function debounce<F extends Procedure>(
8
+ func: F,
9
+ waitMilliseconds = 50,
10
+ options: Options = {
11
+ isImmediate: false
12
+ }
13
+ ): F {
14
+ let timeoutId: any;
15
+
16
+ return function (this: any, ...args: any[]) {
17
+ const doLater = () => {
18
+ timeoutId = undefined;
19
+ if (!options.isImmediate) {
20
+ func.apply(this, args);
21
+ }
22
+ };
23
+
24
+ const shouldCallNow = options.isImmediate && timeoutId === undefined;
25
+
26
+ if (timeoutId !== undefined) {
27
+ clearTimeout(timeoutId);
28
+ }
29
+
30
+ timeoutId = setTimeout(doLater, waitMilliseconds);
31
+
32
+ if (shouldCallNow) {
33
+ func.apply(this, args);
34
+ }
35
+ } as any;
36
+ }
@@ -0,0 +1,4 @@
1
+ export * from './utils';
2
+ export * from './object-utils';
3
+ export * from './debounce';
4
+ export * from './popover/popover';
@@ -0,0 +1,34 @@
1
+ export class ObjectUtils {
2
+ public static get(obj: Record<string, any>, path: string, defaultValue: any): any {
3
+ function stringToPath(path: string) {
4
+ const output: any = [];
5
+ // Split to an array with dot notation
6
+ path.split('.').forEach(item => {
7
+ // Split to an array with bracket notation
8
+ item.split(/\[([^}]+)\]/g).forEach(key => {
9
+ // Push to the new array
10
+ if (key.length > 0) {
11
+ output.push(key);
12
+ }
13
+ });
14
+ });
15
+ return output;
16
+ }
17
+
18
+ // Get the path as an array
19
+ const pathArray = stringToPath(path);
20
+
21
+ let current = obj;
22
+
23
+ // For each item in the path, dig into the object
24
+ for (let i = 0; i < pathArray.length; i++) {
25
+ // If the item isn't found, return the default (or null)
26
+ if (!current[pathArray[i]]) return defaultValue;
27
+
28
+ // Otherwise, update the current value
29
+ current = current[pathArray[i]];
30
+ }
31
+
32
+ return current;
33
+ }
34
+ }
@@ -0,0 +1,30 @@
1
+ .popover-container {
2
+ position: relative;
3
+ .popover-component {
4
+ background-color: #222222;
5
+ border-radius: 4px;
6
+ position: absolute;
7
+ right: 0px;
8
+ font-size: 15px;
9
+ display: block;
10
+ &.visible {
11
+ visibility: visible;
12
+ opacity: 1;
13
+ z-index: 10;
14
+ }
15
+ &.top {
16
+ bottom: 100%;
17
+ margin-bottom: 6px;
18
+ }
19
+ &.bottom {
20
+ top: 100%;
21
+ margin-top: 6px;
22
+ }
23
+ &.right {
24
+ left: 0px;
25
+ }
26
+ &.left {
27
+ right: 0px;
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,178 @@
1
+ import {h, Component, ComponentChild} from 'preact';
2
+ import * as styles from './popover.scss';
3
+
4
+ const {ENTER, Esc} = KalturaPlayer.ui.utils.KeyMap;
5
+
6
+ export enum PopoverVerticalPositions {
7
+ Top = 'top',
8
+ Bottom = 'bottom'
9
+ }
10
+ export enum PopoverHorizontalPositions {
11
+ Left = 'left',
12
+ Right = 'right'
13
+ }
14
+ export enum PopoverTriggerMode {
15
+ Click = 'click',
16
+ Hover = 'hover'
17
+ }
18
+
19
+ const CLOSE_ON_HOVER_DELAY = 500;
20
+
21
+ const defaultProps = {
22
+ verticalPosition: PopoverVerticalPositions.Top,
23
+ horizontalPosition: PopoverHorizontalPositions.Left,
24
+ triggerMode: PopoverTriggerMode.Click,
25
+ className: 'popover',
26
+ closeOnEsc: true,
27
+ closeOnClick: true
28
+ };
29
+
30
+ interface PopoverProps {
31
+ onClose?: () => void;
32
+ onOpen?: () => void;
33
+ closeOnClick: boolean;
34
+ closeOnEsc: boolean;
35
+ verticalPosition: PopoverVerticalPositions;
36
+ horizontalPosition: PopoverHorizontalPositions;
37
+ className: string;
38
+ triggerMode: PopoverTriggerMode;
39
+ content: ComponentChild;
40
+ children: ComponentChild;
41
+ }
42
+
43
+ interface PopoverState {
44
+ open: boolean;
45
+ }
46
+
47
+ export class Popover extends Component<PopoverProps, PopoverState> {
48
+ private _closeTimeout: any = null;
49
+ private _controlElement: HTMLDivElement | null = null;
50
+ static defaultProps = {
51
+ ...defaultProps
52
+ };
53
+ state = {
54
+ open: false
55
+ };
56
+
57
+ componentWillUnmount() {
58
+ this._removeListeners();
59
+ }
60
+
61
+ private _clearTimeout = () => {
62
+ clearTimeout(this._closeTimeout);
63
+ this._closeTimeout = null;
64
+ };
65
+
66
+ private _handleMouseEvent = (event: MouseEvent) => {
67
+ if (!this._controlElement?.contains(event.target as Node | null) && this.props.closeOnClick) {
68
+ this._closePopover();
69
+ }
70
+ };
71
+
72
+ private _handleKeyboardEvent = (event: KeyboardEvent) => {
73
+ if (this._controlElement?.contains(event.target as Node | null) && event.keyCode === ENTER) {
74
+ // handle Enter key pressed on Target icon to prevent triggering of _closePopover twice
75
+ return;
76
+ }
77
+ if ((this.props.closeOnEsc && event.keyCode === Esc) || event.keyCode === ENTER) {
78
+ // handle if ESC or Enter button presesd
79
+ this._closePopover();
80
+ }
81
+ };
82
+
83
+ private _openPopover = () => {
84
+ const {onOpen} = this.props;
85
+ this._clearTimeout();
86
+ this.setState({open: true}, () => {
87
+ this._addListeners();
88
+ if (onOpen) {
89
+ onOpen();
90
+ }
91
+ });
92
+ };
93
+
94
+ private _closePopover = () => {
95
+ const {onClose} = this.props;
96
+ this._clearTimeout();
97
+ this.setState({open: false}, () => {
98
+ this._removeListeners();
99
+ if (onClose) {
100
+ onClose();
101
+ }
102
+ });
103
+ };
104
+
105
+ private _togglePopover = (e: MouseEvent | KeyboardEvent) => {
106
+ if (this.state.open) {
107
+ this._closePopover();
108
+ } else {
109
+ this._openPopover();
110
+ }
111
+ };
112
+ private _handleMouseEnter = () => {
113
+ if (!this.state.open) {
114
+ this._openPopover();
115
+ }
116
+ };
117
+ private _handleMouseLeave = () => {
118
+ this._closeTimeout = setTimeout(this._closePopover, CLOSE_ON_HOVER_DELAY);
119
+ };
120
+ private _handleHoverOnPopover = () => {
121
+ if (this.state.open && this._closeTimeout) {
122
+ this._clearTimeout();
123
+ } else {
124
+ this._closePopover();
125
+ }
126
+ };
127
+ private _addListeners = () => {
128
+ document.addEventListener('click', this._handleMouseEvent);
129
+ document.addEventListener('keydown', this._handleKeyboardEvent);
130
+ };
131
+ private _removeListeners = () => {
132
+ document.removeEventListener('click', this._handleMouseEvent);
133
+ document.removeEventListener('keydown', this._handleKeyboardEvent);
134
+ };
135
+ private _getHoverEvents = () => {
136
+ if (this.props.triggerMode === PopoverTriggerMode.Hover) {
137
+ return {
138
+ targetEvents: {
139
+ onMouseEnter: this._handleMouseEnter,
140
+ onMouseLeave: this._handleMouseLeave
141
+ },
142
+ popoverEvents: {
143
+ onMouseEnter: this._handleHoverOnPopover,
144
+ onMouseLeave: this._handleHoverOnPopover
145
+ }
146
+ };
147
+ }
148
+ return {targetEvents: {onClick: this._togglePopover}, popoverEvents: {}};
149
+ };
150
+ render(props: PopoverProps) {
151
+ if (!props.content || !props.children) {
152
+ return null;
153
+ }
154
+ const {targetEvents, popoverEvents} = this._getHoverEvents();
155
+ return (
156
+ <div className={styles.popoverContainer}>
157
+ <div
158
+ className="popover-anchor-container"
159
+ ref={node => {
160
+ this._controlElement = node;
161
+ }}
162
+ {...targetEvents}
163
+ >
164
+ {props.children}
165
+ </div>
166
+ {this.state.open && (
167
+ <div
168
+ aria-expanded="true"
169
+ className={[props.className, styles.popoverComponent, styles[props.verticalPosition], styles[props.horizontalPosition]].join(' ')}
170
+ {...popoverEvents}
171
+ >
172
+ {props.content}
173
+ </div>
174
+ )}
175
+ </div>
176
+ );
177
+ }
178
+ }
@@ -0,0 +1,86 @@
1
+ export const HOUR = 3600; // seconds in 1 hour
2
+
3
+ export const toSeconds = (val: any, vtt = false): number => {
4
+ const regex = vtt ? /(\d+):(\d{2}):(\d{2}).(\d{2,3}|\d{2})/ : /(\d+):(\d{2}):(\d{2}),((\d{2,3}|\d{2}|\d{1}))?/;
5
+ const parts: any | null[] = regex.exec(val);
6
+ if (parts === null) {
7
+ return 0;
8
+ }
9
+
10
+ for (var i = 1; i < 5; i++) {
11
+ parts[i] = parseInt(parts[i], 10);
12
+ if (isNaN(parts[i])) {
13
+ parts[i] = 0;
14
+ }
15
+ }
16
+ // hours + minutes + seconds + ms
17
+ return parts[1] * HOUR + parts[2] * 60 + parts[3] + parts[4] / 1000;
18
+ };
19
+
20
+ const pad = (number: number) => {
21
+ if (number < 10) {
22
+ return `0${number}`;
23
+ }
24
+ return number;
25
+ };
26
+
27
+ const makeHoursString = (seconds: number): string => {
28
+ const hours = Math.floor(seconds / HOUR);
29
+ if (hours >= 1) {
30
+ return `${hours}:`;
31
+ }
32
+ return '';
33
+ };
34
+
35
+ export const secontsToTime = (seconds: number, longerThanHour: boolean): string => {
36
+ const date = new Date(0);
37
+ date.setSeconds(seconds);
38
+ return `${longerThanHour ? makeHoursString(seconds) : ''}${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}`;
39
+ };
40
+
41
+ export function getConfigValue(value: any, condition: (value: any) => boolean, defaultValue: any) {
42
+ let result = defaultValue;
43
+ if (typeof condition === 'function' && condition(value)) {
44
+ result = value;
45
+ }
46
+ return result;
47
+ }
48
+
49
+ export function isBoolean(value: any) {
50
+ return typeof value === 'boolean';
51
+ }
52
+
53
+ export function makePlainText(captions: Array<CuePointData>): string {
54
+ return captions.reduce((acc: string, next: CuePointData) => {
55
+ return `${acc} ${next.text}`;
56
+ }, '');
57
+ }
58
+
59
+ import {CuePointData, CuePoint} from '../types';
60
+
61
+ const {toHHMMSS} = KalturaPlayer.ui.utils;
62
+ const MAX_CHARACTERS = 77;
63
+
64
+ export const decodeString = (content: any): string => {
65
+ if (typeof content !== 'string') {
66
+ return content;
67
+ }
68
+ return content
69
+ .replace(/&lt;/gi, '<')
70
+ .replace(/&gt;/gi, '>')
71
+ .replace(/&nbsp;/gi, ' ')
72
+ .replace(/&amp;/gi, '&')
73
+ .replace(/&quot;/gi, '"');
74
+ };
75
+
76
+ export const prepareCuePoint = (cuePoint: CuePoint): CuePointData => {
77
+ const {metadata} = cuePoint;
78
+ const itemData: CuePointData = {
79
+ id: cuePoint.id,
80
+ startTime: cuePoint.startTime,
81
+ displayTime: Math.floor(cuePoint.startTime),
82
+ text: decodeString(metadata.text)
83
+ };
84
+
85
+ return itemData;
86
+ };
@@ -1,6 +1,7 @@
1
1
  $main-color: #01accd;
2
2
  $white-color: #ffffff;
3
3
  $gray-color: #cccccc;
4
+ $darken-gray: #444444;
4
5
  $darkgray-color: #333333;
5
6
  $semigray-color: #666666;
6
7
  $lightgray-color: #999999;
@@ -12,3 +13,16 @@ $text-selection-color: #5b80a7;
12
13
  $main-color-darken: darken($main-color, 10%);
13
14
  $root-background: #000000a1;
14
15
  $focus-color: #00cbff;
16
+
17
+ @mixin plugin-scrollbar {
18
+ &::-webkit-scrollbar {
19
+ width: 4px;
20
+ }
21
+ &::-webkit-scrollbar-track {
22
+ background: rgba(33, 33, 33, 0.9);
23
+ }
24
+ &::-webkit-scrollbar-thumb {
25
+ border-radius: 3px;
26
+ background-color: rgba(255, 255, 255, 0.3);
27
+ }
28
+ }
@@ -1,10 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
- <!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
4
- <title>Icons/32/Close</title>
5
- <desc>Created with Sketch.</desc>
6
- <g id="Icons/32/Close" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
7
- <rect id="Bounds" x="0" y="0" width="32" height="32"></rect>
8
- <path d="M17.9113162,16 L24.6072325,9.30408374 C25.1313645,8.77995172 25.1287183,7.92687249 24.6009229,7.3990771 C24.0694478,6.86760201 23.220227,6.86845682 22.6959163,7.39276754 L16,14.0886838 L9.30408374,7.39276754 C8.77995172,6.86863552 7.92687249,6.8712817 7.3990771,7.3990771 C6.86760201,7.93055219 6.86845682,8.77977302 7.39276754,9.30408374 L14.0886838,16 L7.39276754,22.6959163 C6.86863552,23.2200483 6.8712817,24.0731275 7.3990771,24.6009229 C7.93055219,25.132398 8.77977302,25.1315432 9.30408374,24.6072325 L16,17.9113162 L22.6959163,24.6072325 C23.2200483,25.1313645 24.0731275,25.1287183 24.6009229,24.6009229 C25.132398,24.0694478 25.1315432,23.220227 24.6072325,22.6959163 L17.9113162,16 Z" id="Close" fill="rgba(255,255,255,0.8)"></path>
9
- </g>
10
- </svg>
package/src/utils.ts DELETED
@@ -1,192 +0,0 @@
1
- import { xml2js } from "xml-js";
2
- import { Cuepoint } from "@playkit-js-contrib/common";
3
- import { KalturaRequest, KalturaRequestArgs } from 'kaltura-typescript-client/api/kaltura-request';
4
- import { KalturaObjectMetadata } from 'kaltura-typescript-client/api/kaltura-object-base';
5
-
6
- export const HOUR = 3600; // seconds in 1 hour
7
-
8
- export interface CaptionItem extends Cuepoint {
9
- text: string;
10
- id: number;
11
- }
12
-
13
- export const toSeconds = (val: any, vtt = false): number => {
14
- const regex = vtt
15
- ? /(\d+):(\d{2}):(\d{2}).(\d{2,3}|\d{2})/
16
- : /(\d+):(\d{2}):(\d{2}),((\d{2,3}|\d{2}|\d{1}))?/;
17
- const parts: any | null[] = regex.exec(val);
18
- if (parts === null) {
19
- return 0;
20
- }
21
-
22
- for (var i = 1; i < 5; i++) {
23
- parts[i] = parseInt(parts[i], 10);
24
- if (isNaN(parts[i])) {
25
- parts[i] = 0;
26
- }
27
- }
28
- // hours + minutes + seconds + ms
29
- return parts[1] * HOUR + parts[2] * 60 + parts[3] + parts[4] / 1000;
30
- };
31
-
32
- export const getCaptionsByFormat = (captions: any, captionsFormat: string): CaptionItem[] => {
33
- const format = captionsFormat.toLowerCase();
34
- // const a = fromSrt(captions);
35
- switch (format) {
36
- case "1":
37
- return fromSrt(captions);
38
- case "2":
39
- // strip 'span' from the p tags, they break the parser and no time (now) to write a parser
40
- captions = captions
41
- .replace(/<span[^>]+\?>/i, "")
42
- .replace(/<\/span>/i, "")
43
- .replace(/<br><\/br>/g, " ") // remove <br></br>'s as it breaks the parser too.
44
- .replace(/<[//]{0,1}(SPAN|span)[^><]*>/g, "");
45
- return TTML2Obj(captions);
46
- case "3":
47
- return fromVtt(captions);
48
- default:
49
- return [];
50
- }
51
- };
52
-
53
- const fromVtt = (data: string): CaptionItem[] => {
54
- let source: string | string[] = data.replace(/\r/g, "");
55
- // remove webvtt metadata first line/s if exist
56
- // the 200 is optimization - no point break all string to lines and join them together
57
- let linesBefore = source.substring(0, 200);
58
- const linesAfter = source.substring(200);
59
- const lines = linesBefore.split(/\r?\n/);
60
- for (let i = 0; i < 4 ; i++) {
61
- // find first numeric char
62
- if (!/^\d+$/.test(linesBefore[0].charAt(0))) {
63
- // first char in the line is not numeric - this line needs to be removed
64
- lines.shift()
65
- }
66
- }
67
- // re-join
68
- linesBefore = lines.join("\n");
69
- source = linesBefore + linesAfter;
70
- // parse
71
- const regex = /(\d+)?\n?(\d{2}:\d{2}:\d{2}[,.]\d{3}) --> (\d{2}:\d{2}:\d{2}[,.]\d{3}).*\n/g;
72
- source = source.split(regex);
73
- source.shift();
74
- const result = [];
75
- for (let i = 0; i < source.length; i += 4) {
76
- result.push({
77
- id: result.length + 1,
78
- startTime: toSeconds(source[i + 1].trim(), true),
79
- endTime: toSeconds(source[i + 2].trim(), true),
80
- text: source[i + 3].trim()
81
- });
82
- }
83
- return result;
84
- };
85
-
86
- const fromSrt = (data: string): CaptionItem[] => {
87
- let source: string | string[] = data.replace(/\r/g, "");
88
- const regex = /(\d+)?\n?(\d{2}:\d{2}:\d{2}[,.]\d{3}) --> (\d{2}:\d{2}:\d{2}[,.]\d{3}).*\n/g;
89
- source = source.split(regex);
90
- source.shift();
91
- const result = [];
92
- for (let i = 0; i < source.length; i += 4) {
93
- result.push({
94
- id: result.length + 1,
95
- startTime: toSeconds(source[i + 1].trim()),
96
- endTime: toSeconds(source[i + 2].trim()),
97
- text: source[i + 3].trim()
98
- });
99
- }
100
- return result;
101
- };
102
-
103
- export const TTML2Obj = (ttml: any): CaptionItem[] => {
104
- const data: any = xml2js(ttml, { compact: true });
105
- // need only captions for showing. they located in tt.body.div.p.
106
- const chapters = data.tt.body.div.p;
107
- const correctData = chapters.map((item: any, index: number) => {
108
- const { begin, end, ...otherAttributes } = item._attributes;
109
- // convert time to 00:00:00.000 to 00:00:00,000
110
- const endTime = end.replace(/\./g, ",");
111
- const startTime = begin.replace(/\./g, ",");
112
- const prepareObj = {
113
- id: index + 1,
114
- endTime: toSeconds(endTime),
115
- startTime: toSeconds(startTime),
116
- text: (Array.isArray(item._text) ? item._text.join(" ") : item._text) || ""
117
- // all non-required
118
- // otherAttributes: otherAttributes
119
- };
120
- return prepareObj;
121
- });
122
- return correctData;
123
- };
124
-
125
- const pad = (number: number) => {
126
- if (number < 10) {
127
- return `0${number}`;
128
- }
129
- return number;
130
- };
131
-
132
- const makeHoursString = (seconds: number): string => {
133
- const hours = Math.floor(seconds / HOUR)
134
- if (hours >= 1) {
135
- return `${hours}:`
136
- }
137
- return "";
138
- };
139
-
140
- export const secontsToTime = (seconds: number, longerThanHour: boolean): string => {
141
- const date = new Date(0);
142
- date.setSeconds(seconds);
143
- return `${longerThanHour ? makeHoursString(seconds) : ""}${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}`;
144
- };
145
-
146
- export function getConfigValue(value: any, condition: (value: any) => boolean, defaultValue: any) {
147
- let result = defaultValue;
148
- if (typeof condition === "function" && condition(value)) {
149
- result = value;
150
- }
151
- return result;
152
- }
153
-
154
- export function isBoolean(value: any) {
155
- return typeof value === "boolean";
156
- }
157
-
158
- export function makePlainText(captions: Array<CaptionItem>): string {
159
- return captions.reduce((acc: string, next: CaptionItem) => {
160
- return `${acc} ${next.text}`;
161
- }, "");
162
- }
163
-
164
-
165
- // KalturaClient uses custom CaptionAssetServeAction method,
166
- // once KalturaFileRequest is fixed remove custom CaptionAssetServeAction and use
167
- // CaptionAssetServeAction from "kaltura-typescript-client/api/types/CaptionAssetServeAction"
168
- interface CaptionAssetServeActionArgs extends KalturaRequestArgs {
169
- captionAssetId : string;
170
- }
171
- export class CaptionAssetServeAction extends KalturaRequest<{url: string}> {
172
- captionAssetId: any;
173
- constructor(data: CaptionAssetServeActionArgs)
174
- {
175
- super(data, { responseType: 'v', responseSubType: '', responseConstructor: null } as any);
176
- }
177
- protected _getMetadata(): KalturaObjectMetadata
178
- {
179
- const result = super._getMetadata();
180
- Object.assign(
181
- result.properties,
182
- {
183
- service : { type : 'c', default : 'caption_captionasset' },
184
- action : { type : 'c', default : 'serve' },
185
- captionAssetId : { type : 's' }
186
- }
187
- );
188
- return result;
189
- }
190
- }
191
-
192
-