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,112 @@
1
+ import dashjs from 'dashjs';
2
+ import videojs from 'video.js';
3
+ import { find } from 'utils/find';
4
+
5
+ /**
6
+ * Setup audio tracks. Take the tracks from dash and add the tracks to videojs. Listen for when
7
+ * videojs changes tracks and apply that to the dash player because videojs doesn't do this
8
+ * natively.
9
+ *
10
+ * @private
11
+ * @param {videojs} player the videojs player instance
12
+ * @param {videojs.tech} tech the videojs tech being used
13
+ */
14
+ function handlePlaybackMetadataLoaded(player, tech) {
15
+ const mediaPlayer = player.dash.mediaPlayer;
16
+
17
+ const dashAudioTracks = mediaPlayer.getTracksFor('audio');
18
+ const videojsAudioTracks = player.audioTracks();
19
+
20
+ function generateIdFromTrackIndex(index) {
21
+ return `dash-audio-${index}`;
22
+ }
23
+
24
+ function findDashAudioTrack(subDashAudioTracks, videojsAudioTrack) {
25
+ return find(subDashAudioTracks, (track) => generateIdFromTrackIndex(track.index) === videojsAudioTrack.id);
26
+ }
27
+
28
+ // Safari creates a single native `AudioTrack` (not `videojs.AudioTrack`) when loading. Clear all
29
+ // automatically generated audio tracks so we can create them all ourself.
30
+ if (videojsAudioTracks.length) {
31
+ tech.clearTracks(['audio']);
32
+ }
33
+
34
+ const currentAudioTrack = mediaPlayer.getCurrentTrackFor('audio');
35
+
36
+ dashAudioTracks.forEach((dashTrack) => {
37
+ let localizedLabel = null;
38
+
39
+ if (Array.isArray(dashTrack.labels)) {
40
+ for (let i = 0; i < dashTrack.labels.length; i++) {
41
+ if (
42
+ dashTrack.labels[i].lang &&
43
+ player.language().indexOf(dashTrack.labels[i].lang.toLowerCase()) !== -1
44
+ ) {
45
+ localizedLabel = dashTrack.labels[i];
46
+
47
+ break;
48
+ }
49
+ }
50
+ }
51
+
52
+ let label = null;
53
+
54
+ if (localizedLabel) {
55
+ label = localizedLabel.text;
56
+ } else if (Array.isArray(dashTrack.labels) && dashTrack.labels.length === 1) {
57
+ label = dashTrack.labels[0].text;
58
+ } else {
59
+ label = dashTrack.lang;
60
+
61
+ if (dashTrack.roles && dashTrack.roles.length) {
62
+ label += ' (' + dashTrack.roles.join(', ') + ')';
63
+ }
64
+ }
65
+
66
+ // Add the track to the player's audio track list.
67
+ videojsAudioTracks.addTrack(
68
+ new videojs.AudioTrack({
69
+ enabled: dashTrack === currentAudioTrack,
70
+ id: generateIdFromTrackIndex(dashTrack.index),
71
+ kind: dashTrack.kind || 'main',
72
+ label,
73
+ language: dashTrack.lang
74
+ })
75
+ );
76
+ });
77
+
78
+ const audioTracksChangeHandler = () => {
79
+ for (let i = 0; i < videojsAudioTracks.length; i++) {
80
+ const track = videojsAudioTracks[i];
81
+
82
+ if (track.enabled) {
83
+ // Find the audio track we just selected by the id
84
+ const dashAudioTrack = findDashAudioTrack(dashAudioTracks, track);
85
+
86
+ // Set is as the current track
87
+ mediaPlayer.setCurrentTrack(dashAudioTrack);
88
+
89
+ // Stop looping
90
+ // eslint-disable-next-line no-continue
91
+ continue;
92
+ }
93
+ }
94
+ };
95
+
96
+ videojsAudioTracks.addEventListener('change', audioTracksChangeHandler);
97
+ player.dash.mediaPlayer.on(dashjs.MediaPlayer.events.STREAM_TEARDOWN_COMPLETE, () => {
98
+ videojsAudioTracks.removeEventListener('change', audioTracksChangeHandler);
99
+ });
100
+ }
101
+
102
+ /*
103
+ * Call `handlePlaybackMetadataLoaded` when `mediaPlayer` emits
104
+ * `dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED`.
105
+ */
106
+ export default function setupAudioTracks(player, tech) {
107
+ // When `dashjs` finishes loading metadata, create audio tracks for `video.js`.
108
+ player.dash.mediaPlayer.on(
109
+ dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED,
110
+ handlePlaybackMetadataLoaded.bind(null, player, tech)
111
+ );
112
+ }
@@ -0,0 +1,195 @@
1
+ import dashjs from 'dashjs';
2
+ import videojs from 'video.js';
3
+ import window from 'global/window';
4
+
5
+ function find(l, f) {
6
+ for (let i = 0; i < l.length; i++) {
7
+ if (f(l[i])) {
8
+ return l[i];
9
+ }
10
+ }
11
+ }
12
+
13
+ /*
14
+ * Attach text tracks from dash.js to videojs
15
+ *
16
+ * @param {videojs} player the videojs player instance
17
+ * @param {array} tracks the tracks loaded by dash.js to attach to videojs
18
+ *
19
+ * @private
20
+ */
21
+ function attachDashTextTracksToVideojs(player, tech, tracks) {
22
+ const trackDictionary = [];
23
+
24
+ // Add remote tracks
25
+ const tracksAttached = tracks
26
+ // Map input data to match HTMLTrackElement spec
27
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLTrackElement
28
+ .map((track) => {
29
+ let localizedLabel = null;
30
+
31
+ if (Array.isArray(track.labels)) {
32
+ for (let i = 0; i < track.labels.length; i++) {
33
+ if (
34
+ track.labels[i].lang &&
35
+ player.language().indexOf(track.labels[i].lang.toLowerCase()) !== -1
36
+ ) {
37
+ localizedLabel = track.labels[i];
38
+
39
+ break;
40
+ }
41
+ }
42
+ }
43
+
44
+ let label = null;
45
+
46
+ if (localizedLabel) {
47
+ label = localizedLabel.text;
48
+ } else if (Array.isArray(track.labels) && track.labels.length === 1) {
49
+ label = track.labels[0].text;
50
+ } else {
51
+ label = track.lang || track.label;
52
+ }
53
+
54
+ return {
55
+ dashTrack: track,
56
+ trackConfig: {
57
+ label,
58
+ language: track.lang,
59
+ srclang: track.lang,
60
+ kind: track.kind
61
+ }
62
+ };
63
+ })
64
+
65
+ // Add track to videojs track list
66
+ .map(({ trackConfig, dashTrack }) => {
67
+ const remoteTextTrack = player.addRemoteTextTrack(trackConfig, false);
68
+
69
+ trackDictionary.push({ textTrack: remoteTextTrack.track, dashTrack });
70
+
71
+ // Don't add the cues becuase we're going to let dash handle it natively. This will ensure
72
+ // that dash handle external time text files and fragmented text tracks.
73
+ //
74
+ // Example file with external time text files:
75
+ // https://storage.googleapis.com/shaka-demo-assets/sintel-mp4-wvtt/dash.mpd
76
+
77
+ return remoteTextTrack;
78
+ })
79
+
80
+ ;
81
+
82
+ /*
83
+ * Scan `videojs.textTracks()` to find one that is showing. Set the dash text track.
84
+ */
85
+ function updateActiveDashTextTrack() {
86
+ const dashMediaPlayer = player.dash.mediaPlayer;
87
+ const textTracks = player.textTracks();
88
+ let activeTextTrackIndex = -1;
89
+
90
+ // Iterate through the tracks and find the one marked as showing. If none are showing,
91
+ // `activeTextTrackIndex` will be set to `-1`, disabling text tracks.
92
+ for (let i = 0; i < textTracks.length; i += 1) {
93
+ const textTrack = textTracks[i];
94
+
95
+ if (textTrack.mode === 'showing') {
96
+ // Find the dash track we want to use
97
+
98
+ /* jshint loopfunc: true */
99
+ const dictionaryLookupResult = find(trackDictionary,
100
+ (track) => track.textTrack === textTrack);
101
+ /* jshint loopfunc: false */
102
+
103
+ const dashTrackToActivate = dictionaryLookupResult
104
+ ? dictionaryLookupResult.dashTrack
105
+ : null;
106
+
107
+ // If we found a track, get it's index.
108
+ if (dashTrackToActivate) {
109
+ activeTextTrackIndex = tracks.indexOf(dashTrackToActivate);
110
+ }
111
+ }
112
+ }
113
+
114
+ // If the text track has changed, then set it in dash
115
+ if (activeTextTrackIndex !== dashMediaPlayer.getCurrentTextTrackIndex()) {
116
+ dashMediaPlayer.setTextTrack(activeTextTrackIndex);
117
+ }
118
+ }
119
+
120
+ // Update dash when videojs's selected text track changes.
121
+ player.textTracks().on('change', updateActiveDashTextTrack);
122
+
123
+ // Cleanup event listeners whenever we start loading a new source
124
+ player.dash.mediaPlayer.on(dashjs.MediaPlayer.events.STREAM_TEARDOWN_COMPLETE, () => {
125
+ player.textTracks().off('change', updateActiveDashTextTrack);
126
+ });
127
+
128
+ // Initialize the text track on our first run-through
129
+ updateActiveDashTextTrack();
130
+
131
+ return tracksAttached;
132
+ }
133
+
134
+ /*
135
+ * Wait for dash to emit `TEXT_TRACKS_ADDED` and then attach the text tracks loaded by dash if
136
+ * we're not using native text tracks.
137
+ *
138
+ * @param {videojs} player the videojs player instance
139
+ * @private
140
+ */
141
+ export default function setupTextTracks(player, tech, options) {
142
+ // Clear VTTCue if it was shimmed by vttjs and let dash.js use TextTrackCue.
143
+ // This is necessary because dash.js creates text tracks
144
+ // using addTextTrack which is incompatible with vttjs.VTTCue in IE11
145
+ if (window.VTTCue && !(/\[native code\]/).test(window.VTTCue.toString())) {
146
+ window.VTTCue = false;
147
+ }
148
+
149
+ // Store the tracks that we've added so we can remove them later.
150
+ let dashTracksAttachedToVideoJs = [];
151
+
152
+ // We're relying on the user to disable native captions. Show an error if they didn't do so.
153
+ if (tech.featuresNativeTextTracks) {
154
+ videojs.log.error('You must pass {html: {nativeCaptions: false}} in the videojs constructor ' +
155
+ 'to use text tracks in videojs-contrib-dash');
156
+ return;
157
+ }
158
+
159
+ const mediaPlayer = player.dash.mediaPlayer;
160
+
161
+ // Clear the tracks that we added. We don't clear them all because someone else can add tracks.
162
+ function clearDashTracks() {
163
+ dashTracksAttachedToVideoJs.forEach(player.removeRemoteTextTrack.bind(player));
164
+
165
+ dashTracksAttachedToVideoJs = [];
166
+ }
167
+
168
+ // eslint-disable-next-line no-unused-vars
169
+ function handleTextTracksAdded({ index, tracks }) {
170
+ // Stop listening for this event. We only want to hear it once.
171
+ mediaPlayer.off(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
172
+
173
+ // Cleanup old tracks
174
+ clearDashTracks();
175
+
176
+ if (!tracks.length) {
177
+ // Don't try to add text tracks if there aren't any
178
+ return;
179
+ }
180
+
181
+ // Save the tracks so we can remove them later
182
+ dashTracksAttachedToVideoJs = attachDashTextTracksToVideojs(player, tech, tracks, options);
183
+ }
184
+
185
+ // Attach dash text tracks whenever we dash emits `TEXT_TRACKS_ADDED`.
186
+ mediaPlayer.on(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
187
+
188
+ // When the player can play, remove the initialization events. We might not have received
189
+ // TEXT_TRACKS_ADDED` so we have to stop listening for it or we'll get errors when we load new
190
+ // videos and are listening for the same event in multiple places, including cleaned up
191
+ // mediaPlayers.
192
+ mediaPlayer.on(dashjs.MediaPlayer.events.CAN_PLAY, () => {
193
+ mediaPlayer.off(dashjs.MediaPlayer.events.TEXT_TRACKS_ADDED, handleTextTracksAdded);
194
+ });
195
+ }
@@ -0,0 +1,372 @@
1
+ import window from 'global/window';
2
+ import videojs from 'video.js';
3
+ import dashjs from 'dashjs';
4
+ import setupAudioTracks from './setup-audio-tracks';
5
+ import setupTextTracks from './setup-text-tracks';
6
+ import document from 'global/document';
7
+ import { assign } from '../../utils/assign';
8
+
9
+ /**
10
+ * videojs-contrib-dash
11
+ *
12
+ * Use Dash.js to playback DASH content inside of Video.js via a SourceHandler
13
+ */
14
+
15
+ class Html5DashJS {
16
+ constructor(source, tech, options) {
17
+ // Get options from tech if not provided for backwards compatibility
18
+ options = options || tech.options_;
19
+
20
+ this.player = videojs(options.playerId);
21
+ this.player.dash = this.player.dash || {};
22
+
23
+ this.tech_ = tech;
24
+ this.el_ = tech.el();
25
+ this.elParent_ = this.el_.parentNode;
26
+ this.hasFiniteDuration_ = false;
27
+
28
+ // Do nothing if the src is falsey
29
+ if (!source.src) {
30
+ return;
31
+ }
32
+
33
+ // While the manifest is loading and Dash.js has not finished initializing
34
+ // we must defer events and functions calls with isReady_ and then `triggerReady`
35
+ // again later once everything is setup
36
+ tech.isReady_ = false;
37
+
38
+ if (Html5DashJS.updateSourceData) {
39
+ videojs.log.warn('updateSourceData has been deprecated.' +
40
+ ' Please switch to using hook("updatesource", callback).');
41
+ source = Html5DashJS.updateSourceData(source);
42
+ }
43
+
44
+ // call updatesource hooks
45
+ Html5DashJS.hooks('updatesource').forEach((hook) => {
46
+ source = hook(source);
47
+ });
48
+
49
+ const manifestSource = source.src;
50
+
51
+ this.keySystemOptions_ = Html5DashJS.buildDashJSProtData(source.keySystemOptions);
52
+
53
+ // eslint-disable-next-line new-cap
54
+ this.player.dash.mediaPlayer = dashjs.MediaPlayer().create();
55
+
56
+ this.mediaPlayer_ = this.player.dash.mediaPlayer;
57
+
58
+ // For whatever reason, we need to call setTextDefaultEnabled(false) to get
59
+ // VTT captions to show, even though we're doing virtually the same thing
60
+ // in setup-text-tracks.js
61
+ this.mediaPlayer_.setTextDefaultEnabled(false);
62
+
63
+ // Log MedaPlayer messages through video.js
64
+ if (Html5DashJS.useVideoJSDebug) {
65
+ videojs.log.warn('useVideoJSDebug has been deprecated.' +
66
+ ' Please switch to using hook("beforeinitialize", callback).');
67
+ Html5DashJS.useVideoJSDebug(this.mediaPlayer_);
68
+ }
69
+
70
+ if (Html5DashJS.beforeInitialize) {
71
+ videojs.log.warn('beforeInitialize has been deprecated.' +
72
+ ' Please switch to using hook("beforeinitialize", callback).');
73
+ Html5DashJS.beforeInitialize(this.player, this.mediaPlayer_);
74
+ }
75
+
76
+ Html5DashJS.hooks('beforeinitialize').forEach((hook) => {
77
+ hook(this.player, this.mediaPlayer_);
78
+ });
79
+
80
+ // Must run controller before these two lines or else there is no
81
+ // element to bind to.
82
+ this.mediaPlayer_.initialize();
83
+
84
+ // Retrigger a dash.js-specific error event as a player error
85
+ // See src/streaming/utils/ErrorHandler.js in dash.js code
86
+ // Handled with error (playback is stopped):
87
+ // - capabilityError
88
+ // - downloadError
89
+ // - manifestError
90
+ // - mediaSourceError
91
+ // - mediaKeySessionError
92
+ // Not handled:
93
+ // - timedTextError (video can still play)
94
+ // - mediaKeyMessageError (only fires under 'might not work' circumstances)
95
+ // eslint-disable-next-line complexity
96
+ this.retriggerError_ = (event) => {
97
+ if (event.type === 'error') {
98
+
99
+ /* initialize errros for ref https://cdn.dashjs.org/latest/jsdoc/core_errors_Errors.js.html
100
+ map to general init error
101
+ */
102
+ if (event.error.code >= 10 && event.error.code <= 35) {
103
+ this.player.error({ code: 4, dashjsErrorCode: event.error.code, message: event.error.message });
104
+ } else if (event.error.code < 10) { // network errors
105
+ this.player.error({ code: event.error.code, dashjsErrorCode: event.error.code, message: event.error.message });
106
+ } else if (event.error.code >= 100 && event.error.code <= 114) { // Protection Errors https://cdn.dashjs.org/latest/jsdoc/streaming_protection_errors_ProtectionErrors.js.html
107
+ this.player.error({ code: 5, dashjsErrorCode: event.error.code, message: event.error.message });
108
+ } else {
109
+ this.player.error({ code: event.error.code, message: event.error.message });
110
+ }
111
+ }
112
+
113
+ // only reset the dash player in 10ms async, so that the rest of the
114
+ // calling function finishes
115
+ setTimeout(() => {
116
+ this.mediaPlayer_.reset();
117
+ }, 10);
118
+ };
119
+
120
+ this.mediaPlayer_.on(dashjs.MediaPlayer.events.ERROR, this.retriggerError_);
121
+
122
+ this.getDuration_ = (event) => {
123
+ const periods = event.data.Period_asArray;
124
+ const oldHasFiniteDuration = this.hasFiniteDuration_;
125
+
126
+ if (event.data.mediaPresentationDuration || periods[periods.length - 1].duration) {
127
+ this.hasFiniteDuration_ = true;
128
+ } else {
129
+ // in case we run into a weird situation where we're VOD but then
130
+ // switch to live
131
+ this.hasFiniteDuration_ = false;
132
+ }
133
+
134
+ if (this.hasFiniteDuration_ !== oldHasFiniteDuration) {
135
+ this.player.trigger('durationchange');
136
+ }
137
+ };
138
+
139
+ this.mediaPlayer_.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, this.getDuration_);
140
+
141
+ // Apply all dash options that are set
142
+ if (options.dash) {
143
+ Object.keys(options.dash).forEach((key) => {
144
+ const dashOptionsKey = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
145
+ let value = options.dash[key];
146
+
147
+ // eslint-disable-next-line no-prototype-builtins
148
+ if (this.mediaPlayer_.hasOwnProperty(dashOptionsKey)) {
149
+ // Providing a key without `set` prefix is now deprecated.
150
+ videojs.log.warn('Using dash options in videojs-contrib-dash without the set prefix ' +
151
+ `has been deprecated. Change '${key}' to '${dashOptionsKey}'`);
152
+
153
+ // Set key so it will still work
154
+ key = dashOptionsKey;
155
+ }
156
+
157
+ // eslint-disable-next-line no-prototype-builtins
158
+ if (!this.mediaPlayer_.hasOwnProperty(key)) {
159
+ videojs.log.warn(
160
+ `Warning: dash configuration option unrecognized: ${key}`
161
+ );
162
+
163
+ return;
164
+ }
165
+
166
+ // Guarantee `value` is an array
167
+ if (!Array.isArray(value)) {
168
+ value = [value];
169
+ }
170
+
171
+ this.mediaPlayer_[key](...value);
172
+ });
173
+ }
174
+
175
+ this.mediaPlayer_.attachView(this.el_);
176
+
177
+ // Dash.js autoplays by default, video.js will handle autoplay
178
+ this.mediaPlayer_.setAutoPlay(false);
179
+
180
+ // Setup audio tracks
181
+ // eslint-disable-next-line no-useless-call
182
+ setupAudioTracks.call(null, this.player, tech);
183
+
184
+ // Setup text tracks
185
+ // eslint-disable-next-line no-useless-call
186
+ setupTextTracks.call(null, this.player, tech, options);
187
+
188
+ // Attach the source with any protection data
189
+ this.mediaPlayer_.setProtectionData(this.keySystemOptions_);
190
+ this.mediaPlayer_.attachSource(manifestSource);
191
+ this.tech_.triggerReady();
192
+ // map videojs seek
193
+ this.player.on(this.tech_, 'seeking', () => {
194
+ // handle seek the same way as in dash.js
195
+ this.mediaPlayer_.seek((this.tech_.currentTime() - 8).toFixed(2));
196
+ });
197
+ }
198
+
199
+ /*
200
+ * Iterate over the `keySystemOptions` array and convert each object into
201
+ * the type of object Dash.js expects in the `protData` argument.
202
+ *
203
+ * Also rename 'licenseUrl' property in the options to an 'serverURL' property
204
+ */
205
+ static buildDashJSProtData(keySystemOptions) {
206
+ const output = {};
207
+
208
+ if (!keySystemOptions || !Array.isArray(keySystemOptions)) {
209
+ return null;
210
+ }
211
+
212
+ for (let i = 0; i < keySystemOptions.length; i++) {
213
+ const keySystem = keySystemOptions[i];
214
+ const options = videojs.mergeOptions({}, keySystem.options);
215
+
216
+ if (options.licenseUrl) {
217
+ options.serverURL = options.licenseUrl;
218
+ delete options.licenseUrl;
219
+ }
220
+
221
+ output[keySystem.name] = options;
222
+ }
223
+
224
+ return output;
225
+ }
226
+
227
+ dispose() {
228
+ if (this.mediaPlayer_) {
229
+ this.mediaPlayer_.off(dashjs.MediaPlayer.events.ERROR, this.retriggerError_);
230
+ this.mediaPlayer_.off(dashjs.MediaPlayer.events.MANIFEST_LOADED, this.getDuration_);
231
+ this.mediaPlayer_.reset();
232
+ }
233
+
234
+ if (this.player.dash) {
235
+ delete this.player.dash;
236
+ }
237
+ }
238
+
239
+ duration() {
240
+ if (this.mediaPlayer_.isDynamic() && !this.hasFiniteDuration_) {
241
+ return Infinity;
242
+ }
243
+ return this.mediaPlayer_.duration();
244
+
245
+ }
246
+
247
+ /**
248
+ * Get a list of hooks for a specific lifecycle
249
+ *
250
+ * @param {string} type the lifecycle to get hooks from
251
+ * @param {Function=|Function[]=} hook Optionally add a hook tothe lifecycle
252
+ * @return {Array} an array of hooks or epty if none
253
+ * @method hooks
254
+ */
255
+ static hooks(type, hook) {
256
+ Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type] || [];
257
+
258
+ if (hook) {
259
+ Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].concat(hook);
260
+ }
261
+
262
+ return Html5DashJS.hooks_[type];
263
+ }
264
+
265
+ /**
266
+ * Add a function hook to a specific dash lifecycle
267
+ *
268
+ * @param {string} type the lifecycle to hook the function to
269
+ * @param {Function|Function[]} hook the function or array of functions to attach
270
+ * @method hook
271
+ */
272
+ static hook(type, hook) {
273
+ Html5DashJS.hooks(type, hook);
274
+ }
275
+
276
+ /**
277
+ * Remove a hook from a specific dash lifecycle.
278
+ *
279
+ * @param {string} type the lifecycle that the function hooked to
280
+ * @param {Function} hook The hooked function to remove
281
+ * @return {boolean} True if the function was removed, false if not found
282
+ * @method removeHook
283
+ */
284
+ static removeHook(type, hook) {
285
+ const index = Html5DashJS.hooks(type).indexOf(hook);
286
+
287
+ if (index === -1) {
288
+ return false;
289
+ }
290
+
291
+ Html5DashJS.hooks_[type] = Html5DashJS.hooks_[type].slice();
292
+ Html5DashJS.hooks_[type].splice(index, 1);
293
+
294
+ return true;
295
+ }
296
+ }
297
+
298
+ Html5DashJS.hooks_ = {};
299
+
300
+ const canHandleKeySystems = function(source) {
301
+ // copy the source
302
+ source = assign({}, source);
303
+
304
+ if (Html5DashJS.updateSourceData) {
305
+ videojs.log.warn('updateSourceData has been deprecated.' +
306
+ ' Please switch to using hook("updatesource", callback).');
307
+ source = Html5DashJS.updateSourceData(source);
308
+ }
309
+
310
+ // call updatesource hooks
311
+ Html5DashJS.hooks('updatesource').forEach((hook) => {
312
+ source = hook(source);
313
+ });
314
+
315
+ const videoEl = document.createElement('video');
316
+
317
+ if (source.keySystemOptions &&
318
+ !(window.navigator.requestMediaKeySystemAccess ||
319
+ // IE11 Win 8.1
320
+ videoEl.msSetMediaKeys)) {
321
+ return false;
322
+ }
323
+
324
+ return true;
325
+ };
326
+
327
+ videojs.DashSourceHandler = function() {
328
+ return {
329
+ canHandleSource(source) {
330
+ const dashExtRE = /\.mpd/i;
331
+
332
+ if (!canHandleKeySystems(source)) {
333
+ return '';
334
+ }
335
+
336
+ if (videojs.DashSourceHandler.canPlayType(source.type)) {
337
+ return 'probably';
338
+ } else if (dashExtRE.test(source.src)) {
339
+ return 'maybe';
340
+ }
341
+ return '';
342
+
343
+ },
344
+
345
+ handleSource(source, tech, options) {
346
+ return new Html5DashJS(source, tech, options);
347
+ },
348
+
349
+ canPlayType(type) {
350
+ return videojs.DashSourceHandler.canPlayType(type);
351
+ }
352
+ };
353
+ };
354
+
355
+ videojs.DashSourceHandler.canPlayType = function(type) {
356
+ const dashTypeRE = /^application\/dash\+xml/i;
357
+
358
+ if (dashTypeRE.test(type)) {
359
+ return 'probably';
360
+ }
361
+
362
+ return '';
363
+ };
364
+
365
+ // Only add the SourceHandler if the browser supports MediaSourceExtensions
366
+ if (window.MediaSource) {
367
+ // eslint-disable-next-line new-cap
368
+ videojs.getTech('Html5').registerSourceHandler(videojs.DashSourceHandler(), 0);
369
+ }
370
+
371
+ videojs.Html5DashJS = Html5DashJS;
372
+ export default Html5DashJS;