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.
Files changed (46) hide show
  1. package/CHANGELOG.md +140 -21
  2. package/dist/cld-video-player.css +229 -2079
  3. package/dist/cld-video-player.js +200 -483
  4. package/dist/cld-video-player.light.css +231 -2081
  5. package/dist/cld-video-player.light.js +71 -23
  6. package/dist/cld-video-player.light.min.css +1 -1
  7. package/dist/cld-video-player.light.min.js +2 -23
  8. package/dist/cld-video-player.light.min.js.LICENSE.txt +23 -0
  9. package/dist/cld-video-player.min.css +1 -1
  10. package/dist/cld-video-player.min.js +2 -27
  11. package/dist/cld-video-player.min.js.LICENSE.txt +26 -0
  12. package/docs/interaction-area.html +104 -64
  13. package/package.json +23 -23
  14. package/src/assets/styles/components/interaction-areas.scss +165 -0
  15. package/src/assets/styles/components/themedButton.scss +48 -0
  16. package/src/assets/styles/icons.scss +149 -217
  17. package/src/assets/styles/main.scss +8 -30
  18. package/src/components/interaction-area/interaction-area.const.js +30 -0
  19. package/src/components/interaction-area/interaction-area.service.js +225 -0
  20. package/src/components/interaction-area/interaction-area.utils.js +236 -0
  21. package/src/components/logoButton/logo-button.js +3 -6
  22. package/src/components/themeButton/themedButton.const.js +3 -0
  23. package/src/components/themeButton/themedButton.js +25 -0
  24. package/src/config/defaults.js +3 -2
  25. package/src/extended-events.js +3 -0
  26. package/src/plugins/cloudinary/common.js +14 -6
  27. package/src/plugins/cloudinary/models/video-source/video-source.js +16 -12
  28. package/src/plugins/cloudinary/models/video-source/video-source.utils.js +28 -1
  29. package/src/plugins/colors/index.js +6 -1
  30. package/src/plugins/dash/videojs-dash.js +1 -1
  31. package/src/plugins/floating-player/index.js +3 -1
  32. package/src/utils/array.js +21 -0
  33. package/src/utils/dom.js +41 -2
  34. package/src/utils/object.js +26 -0
  35. package/src/utils/time.js +28 -1
  36. package/src/utils/type-inference.js +5 -1
  37. package/src/validators/validators-functions.js +48 -0
  38. package/src/validators/validators-types.js +78 -0
  39. package/src/validators/validators.js +110 -0
  40. package/src/video-player.const.js +23 -16
  41. package/src/video-player.js +47 -127
  42. package/src/video-player.utils.js +9 -70
  43. package/test/isValidConfig.test.js +224 -0
  44. package/test/unit/utils.test.js +27 -0
  45. package/test/unit/videoSource.test.js +155 -0
  46. 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 isLight = LogoButton.isLight(opts) ? LIGHT_BG_ICON : DARK_BG_ICON;
15
+ const _isLight = isLight(opts) ? LIGHT_BG_ICON : DARK_BG_ICON;
19
16
 
20
- const bgIcon = opts.logoImageUrl ? opts.logoImageUrl : isLight;
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,3 @@
1
+ export const BUTTON_THEME = {
2
+ TRANSPARENT_WHITE: 'transparent-white'
3
+ };
@@ -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
+
@@ -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: false,
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: false,
21
+ floatingWhenNotVisible: FLOATING_TO.NONE,
21
22
  hideContextMenu: false,
22
23
  analytics: false,
23
24
  playedEventPercents: [25, 50, 75, 100]
@@ -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 'vp9':
187
- return { video_codec: 'vp9' };
188
- case 'hev1':
189
- return { video_codec: 'h265' };
190
- case 'h264':
191
- return { video_codec: 'h264:baseline:3.0' };
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
- let type = this.sourceTypes().length > 1 ? null : this.sourceTypes()[0];
174
+ const type = this.sourceTypes().length > 1 ? null : this.sourceTypes()[0];
174
175
  return [this.generateRawSource(this.publicId(), type)];
175
176
  }
176
- let isIe = typeof navigator !== 'undefined' && (/MSIE/.test(navigator.userAgent) || /Trident\//.test(navigator.appVersion));
177
- let srcs = this.sourceTypes().map((sourceType) => {
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 = Array.isArray(srcTransformation) ? srcTransformation : [srcTransformation];
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 && !hasCodecSrcTrans) {
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
- let queryString = this.queryParams() ? objectToQuerystring(this.queryParams()) : '';
201
+ const queryString = this.queryParams() ? objectToQuerystring(this.queryParams()) : '';
200
202
 
201
- let src = this.config().url(this.publicId(), opts);
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
- src += src.indexOf('?') > -1 ? queryString.replace('?', '&') : queryString;
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) {