cloudinary-video-player 1.6.2-edge.13

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 (226) hide show
  1. package/.eslintignore +4 -0
  2. package/.snyk +19 -0
  3. package/.travis.yml +8 -0
  4. package/CHANGELOG.md +329 -0
  5. package/LICENSE +21 -0
  6. package/README.md +87 -0
  7. package/dist/cld-video-player.css +2110 -0
  8. package/dist/cld-video-player.js +5249 -0
  9. package/dist/cld-video-player.light.css +1766 -0
  10. package/dist/cld-video-player.light.js +1399 -0
  11. package/dist/cld-video-player.light.min.css +1 -0
  12. package/dist/cld-video-player.light.min.js +2 -0
  13. package/dist/cld-video-player.light.min.js.LICENSE.txt +23 -0
  14. package/dist/cld-video-player.min.css +1 -0
  15. package/dist/cld-video-player.min.js +2 -0
  16. package/dist/cld-video-player.min.js.LICENSE.txt +26 -0
  17. package/dist/fonts/cloudinary_icon_for_black_bg.svg +69 -0
  18. package/dist/fonts/cloudinary_icon_for_white_bg.svg +69 -0
  19. package/docs/360.html +102 -0
  20. package/docs/_template.html +93 -0
  21. package/docs/adaptive-streaming.html +297 -0
  22. package/docs/analytics.html +140 -0
  23. package/docs/api.html +302 -0
  24. package/docs/audio.html +136 -0
  25. package/docs/autoplay-fallback.html +138 -0
  26. package/docs/autoplay-on-scroll.html +107 -0
  27. package/docs/codec-fallback.html +158 -0
  28. package/docs/colors.html +135 -0
  29. package/docs/components.html +284 -0
  30. package/docs/custom-cld-errors.html +134 -0
  31. package/docs/floating-player.html +98 -0
  32. package/docs/fluid.html +117 -0
  33. package/docs/force-hls-subtitles-ios.html +159 -0
  34. package/docs/index.html +83 -0
  35. package/docs/interaction-area.html +398 -0
  36. package/docs/live-customer.html +128 -0
  37. package/docs/multiple-players.html +125 -0
  38. package/docs/playlist-by-tag-cap.html +182 -0
  39. package/docs/playlist-by-tag.html +133 -0
  40. package/docs/playlist.html +133 -0
  41. package/docs/poster.html +155 -0
  42. package/docs/raw-url.html +104 -0
  43. package/docs/recommendations.html +155 -0
  44. package/docs/scripts.js +156 -0
  45. package/docs/seek-thumbs.html +90 -0
  46. package/docs/shoppable.html +335 -0
  47. package/docs/subtitles-and-captions.html +267 -0
  48. package/docs/transformations.html +171 -0
  49. package/docs/ui-config.html +108 -0
  50. package/docs/vast-vpaid.html +149 -0
  51. package/env.example.js +6 -0
  52. package/env.js +6 -0
  53. package/jest-puppeteer.config.js +14 -0
  54. package/jest.config.js +196 -0
  55. package/package.json +99 -0
  56. package/sandbox.config.json +3 -0
  57. package/setupJest.js +1 -0
  58. package/src/assets/fonts/VideoJS.svg +120 -0
  59. package/src/assets/fonts/VideoJS.ttf +0 -0
  60. package/src/assets/fonts/VideoJS.woff +0 -0
  61. package/src/assets/fonts/icons.json +120 -0
  62. package/src/assets/icons/cloudinary_icon_for_black_bg.svg +69 -0
  63. package/src/assets/icons/cloudinary_icon_for_white_bg.svg +69 -0
  64. package/src/assets/icons/cloudinary_logo_for_dark_bg.svg +188 -0
  65. package/src/assets/icons/cloudinary_logo_for_white_bg.svg +188 -0
  66. package/src/assets/icons/info-circle.svg +17 -0
  67. package/src/assets/styles/ads-label.scss +16 -0
  68. package/src/assets/styles/components/interaction-areas.scss +158 -0
  69. package/src/assets/styles/components/playlist.scss +213 -0
  70. package/src/assets/styles/components/themedButton.scss +48 -0
  71. package/src/assets/styles/components/thumbnail.scss +94 -0
  72. package/src/assets/styles/components/title-bar.scss +67 -0
  73. package/src/assets/styles/components/triangle-volume-bar.scss +52 -0
  74. package/src/assets/styles/icons.scss +257 -0
  75. package/src/assets/styles/main.scss +324 -0
  76. package/src/assets/styles/mixins/aspect-ratio.scss +16 -0
  77. package/src/assets/styles/mixins/disable-transition.scss +3 -0
  78. package/src/assets/styles/mixins/mixins.scss +5 -0
  79. package/src/assets/styles/mixins/skin.scss +64 -0
  80. package/src/assets/styles/variables.scss +2 -0
  81. package/src/assets/styles/videojs-ima.scss +252 -0
  82. package/src/components/component-utils.js +20 -0
  83. package/src/components/index.js +21 -0
  84. package/src/components/interaction-area/interaction-area.const.js +30 -0
  85. package/src/components/interaction-area/interaction-area.service.js +223 -0
  86. package/src/components/interaction-area/interaction-area.utils.js +236 -0
  87. package/src/components/jumpButtons/jump-10-minus.js +21 -0
  88. package/src/components/jumpButtons/jump-10-plus.js +20 -0
  89. package/src/components/logoButton/logo-button.const.js +3 -0
  90. package/src/components/logoButton/logo-button.js +30 -0
  91. package/src/components/logoButton/logo-button.scss +15 -0
  92. package/src/components/playlist/components/playlist-button.js +34 -0
  93. package/src/components/playlist/components/playlist-next-button.js +18 -0
  94. package/src/components/playlist/components/playlist-previous-button.js +18 -0
  95. package/src/components/playlist/components/playlist.js +5 -0
  96. package/src/components/playlist/components/playlist.scss +15 -0
  97. package/src/components/playlist/components/upcoming-video-overlay.js +149 -0
  98. package/src/components/playlist/components/upcoming-video-overlay.scss +86 -0
  99. package/src/components/playlist/layout/playlist-layout-custom.js +21 -0
  100. package/src/components/playlist/layout/playlist-layout-horizontal.js +16 -0
  101. package/src/components/playlist/layout/playlist-layout-vertical.js +19 -0
  102. package/src/components/playlist/layout/playlist-layout.js +110 -0
  103. package/src/components/playlist/panel/playlist-panel-item.js +86 -0
  104. package/src/components/playlist/panel/playlist-panel.js +92 -0
  105. package/src/components/playlist/playlist-widget.js +119 -0
  106. package/src/components/playlist/playlist.const.js +14 -0
  107. package/src/components/playlist/playlist.js +413 -0
  108. package/src/components/playlist/thumbnail/thumbnail.js +69 -0
  109. package/src/components/progress-control-events-blocker/progress-control-events-blocker.js +17 -0
  110. package/src/components/qualitySelector/quality-selector.scss +10 -0
  111. package/src/components/qualitySelector/qualitySelector.js +152 -0
  112. package/src/components/recommendations-overlay/index.js +3 -0
  113. package/src/components/recommendations-overlay/recommendations-overlay-content.js +57 -0
  114. package/src/components/recommendations-overlay/recommendations-overlay-hide-button.js +18 -0
  115. package/src/components/recommendations-overlay/recommendations-overlay-item.js +35 -0
  116. package/src/components/recommendations-overlay/recommendations-overlay-primary-item.js +81 -0
  117. package/src/components/recommendations-overlay/recommendations-overlay-secondary-item.js +48 -0
  118. package/src/components/recommendations-overlay/recommendations-overlay-secondary-items-container.js +35 -0
  119. package/src/components/recommendations-overlay/recommendations-overlay.js +94 -0
  120. package/src/components/recommendations-overlay/recommendations-overlay.scss +182 -0
  121. package/src/components/shoppable-bar/layout/bar-layout.js +111 -0
  122. package/src/components/shoppable-bar/layout/shoppable-panel-toggle.js +64 -0
  123. package/src/components/shoppable-bar/layout/shoppable-products-overlay.js +87 -0
  124. package/src/components/shoppable-bar/panel/shoppable-panel-item.js +105 -0
  125. package/src/components/shoppable-bar/panel/shoppable-panel.js +172 -0
  126. package/src/components/shoppable-bar/shoppable-post-widget.js +110 -0
  127. package/src/components/shoppable-bar/shoppable-widget.const.js +52 -0
  128. package/src/components/shoppable-bar/shoppable-widget.js +111 -0
  129. package/src/components/shoppable-bar/shoppable-widget.scss +359 -0
  130. package/src/components/themeButton/themedButton.const.js +3 -0
  131. package/src/components/themeButton/themedButton.js +25 -0
  132. package/src/components/title-bar/title-bar.js +79 -0
  133. package/src/config/defaults.js +25 -0
  134. package/src/extended-events.js +228 -0
  135. package/src/index.js +18 -0
  136. package/src/mixins/eventable.js +54 -0
  137. package/src/mixins/playlistable.js +106 -0
  138. package/src/plugins/analytics/index.js +245 -0
  139. package/src/plugins/autoplay-on-scroll/index.js +86 -0
  140. package/src/plugins/cloudinary/common.js +216 -0
  141. package/src/plugins/cloudinary/event-handler-registry.js +46 -0
  142. package/src/plugins/cloudinary/index.js +345 -0
  143. package/src/plugins/cloudinary/models/audio-source/audio-source.const.js +11 -0
  144. package/src/plugins/cloudinary/models/audio-source/audio-source.js +82 -0
  145. package/src/plugins/cloudinary/models/base-source.js +107 -0
  146. package/src/plugins/cloudinary/models/image-source.js +26 -0
  147. package/src/plugins/cloudinary/models/video-source/video-source.const.js +32 -0
  148. package/src/plugins/cloudinary/models/video-source/video-source.js +239 -0
  149. package/src/plugins/cloudinary/models/video-source/video-source.utils.js +57 -0
  150. package/src/plugins/colors/index.js +303 -0
  151. package/src/plugins/context-menu/components/context-menu-item.js +12 -0
  152. package/src/plugins/context-menu/components/context-menu.js +63 -0
  153. package/src/plugins/context-menu/context-menu.scss +30 -0
  154. package/src/plugins/context-menu/contextMenuContent.js +53 -0
  155. package/src/plugins/context-menu/index.js +134 -0
  156. package/src/plugins/dash/index.js +26 -0
  157. package/src/plugins/dash/setup-audio-tracks.js +112 -0
  158. package/src/plugins/dash/setup-text-tracks.js +195 -0
  159. package/src/plugins/dash/videojs-dash.js +372 -0
  160. package/src/plugins/floating-player/floating-player.scss +74 -0
  161. package/src/plugins/floating-player/index.js +129 -0
  162. package/src/plugins/ima/index.js +1775 -0
  163. package/src/plugins/index.js +31 -0
  164. package/src/plugins/interactive-plugin/index.js +10 -0
  165. package/src/plugins/videojs-http-source-selector/components/SourceMenuButton.js +98 -0
  166. package/src/plugins/videojs-http-source-selector/components/SourceMenuItem.js +52 -0
  167. package/src/plugins/videojs-http-source-selector/plugin.js +82 -0
  168. package/src/plugins/videojs-http-source-selector/plugin.scss +9 -0
  169. package/src/plugins/vtt-thumbnails/index.js +526 -0
  170. package/src/plugins/vtt-thumbnails/vtt-thumbnails.scss +29 -0
  171. package/src/utils/api.js +32 -0
  172. package/src/utils/apply-with-props.js +32 -0
  173. package/src/utils/array.js +22 -0
  174. package/src/utils/assign.js +27 -0
  175. package/src/utils/attributes-normalizer.js +72 -0
  176. package/src/utils/cloudinary.js +165 -0
  177. package/src/utils/css-prefix.js +43 -0
  178. package/src/utils/dom.js +74 -0
  179. package/src/utils/find.js +28 -0
  180. package/src/utils/fontFace.js +25 -0
  181. package/src/utils/groupBy.js +12 -0
  182. package/src/utils/index.js +29 -0
  183. package/src/utils/matches.js +11 -0
  184. package/src/utils/mixin.js +5 -0
  185. package/src/utils/object.js +26 -0
  186. package/src/utils/playButton.js +9 -0
  187. package/src/utils/positioning.js +78 -0
  188. package/src/utils/querystring.js +12 -0
  189. package/src/utils/slicing.js +21 -0
  190. package/src/utils/string.js +15 -0
  191. package/src/utils/throttle.js +30 -0
  192. package/src/utils/time.js +77 -0
  193. package/src/utils/type-inference.js +35 -0
  194. package/src/validators/validators-functions.js +48 -0
  195. package/src/validators/validators-types.js +78 -0
  196. package/src/validators/validators.js +110 -0
  197. package/src/video-player.const.js +68 -0
  198. package/src/video-player.js +761 -0
  199. package/src/video-player.utils.js +123 -0
  200. package/test/adaptive-streaming.test.js +38 -0
  201. package/test/ads.test.js +35 -0
  202. package/test/analytics.test.js +111 -0
  203. package/test/api.test.js +111 -0
  204. package/test/autoplay.scroll.test.js +23 -0
  205. package/test/basic-ui.test.js +59 -0
  206. package/test/colors.test.js +58 -0
  207. package/test/components.test.js +21 -0
  208. package/test/custom-error.test.js +24 -0
  209. package/test/fluid.test.js +36 -0
  210. package/test/isValidConfig.test.js +224 -0
  211. package/test/mocks/cloudinary-core-mock.js +0 -0
  212. package/test/mocks/styleMock.js +1 -0
  213. package/test/multiplayer.test.js +25 -0
  214. package/test/playlist.test.js +60 -0
  215. package/test/puppeteer/vp-env.js +19 -0
  216. package/test/recommendations.test.js +38 -0
  217. package/test/title-bar.test.js +28 -0
  218. package/test/ui-conf.test.js +49 -0
  219. package/test/unit/cloudinaryConfig.test.js +22 -0
  220. package/test/unit/cloudinaryUtils.test.js +53 -0
  221. package/test/unit/utils.test.js +27 -0
  222. package/test/unit/videoSource.test.js +454 -0
  223. package/tsconfig.json +15 -0
  224. package/types/video-player-tests.js +12 -0
  225. package/types/video-player-tests.ts +31 -0
  226. package/types/video-player.d.ts +570 -0
