cloudinary-video-player 1.5.5 → 1.5.9
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 +140 -21
- package/dist/cld-video-player.css +229 -2079
- package/dist/cld-video-player.js +200 -483
- package/dist/cld-video-player.light.css +231 -2081
- package/dist/cld-video-player.light.js +71 -23
- package/dist/cld-video-player.light.min.css +1 -1
- package/dist/cld-video-player.light.min.js +2 -23
- package/dist/cld-video-player.light.min.js.LICENSE.txt +23 -0
- package/dist/cld-video-player.min.css +1 -1
- package/dist/cld-video-player.min.js +2 -27
- package/dist/cld-video-player.min.js.LICENSE.txt +26 -0
- package/docs/interaction-area.html +104 -64
- package/package.json +23 -23
- package/src/assets/styles/components/interaction-areas.scss +165 -0
- package/src/assets/styles/components/themedButton.scss +48 -0
- package/src/assets/styles/icons.scss +149 -217
- package/src/assets/styles/main.scss +8 -30
- package/src/components/interaction-area/interaction-area.const.js +30 -0
- package/src/components/interaction-area/interaction-area.service.js +225 -0
- package/src/components/interaction-area/interaction-area.utils.js +236 -0
- package/src/components/logoButton/logo-button.js +3 -6
- package/src/components/themeButton/themedButton.const.js +3 -0
- package/src/components/themeButton/themedButton.js +25 -0
- package/src/config/defaults.js +3 -2
- package/src/extended-events.js +3 -0
- package/src/plugins/cloudinary/common.js +14 -6
- package/src/plugins/cloudinary/models/video-source/video-source.js +16 -12
- package/src/plugins/cloudinary/models/video-source/video-source.utils.js +28 -1
- package/src/plugins/colors/index.js +6 -1
- package/src/plugins/dash/videojs-dash.js +1 -1
- package/src/plugins/floating-player/index.js +3 -1
- package/src/utils/array.js +21 -0
- package/src/utils/dom.js +41 -2
- package/src/utils/object.js +26 -0
- package/src/utils/time.js +28 -1
- package/src/utils/type-inference.js +5 -1
- package/src/validators/validators-functions.js +48 -0
- package/src/validators/validators-types.js +78 -0
- package/src/validators/validators.js +110 -0
- package/src/video-player.const.js +23 -16
- package/src/video-player.js +47 -127
- package/src/video-player.utils.js +9 -70
- package/test/isValidConfig.test.js +224 -0
- package/test/unit/utils.test.js +27 -0
- package/test/unit/videoSource.test.js +155 -0
- package/types/video-player.d.ts +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import videojs from 'video.js';
|
|
2
|
+
import {
|
|
3
|
+
CLOSE_INTERACTION_AREA_LAYOUT_DELAY,
|
|
4
|
+
DEFAULT_INTERACTION_ARE_TRANSITION,
|
|
5
|
+
INTERACTION_AREAS_CONTAINER_CLASS_NAME, TEMPLATE_INTERACTION_AREAS_VTT
|
|
6
|
+
} from './interaction-area.const';
|
|
7
|
+
import {
|
|
8
|
+
createInteractionAreaLayoutMessage,
|
|
9
|
+
getInteractionAreaItem,
|
|
10
|
+
getZoomTransformation,
|
|
11
|
+
removeInteractionAreasContainer,
|
|
12
|
+
setInteractionAreasContainer,
|
|
13
|
+
setInteractionAreasContainerSize,
|
|
14
|
+
shouldShowAreaLayoutMessage,
|
|
15
|
+
updateInteractionAreasItem
|
|
16
|
+
} from './interaction-area.utils';
|
|
17
|
+
import { addEventListener, createElement } from '../../utils/dom';
|
|
18
|
+
import { throttle } from '../../utils/time';
|
|
19
|
+
import { get } from '../../utils/object';
|
|
20
|
+
import { noop } from '../../utils/type-inference';
|
|
21
|
+
import { addMetadataTrack } from '../../video-player.utils';
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export const interactionAreaService = (player, playerOptions, videojsOptions) => {
|
|
25
|
+
|
|
26
|
+
let firstPlayed = false;
|
|
27
|
+
let isZoomed = false;
|
|
28
|
+
let currentTrack = null;
|
|
29
|
+
let unZoom = noop;
|
|
30
|
+
|
|
31
|
+
const shouldLayoutMessage = () => shouldShowAreaLayoutMessage(videojsOptions.interactionDisplay);
|
|
32
|
+
|
|
33
|
+
function isInteractionAreasEnabled(enabled = false) {
|
|
34
|
+
const interactionAreasConfig = getInteractionAreasConfig();
|
|
35
|
+
return enabled || (interactionAreasConfig && interactionAreasConfig.enable);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setAreasPositionListener() {
|
|
39
|
+
currentTrack && player.videojs.removeRemoteTextTrack(currentTrack);
|
|
40
|
+
|
|
41
|
+
const isEnabled = isInteractionAreasEnabled();
|
|
42
|
+
const interactionAreasConfig = getInteractionAreasConfig();
|
|
43
|
+
|
|
44
|
+
if (!isEnabled || isZoomed) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(interactionAreasConfig.template)) {
|
|
49
|
+
addInteractionAreasItems(interactionAreasConfig.template);
|
|
50
|
+
setContainerSize();
|
|
51
|
+
} else {
|
|
52
|
+
const vttUrl = interactionAreasConfig.vttUrl || TEMPLATE_INTERACTION_AREAS_VTT[interactionAreasConfig.template];
|
|
53
|
+
|
|
54
|
+
if (vttUrl) {
|
|
55
|
+
currentTrack = addMetadataTrack(player.videojs, vttUrl);
|
|
56
|
+
addCueListener(currentTrack);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setGoBackButton() {
|
|
62
|
+
const button = createElement('div', { 'class': 'go-back-button' });
|
|
63
|
+
|
|
64
|
+
button.addEventListener('click', () => {
|
|
65
|
+
unZoom();
|
|
66
|
+
}, false);
|
|
67
|
+
|
|
68
|
+
const tracksContainer = createElement('div', { 'class': INTERACTION_AREAS_CONTAINER_CLASS_NAME }, button);
|
|
69
|
+
setInteractionAreasContainer(player.videojs, tracksContainer);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getInteractionAreasConfig() {
|
|
73
|
+
const { cldSrc } = player.videojs.currentSource();
|
|
74
|
+
return cldSrc && cldSrc.getInteractionAreas();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function removeLayoutMessage() {
|
|
78
|
+
removeInteractionAreasContainer(player.videojs);
|
|
79
|
+
setAreasPositionListener();
|
|
80
|
+
player.play();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setLayoutMessage() {
|
|
84
|
+
if (!isInteractionAreasEnabled()) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (shouldLayoutMessage()) {
|
|
89
|
+
let layoutMessageTimout = null;
|
|
90
|
+
const showItAgainCheckbox = get(videojsOptions, 'interactionDisplay.layout.showAgain', false);
|
|
91
|
+
player.pause();
|
|
92
|
+
|
|
93
|
+
createInteractionAreaLayoutMessage(player.videojs, () => {
|
|
94
|
+
clearTimeout(layoutMessageTimout);
|
|
95
|
+
removeLayoutMessage();
|
|
96
|
+
}, showItAgainCheckbox);
|
|
97
|
+
|
|
98
|
+
if (!showItAgainCheckbox) {
|
|
99
|
+
layoutMessageTimout = setTimeout(removeLayoutMessage, CLOSE_INTERACTION_AREA_LAYOUT_DELAY);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
removeLayoutMessage();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function init() {
|
|
107
|
+
|
|
108
|
+
if (isInteractionAreasEnabled()) {
|
|
109
|
+
|
|
110
|
+
player.videojs.el().classList.add('interaction-areas');
|
|
111
|
+
|
|
112
|
+
player.videojs.one('play', () => {
|
|
113
|
+
firstPlayed = true;
|
|
114
|
+
setLayoutMessage();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
player.videojs.on('sourcechanged', () => {
|
|
118
|
+
firstPlayed && setAreasPositionListener();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const setInteractionAreasContainerSize = throttle(setContainerSize, 100);
|
|
122
|
+
|
|
123
|
+
player.videojs.on('fullscreenchange', () => {
|
|
124
|
+
// waiting for fullscreen will end
|
|
125
|
+
setTimeout(setInteractionAreasContainerSize, 100);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const resizeDestroy = addEventListener(window, 'resize', setContainerSize, false);
|
|
129
|
+
|
|
130
|
+
player.videojs.on('dispose', resizeDestroy);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
player.videojs.on('ended', () => {
|
|
134
|
+
unZoom();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onZoom(src, newOption, item) {
|
|
139
|
+
const currentSource = player.videojs.currentSource();
|
|
140
|
+
const { cldSrc } = currentSource;
|
|
141
|
+
const currentSrcOptions = cldSrc.getInitOptions();
|
|
142
|
+
const option = newOption || { transformation: currentSrcOptions.transformation.toOptions() };
|
|
143
|
+
const transformation = !src && getZoomTransformation(player.videoElement, item);
|
|
144
|
+
const sourceOptions = transformation ? videojs.mergeOptions({ transformation }, option) : option;
|
|
145
|
+
|
|
146
|
+
const newSource = cldSrc.isRawUrl ? currentSource.src : { publicId: cldSrc.publicId() };
|
|
147
|
+
player.source(transformation ? { publicId: cldSrc.publicId() } : src, sourceOptions).play();
|
|
148
|
+
|
|
149
|
+
isZoomed = true;
|
|
150
|
+
|
|
151
|
+
setGoBackButton();
|
|
152
|
+
|
|
153
|
+
unZoom = () => {
|
|
154
|
+
if (isZoomed) {
|
|
155
|
+
isZoomed = false;
|
|
156
|
+
player.source(newSource, currentSrcOptions).play();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function onInteractionAreasClick({ event, item, index }) {
|
|
162
|
+
const interactionAreasConfig = getInteractionAreasConfig();
|
|
163
|
+
|
|
164
|
+
interactionAreasConfig.onClick && interactionAreasConfig.onClick({
|
|
165
|
+
item,
|
|
166
|
+
index,
|
|
167
|
+
event,
|
|
168
|
+
zoom: (source, option) => {
|
|
169
|
+
onZoom(source, option, item);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function addInteractionAreasItems(interactionAreasData, previousInteractionAreasData, durationTime = 0) {
|
|
175
|
+
const configs = { playerOptions: playerOptions, videojsOptions: videojsOptions };
|
|
176
|
+
|
|
177
|
+
if (previousInteractionAreasData) {
|
|
178
|
+
updateInteractionAreasItem(player.videojs, configs, interactionAreasData, previousInteractionAreasData, durationTime, onInteractionAreasClick);
|
|
179
|
+
} else {
|
|
180
|
+
const interactionAreasItems = interactionAreasData.map((item, index) => {
|
|
181
|
+
return getInteractionAreaItem(configs, item, index, durationTime, (event) => {
|
|
182
|
+
onInteractionAreasClick({ event, item, index });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
setInteractionAreasContainer(player.videojs, createElement('div', { 'class': INTERACTION_AREAS_CONTAINER_CLASS_NAME }, interactionAreasItems));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function setContainerSize() {
|
|
191
|
+
if (isInteractionAreasEnabled()) {
|
|
192
|
+
setInteractionAreasContainerSize(player.videojs, player.videoElement);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function addCueListener(track) {
|
|
197
|
+
if (!track) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let previousTracksData = null;
|
|
202
|
+
|
|
203
|
+
track.addEventListener('cuechange', () => {
|
|
204
|
+
const activeCue = track.activeCues && track.activeCues[0];
|
|
205
|
+
|
|
206
|
+
if (activeCue) {
|
|
207
|
+
const durationTime = Math.max(Math.floor((activeCue.endTime - activeCue.startTime) * 1000), DEFAULT_INTERACTION_ARE_TRANSITION);
|
|
208
|
+
|
|
209
|
+
const tracksData = JSON.parse(activeCue.text);
|
|
210
|
+
|
|
211
|
+
addInteractionAreasItems(tracksData, previousTracksData, durationTime);
|
|
212
|
+
!previousTracksData && setContainerSize();
|
|
213
|
+
previousTracksData = tracksData;
|
|
214
|
+
} else {
|
|
215
|
+
removeInteractionAreasContainer(player.videojs);
|
|
216
|
+
previousTracksData = null;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
init
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { elementsCreator, styleElement } from '../../utils/dom';
|
|
2
|
+
import { get } from '../../utils/object';
|
|
3
|
+
import {
|
|
4
|
+
CLOSE_INTERACTION_AREA_LAYOUT_DELAY,
|
|
5
|
+
INTERACTION_AREA_HAND_ICON,
|
|
6
|
+
INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME,
|
|
7
|
+
INTERACTION_AREAS_CONTAINER_CLASS_NAME,
|
|
8
|
+
INTERACTION_AREAS_PREFIX,
|
|
9
|
+
INTERACTION_AREAS_THEME
|
|
10
|
+
} from './interaction-area.const';
|
|
11
|
+
import { noop } from '../../utils/type-inference';
|
|
12
|
+
import { getDefaultPlayerColor } from '../../plugins/colors';
|
|
13
|
+
import { forEach, some } from '../../utils/array';
|
|
14
|
+
import { themedButton } from '../themeButton/themedButton';
|
|
15
|
+
import { BUTTON_THEME } from '../themeButton/themedButton.const';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const getInteractionAreaItemId = (item, index) => item.id || item.type || `id_${index}`;
|
|
19
|
+
|
|
20
|
+
export const getInteractionAreaItem = ({ playerOptions, videojsOptions }, item, index, durationTime, onClick) => {
|
|
21
|
+
const defaultColor = getDefaultPlayerColor(videojsOptions);
|
|
22
|
+
const accentColor = playerOptions && playerOptions.colors ? playerOptions.colors.accent : defaultColor.accent;
|
|
23
|
+
|
|
24
|
+
// theme = 'pulsing' / 'shadowed'
|
|
25
|
+
const theme = get(videojsOptions, 'interactionDisplay.theme.template', INTERACTION_AREAS_THEME.PULSING);
|
|
26
|
+
|
|
27
|
+
return elementsCreator({
|
|
28
|
+
tag: 'div',
|
|
29
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-item theme-${theme}`, 'data-id': getInteractionAreaItemId(item, index) },
|
|
30
|
+
style: {
|
|
31
|
+
left: `${item.left}%`,
|
|
32
|
+
top: `${item.top}%`,
|
|
33
|
+
width: `${item.width}%`,
|
|
34
|
+
height: `${item.height}%`,
|
|
35
|
+
transitionDuration: `${durationTime}ms`
|
|
36
|
+
},
|
|
37
|
+
event: {
|
|
38
|
+
name: 'click',
|
|
39
|
+
callback: onClick
|
|
40
|
+
},
|
|
41
|
+
children: [
|
|
42
|
+
{
|
|
43
|
+
tag: 'div',
|
|
44
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-area-marker` },
|
|
45
|
+
children: [
|
|
46
|
+
{
|
|
47
|
+
tag: 'div',
|
|
48
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-marker-shadow` },
|
|
49
|
+
style: { [theme === INTERACTION_AREAS_THEME.SHADOWED ? 'backgroundColor' : 'borderColor']: accentColor }
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
tag: 'div',
|
|
53
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-marker-main` },
|
|
54
|
+
style: { borderColor: accentColor }
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
export const percentageToFixedValue = (outOf, value) => (outOf / (100 / +value));
|
|
64
|
+
|
|
65
|
+
export const getZoomTransformation = (videoElement, interactionAreaItem) => {
|
|
66
|
+
|
|
67
|
+
const { videoHeight, videoWidth } = videoElement;
|
|
68
|
+
|
|
69
|
+
const itemX = percentageToFixedValue(videoWidth, interactionAreaItem.left);
|
|
70
|
+
const itemY = percentageToFixedValue(videoHeight, interactionAreaItem.top);
|
|
71
|
+
const itemWidth = percentageToFixedValue(videoWidth, interactionAreaItem.width);
|
|
72
|
+
const itemHeight = percentageToFixedValue(videoHeight, interactionAreaItem.height);
|
|
73
|
+
|
|
74
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
75
|
+
const itemAspectRatio = itemWidth / itemHeight;
|
|
76
|
+
|
|
77
|
+
const width = Math.round(itemAspectRatio > 1 || videoAspectRatio > 1 ? itemHeight * itemAspectRatio : itemWidth);
|
|
78
|
+
const height = Math.round(width / videoAspectRatio);
|
|
79
|
+
|
|
80
|
+
const x = Math.round(itemX - (width - itemWidth) / 2);
|
|
81
|
+
const y = Math.round(itemY - (height - itemHeight) / 2);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
width,
|
|
85
|
+
height,
|
|
86
|
+
x: Math.min(Math.max(x, 0), videoWidth - width),
|
|
87
|
+
y: Math.min(Math.max(y, 0), videoHeight - height),
|
|
88
|
+
crop: 'crop'
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
export const setInteractionAreasContainer = (videojs, newInteractionAreasContainer) => {
|
|
94
|
+
const currentInteractionAreasContainer = getInteractionAreasContainer(videojs);
|
|
95
|
+
|
|
96
|
+
if (currentInteractionAreasContainer) {
|
|
97
|
+
currentInteractionAreasContainer.replaceWith(newInteractionAreasContainer);
|
|
98
|
+
} else {
|
|
99
|
+
// do not use element.append for ie11 support
|
|
100
|
+
videojs.el().appendChild(newInteractionAreasContainer);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getInteractionAreaElementById = (interactionAreasContainer, item, index) => interactionAreasContainer.querySelector(`[data-id=${getInteractionAreaItemId(item, index)}]`);
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export const updateInteractionAreasItem = (videojs, configs, interactionAreasData, previousInteractionAreasData, durationTime, onClick) => {
|
|
108
|
+
const interactionAreasContainer = getInteractionAreasContainer(videojs);
|
|
109
|
+
|
|
110
|
+
forEach(interactionAreasData, (item, index) => {
|
|
111
|
+
const itemElement = getInteractionAreaElementById(interactionAreasContainer, item, index);
|
|
112
|
+
const itemId = getInteractionAreaItemId(item);
|
|
113
|
+
const isExistItem = some(previousInteractionAreasData, i => getInteractionAreaItemId(i) === itemId);
|
|
114
|
+
|
|
115
|
+
// in case the element of the item is in the dom and exist in the previous data , it update the element position
|
|
116
|
+
if (isExistItem && itemElement) {
|
|
117
|
+
styleElement(itemElement, {
|
|
118
|
+
left: `${item.left}%`,
|
|
119
|
+
top: `${item.top}%`,
|
|
120
|
+
width: `${item.width}%`,
|
|
121
|
+
height: `${item.height}%`,
|
|
122
|
+
transitionDuration: `${durationTime}ms`
|
|
123
|
+
});
|
|
124
|
+
// if the element did not exist before , not in the dom and not in the previous data , it add a new element
|
|
125
|
+
} else if (!isExistItem && !itemElement) {
|
|
126
|
+
// do not use element.append for ie11 support
|
|
127
|
+
interactionAreasContainer.appendChild(getInteractionAreaItem(configs, item, index, durationTime, (event) => {
|
|
128
|
+
onClick({ event, item, index });
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// checking the previous data for element that should be removed if not exist in the new data object.
|
|
134
|
+
forEach(previousInteractionAreasData, (item, index) => {
|
|
135
|
+
const itemElement = getInteractionAreaElementById(interactionAreasContainer, item, index);
|
|
136
|
+
const itemId = getInteractionAreaItemId(item);
|
|
137
|
+
const shouldBeRemoved = !some(interactionAreasData, i => getInteractionAreaItemId(i) === itemId);
|
|
138
|
+
|
|
139
|
+
if (itemElement && shouldBeRemoved) {
|
|
140
|
+
// do not use element.remove for ie11 support
|
|
141
|
+
itemElement.parentNode.removeChild(itemElement);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const shouldShowAreaLayoutMessage = (interactionAreasConfig) => {
|
|
148
|
+
const isLayoutEnabled = get(interactionAreasConfig, 'layout.enable', true);
|
|
149
|
+
|
|
150
|
+
return isLayoutEnabled && localStorage.getItem(INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME) !== 'true';
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
const onClickInteractionAreaLayoutClick = (checked, onClick = noop) => {
|
|
155
|
+
localStorage.setItem(INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME, JSON.parse(checked));
|
|
156
|
+
onClick();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const createInteractionAreaLayoutMessage = (videojs, onClick, showItAgainCheckbox = false) => {
|
|
160
|
+
|
|
161
|
+
let checked = false;
|
|
162
|
+
|
|
163
|
+
const id = `checkbox_${Math.round(Math.random() * 10000)}`;
|
|
164
|
+
|
|
165
|
+
const tracksContainer = elementsCreator({
|
|
166
|
+
tag: 'div',
|
|
167
|
+
attr: { class: `${INTERACTION_AREAS_CONTAINER_CLASS_NAME} ${INTERACTION_AREAS_PREFIX}-layout-message ${showItAgainCheckbox ? '' : 'clickable'}` },
|
|
168
|
+
onClick: !showItAgainCheckbox ? () => onClickInteractionAreaLayoutClick(checked, onClick) : null,
|
|
169
|
+
children: [
|
|
170
|
+
{
|
|
171
|
+
tag: 'img',
|
|
172
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-layout-icon`, src: INTERACTION_AREA_HAND_ICON }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
tag: 'h3',
|
|
176
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-layout-message-title` },
|
|
177
|
+
children: 'Tap on dots to zoom for a product.'
|
|
178
|
+
},
|
|
179
|
+
themedButton({
|
|
180
|
+
text: 'Got it',
|
|
181
|
+
theme: BUTTON_THEME.TRANSPARENT_WHITE,
|
|
182
|
+
loadingDelay: showItAgainCheckbox ? 0 : CLOSE_INTERACTION_AREA_LAYOUT_DELAY,
|
|
183
|
+
onClick: showItAgainCheckbox ? () => onClickInteractionAreaLayoutClick(checked, onClick) : null
|
|
184
|
+
}),
|
|
185
|
+
showItAgainCheckbox && {
|
|
186
|
+
tag: 'div',
|
|
187
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-layout-message-do-not-show` },
|
|
188
|
+
children: [
|
|
189
|
+
{
|
|
190
|
+
tag: 'input',
|
|
191
|
+
attr: { type: 'checkbox', class: `${INTERACTION_AREAS_PREFIX}-layout-message-checkbox`, id },
|
|
192
|
+
event: {
|
|
193
|
+
name: 'input',
|
|
194
|
+
callback: (event) => {
|
|
195
|
+
checked = event.target.checked;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
tag: 'label',
|
|
201
|
+
attr: { class: `${INTERACTION_AREAS_PREFIX}-layout-message-checkbox-title`, for: id },
|
|
202
|
+
children: 'Don׳t show it again'
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
].filter(i => i)
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
setInteractionAreasContainer(videojs, tracksContainer);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const getInteractionAreasContainer = (videojs) => videojs.el().querySelector(`.${INTERACTION_AREAS_CONTAINER_CLASS_NAME}`);
|
|
213
|
+
|
|
214
|
+
export const removeInteractionAreasContainer = (videojs) => {
|
|
215
|
+
const interactionAreasContainer = getInteractionAreasContainer(videojs);
|
|
216
|
+
|
|
217
|
+
// do not use element.remove for ie11 support
|
|
218
|
+
interactionAreasContainer && interactionAreasContainer.parentNode.removeChild(interactionAreasContainer);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const setInteractionAreasContainerSize = (videojs, videoElement) => {
|
|
222
|
+
|
|
223
|
+
const interactionAreasContainer = getInteractionAreasContainer(videojs);
|
|
224
|
+
|
|
225
|
+
if (!interactionAreasContainer) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { videoHeight, videoWidth } = videoElement;
|
|
230
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
231
|
+
|
|
232
|
+
const width = videoAspectRatio * videoElement.clientHeight;
|
|
233
|
+
|
|
234
|
+
interactionAreasContainer.style.width = `${videoElement.clientWidth < width ? '100%' : width}px`;
|
|
235
|
+
interactionAreasContainer.style.height = videoElement.clientWidth < width ? `${videoElement.clientWidth / videoAspectRatio}px` : '100%';
|
|
236
|
+
};
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import videojs from 'video.js';
|
|
2
2
|
import './logo-button.scss';
|
|
3
3
|
import { DARK_BG_ICON, LIGHT_BG_ICON } from './logo-button.const';
|
|
4
|
+
import { isLight } from '../../video-player.utils';
|
|
4
5
|
|
|
5
6
|
// support VJS5 & VJS6 at the same time
|
|
6
7
|
const ClickableComponent = videojs.getComponent('ClickableComponent');
|
|
7
8
|
|
|
8
9
|
class LogoButton extends ClickableComponent {
|
|
9
10
|
|
|
10
|
-
static isLight(opts) {
|
|
11
|
-
return opts.class.indexOf('cld-video-player-skin-light') > -1 || opts.skin === 'light';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
11
|
createEl() {
|
|
15
12
|
const opts = this.options_.playerOptions;
|
|
16
13
|
const display = opts.showLogo ? 'block' : 'none';
|
|
17
14
|
|
|
18
|
-
const
|
|
15
|
+
const _isLight = isLight(opts) ? LIGHT_BG_ICON : DARK_BG_ICON;
|
|
19
16
|
|
|
20
|
-
const bgIcon = opts.logoImageUrl ? opts.logoImageUrl :
|
|
17
|
+
const bgIcon = opts.logoImageUrl ? opts.logoImageUrl : _isLight;
|
|
21
18
|
|
|
22
19
|
return videojs.dom.createEl('a', {}, {
|
|
23
20
|
class: 'vjs-control vjs-cloudinary-button vjs-button',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { elementsCreator } from '../../utils/dom';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const themedButton = ({ text, onClick, theme = '', loadingDelay = 0 }) => {
|
|
5
|
+
return elementsCreator({
|
|
6
|
+
tag: 'button',
|
|
7
|
+
attr: { class: `vp-theme-button theme-${theme}` },
|
|
8
|
+
onClick,
|
|
9
|
+
children: [
|
|
10
|
+
{
|
|
11
|
+
tag: 'div',
|
|
12
|
+
attr: { class: 'vp-loading-bar' },
|
|
13
|
+
style: {
|
|
14
|
+
'animation-duration': `${loadingDelay}ms`
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
tag: 'div',
|
|
19
|
+
attr: { class: 'content' },
|
|
20
|
+
children: text
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
package/src/config/defaults.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import contextMenuContent from '../plugins/context-menu/contextMenuContent';
|
|
2
|
+
import { FLOATING_TO, PRELOAD } from '../video-player.const';
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
logoOnclickUrl: 'https://cloudinary.com/',
|
|
@@ -9,7 +10,7 @@ export default {
|
|
|
9
10
|
controlBar: {
|
|
10
11
|
'pictureInPictureToggle': false
|
|
11
12
|
},
|
|
12
|
-
preload:
|
|
13
|
+
preload: PRELOAD.AUTO,
|
|
13
14
|
loop: false,
|
|
14
15
|
muted: false,
|
|
15
16
|
posterOptions: {},
|
|
@@ -17,7 +18,7 @@ export default {
|
|
|
17
18
|
contextMenu: {
|
|
18
19
|
content: contextMenuContent
|
|
19
20
|
},
|
|
20
|
-
floatingWhenNotVisible:
|
|
21
|
+
floatingWhenNotVisible: FLOATING_TO.NONE,
|
|
21
22
|
hideContextMenu: false,
|
|
22
23
|
analytics: false,
|
|
23
24
|
playedEventPercents: [25, 50, 75, 100]
|
package/src/extended-events.js
CHANGED
|
@@ -86,6 +86,9 @@ class ExtendedEvents extends EventEmitter {
|
|
|
86
86
|
|
|
87
87
|
if (Math.abs(_seekStart - _seekEnd) > 1) {
|
|
88
88
|
_seeking = true;
|
|
89
|
+
|
|
90
|
+
// should empty _timesTracked array on seek, needed for 'timeplayed' event
|
|
91
|
+
resetPerVideoState();
|
|
89
92
|
_emit('seek', { seekStart: _seekStart, seekEnd: _seekEnd });
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -178,17 +178,25 @@ const h264avcToString = (s) => {
|
|
|
178
178
|
return s;
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
export const VIDEO_CODEC = {
|
|
182
|
+
VP9: 'vp9',
|
|
183
|
+
HEV1: 'hev1',
|
|
184
|
+
H265: 'h265',
|
|
185
|
+
H264: 'h264'
|
|
186
|
+
};
|
|
187
|
+
|
|
181
188
|
const codecToSrcTransformation = (codec) => {
|
|
182
189
|
if (!codec) {
|
|
183
190
|
return {};
|
|
184
191
|
}
|
|
192
|
+
|
|
185
193
|
switch (codec) {
|
|
186
|
-
case
|
|
187
|
-
return { video_codec:
|
|
188
|
-
case
|
|
189
|
-
return { video_codec:
|
|
190
|
-
case
|
|
191
|
-
return { video_codec:
|
|
194
|
+
case VIDEO_CODEC.VP9:
|
|
195
|
+
return { video_codec: VIDEO_CODEC.VP9 };
|
|
196
|
+
case VIDEO_CODEC.HEV1:
|
|
197
|
+
return { video_codec: VIDEO_CODEC.H265 };
|
|
198
|
+
case VIDEO_CODEC.H264:
|
|
199
|
+
return { video_codec: `${VIDEO_CODEC.H264}:baseline:3.0` };
|
|
192
200
|
default:
|
|
193
201
|
return { video_codec: h264avcToString(codec) };
|
|
194
202
|
}
|
|
@@ -4,7 +4,6 @@ import { normalizeOptions, isSrcEqual } from '../../common';
|
|
|
4
4
|
import { sliceAndUnsetProperties } from 'utils/slicing';
|
|
5
5
|
import { assign } from 'utils/assign';
|
|
6
6
|
import { objectToQuerystring } from 'utils/querystring';
|
|
7
|
-
import { isKeyInTransformation } from 'utils/cloudinary';
|
|
8
7
|
import { default as vjs } from 'video.js';
|
|
9
8
|
import {
|
|
10
9
|
CONTAINER_MIME_TYPES,
|
|
@@ -13,7 +12,8 @@ import {
|
|
|
13
12
|
URL_PATTERN,
|
|
14
13
|
VIDEO_SUFFIX_REMOVAL_PATTERN
|
|
15
14
|
} from './video-source.const';
|
|
16
|
-
import { formatToMimeTypeAndTransformation, normalizeFormat } from './video-source.utils';
|
|
15
|
+
import { formatToMimeTypeAndTransformation, isCodecAlreadyExist, normalizeFormat } from './video-source.utils';
|
|
16
|
+
import { castArray } from '../../../../utils/array';
|
|
17
17
|
|
|
18
18
|
let objectId = 0;
|
|
19
19
|
|
|
@@ -68,6 +68,7 @@ class VideoSource extends BaseSource {
|
|
|
68
68
|
this._interactionAreas = null;
|
|
69
69
|
this._type = 'VideoSource';
|
|
70
70
|
this.isRawUrl = isRawUrl;
|
|
71
|
+
this._rawTransformation = options.raw_transformation;
|
|
71
72
|
this.withCredentials = !!withCredentials;
|
|
72
73
|
this.getInitOptions = () => initOptions;
|
|
73
74
|
|
|
@@ -170,41 +171,44 @@ class VideoSource extends BaseSource {
|
|
|
170
171
|
|
|
171
172
|
generateSources() {
|
|
172
173
|
if (this.isRawUrl) {
|
|
173
|
-
|
|
174
|
+
const type = this.sourceTypes().length > 1 ? null : this.sourceTypes()[0];
|
|
174
175
|
return [this.generateRawSource(this.publicId(), type)];
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
|
|
178
|
+
const srcs = this.sourceTypes().map((sourceType) => {
|
|
178
179
|
const srcTransformation = this.sourceTransformation()[sourceType] || this.transformation();
|
|
179
180
|
const format = normalizeFormat(sourceType);
|
|
180
181
|
const isAdaptive = (['mpd', 'm3u8'].indexOf(format) !== -1);
|
|
181
182
|
const opts = {};
|
|
182
183
|
|
|
183
184
|
if (srcTransformation) {
|
|
184
|
-
opts.transformation =
|
|
185
|
+
opts.transformation = castArray(srcTransformation);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
assign(opts, { resource_type: 'video', format });
|
|
188
189
|
|
|
189
|
-
const hasCodecSrcTrans = (isKeyInTransformation(opts.transformation, 'video_codec') || isKeyInTransformation(opts.transformation, 'streaming_profile'));
|
|
190
190
|
const [type, codecTrans] = formatToMimeTypeAndTransformation(sourceType);
|
|
191
|
+
|
|
191
192
|
// If user's transformation include video_codec then don't add another video codec to transformation
|
|
192
|
-
if (codecTrans && !
|
|
193
|
+
if (codecTrans && !isCodecAlreadyExist(opts.transformation, this._rawTransformation)) {
|
|
193
194
|
opts.transformation.push(codecTrans);
|
|
194
195
|
}
|
|
196
|
+
|
|
195
197
|
if (opts.format === 'auto') {
|
|
196
198
|
delete opts.format;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
const queryString = this.queryParams() ? objectToQuerystring(this.queryParams()) : '';
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
const src = this.config().url(this.publicId(), opts);
|
|
202
204
|
// if src is a url that already contains query params then replace '?' with '&'
|
|
203
|
-
|
|
205
|
+
const params = src.indexOf('?') > -1 ? queryString.replace('?', '&') : queryString;
|
|
204
206
|
|
|
205
|
-
return { type, src, cldSrc: this, isAdaptive: isAdaptive, withCredentials: this.withCredentials };
|
|
207
|
+
return { type, src: src + params, cldSrc: this, isAdaptive: isAdaptive, withCredentials: this.withCredentials };
|
|
206
208
|
});
|
|
207
209
|
|
|
210
|
+
const isIe = typeof navigator !== 'undefined' && (/MSIE/.test(navigator.userAgent) || /Trident\//.test(navigator.appVersion));
|
|
211
|
+
|
|
208
212
|
if (isIe) {
|
|
209
213
|
return srcs.filter(s => s.type !== 'video/mp4; codec="hev1.1.6.L93.B0"');
|
|
210
214
|
} else if (vjs.browser.IS_ANY_SAFARI) {
|