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,216 @@
1
+ import cloudinary from 'cloudinary-core';
2
+ import { assign } from 'utils/assign';
3
+ import { sliceAndUnsetProperties } from 'utils/slicing';
4
+ import { isString, isPlainObject } from 'utils/type-inference';
5
+
6
+ const normalizeOptions = (publicId, options, { tolerateMissingId = false } = {}) => {
7
+ if (isPlainObject(publicId)) {
8
+ const _options = assign({}, publicId);
9
+
10
+ publicId = sliceAndUnsetProperties(_options, 'publicId').publicId;
11
+
12
+ if (!isString(publicId) && !tolerateMissingId) {
13
+ throw new Error('Source is missing \'publicId\'.');
14
+ }
15
+
16
+ if (options) {
17
+ options = assign({}, _options, options);
18
+ }
19
+ }
20
+
21
+ return { publicId, options };
22
+ };
23
+
24
+ const isSrcEqual = (source1, source2) => {
25
+ let src1 = source1;
26
+ let src2 = source2;
27
+
28
+ if (typeof source1 === 'object') {
29
+ src1 = source1.src;
30
+ }
31
+
32
+ if (typeof source2 === 'object') {
33
+ src2 = source2.src;
34
+ }
35
+
36
+ if (/^\/\//.test(src1)) {
37
+ src2 = src2.slice(src2.indexOf('//'));
38
+ }
39
+ if (/^\/\//.test(src2)) {
40
+ src1 = src1.slice(src1.indexOf('//'));
41
+ }
42
+
43
+ return src1 === src2;
44
+ };
45
+
46
+ const mergeCloudinaryConfig = (config1, config2) => {
47
+ if (config1.constructor.name === 'Cloudinary' && config1.config) {
48
+ config1 = config1.config();
49
+ }
50
+
51
+ const newConfig = new cloudinary.Cloudinary(config1);
52
+
53
+ if (config2.constructor.name === 'Cloudinary' && config2.config) {
54
+ config2 = config1.config();
55
+ }
56
+
57
+ return newConfig.config(config2);
58
+ };
59
+
60
+ const mergeTransformation = (transformation1, transformation2) => {
61
+ if (transformation1.constructor.name === 'Transformation' && transformation1.toOptions) {
62
+ transformation1 = transformation1.toOptions();
63
+ }
64
+
65
+ const newTransformation = new cloudinary.Transformation(transformation1);
66
+
67
+ return newTransformation.fromOptions(transformation2);
68
+ };
69
+
70
+ const ERROR_CODE = {
71
+ NO_SUPPORTED_MEDIA: 6,
72
+ CUSTOM: 10,
73
+ UNKNOWN_CUSTOMER: 11,
74
+ RESOURCE_NOT_FOUND: 12,
75
+ PRIVATE_RESOURCE: 13,
76
+ UNAUTHENTICATED: 14
77
+ };
78
+
79
+ const cloudinaryErrorsConverter = ({ errorMsg, publicId, cloudName, statusCode }) => {
80
+ const msg = 'Video cannot be played';
81
+ let error = { code: ERROR_CODE.CUSTOM, message: `${msg}${errorMsg ? '- ' + errorMsg : ''}`, statusCode: statusCode };
82
+ let err = errorMsg.toLowerCase();
83
+ if (err.startsWith('unknown customer')) {
84
+ error.code = ERROR_CODE.UNKNOWN_CUSTOMER;
85
+ error.message = `${msg} Unknown cloud-name ${cloudName}`;
86
+ }
87
+ if (err.startsWith('resource not found')) {
88
+ error.code = ERROR_CODE.RESOURCE_NOT_FOUND;
89
+ error.message = `${msg} Public ID ${publicId} not found`;
90
+ }
91
+ if (err.startsWith('private resource')) {
92
+ error.code = ERROR_CODE.PRIVATE_RESOURCE;
93
+ error.message = `${msg} Private video`;
94
+ }
95
+ if (err.startsWith('unauthenticated access')) {
96
+ error.message = `${msg} Requires authentication`;
97
+ error.code = ERROR_CODE.UNAUTHENTICATED;
98
+ }
99
+ return error;
100
+ };
101
+
102
+ const codecShorthandTrans = (short) => {
103
+ const transTable = {
104
+ h265: 'hev1.1.6.L93.B0',
105
+ vp9: 'vp09.00.50.08',
106
+ h264: 'avc1.42E01E'
107
+ };
108
+ return transTable[short] ? transTable[short] : short;
109
+ };
110
+
111
+ const ISOAVC_MAP = {
112
+ 'avc1': 'h264',
113
+ 'avc2': 'h264',
114
+ 'svc1': 'Scalable Video Coding',
115
+ 'mvc1': 'Multiview Video Coding',
116
+ 'mvc2': 'Multiview Video Coding'
117
+ };
118
+
119
+
120
+ const PROFILE = {
121
+ '0': 'No', // 0 - *** when profile=RCDO and level=0 - "RCDO" - RCDO bitstream MUST obey to all the constraints of the Baseline profile
122
+ '42': 'baseline', // 66 in-decimal
123
+ '4d': 'main', // 77 in-decimal
124
+ '58': 'extended', // 88 in-decimal
125
+ '64': 'high', // 100 in-decimal
126
+ '6e': 'high 10', // 110 in-decimal
127
+ '7a': 'high 4:2:2', // 122 in-decimal
128
+ 'f4': 'high 4:4:4', // 244 in-decimal
129
+ '2c': 'CAVLC 4:4:4', // 44 in-decimal
130
+
131
+ // profiles for SVC - Scalable Video Coding extension to H.264
132
+ '53': 'Scalable Baseline', // 83 in-decimal
133
+ '56': 'Scalable High', // 86 in-decimal
134
+
135
+ // profiles for MVC - Multiview Video Coding extension to H.264
136
+ '80': 'Stereo High', // 128 in-decimal
137
+ '76': 'Multiview High', // 118 in-decimal
138
+ '8a': 'Multiview Depth High' // 138 in-decimal
139
+ };
140
+
141
+
142
+ function avcotiToStr(s) {
143
+ let REGEX = /([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i;
144
+
145
+ if (REGEX.test(s) === false) {
146
+ throw new Error('error: please provide a 3-bytes hex-sequence for example: 42001e');
147
+ }
148
+ let matches = s.match(REGEX);
149
+ matches.shift(); // kills first one (regex matchs entire string)
150
+
151
+ let profile_idc = matches[0];
152
+ profile_idc = PROFILE[profile_idc];
153
+ profile_idc = typeof profile_idc === 'string' ? profile_idc : 'Unknown'; // explicit fix.
154
+
155
+ // constraint_set_flags = matches[1]; //maybe some other time..
156
+
157
+ let level_idc = matches[2];
158
+ level_idc = parseInt(level_idc, 16); // will give something like 30 (integer thirty)
159
+ level_idc = String(level_idc).split('').join('.'); // will give something like "3.0"
160
+ return `${profile_idc}:${level_idc}`;
161
+ }
162
+
163
+
164
+ const h264avcToString = (s) => {
165
+ let REGEX = /(avc1|avc2|svc1|mvc1|mvc2)\.([0-9a-f]{6})/i;
166
+ if (REGEX.test('avc1.42001e') === false) {
167
+ throw new Error('Codec string is not formatted according to H.264/AVC standards for example avc1.42001e (maybe an iOS friendly version...)');
168
+ }
169
+ let matches = s.match(REGEX);
170
+ if (matches !== null) {
171
+ matches.shift(); // first one is the entire-string.
172
+
173
+ let vc_codec = ISOAVC_MAP[matches[0]];
174
+ let avc_codec = typeof avc_codec === 'string' ? avc_codec : 'Unknown'; // explicit fix
175
+
176
+ return vc_codec + ':' + avcotiToStr(matches[1]);
177
+ }
178
+ return s;
179
+ };
180
+
181
+ export const VIDEO_CODEC = {
182
+ VP9: 'vp9',
183
+ HEV1: 'hev1',
184
+ H265: 'h265',
185
+ H264: 'h264'
186
+ };
187
+
188
+ const codecToSrcTransformation = (codec) => {
189
+ if (!codec) {
190
+ return {};
191
+ }
192
+
193
+ switch (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` };
200
+ default:
201
+ return { video_codec: h264avcToString(codec) };
202
+ }
203
+ };
204
+
205
+
206
+ export {
207
+ normalizeOptions,
208
+ isSrcEqual,
209
+ mergeCloudinaryConfig,
210
+ mergeTransformation,
211
+ cloudinaryErrorsConverter,
212
+ codecShorthandTrans,
213
+ h264avcToString,
214
+ codecToSrcTransformation,
215
+ ERROR_CODE
216
+ };
@@ -0,0 +1,46 @@
1
+ import { findIndex } from 'utils/find';
2
+
3
+ class EventHandlerRegistry {
4
+ constructor(emitter) {
5
+ this._emitter = emitter;
6
+ this._eventHandlers = [];
7
+ }
8
+
9
+ on(type, handler) {
10
+ this._eventHandlers.push({ type, handler });
11
+ this._emitter.on(type, handler);
12
+ }
13
+
14
+ one(type, handler) {
15
+ const wrapper = (...args) => {
16
+ handler(...args);
17
+ this.off(type, handler);
18
+ };
19
+
20
+ this._eventHandlers.push({ type, handler, wrapper });
21
+ this._emitter.one(type, handler);
22
+ }
23
+
24
+ off(type, handler) {
25
+ const index = findIndex(this._eventHandlers, (event) =>
26
+ event.type === type && event.handler === handler);
27
+
28
+ if (index === -1) {
29
+ return;
30
+ }
31
+
32
+ const event = this._eventHandlers[index];
33
+
34
+ this._emitter.off(type, event.wrapper || event.handler);
35
+
36
+ this._eventHandlers.splice(index, 1);
37
+ }
38
+
39
+ removeAllListeners() {
40
+ this._eventHandlers.forEach((event) => {
41
+ this.off(event);
42
+ });
43
+ }
44
+ }
45
+
46
+ export default EventHandlerRegistry;
@@ -0,0 +1,345 @@
1
+ import cloudinary from 'cloudinary-core';
2
+ import { default as vjs } from 'video.js';
3
+ import { mixin } from 'utils/mixin';
4
+ import { applyWithProps } from 'utils/apply-with-props';
5
+ import { sliceAndUnsetProperties } from 'utils/slicing';
6
+ import { getCloudinaryInstanceOf, isKeyInTransformation } from 'utils/cloudinary';
7
+ import { assign } from 'utils/assign';
8
+ import { normalizeOptions, mergeTransformation, mergeCloudinaryConfig, codecShorthandTrans } from './common';
9
+ import Playlistable from 'mixins/playlistable';
10
+ import VideoSource from './models/video-source/video-source';
11
+ import EventHandlerRegistry from './event-handler-registry';
12
+ import AudioSource from './models/audio-source/audio-source';
13
+ import { isFunction } from '../../utils/type-inference';
14
+
15
+ const DEFAULT_PARAMS = {
16
+ transformation: {},
17
+ sourceTypes: [],
18
+ sourceTransformation: [],
19
+ posterOptions: {}
20
+ };
21
+
22
+ export const CONSTRUCTOR_PARAMS = ['cloudinaryConfig', 'transformation',
23
+ 'sourceTypes', 'sourceTransformation', 'posterOptions', 'autoShowRecommendations'];
24
+
25
+ class CloudinaryContext extends mixin(Playlistable) {
26
+ constructor(player, options = {}) {
27
+ super(player, options);
28
+
29
+ this.player = player;
30
+ options = assign({}, DEFAULT_PARAMS, options);
31
+
32
+ let _source = null;
33
+ let _sources = null;
34
+ let _lastSource = null;
35
+ let _lastPlaylist = null;
36
+ let _posterOptions = null;
37
+ let _cloudinaryConfig = null;
38
+ let _transformation = null;
39
+ let _sourceTypes = null;
40
+ let _sourceTransformation = null;
41
+ let _chainTarget = options.chainTarget;
42
+ let _playerEvents = null;
43
+ let _recommendations = null;
44
+ let _autoShowRecommendations = false;
45
+
46
+ this.source = (source, options = {}) => {
47
+ options = assign({}, options);
48
+
49
+ if (!source) {
50
+ return _source;
51
+ }
52
+
53
+ let src = null;
54
+
55
+ if (source instanceof VideoSource) {
56
+ src = source;
57
+ } else {
58
+ let { publicId, options: _options } = normalizeOptions(source, options);
59
+ src = this.buildSource(publicId, _options);
60
+ }
61
+
62
+ if (src.recommendations()) {
63
+ const recommendations = src.recommendations();
64
+
65
+ let itemBuilder = null;
66
+ let disableAutoShow = false;
67
+
68
+ if (options.recommendationOptions) {
69
+ ({ disableAutoShow, itemBuilder } = sliceAndUnsetProperties(options.recommendationOptions, 'disableAutoShow', 'itemBuilder'));
70
+ }
71
+ setRecommendations(recommendations, { disableAutoShow, itemBuilder });
72
+ } else {
73
+ unsetRecommendations();
74
+ }
75
+
76
+ _source = src;
77
+ if (!options.skipRefresh) {
78
+ refresh();
79
+ }
80
+ this.player.trigger('cldsourcechanged', { source: src });
81
+
82
+ return _chainTarget;
83
+ };
84
+
85
+ this.buildSource = (publicId, options = {}) => {
86
+ let builtSrc = null;
87
+ ({ publicId, options } = normalizeOptions(publicId, options));
88
+
89
+ options.cloudinaryConfig = mergeCloudinaryConfig(this.cloudinaryConfig(), options.cloudinaryConfig || {});
90
+ options.transformation = mergeTransformation(this.transformation(), options.transformation || {});
91
+ options.sourceTransformation = options.sourceTransformation || this.sourceTransformation();
92
+ options.sourceTypes = options.sourceTypes || this.sourceTypes();
93
+ options.poster = options.poster || posterOptionsForCurrent();
94
+ options.queryParams = options.usageReport ? { _s: `vp-${VERSION}` } : {};
95
+
96
+ if (options.sourceTypes.indexOf('audio') > -1) {
97
+ builtSrc = new AudioSource(publicId, options);
98
+ } else {
99
+ builtSrc = new VideoSource(publicId, options);
100
+ }
101
+
102
+ return builtSrc;
103
+ };
104
+
105
+ this.posterOptions = (options) => {
106
+ if (!options) {
107
+ return _posterOptions;
108
+ }
109
+
110
+ _posterOptions = options;
111
+
112
+ return _chainTarget;
113
+ };
114
+
115
+ this.cloudinaryConfig = (config) => {
116
+ if (!config) {
117
+ return _cloudinaryConfig;
118
+ }
119
+
120
+ _cloudinaryConfig = getCloudinaryInstanceOf(cloudinary.Cloudinary, config);
121
+
122
+ return _chainTarget;
123
+ };
124
+
125
+ this.transformation = (trans) => {
126
+ if (!trans) {
127
+ return _transformation;
128
+ }
129
+
130
+ _transformation = getCloudinaryInstanceOf(cloudinary.Transformation, trans);
131
+
132
+ return _chainTarget;
133
+ };
134
+
135
+ this.sourceTypes = (types) => {
136
+ if (!types) {
137
+ return _sourceTypes;
138
+ }
139
+
140
+ _sourceTypes = types;
141
+
142
+ return _chainTarget;
143
+ };
144
+
145
+ this.getCurrentSources = () => _sources;
146
+
147
+ this.sourceTransformation = (trans) => {
148
+ if (!trans) {
149
+ return _sourceTransformation;
150
+ }
151
+
152
+ _sourceTransformation = trans;
153
+
154
+ return _chainTarget;
155
+ };
156
+
157
+ this.on = (...args) => _playerEvents.on(...args);
158
+ this.one = (...args) => _playerEvents.one(...args);
159
+ this.off = (...args) => _playerEvents.off(...args);
160
+
161
+ this.autoShowRecommendations = (autoShow) => {
162
+ if (autoShow === undefined) {
163
+ return _autoShowRecommendations;
164
+ }
165
+
166
+ _autoShowRecommendations = autoShow;
167
+
168
+ return _chainTarget;
169
+ };
170
+
171
+ this.dispose = () => {
172
+ if (this.playlist()) {
173
+ this.disposePlaylist();
174
+ }
175
+ unsetRecommendations();
176
+ _source = undefined;
177
+ _playerEvents.removeAllListeners();
178
+ };
179
+
180
+ const setRecommendations = (recommendations, { disableAutoShow = false, itemBuilder = null }) => {
181
+ unsetRecommendations();
182
+
183
+ if (!Array.isArray(recommendations) && typeof recommendations !== 'function' && !recommendations.then) {
184
+ throw new Error('"recommendations" must be either an array or a function');
185
+ }
186
+
187
+ _recommendations = {};
188
+
189
+ itemBuilder = itemBuilder || ((source) => ({ source: source instanceof VideoSource ? source : this.buildSource(source), action: () => this.source(source) }));
190
+
191
+ _recommendations.sourceChangedHandler = () => {
192
+ const trigger = (sources) => {
193
+ if (typeof sources !== 'undefined' && sources.length > 0) {
194
+ const items = sources.map((_source) => itemBuilder(_source));
195
+ this.player.trigger('recommendationschanged', { items });
196
+ } else {
197
+ this.player.trigger('recommendationsnoshow');
198
+ }
199
+ _recommendations.sources = sources;
200
+ };
201
+
202
+ if (isFunction(recommendations)) {
203
+ trigger(recommendations());
204
+ } else if (recommendations.then) {
205
+ recommendations.then(trigger);
206
+ } else {
207
+ trigger(recommendations);
208
+ }
209
+ };
210
+
211
+ this.one('cldsourcechanged', _recommendations.sourceChangedHandler);
212
+
213
+ _recommendations.endedHandler = () => {
214
+ if (!disableAutoShow && this.autoShowRecommendations()) {
215
+ this.player.trigger('recommendationsshow');
216
+ }
217
+ };
218
+
219
+ this.on('ended', _recommendations.endedHandler);
220
+ };
221
+
222
+ const unsetRecommendations = () => {
223
+ if (_recommendations) {
224
+ this.off('cldsourcechanged', _recommendations.sourceChangedHandler);
225
+ this.off('ended', _recommendations.endedHandler);
226
+ delete _recommendations.endedHandler;
227
+ delete _recommendations.sourceChangedHandler;
228
+ }
229
+
230
+ _recommendations = null;
231
+ };
232
+
233
+ const refresh = () => {
234
+ const src = this.source();
235
+ if (src.poster()) {
236
+ this.player.poster(src.poster().url());
237
+ }
238
+
239
+ _sources = src.generateSources().reduce((srcs, src) => {
240
+ if (src.isAdaptive) {
241
+ let codec = src.type.split('; ')[1] || null;
242
+ if (codec && 'MediaSource' in window) {
243
+ const parts = src.type.split('; ');
244
+ let typeStr = `video/mp4; ${parts[1] || ''}`;
245
+ const canPlay = testCanPlayTypeAndTypeSupported(typeStr);
246
+ if (vjs.browser.IS_ANY_SAFARI) {
247
+ // work around safari saying it cant play h265
248
+ src.type = `${parts[0]}; ${codecShorthandTrans('h264')}`;
249
+ }
250
+ if (canPlay) {
251
+ srcs.push(src);
252
+ }
253
+ } else {
254
+ srcs.push(src);
255
+ }
256
+ } else {
257
+ srcs.push(src);
258
+ }
259
+ return srcs;
260
+ }, []);
261
+ this.player.src(_sources);
262
+
263
+ _lastSource = src;
264
+ _lastPlaylist = this.playlist();
265
+ };
266
+ const testCanPlayTypeAndTypeSupported = (codec) => {
267
+ const v = document.createElement('video');
268
+ return v.canPlayType(codec) || 'MediaSource' in window && MediaSource.isTypeSupported(codec);
269
+ };
270
+
271
+ const posterOptionsForCurrent = () => {
272
+ const opts = assign({}, this.posterOptions());
273
+ if (opts.transformation) {
274
+ if ((opts.transformation.width || opts.transformation.height) && !opts.transformation.crop) {
275
+ opts.transformation.crop = 'scale';
276
+ }
277
+ }
278
+
279
+ opts.transformation = getCloudinaryInstanceOf(cloudinary.Transformation, opts.transformation || {});
280
+
281
+ // Set poster dimensions to player actual size.
282
+ // (unless they were explicitly set via `posterOptions`)
283
+ const playerEl = this.player.el();
284
+ if (playerEl && playerEl.clientWidth && playerEl.clientHeight && !isKeyInTransformation(opts.transformation, 'width') && !isKeyInTransformation(opts.transformation, 'height')) {
285
+ const roundUp100 = (val) => 100 * Math.ceil(val / 100);
286
+
287
+ opts.transformation
288
+ .width(roundUp100(playerEl.clientWidth))
289
+ .height(roundUp100(playerEl.clientHeight))
290
+ .crop('limit');
291
+ }
292
+
293
+ return opts;
294
+ };
295
+
296
+ // Handle external (non-cloudinary plugin) source changes (e.g. by ad plugins)
297
+ const syncState = (_, data) => {
298
+ let src = data.to;
299
+
300
+ // When source is cloudinary's
301
+ if (_lastSource && _lastSource.contains(src)) {
302
+ // If plugin state doesn't have an active VideoSource
303
+ if (!this.source()) {
304
+ // We might have been running a playlist, reset playlist's state.
305
+ if (_lastPlaylist) {
306
+ this.playlist(_lastPlaylist);
307
+ }
308
+ // Rebuild last source state without calling vjs's 'src' and 'poster'
309
+ this.source(_lastSource, { skipRefresh: true });
310
+ }
311
+ } else {
312
+ // Used by cloudinary-only components
313
+ this.player.trigger('cldsourcechanged', {});
314
+
315
+ // When source isn't cloudinary's - reset the plugin's state.
316
+ this.dispose();
317
+ }
318
+ };
319
+
320
+ _playerEvents = new EventHandlerRegistry(this.player);
321
+
322
+ const constructorParams = sliceAndUnsetProperties(options, ...CONSTRUCTOR_PARAMS);
323
+
324
+ applyWithProps(this, constructorParams);
325
+
326
+ this.on('sourcechanged', syncState);
327
+ }
328
+
329
+ currentSourceType() {
330
+ return this.source().getType();
331
+ }
332
+
333
+ currentPublicId() {
334
+ return this.source() && this.source().publicId();
335
+ }
336
+
337
+ currentPoster() {
338
+ return this.source() && this.source().poster();
339
+ }
340
+ }
341
+
342
+ export default function(options = {}) {
343
+ options.chainTarget = options.chainTarget || this;
344
+ this.cloudinary = new CloudinaryContext(this, options);
345
+ }
@@ -0,0 +1,11 @@
1
+ export const DEFAULT_POSTER_PARAMS = { format: 'jpg', resource_type: 'video', transformation: { flags: 'waveform' } };
2
+
3
+ export const COMMON_AUDIO_FORMATS = ['mp3', 'ogg', 'wav', 'mp4'];
4
+
5
+ export const AUDIO_SUFFIX_REMOVAL_PATTERN = RegExp(`\\.(${COMMON_AUDIO_FORMATS.join('|')})$$`);
6
+
7
+ export const DEFAULT_AUDIO_PARAMS = {
8
+ resource_type: 'video',
9
+ type: 'upload',
10
+ transformation: []
11
+ };
@@ -0,0 +1,82 @@
1
+ import VideoSource from '../video-source/video-source';
2
+ import ImageSource from '../image-source';
3
+ import { normalizeOptions } from '../../common';
4
+ import { sliceAndUnsetProperties } from 'utils/slicing';
5
+ import { assign } from 'utils/assign';
6
+ import { objectToQuerystring } from 'utils/querystring';
7
+ import { AUDIO_SUFFIX_REMOVAL_PATTERN, DEFAULT_AUDIO_PARAMS, DEFAULT_POSTER_PARAMS } from './audio-source.const';
8
+
9
+
10
+ class AudioSource extends VideoSource {
11
+
12
+ constructor(publicId, options = {}) {
13
+ ({ publicId, options } = normalizeOptions(publicId, options));
14
+
15
+ publicId = publicId.replace(AUDIO_SUFFIX_REMOVAL_PATTERN, '');
16
+
17
+ options = assign({}, DEFAULT_AUDIO_PARAMS, options);
18
+ const { poster } = sliceAndUnsetProperties(options, 'poster');
19
+
20
+ super(publicId, options);
21
+ this._poster = null;
22
+ this._type = 'AudioSource';
23
+ this.poster(poster);
24
+ }
25
+
26
+ getPoster () {
27
+ return this._poster;
28
+ }
29
+
30
+ poster(publicId, options = {}) {
31
+ if (!publicId) {
32
+ return this._poster;
33
+ }
34
+
35
+ if (publicId instanceof ImageSource) {
36
+ this._poster = publicId;
37
+ return this;
38
+ }
39
+
40
+ ({ publicId, options } = normalizeOptions(publicId, options, { tolerateMissingId: true }));
41
+
42
+ if (!publicId) {
43
+ publicId = this.publicId();
44
+ options = assign({}, options, DEFAULT_POSTER_PARAMS);
45
+ }
46
+
47
+ options.cloudinaryConfig = options.cloudinaryConfig || this.cloudinaryConfig();
48
+ this._poster = new ImageSource(publicId, options);
49
+
50
+ return this;
51
+ }
52
+
53
+ generateSources() {
54
+ return this.sourceTypes().map((sourceType) => {
55
+
56
+ if (sourceType === 'audio') {
57
+ const format = 'mp3';
58
+ const opts = {};
59
+ const srcTransformation = this.sourceTransformation()[sourceType] || [this.transformation()];
60
+
61
+ if (srcTransformation) {
62
+ opts.transformation = srcTransformation;
63
+ }
64
+
65
+ assign(opts, { resource_type: 'video', format });
66
+ let queryString = '';
67
+
68
+ if (this.queryParams()) {
69
+ queryString = objectToQuerystring(this.queryParams());
70
+ }
71
+
72
+ const src = `${this.config().url(this.publicId(), opts)}${queryString}`;
73
+ const type = 'video/mp4';
74
+ return { type, src, cldSrc: this, poster: this.getPoster().url() };
75
+ } else {
76
+ return null;
77
+ }
78
+ }, this);
79
+ }
80
+ }
81
+
82
+ export default AudioSource;