@playkit-js/manual-hotspots 3.3.1-canary.0-7e9f2da → 3.3.1-canary.0-b776562
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 +1 -1
- package/dist/playkit-manual-hotspots.js +1 -1
- package/package.json +3 -4
- package/src/components/Hotspot.tsx +0 -267
- package/src/components/HotspotWrapper.tsx +0 -115
- package/src/events/events.ts +0 -4
- package/src/global.d.ts +0 -4
- package/src/hotspots-plugin.tsx +0 -180
- package/src/index.ts +0 -15
- package/src/utils/analyticsEvents.ts +0 -3
- package/src/utils/hotspot-config.ts +0 -54
- package/src/utils/hotspot-loader.ts +0 -72
- package/src/utils/hotspot-normalizer.ts +0 -104
- package/src/utils/hotspot-presets.ts +0 -51
- package/src/utils/hotspot-timeline-simple.ts +0 -78
- package/src/utils/hotspot.ts +0 -88
- package/src/utils/scale-video.ts +0 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playkit-js/manual-hotspots",
|
|
3
|
-
"version": "3.3.1-canary.0-
|
|
3
|
+
"version": "3.3.1-canary.0-b776562",
|
|
4
4
|
"private": false,
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/kaltura-ps/playkit-js-manual-hotspots/issues"
|
|
@@ -42,8 +42,7 @@
|
|
|
42
42
|
"docs",
|
|
43
43
|
"LICENSE",
|
|
44
44
|
"README.md",
|
|
45
|
-
"CHANGELOG.md"
|
|
46
|
-
"src"
|
|
45
|
+
"CHANGELOG.md"
|
|
47
46
|
],
|
|
48
47
|
"scripts": {
|
|
49
48
|
"disabled-preinstall": "npx --yes --prefer-online --registry=https://npm.pkg.github.com --package @kaltura/kaltura-tools@latest kaltura-tools check --root=.",
|
|
@@ -64,7 +63,7 @@
|
|
|
64
63
|
"test:watch": "yarn run test:prepare && cypress open",
|
|
65
64
|
"test": "yarn run test:prepare && yarn run cy:run",
|
|
66
65
|
"release": "standard-version",
|
|
67
|
-
"pushTaggedRelease": "git push --follow-tags --no-verify origin
|
|
66
|
+
"pushTaggedRelease": "git push --follow-tags --no-verify origin main",
|
|
68
67
|
"prettier:fix": "prettier --write ."
|
|
69
68
|
},
|
|
70
69
|
"publishConfig": {
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import {h, Component} from 'preact';
|
|
2
|
-
import {A11yWrapper} from '@playkit-js/common';
|
|
3
|
-
import {LayoutHotspot} from '../utils/hotspot';
|
|
4
|
-
import {AnalyticsEvents} from '../utils/analyticsEvents';
|
|
5
|
-
import {HotspotsEvents} from '../events/events';
|
|
6
|
-
|
|
7
|
-
const defaultContainerStyles = {
|
|
8
|
-
position: 'absolute',
|
|
9
|
-
display: 'table',
|
|
10
|
-
boxSizing: 'border-box'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const defaultButtonsStyles = {
|
|
14
|
-
position: 'relative',
|
|
15
|
-
width: '100%',
|
|
16
|
-
height: 'inherit',
|
|
17
|
-
appearance: 'none',
|
|
18
|
-
border: 'none',
|
|
19
|
-
display: 'flex',
|
|
20
|
-
cursor: 'pointer',
|
|
21
|
-
wordBreak: 'break-all',
|
|
22
|
-
textRendering: 'geometricPrecision',
|
|
23
|
-
overflow: 'hidden',
|
|
24
|
-
textOverflow: 'ellipsis',
|
|
25
|
-
padding: '3px',
|
|
26
|
-
alignItems: 'center',
|
|
27
|
-
justifyContent: 'center',
|
|
28
|
-
outline: 0,
|
|
29
|
-
lineHeight: 'normal'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type Props = {
|
|
33
|
-
visible: boolean;
|
|
34
|
-
hotspot: LayoutHotspot;
|
|
35
|
-
styles?: {[key: string]: any};
|
|
36
|
-
pauseVideo(): void;
|
|
37
|
-
exitFullscreen(): void;
|
|
38
|
-
seekTo(time: number): void;
|
|
39
|
-
sendAnalytics(event: AnalyticsEvents): void;
|
|
40
|
-
dispatcher(name: string, payload?: any): void
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type State = {
|
|
44
|
-
disableClick: boolean;
|
|
45
|
-
isReady: boolean;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const defaultProps = {
|
|
49
|
-
styles: {}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function prepareUrl(url: string): string {
|
|
53
|
-
if (url.startsWith('mailto:')) {
|
|
54
|
-
return url;
|
|
55
|
-
}
|
|
56
|
-
if (!url.match(/^https?:\/\//i)) {
|
|
57
|
-
url = 'http://' + url;
|
|
58
|
-
}
|
|
59
|
-
return url;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function scrollToAnchor(selector: string, scrollBehavior: ScrollBehavior = 'smooth', offsetY: number = 0): void {
|
|
63
|
-
const element = document.querySelector(selector);
|
|
64
|
-
|
|
65
|
-
if (!element) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (offsetY !== 0) {
|
|
70
|
-
const elementRect = element.getBoundingClientRect();
|
|
71
|
-
const absoluteTop = elementRect.top + window.scrollY - offsetY;
|
|
72
|
-
window.scrollTo({
|
|
73
|
-
top: absoluteTop,
|
|
74
|
-
behavior: scrollBehavior
|
|
75
|
-
});
|
|
76
|
-
} else {
|
|
77
|
-
element.scrollIntoView({behavior: scrollBehavior});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function sendPostMessage(message: unknown, targetOrigin: string = '*', hotspotId: string, hotspotLabel?: string): void {
|
|
82
|
-
const payload = {
|
|
83
|
-
source: 'kaltura-hotspot',
|
|
84
|
-
hotspotId,
|
|
85
|
-
hotspotLabel,
|
|
86
|
-
data: message,
|
|
87
|
-
timestamp: Date.now()
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
window.top?.postMessage(payload, targetOrigin);
|
|
92
|
-
} catch (e) {
|
|
93
|
-
// do nothing
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const MINIMAL_FONT_SIZE = 10;
|
|
98
|
-
|
|
99
|
-
export default class Hotspot extends Component<Props, State> {
|
|
100
|
-
static defaultProps = defaultProps;
|
|
101
|
-
|
|
102
|
-
hotspotRef: HTMLDivElement | null = null;
|
|
103
|
-
|
|
104
|
-
state = {
|
|
105
|
-
disableClick: true,
|
|
106
|
-
isReady: false
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
componentDidMount() {
|
|
110
|
-
const {hotspot, dispatcher} = this.props;
|
|
111
|
-
|
|
112
|
-
if (!hotspot || !hotspot.onClick) {
|
|
113
|
-
this.setState({ isReady: true });
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.setState({
|
|
118
|
-
isReady: true,
|
|
119
|
-
disableClick: !this.isClickable()
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const {id, label} = hotspot;
|
|
123
|
-
dispatcher(HotspotsEvents.HOTSPOT_DISPLAYED, {id, label});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
isClickable = (): boolean => {
|
|
127
|
-
const {
|
|
128
|
-
hotspot: {onClick}
|
|
129
|
-
} = this.props;
|
|
130
|
-
|
|
131
|
-
if (!onClick) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
switch (onClick.type) {
|
|
136
|
-
case 'jumpToTime':
|
|
137
|
-
return typeof onClick.jumpToTime !== 'undefined';
|
|
138
|
-
case 'openUrl':
|
|
139
|
-
case 'openUrlInNewTab':
|
|
140
|
-
return !!onClick.url;
|
|
141
|
-
case 'scrollToAnchor':
|
|
142
|
-
return !!onClick.selector;
|
|
143
|
-
case 'postMessage':
|
|
144
|
-
return onClick.message !== undefined;
|
|
145
|
-
default:
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
handleClick = () => {
|
|
151
|
-
const {hotspot, dispatcher} = this.props;
|
|
152
|
-
const {disableClick} = this.state;
|
|
153
|
-
|
|
154
|
-
const {id, label} = hotspot;
|
|
155
|
-
dispatcher(HotspotsEvents.HOTSPOT_CLICK, {id, label});
|
|
156
|
-
|
|
157
|
-
if (!hotspot.onClick || disableClick) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
switch (hotspot.onClick.type) {
|
|
162
|
-
case 'jumpToTime':
|
|
163
|
-
if (typeof hotspot.onClick.jumpToTime === 'undefined') {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.props.seekTo(hotspot.onClick.jumpToTime / 1000);
|
|
168
|
-
break;
|
|
169
|
-
case 'openUrl':
|
|
170
|
-
{
|
|
171
|
-
if (!hotspot.onClick.url) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const url = prepareUrl(hotspot.onClick.url);
|
|
175
|
-
window.open(url, '_top');
|
|
176
|
-
this.props.sendAnalytics({
|
|
177
|
-
eventNumber: 47,
|
|
178
|
-
target: url,
|
|
179
|
-
hotspotId: hotspot.id
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
break;
|
|
183
|
-
case 'openUrlInNewTab':
|
|
184
|
-
{
|
|
185
|
-
if (!hotspot.onClick.url) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
this.props.pauseVideo();
|
|
190
|
-
|
|
191
|
-
const url = prepareUrl(hotspot.onClick.url);
|
|
192
|
-
try {
|
|
193
|
-
window.open(url, '_blank');
|
|
194
|
-
this.props.sendAnalytics({
|
|
195
|
-
eventNumber: 47,
|
|
196
|
-
target: url,
|
|
197
|
-
hotspotId: hotspot.id
|
|
198
|
-
});
|
|
199
|
-
} catch (e) {
|
|
200
|
-
// do nothing
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
|
|
205
|
-
case 'scrollToAnchor':
|
|
206
|
-
{
|
|
207
|
-
if (!hotspot.onClick.selector) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
this.props.exitFullscreen();
|
|
211
|
-
this.props.pauseVideo();
|
|
212
|
-
scrollToAnchor(hotspot.onClick.selector, hotspot.onClick.scrollBehavior, hotspot.onClick.offsetY);
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
|
|
216
|
-
case 'postMessage':
|
|
217
|
-
{
|
|
218
|
-
if (hotspot.onClick.message === undefined) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (hotspot.onClick.pauseVideo) {
|
|
222
|
-
this.props.pauseVideo();
|
|
223
|
-
}
|
|
224
|
-
sendPostMessage(hotspot.onClick.message, hotspot.onClick.targetOrigin, id, label);
|
|
225
|
-
}
|
|
226
|
-
break;
|
|
227
|
-
|
|
228
|
-
default:
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
render() {
|
|
234
|
-
const {hotspot} = this.props;
|
|
235
|
-
const {layout, label} = hotspot;
|
|
236
|
-
const {isReady, disableClick} = this.state;
|
|
237
|
-
|
|
238
|
-
if (!isReady || !this.props.visible) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const containerStyles = {
|
|
243
|
-
...defaultContainerStyles,
|
|
244
|
-
top: layout.y,
|
|
245
|
-
left: layout.x,
|
|
246
|
-
height: layout.height,
|
|
247
|
-
width: layout.width
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const buttonStyles = {
|
|
251
|
-
...defaultButtonsStyles,
|
|
252
|
-
...hotspot.styles,
|
|
253
|
-
cursor: disableClick ? 'default' : 'pointer',
|
|
254
|
-
maxWidth: `${layout.width}px`,
|
|
255
|
-
fontSize: `${hotspot.relativeStyle.fontSize}px`,
|
|
256
|
-
borderRadius: `${hotspot.relativeStyle.radiusBorder}px`,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
return (
|
|
260
|
-
<A11yWrapper onClick={this.handleClick} role='alert' aria-live="polite">
|
|
261
|
-
<div ref={(ref) => (this.hotspotRef = ref)} tabIndex={0} role="button" aria-label={label} aria-disabled={disableClick} style={containerStyles} data-testid="hotspots_hotspot">
|
|
262
|
-
<div style={buttonStyles}>{label}</div>
|
|
263
|
-
</div>
|
|
264
|
-
</A11yWrapper>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {h, Component, createRef} from 'preact';
|
|
2
|
-
import {LayoutHotspot, shallowCompareHotspots} from '../utils/hotspot';
|
|
3
|
-
import Hotspot from './Hotspot';
|
|
4
|
-
import {AnalyticsEvents} from '../utils/analyticsEvents';
|
|
5
|
-
|
|
6
|
-
const { withPlayer } = KalturaPlayer.ui.components;
|
|
7
|
-
const { withText, Text } = KalturaPlayer.ui.preacti18n;
|
|
8
|
-
const translates = {
|
|
9
|
-
hotspotRemoved: (
|
|
10
|
-
<Text id="hotspots.hotspot_removed" fields={{ hotspotLabel: '' }}>
|
|
11
|
-
{`{hotspotLabel} hotspot removed`}
|
|
12
|
-
</Text>
|
|
13
|
-
)
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const hotspotsContainerStyles = {
|
|
17
|
-
position: 'absolute',
|
|
18
|
-
display: 'block',
|
|
19
|
-
overflow: 'visible',
|
|
20
|
-
top: 0,
|
|
21
|
-
left: 0,
|
|
22
|
-
width: 0,
|
|
23
|
-
height: 0
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export interface Props {
|
|
27
|
-
hotspots: LayoutHotspot[];
|
|
28
|
-
pauseVideo(): void;
|
|
29
|
-
seekTo(time: number): void;
|
|
30
|
-
exitFullscreen(): void;
|
|
31
|
-
sendAnalytics(event: AnalyticsEvents): void;
|
|
32
|
-
dispatcher(name: string, payload?: any): void;
|
|
33
|
-
hotspotRemoved?: string;
|
|
34
|
-
player?: any;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@withPlayer
|
|
38
|
-
@withText(translates)
|
|
39
|
-
export default class HotspotWrapper extends Component<Props> {
|
|
40
|
-
|
|
41
|
-
private liveRegionRef = createRef<HTMLDivElement>();
|
|
42
|
-
private previousHotspotMap: Map<string, string> = new Map();
|
|
43
|
-
|
|
44
|
-
shouldComponentUpdate(nextProps: Props) {
|
|
45
|
-
return !shallowCompareHotspots(this.props.hotspots, nextProps.hotspots);
|
|
46
|
-
}
|
|
47
|
-
componentDidUpdate() {
|
|
48
|
-
const currentMap = new Map<string, string>(
|
|
49
|
-
this.props.hotspots
|
|
50
|
-
.filter(h => typeof h.label === 'string')
|
|
51
|
-
.map(h => [h.id, h.label!])
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
let announced = false;
|
|
55
|
-
this.previousHotspotMap.forEach((label, id) => {
|
|
56
|
-
if (!currentMap.has(id) && !announced) {
|
|
57
|
-
if (this.props.hotspotRemoved) {
|
|
58
|
-
const message = this.props.hotspotRemoved.replace('{hotspotLabel}', label);
|
|
59
|
-
this.announceHotspotChange(message);
|
|
60
|
-
}
|
|
61
|
-
announced = true;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
this.previousHotspotMap = currentMap;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private announceHotspotChange(message: string) {
|
|
69
|
-
const liveRegion = this.liveRegionRef.current;
|
|
70
|
-
if (!liveRegion) return;
|
|
71
|
-
liveRegion.textContent = '';
|
|
72
|
-
//setTimeout ensures the DOM change happens in two separate cycles
|
|
73
|
-
// which solves the issue that the hotspot removal is announced only the first time.
|
|
74
|
-
setTimeout(() => {
|
|
75
|
-
liveRegion.textContent = message;
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private renderHotspots = (visualHotspot: LayoutHotspot[]) => {
|
|
80
|
-
if (!visualHotspot) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const {seekTo, pauseVideo, exitFullscreen, sendAnalytics, dispatcher} = this.props;
|
|
85
|
-
return visualHotspot.map(hotspotData => (
|
|
86
|
-
<Hotspot dispatcher={dispatcher} pauseVideo={pauseVideo} exitFullscreen={exitFullscreen} seekTo={seekTo} key={hotspotData.id} visible={true} hotspot={hotspotData} sendAnalytics={sendAnalytics} />
|
|
87
|
-
));
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
render() {
|
|
91
|
-
const {hotspots} = this.props;
|
|
92
|
-
const hotspotsElements = this.renderHotspots(hotspots);
|
|
93
|
-
const targetId = this.props.player?.config?.targetId;
|
|
94
|
-
const liveRegionId = `hotspot-liveRegion-${targetId}`;
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div style={hotspotsContainerStyles} data-testid="hotspots_hotspotsContainer">
|
|
98
|
-
{hotspotsElements}
|
|
99
|
-
<div
|
|
100
|
-
ref={this.liveRegionRef}
|
|
101
|
-
id={liveRegionId}
|
|
102
|
-
aria-live="polite"
|
|
103
|
-
role="status"
|
|
104
|
-
style={{
|
|
105
|
-
position: 'absolute',
|
|
106
|
-
left: '-9999px',
|
|
107
|
-
width: '1px',
|
|
108
|
-
height: '1px',
|
|
109
|
-
overflow: 'hidden'
|
|
110
|
-
}}
|
|
111
|
-
/>
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/events/events.ts
DELETED
package/src/global.d.ts
DELETED
package/src/hotspots-plugin.tsx
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import {h, ComponentChildren} from 'preact';
|
|
2
|
-
import {FloatingItem, FloatingManager, FloatingItemProps} from '@playkit-js/ui-managers';
|
|
3
|
-
|
|
4
|
-
import {RawLayoutHotspot, LayoutHotspot, Canvas, RawFloatingCuepoint, Layout, Style} from './utils/hotspot';
|
|
5
|
-
import HotspotWrapper from './components/HotspotWrapper';
|
|
6
|
-
import {ScaleCalculation, scaleVideo} from './utils/scale-video';
|
|
7
|
-
import {HotspotTimelineSimple} from './utils/hotspot-timeline-simple';
|
|
8
|
-
import {HotspotInputConfig} from './utils/hotspot-config';
|
|
9
|
-
import {HotspotLoader} from './utils/hotspot-loader';
|
|
10
|
-
|
|
11
|
-
interface HotspotsPluginConfig {
|
|
12
|
-
hotspots?: HotspotInputConfig[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class HotspotsPlugin extends KalturaPlayer.core.BasePlugin {
|
|
16
|
-
static defaultConfig: HotspotsPluginConfig = {
|
|
17
|
-
hotspots: []
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
private _player: KalturaPlayerTypes.Player;
|
|
21
|
-
private _hotspots: LayoutHotspot[] = [];
|
|
22
|
-
private _floatingItem: FloatingItem | null = null;
|
|
23
|
-
private _canvas: Canvas | null = null;
|
|
24
|
-
private _timeline: HotspotTimelineSimple<RawLayoutHotspot> = new HotspotTimelineSimple();
|
|
25
|
-
|
|
26
|
-
constructor(name: string, player: KalturaPlayerTypes.Player, config: HotspotsPluginConfig) {
|
|
27
|
-
super(name, player, config);
|
|
28
|
-
this._player = player;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
static isValid(): boolean {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private get floatingManager(): FloatingManager {
|
|
36
|
-
return (this.player.getService('floatingManager') as FloatingManager) || {};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
loadMedia(): void {
|
|
40
|
-
this._addHotspotsContainer();
|
|
41
|
-
this._initTimeline();
|
|
42
|
-
this.eventManager.listen(this._player, this._player.Event.TIME_UPDATE, (): void => {
|
|
43
|
-
const currentTimeMs = this._player.currentTime * 1000;
|
|
44
|
-
|
|
45
|
-
if (this._timeline.update(currentTimeMs)) {
|
|
46
|
-
this._hotspots = this._recalculateCuepointLayout(this._timeline.getVisibleHotspots());
|
|
47
|
-
this._updateHotspotsContainer();
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private _initTimeline(): void {
|
|
53
|
-
const loader = new HotspotLoader(this.logger);
|
|
54
|
-
const rawHotspots = loader.load(this.config.hotspots);
|
|
55
|
-
|
|
56
|
-
if (!rawHotspots) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this._timeline.initialize(rawHotspots);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private _calculateLayout(cuepoint: RawFloatingCuepoint, scaleCalculation: ScaleCalculation): Layout {
|
|
64
|
-
const {rawLayout} = cuepoint;
|
|
65
|
-
return {
|
|
66
|
-
x: scaleCalculation.left + rawLayout.relativeX * scaleCalculation.width,
|
|
67
|
-
y: scaleCalculation.top + rawLayout.relativeY * scaleCalculation.height,
|
|
68
|
-
width: rawLayout.relativeWidth * scaleCalculation.width,
|
|
69
|
-
height: rawLayout.relativeHeight * scaleCalculation.height
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private _calculateStyle(cuepoint: LayoutHotspot, scaleCalculation: ScaleCalculation): Style {
|
|
74
|
-
const {rawLayout, styles} = cuepoint;
|
|
75
|
-
return {
|
|
76
|
-
fontSize: parseInt(styles['font-size'])/ rawLayout.stageWidth * scaleCalculation.width,
|
|
77
|
-
radiusBorder: parseInt(styles['border-radius'])/ rawLayout.stageWidth * scaleCalculation.width,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private _recalculateCuepointLayout = (hotspots: RawLayoutHotspot[] | LayoutHotspot[]): LayoutHotspot[] => {
|
|
82
|
-
this.logger.debug('calculating cuepoint layout based on video/player sizes');
|
|
83
|
-
|
|
84
|
-
if (!this._canvas?.playerSize || !this._canvas?.videoSize) {
|
|
85
|
-
this.logger.warn('missing video/player sizes, hide all cuepoint');
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const {width: playerWidth, height: playerHeight} = this._canvas.playerSize;
|
|
90
|
-
const {width: videoWidth, height: videoHeight} = this._canvas.videoSize;
|
|
91
|
-
const canCalcaulateLayout = playerWidth && playerHeight && videoWidth && videoHeight;
|
|
92
|
-
|
|
93
|
-
if (!canCalcaulateLayout) {
|
|
94
|
-
this.logger.warn('missing video/player sizes, hide all cuepoint');
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const scaleCalculation = scaleVideo(videoWidth, videoHeight, playerWidth, playerHeight, true);
|
|
99
|
-
|
|
100
|
-
this.logger.debug('recalculate cuepoint layout based on new sizes');
|
|
101
|
-
return hotspots.map(cuepoint => ({
|
|
102
|
-
...cuepoint,
|
|
103
|
-
layout: this._calculateLayout(cuepoint as any, scaleCalculation),
|
|
104
|
-
relativeStyle: this._calculateStyle(cuepoint as any, scaleCalculation)
|
|
105
|
-
}));
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
private _addHotspotsContainer(): void {
|
|
109
|
-
// TODO - fire on click
|
|
110
|
-
this._floatingItem = this.floatingManager.add({
|
|
111
|
-
label: 'Manual Hotspots',
|
|
112
|
-
mode: 'MediaLoaded',
|
|
113
|
-
position: 'VideoArea',
|
|
114
|
-
renderContent: this._renderRoot
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private _updateHotspotsContainer(): void {
|
|
119
|
-
if (this._floatingItem) {
|
|
120
|
-
this._floatingItem.update();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private _pauseVideo = (): void => {
|
|
125
|
-
this._player.pause();
|
|
126
|
-
};
|
|
127
|
-
private _seekTo = (time: number): void => {
|
|
128
|
-
this._player.currentTime = time;
|
|
129
|
-
};
|
|
130
|
-
private _exitFullscreen = (): void => {
|
|
131
|
-
// @ts-expect-error bad types
|
|
132
|
-
if (this._player.isFullscreen()) {
|
|
133
|
-
// @ts-expect-error bad types
|
|
134
|
-
this._player.exitFullscreen();
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
private _checkIfResizeHappened = (newCanvas: Canvas): boolean => {
|
|
139
|
-
if (!this._canvas) {
|
|
140
|
-
this._canvas = newCanvas;
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
const prevPlayerSize = this._canvas.playerSize;
|
|
144
|
-
const prevVideoSize = this._canvas.videoSize;
|
|
145
|
-
const nextPlayerSize = newCanvas.playerSize;
|
|
146
|
-
const nextVideoSize = newCanvas.videoSize;
|
|
147
|
-
if (
|
|
148
|
-
prevPlayerSize.width !== nextPlayerSize.width ||
|
|
149
|
-
prevPlayerSize.height !== nextPlayerSize.height ||
|
|
150
|
-
prevVideoSize.width !== nextVideoSize.width ||
|
|
151
|
-
prevVideoSize.height !== nextVideoSize.height
|
|
152
|
-
) {
|
|
153
|
-
this._canvas = newCanvas;
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
return false;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
private _renderRoot = (floatingItemProps: FloatingItemProps): ComponentChildren => {
|
|
160
|
-
if (this._checkIfResizeHappened(floatingItemProps.canvas)) {
|
|
161
|
-
this._hotspots = this._recalculateCuepointLayout(this._hotspots);
|
|
162
|
-
}
|
|
163
|
-
return (
|
|
164
|
-
<HotspotWrapper dispatcher={(eventType, payload) => this.dispatchEvent(eventType, payload)} key={'hotspotWrapper'} hotspots={this._hotspots} pauseVideo={this._pauseVideo} seekTo={this._seekTo} exitFullscreen={this._exitFullscreen} sendAnalytics={() => {}} />
|
|
165
|
-
);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
reset(): void {
|
|
169
|
-
this.eventManager.removeAll();
|
|
170
|
-
this._timeline.clear();
|
|
171
|
-
this._hotspots = [];
|
|
172
|
-
this._canvas = null;
|
|
173
|
-
if (this._floatingItem) {
|
|
174
|
-
this.floatingManager.remove(this._floatingItem);
|
|
175
|
-
this._floatingItem = null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
destroy(): void {}
|
|
180
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {HotspotsPlugin} from './hotspots-plugin';
|
|
2
|
-
import {registerPlugin} from '@playkit-js/kaltura-player-js';
|
|
3
|
-
|
|
4
|
-
declare var __VERSION__: string;
|
|
5
|
-
declare var __NAME__: string;
|
|
6
|
-
|
|
7
|
-
const VERSION = __VERSION__;
|
|
8
|
-
const NAME = __NAME__;
|
|
9
|
-
|
|
10
|
-
export {HotspotsPlugin as Plugin};
|
|
11
|
-
export {VERSION, NAME};
|
|
12
|
-
export {HotspotsEvents} from './events/events';
|
|
13
|
-
|
|
14
|
-
const pluginName: string = 'playkit-js-manual-hotspots';
|
|
15
|
-
registerPlugin(pluginName, HotspotsPlugin as any);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import {PositionPreset, SizePreset} from './hotspot-presets';
|
|
2
|
-
import {OnClickAction} from './hotspot';
|
|
3
|
-
|
|
4
|
-
interface StyleConfig {
|
|
5
|
-
background?: string;
|
|
6
|
-
color?: string;
|
|
7
|
-
'border-radius'?: string;
|
|
8
|
-
'font-size'?: string;
|
|
9
|
-
'font-weight'?: string;
|
|
10
|
-
'font-family'?: string;
|
|
11
|
-
[key: string]: string | undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface HotspotInputConfig {
|
|
15
|
-
id?: string;
|
|
16
|
-
startTime: number;
|
|
17
|
-
endTime?: number;
|
|
18
|
-
label?: string;
|
|
19
|
-
layout?: {
|
|
20
|
-
position?: PositionPreset;
|
|
21
|
-
size?: SizePreset;
|
|
22
|
-
|
|
23
|
-
// overrides position preset
|
|
24
|
-
relativeX?: number;
|
|
25
|
-
relativeY?: number;
|
|
26
|
-
|
|
27
|
-
// overrides size preset
|
|
28
|
-
relativeWidth?: number;
|
|
29
|
-
relativeHeight?: number;
|
|
30
|
-
|
|
31
|
-
// overrides for stage size used in font scaling
|
|
32
|
-
stageWidth?: number;
|
|
33
|
-
stageHeight?: number;
|
|
34
|
-
};
|
|
35
|
-
styles?: StyleConfig;
|
|
36
|
-
onClick?: OnClickAction;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface HotspotConfigFull {
|
|
40
|
-
id: string;
|
|
41
|
-
startTime: number;
|
|
42
|
-
endTime?: number;
|
|
43
|
-
label?: string;
|
|
44
|
-
layout: {
|
|
45
|
-
relativeX: number;
|
|
46
|
-
relativeY: number;
|
|
47
|
-
relativeWidth: number;
|
|
48
|
-
relativeHeight: number;
|
|
49
|
-
stageWidth: number;
|
|
50
|
-
stageHeight: number;
|
|
51
|
-
};
|
|
52
|
-
styles: StyleConfig;
|
|
53
|
-
onClick?: OnClickAction;
|
|
54
|
-
}
|