@@ -0,0 +1,64 @@
1
+ import videojs from 'video.js';
2
+ import {
3
+ CLD_SPBL_TOGGLE_CLASS,
4
+ CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS,
5
+ CLD_SPBL_TOGGLE_ICON_CLASS,
6
+ CLOSE_ICON_CLASS,
7
+ ICON_CART_CLASS,
8
+ SHOPPABLE_ANIMATION_CLASS
9
+ } from '../shoppable-widget.const';
10
+ const dom = videojs.dom || videojs;
11
+
12
+ const ClickableComponent = videojs.getComponent('ClickableComponent');
13
+
14
+ class ShoppablePanelToggle extends ClickableComponent {
15
+
16
+ constructor(player, options) {
17
+ super(player, options);
18
+ this.options_ = options;
19
+ }
20
+
21
+ handleClick(event) {
22
+ event.preventDefault();
23
+ event.stopPropagation();
24
+ this.options_.clickHandler();
25
+ }
26
+
27
+ createEl() {
28
+ let iconProps = {};
29
+ let iconAttrs = {};
30
+ if (this.options_.toggleIcon) {
31
+ iconProps = {
32
+ className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS} ${CLOSE_ICON_CLASS}`
33
+ };
34
+ iconAttrs = {
35
+ style: `background-image: url(${this.options_.toggleIcon})`
36
+ };
37
+ } else {
38
+ iconProps = {
39
+ className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${ICON_CART_CLASS}`
40
+ };
41
+ }
42
+ const icon = dom.createEl('span', iconProps, iconAttrs);
43
+
44
+ const el = super.createEl('a', {
45
+ className: `${CLD_SPBL_TOGGLE_CLASS} base-color-bg`
46
+ });
47
+ el.appendChild(icon);
48
+
49
+ this.player_.on('productBarMin', () => {
50
+ setTimeout(() => {
51
+ icon.classList.add(SHOPPABLE_ANIMATION_CLASS);
52
+ setTimeout(() => {
53
+ icon.classList.remove(SHOPPABLE_ANIMATION_CLASS);
54
+ }, 1000);
55
+ }, 500);
56
+ });
57
+
58
+ return el;
59
+ }
60
+ }
61
+
62
+ videojs.registerComponent('shoppablePanelToggle', ShoppablePanelToggle);
63
+
64
+ export default ShoppablePanelToggle;
@@ -0,0 +1,87 @@
1
+ import videojs from 'video.js';
2
+ const dom = videojs.dom || videojs;
3
+ import { parseTime } from 'utils/time';
4
+ import { find } from 'utils/find';
5
+ import {
6
+ SHOPPABLE_PANEL_HIDDEN_CLASS,
7
+ SHOPPABLE_PANEL_VISIBLE_CLASS,
8
+ SHOPPABLE_PRODUCTS_OVERLAY_CLASS
9
+ } from '../shoppable-widget.const';
10
+
11
+ const Component = videojs.getComponent('Component');
12
+
13
+ class ShoppableProductsOverlay extends Component {
14
+
15
+ constructor(player, options = {}) {
16
+ super(player, options);
17
+ this.options_ = options;
18
+ this.player_ = player;
19
+
20
+ this.player_.on('showProductsOverlay', () => {
21
+ this.renderProducts();
22
+ });
23
+
24
+ this.dispose = () => {
25
+ this.layout_.dispose();
26
+ };
27
+ }
28
+
29
+ renderProducts() {
30
+ // Close products side-panel
31
+ this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
32
+ this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
33
+ this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
34
+
35
+ this.layout_.innerHTML = '';
36
+
37
+ // Filter products with appearance on currentTime
38
+ const currentTime = this.player_.currentTime();
39
+ const currentProducts = this.options_.products.filter(product => product.hotspots && product.hotspots.some(a => parseTime(a.time) === currentTime));
40
+
41
+ currentProducts.forEach(product => {
42
+ const hotspot = find(product.hotspots, (hs) => parseTime(hs.time) === currentTime);
43
+
44
+ const productName = dom.createEl('div',
45
+ { className: 'cld-spbl-product-hotspot-name' },
46
+ {},
47
+ product.productName
48
+ );
49
+ const productTooltip = dom.createEl('div',
50
+ { className: 'cld-spbl-product-tooltip cld-spbl-product-tooltip-' + hotspot.tooltipPosition },
51
+ {},
52
+ productName
53
+ );
54
+ const productHotSpot = dom.createEl('a',
55
+ {
56
+ className: 'cld-spbl-product-hotspot accent-color-text',
57
+ href: hotspot.clickUrl,
58
+ target: '_blank'
59
+ },
60
+ { style: 'left:' + hotspot.x + '; top:' + hotspot.y + ';' },
61
+ productTooltip
62
+ );
63
+
64
+ this.layout_.appendChild(productHotSpot);
65
+ });
66
+
67
+ // Remove
68
+ this.player_.one('seeking', (e) => this.clearLayout(e));
69
+ this.player_.one('play', (e) => this.clearLayout(e));
70
+ }
71
+
72
+ clearLayout() {
73
+ this.layout_.innerHTML = '';
74
+ this.player_.removeClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
75
+ }
76
+
77
+ createEl() {
78
+ this.layout_ = dom.createEl('div', { className: 'cld-spbl-products-overlay' });
79
+
80
+ return this.layout_;
81
+ }
82
+
83
+ }
84
+
85
+ videojs.registerComponent('ShoppableProductsOverlay', ShoppableProductsOverlay);
86
+
87
+ export default ShoppableProductsOverlay;
@@ -0,0 +1,105 @@
1
+ import videojs from 'video.js';
2
+ import { elMatches } from 'utils/matches';
3
+
4
+ const ClickableComponent = videojs.getComponent('ClickableComponent');
5
+ const dom = videojs.dom || videojs;
6
+ import ImageSource from '../../../plugins/cloudinary/models/image-source';
7
+ import {
8
+ CLD_SPBL_IMAGE,
9
+ CLD_SPBL_ITEM,
10
+ SHOPPABLE_CLICK_ACTIONS, SHOPPABLE_HOVER_ACTIONS,
11
+ SHOPPABLE_WIDGET_RESPONSIVE_CLASS
12
+ } from '../shoppable-widget.const';
13
+
14
+ class ShoppablePanelItem extends ClickableComponent {
15
+
16
+ constructor(player, initOptions) {
17
+ super(player, initOptions);
18
+ this.options_ = initOptions;
19
+ this.isDragged = false;
20
+ }
21
+
22
+ handleClick(event) {
23
+ event.preventDefault();
24
+ event.stopPropagation();
25
+
26
+ if (!elMatches(this.el_, `.dragged .${CLD_SPBL_ITEM}`)) {
27
+ // Prevent click event if dragged
28
+ this.options_.clickHandler(event);
29
+ }
30
+ this.isDragged = false;
31
+ }
32
+
33
+ getTitle() {
34
+ return this.options_.conf.title;
35
+ }
36
+
37
+ createEl() {
38
+ const el = super.createEl('a', {
39
+ className: `${CLD_SPBL_ITEM} base-color-bg accent-color-text`,
40
+ href: '#'
41
+ });
42
+
43
+ el.setAttribute('data-product-id', this.options_.conf.productId || '');
44
+ el.setAttribute('data-product-name', this.options_.conf.productName || '');
45
+
46
+ if (this.options_.conf.onHover) {
47
+ addOnHover(el, this.options_.conf.onHover, this.options_.item.cloudinaryConfig());
48
+ }
49
+ if (this.options_.conf.onClick) {
50
+ addOnClick(el, this.options_.conf.onClick);
51
+ }
52
+
53
+ const img = super.createEl('img',
54
+ { className: `${CLD_SPBL_IMAGE} ${SHOPPABLE_WIDGET_RESPONSIVE_CLASS}` },
55
+ { 'data-src': this.options_.item.url() }
56
+ );
57
+
58
+ el.appendChild(img);
59
+
60
+ if (this.getTitle()) {
61
+ const info = dom.createEl('div', { className: 'cld-spbl-item-info base-color-semi-bg text-color-text' });
62
+ const title = dom.createEl('span', { className: 'cld-spbl-item-title' }, {}, this.getTitle());
63
+ info.appendChild(title);
64
+ el.appendChild(info);
65
+ }
66
+
67
+ return el;
68
+ }
69
+ }
70
+
71
+ const addOnHover = (el, conf, cldConf) => {
72
+ el.setAttribute('data-hover-action', conf.action);
73
+ if (conf.action === SHOPPABLE_HOVER_ACTIONS.OVERLAY) {
74
+ const overlayText = dom.createEl('span', { className: 'cld-spbl-overlay-text base-color-text' }, {}, conf.args);
75
+ const overlay = dom.createEl('span', { className: 'cld-spbl-overlay text-color-semi-bg base-color-text' }, { title: conf.args }, overlayText);
76
+ el.appendChild(overlay);
77
+ } else {
78
+
79
+ const switchImgSource = new ImageSource(conf.args.publicId, {
80
+ cloudinaryConfig: cldConf,
81
+ transformation: conf.args.transformation
82
+ });
83
+
84
+ const hoverImg = dom.createEl('img',
85
+ { className: `${CLD_SPBL_IMAGE} cld-spbl-hover-img ${SHOPPABLE_WIDGET_RESPONSIVE_CLASS}` },
86
+ { 'data-src': switchImgSource.url() }
87
+ );
88
+
89
+ el.appendChild(hoverImg);
90
+ }
91
+ };
92
+
93
+ const addOnClick = (el, conf) => {
94
+ el.setAttribute('data-click-action', conf.action);
95
+ el.setAttribute('data-pause', conf.pause);
96
+ if (conf.action === SHOPPABLE_CLICK_ACTIONS.SEEk) {
97
+ el.setAttribute('data-seek', conf.args.time);
98
+ } else if (conf.action === SHOPPABLE_CLICK_ACTIONS.GO_TO) {
99
+ el.setAttribute('data-goto-url', conf.args.url);
100
+ }
101
+ };
102
+
103
+ videojs.registerComponent('shoppablePanelItem', ShoppablePanelItem);
104
+
105
+ export default ShoppablePanelItem;
@@ -0,0 +1,172 @@
1
+ import videojs from 'video.js';
2
+ import { assign } from 'utils/assign';
3
+ import { throttle } from 'utils/throttle';
4
+ import { parseTime } from 'utils/time';
5
+ import 'assets/styles/components/playlist.scss';
6
+ import ShoppablePanelItem from './shoppable-panel-item';
7
+ import ImageSource from '../../../plugins/cloudinary/models/image-source';
8
+ import {
9
+ CLD_SPBL_PANEL_CLASS,
10
+ SHOPPABLE_CLICK_ACTIONS,
11
+ SHOPPABLE_PANEL_HIDDEN_CLASS,
12
+ SHOPPABLE_PANEL_VISIBLE_CLASS,
13
+ SHOPPABLE_PRODUCTS_OVERLAY_CLASS
14
+ } from '../shoppable-widget.const';
15
+
16
+ const Component = videojs.getComponent('Component');
17
+
18
+ class ShoppablePanel extends Component {
19
+
20
+ constructor(player, options = {}) {
21
+ super(player, options);
22
+ this.options = options;
23
+
24
+ const itemChangeHandler = () => {
25
+ this.render();
26
+ };
27
+ player.on('shoppableitemchanged', itemChangeHandler);
28
+
29
+ this.render();
30
+
31
+ this.dispose = () => {
32
+ super.dispose();
33
+ player.off('shoppableitemchanged', itemChangeHandler);
34
+ };
35
+ }
36
+
37
+ createEl() {
38
+ const el = super.createEl();
39
+ [CLD_SPBL_PANEL_CLASS, 'base-color-bg'].map(cls => el.classList.add(cls));
40
+ return el;
41
+ }
42
+
43
+ removeAll() {
44
+ const childrens = this.children();
45
+ for (let i = childrens.length - 1; i >= 0; --i) {
46
+ this.removeChild(childrens[i]);
47
+ }
48
+ }
49
+
50
+ getItems() {
51
+ const cloudinaryConfig = this.player_.cloudinary.cloudinaryConfig();
52
+ return this.options.products.map(product => {
53
+ if (product.onHover && typeof product.onHover.args === 'object') {
54
+ product.onHover.args.transformation = assign({},
55
+ this.options.transformation,
56
+ product.onHover.args.transformation
57
+ );
58
+ }
59
+ const conf = {
60
+ productId: product.productId,
61
+ productName: product.productName,
62
+ title: product.title,
63
+ onHover: product.onHover,
64
+ onClick: product.onClick,
65
+ startTime: product.startTime,
66
+ endTime: product.endTime
67
+ };
68
+ const imageSource = new ImageSource(product.publicId, {
69
+ cloudinaryConfig: cloudinaryConfig,
70
+ transformation: assign({}, this.options.transformation, product.transformation)
71
+ });
72
+ return {
73
+ imageSrc: imageSource,
74
+ conf: conf
75
+ };
76
+ });
77
+ }
78
+
79
+ scrollToActiveItem() {
80
+ const activeItems = this.el_.getElementsByClassName('active');
81
+ if (activeItems.length > 0) {
82
+ const toScroll = activeItems[0].offsetTop - 12;
83
+ // Test for native scrollTo support (IE will fail)
84
+ if ('scrollBehavior' in document.documentElement.style) {
85
+ this.el_.scrollTo({
86
+ top: toScroll,
87
+ behavior: 'smooth'
88
+ });
89
+ } else {
90
+ this.el_.scrollTop = toScroll;
91
+ }
92
+ }
93
+ }
94
+
95
+ render() {
96
+ this.removeAll();
97
+
98
+ const items = this.getItems();
99
+
100
+ const throttledScrollToActiveItem = throttle(() => this.scrollToActiveItem(), 1000);
101
+
102
+ items.forEach((item, index) => {
103
+ const shoppablePanelItem = new ShoppablePanelItem(this.player(), {
104
+ item: item.imageSrc,
105
+ conf: item.conf,
106
+ next: index === 1,
107
+ current: index === 0,
108
+ clickHandler: (e) => {
109
+ let target = e.currentTarget || e.target;
110
+ let evName = this.player_.ended() ? 'productClickPost' : 'productClick';
111
+ this.player_.trigger(evName, { productId: target.dataset.productId, productName: target.dataset.productName });
112
+
113
+ // Go to URL, or seek video (set currentTime)
114
+ if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.GO_TO) {
115
+ window.open(target.dataset.gotoUrl, '_blank');
116
+ } else if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.SEEk) {
117
+ const gotoSecs = parseTime(target.dataset.seek);
118
+ if (gotoSecs !== null) {
119
+ this.player_.addClass('vjs-has-started'); // Hide the poster image
120
+ if (this.player_.postModal) {
121
+ this.player_.postModal.close();
122
+ }
123
+ this.player_.currentTime(gotoSecs);
124
+ // Close products side-panel
125
+ this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
126
+ this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
127
+ this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
128
+ // Wait for the time update and show the tooltips
129
+ this.player_.one('seeked', () => this.player_.trigger('showProductsOverlay'));
130
+ }
131
+ }
132
+
133
+ // pause - true (default), false, or number of seconds
134
+ if (target.dataset.pause !== 'false') {
135
+ this.player_.pause();
136
+ if (parseTime(target.dataset.pause)) {
137
+ setTimeout(() => {
138
+ this.player_.play();
139
+ }, parseTime(target.dataset.pause) * 1000);
140
+ }
141
+ }
142
+ }
143
+ });
144
+
145
+ shoppablePanelItem.on('mouseover', e => {
146
+ let target = e.currentTarget || e.target;
147
+ let evName = this.player_.ended() ? 'productHoverPost' : 'productHover';
148
+ this.player_.trigger(evName, { productId: target.dataset.productId, productName: target.dataset.productName });
149
+ });
150
+
151
+ if (typeof item.conf.startTime !== 'undefined' && typeof item.conf.endTime !== 'undefined') {
152
+ this.player_.on('timeupdate', () => {
153
+ const time = this.player_.currentTime();
154
+ if (time >= item.conf.startTime && time < item.conf.endTime) {
155
+ shoppablePanelItem.el_.classList.add('active');
156
+ throttledScrollToActiveItem();
157
+ } else if (shoppablePanelItem.el_.classList.contains('active')) {
158
+ shoppablePanelItem.el_.classList.remove('active');
159
+ }
160
+ });
161
+ }
162
+
163
+ this.addChild(shoppablePanelItem);
164
+
165
+ });
166
+
167
+ }
168
+ }
169
+
170
+ videojs.registerComponent('shoppablePanel', ShoppablePanel);
171
+
172
+ export default ShoppablePanel;
@@ -0,0 +1,110 @@
1
+ import videojs from 'video.js';
2
+ import ShoppablePanel from './panel/shoppable-panel.js';
3
+ import { CLD_SPBL_PANEL_CLASS } from './shoppable-widget.const';
4
+ const dom = videojs.dom || videojs;
5
+
6
+ class ShoppablePostWidget {
7
+ constructor(player, options = {}) {
8
+ this.options_ = { ...options, postPlay: true };
9
+ this.player_ = player;
10
+ this.render();
11
+
12
+ // Handle drag-to-scroll
13
+ this.handleDragToScroll();
14
+
15
+ this.dispose = () => {
16
+ this.layout_.dispose();
17
+ };
18
+ }
19
+
20
+ handleDragToScroll() {
21
+ const postModal = this.player_.postModal.el_;
22
+ const slider = postModal.querySelector(`.${CLD_SPBL_PANEL_CLASS}`);
23
+
24
+ let isDown = false;
25
+ let startX = 0;
26
+ let scrollLeft = 0;
27
+
28
+ slider.addEventListener('mousedown', (e) => {
29
+ isDown = true;
30
+ startX = e.pageX - slider.offsetLeft;
31
+ scrollLeft = slider.scrollLeft;
32
+ });
33
+
34
+ document.addEventListener('mouseup', (e) => {
35
+ isDown = false;
36
+ setTimeout(() => {
37
+ slider.classList.remove('dragged');
38
+ }, 300);
39
+
40
+ const x = e.pageX - slider.offsetLeft;
41
+ const walk = x - startX;
42
+ if (Math.abs(walk) > 5) {
43
+ e.preventDefault();
44
+ }
45
+ });
46
+
47
+ document.addEventListener('mousemove', (e) => {
48
+ if (!isDown) {
49
+ return;
50
+ }
51
+ e.preventDefault();
52
+ const x = e.pageX - slider.offsetLeft;
53
+ const walk = (x - startX);
54
+ slider.scrollLeft = scrollLeft - walk;
55
+ if (Math.abs(walk) > 5 && !slider.classList.contains('dragged')) {
56
+ slider.classList.add('dragged');
57
+ }
58
+ });
59
+ }
60
+
61
+ render() {
62
+
63
+ this.player_.postModal = null;
64
+
65
+ const el = dom.createEl('div', { className: 'cld-spbl-post-play' });
66
+ const panel = new ShoppablePanel(this.player_, this.options_);
67
+
68
+ const title = dom.createEl('div', { className: 'cld-spbl-post-title base-color-text' }, {}, this.options_.bannerMsg || 'Shop the Video');
69
+
70
+ // Background - poster + blur effect
71
+ const bgSrc = this.player_.cloudinary.currentPoster();
72
+ bgSrc.transformation([
73
+ bgSrc.transformation().toOptions(),
74
+ { effect: 'blur:3000' }
75
+ ]);
76
+
77
+ const panelBg = dom.createEl('div', {
78
+ className: 'cld-spbl-post-play-bg',
79
+ style: `background-image: url("${bgSrc.url()}")`
80
+ });
81
+
82
+ const replayBtn = dom.createEl('button',
83
+ {
84
+ className: 'cld-spbl-replay-btn base-color-bg vjs-icon-replay',
85
+ onclick: () => {
86
+ this.player_.trigger('replay');
87
+ this.player_.postModal.close();
88
+ this.player_.play();
89
+ }
90
+ },
91
+ {},
92
+ 'Replay'
93
+ );
94
+
95
+ el.appendChild(panelBg);
96
+ el.appendChild(title);
97
+ el.appendChild(panel.el());
98
+ el.appendChild(replayBtn);
99
+
100
+ this.player_.postModal = this.player_.createModal(el, { name: 'postModal', uncloseable: true });
101
+
102
+ this.player_.addClass('cld-spbl-post-modal');
103
+ this.player_.postModal.on('beforemodalclose', () => {
104
+ this.player_.removeClass('cld-spbl-post-modal');
105
+ });
106
+ }
107
+
108
+ }
109
+
110
+ export default ShoppablePostWidget;
@@ -0,0 +1,52 @@
1
+ export const SHOPPABLE_WIDGET_OPTIONS_DEFAULTS = {
2
+ location: 'right',
3
+ toggleIcon: '',
4
+ width: '20%',
5
+ startState: 'openOnPlay',
6
+ autoClose: 2,
7
+ transformation: {
8
+ quality: 'auto',
9
+ width: 'auto',
10
+ fetch_format: 'auto',
11
+ crop: 'scale'
12
+ },
13
+ products: [],
14
+ showPostPlayOverlay: false
15
+ };
16
+
17
+ export const SHOPPABLE_CLICK_ACTIONS = {
18
+ GO_TO: 'goto',
19
+ SEEk: 'seek'
20
+ };
21
+
22
+ export const SHOPPABLE_HOVER_ACTIONS = {
23
+ OVERLAY: 'overlay'
24
+ };
25
+
26
+ export const SHOPPABLE_WIDGET_RESPONSIVE_CLASS = 'cld-vp-responsive';
27
+
28
+ export const SHOPPABLE_PANEL_VISIBLE_CLASS = 'shoppable-panel-visible';
29
+
30
+ export const SHOPPABLE_PANEL_HIDDEN_CLASS = 'shoppable-panel-hidden';
31
+
32
+ export const SHOPPABLE_PRODUCTS_OVERLAY_CLASS = 'shoppable-products-overlay';
33
+
34
+ export const CLD_SPBL_PANEL_CLASS = 'cld-spbl-panel';
35
+
36
+ export const CLD_SPBL_TOGGLE_CLASS = 'cld-spbl-toggle';
37
+
38
+ export const CLD_SPBL_TOGGLE_ICON_CLASS = 'cld-spbl-toggle-icon';
39
+
40
+ export const CLD_SPBL_INNER_BAR = 'cld-spbl-bar-inner';
41
+
42
+ export const CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS = 'cld-spbl-toggle-custom-icon';
43
+
44
+ export const ICON_CART_CLASS = 'vjs-icon-cart';
45
+
46
+ export const CLOSE_ICON_CLASS = 'vjs-icon-close';
47
+
48
+ export const SHOPPABLE_ANIMATION_CLASS = 'animate';
49
+
50
+ export const CLD_SPBL_ITEM = 'cld-spbl-item';
51
+
52
+ export const CLD_SPBL_IMAGE = 'cld-spbl-img';
@@ -0,0 +1,111 @@
1
+ import videojs from 'video.js';
2
+ import ShoppableBarLayout from './layout/bar-layout';
3
+ import ShoppablePostWidget from './shoppable-post-widget';
4
+ import './shoppable-widget.scss';
5
+ import {
6
+ CLD_SPBL_INNER_BAR,
7
+ CLD_SPBL_PANEL_CLASS,
8
+ CLD_SPBL_TOGGLE_CLASS,
9
+ SHOPPABLE_PANEL_VISIBLE_CLASS,
10
+ SHOPPABLE_WIDGET_OPTIONS_DEFAULTS,
11
+ SHOPPABLE_WIDGET_RESPONSIVE_CLASS
12
+ } from './shoppable-widget.const';
13
+
14
+
15
+ class ShoppableWidget {
16
+
17
+ constructor(player, initOptions = {}) {
18
+ this.options_ = videojs.mergeOptions(SHOPPABLE_WIDGET_OPTIONS_DEFAULTS, initOptions);
19
+ this.player_ = player;
20
+
21
+ if (this.options_.showPostPlayOverlay) {
22
+ this.player_.on('ended', () => {
23
+ this.player_.addChild(new ShoppablePostWidget(this.player_, this.options_));
24
+ // Handle responsive images.
25
+ this.player_.player_.cloudinary.cloudinaryConfig().responsive({
26
+ responsive_class: SHOPPABLE_WIDGET_RESPONSIVE_CLASS
27
+ });
28
+ });
29
+ }
30
+
31
+
32
+ const width = this.options_.width;
33
+
34
+ this._injectCSS(`
35
+ .${CLD_SPBL_INNER_BAR} {
36
+ transform: translateX(${width});
37
+ }
38
+ .${SHOPPABLE_PANEL_VISIBLE_CLASS} .vjs-control-bar {
39
+ width: calc(100% - ${width});
40
+ }
41
+ .${CLD_SPBL_TOGGLE_CLASS} {
42
+ right: ${width};
43
+ }
44
+ .${CLD_SPBL_PANEL_CLASS}{
45
+ width: ${width};
46
+ }
47
+ `);
48
+
49
+ this._setListeners();
50
+ }
51
+
52
+ _setListeners() {
53
+ const resizeHandler = this._resizeHandler.bind(this);
54
+
55
+ this.player_.on('resize', resizeHandler);
56
+ window.addEventListener('resize', resizeHandler);
57
+
58
+ this.dispose = () => {
59
+ this.player_.off('resize', resizeHandler);
60
+ window.removeEventListener('resize', resizeHandler);
61
+ this.layout_.dispose();
62
+ };
63
+ }
64
+
65
+ _injectCSS(css) {
66
+ const style = document.createElement('style');
67
+ style.innerHTML = css;
68
+ this.player_.el_.appendChild(style);
69
+ }
70
+
71
+ _resizeHandler() {
72
+
73
+ const shoppableBarBreakpoints = [
74
+ ['sm', 0, 80],
75
+ ['md', 81, 110],
76
+ ['lg', 111, 170]
77
+ ];
78
+
79
+ const shoppableBarWidth = parseFloat(this.options_.width) / 100.0 * this.player_.el_.clientWidth;
80
+ let inRange = false;
81
+ if (shoppableBarWidth) {
82
+ for (const [name, min, max] of shoppableBarBreakpoints) {
83
+ if (shoppableBarWidth > min && shoppableBarWidth <= max) {
84
+ this.layout_.contentWrpEl_.setAttribute('size', name);
85
+ inRange = name;
86
+ }
87
+ }
88
+ if (!inRange) {
89
+ this.layout_.contentWrpEl_.removeAttribute('size');
90
+ }
91
+ }
92
+ }
93
+
94
+ init() {
95
+ this.render();
96
+ }
97
+
98
+ render() {
99
+ this.layout_ = new ShoppableBarLayout(this.player_, this.options_);
100
+ this.player_.on('loadeddata', () => {
101
+ // Handle responsive images.
102
+ this.player_.player_.cloudinary.cloudinaryConfig().responsive({
103
+ responsive_class: SHOPPABLE_WIDGET_RESPONSIVE_CLASS
104
+ });
105
+ });
106
+ }
107
+
108
+ }
109
+
110
+
111
+ export default ShoppableWidget;