@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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +5 -5
- package/README.md +122 -24
- package/dist/1501adfdf5c835667ce7.svg +9 -0
- package/dist/301e7a199b2cd06c2edf.svg +9 -0
- package/dist/33bce27c0f546e80478c.svg +36 -0
- package/dist/6a4867d3d9170cc2a24d.svg +9 -0
- package/dist/73bab0af28a1c7aed29f.svg +9 -0
- package/dist/84087eb1cff72e5e6bd3.svg +9 -0
- package/dist/95d7192dc427afb678d0.svg +9 -0
- package/dist/cea5d6a7f050cbd199a1.svg +9 -0
- package/dist/d93f06ff32cdfcd016df.svg +9 -0
- package/dist/e5496f4c01207db44ffc.svg +9 -0
- package/dist/playkit-transcript.js +1 -31
- package/dist/playkit-transcript.js.map +1 -1
- package/package.json +53 -50
- package/src/components/a11y-wrapper/a11y-wrapper.ts +26 -0
- package/src/components/a11y-wrapper/index.ts +1 -0
- package/src/components/caption/caption.scss +1 -1
- package/src/components/caption/caption.tsx +118 -140
- package/src/components/caption/index.ts +1 -1
- package/src/components/caption-list/captionList.scss +8 -8
- package/src/components/caption-list/captionList.tsx +115 -117
- package/src/components/caption-list/index.ts +1 -1
- package/src/components/close-button/close-button.scss +11 -0
- package/src/components/close-button/index.tsx +23 -0
- package/src/components/download-print-menu/download-print-menu.scss +49 -48
- package/src/components/download-print-menu/download-print-menu.tsx +147 -125
- package/src/components/download-print-menu/index.ts +1 -1
- package/src/components/icons/index.ts +11 -0
- package/src/components/plugin-button/plugin-button.scss +26 -0
- package/src/components/plugin-button/plugin-button.tsx +29 -0
- package/src/components/popover-menu/index.ts +1 -1
- package/src/components/popover-menu/popover-menu.scss +3 -3
- package/src/components/popover-menu/popover-menu.tsx +25 -25
- package/src/components/search/index.ts +1 -1
- package/src/components/search/search.scss +1 -1
- package/src/components/search/search.tsx +137 -144
- package/src/components/spinner/index.ts +1 -1
- package/src/components/spinner/spinner.scss +58 -50
- package/src/components/spinner/spinner.tsx +9 -9
- package/src/components/transcript/index.ts +1 -1
- package/src/components/transcript/transcript.scss +9 -33
- package/src/components/transcript/transcript.tsx +333 -454
- package/src/global.d.ts +6 -6
- package/src/index.ts +13 -1
- package/src/transcript-plugin.scss +3 -3
- package/src/transcript-plugin.tsx +210 -391
- package/src/types/index.ts +3 -0
- package/src/types/transcript-config.ts +11 -0
- package/src/types/transcript-item-data.ts +29 -0
- package/src/types/types-ui.ts +11 -0
- package/src/utils/debounce.ts +36 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/object-utils.ts +34 -0
- package/src/utils/popover/popover.scss +30 -0
- package/src/utils/popover/popover.tsx +178 -0
- package/src/utils/utils.ts +86 -0
- package/src/variables.scss +14 -0
- package/src/assets/close.svg +0 -10
- 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,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,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(/</gi, '<')
|
|
70
|
+
.replace(/>/gi, '>')
|
|
71
|
+
.replace(/ /gi, ' ')
|
|
72
|
+
.replace(/&/gi, '&')
|
|
73
|
+
.replace(/"/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
|
+
};
|
package/src/variables.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/assets/close.svg
DELETED
|
@@ -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
|
-
|