@cloudnest/redxplyr 1.0.0

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 (127) hide show
  1. package/.editorconfig +10 -0
  2. package/.gitpod.yml +6 -0
  3. package/.node-version +1 -0
  4. package/.prettierrc +7 -0
  5. package/.stickler.yml +5 -0
  6. package/.stylelintrc.json +26 -0
  7. package/CHANGELOG.md +16 -0
  8. package/CONTRIBUTING.md +34 -0
  9. package/CONTROLS.md +49 -0
  10. package/Dockerfile +32 -0
  11. package/LICENSE.md +22 -0
  12. package/README.md +194 -0
  13. package/cspell.json +48 -0
  14. package/dist/redxplyr.css +1 -0
  15. package/dist/redxplyr.js +8801 -0
  16. package/dist/redxplyr.min.js +2 -0
  17. package/dist/redxplyr.min.js.map +1 -0
  18. package/dist/redxplyr.min.mjs +1 -0
  19. package/dist/redxplyr.min.mjs.map +1 -0
  20. package/dist/redxplyr.mjs +8793 -0
  21. package/dist/redxplyr.polyfilled.js +9294 -0
  22. package/dist/redxplyr.polyfilled.min.js +2 -0
  23. package/dist/redxplyr.polyfilled.min.js.map +1 -0
  24. package/dist/redxplyr.polyfilled.min.mjs +1 -0
  25. package/dist/redxplyr.polyfilled.min.mjs.map +1 -0
  26. package/dist/redxplyr.polyfilled.mjs +9286 -0
  27. package/dist/redxplyr.svg +1 -0
  28. package/eslint.config.mjs +39 -0
  29. package/gulpfile.js +8 -0
  30. package/package.json +114 -0
  31. package/pnpm-workspace.yaml +8 -0
  32. package/src/js/captions.js +411 -0
  33. package/src/js/config/defaults.js +459 -0
  34. package/src/js/config/states.js +10 -0
  35. package/src/js/config/types.js +34 -0
  36. package/src/js/console.js +28 -0
  37. package/src/js/controls.js +1870 -0
  38. package/src/js/fullscreen.js +305 -0
  39. package/src/js/html5.js +148 -0
  40. package/src/js/listeners.js +854 -0
  41. package/src/js/media.js +61 -0
  42. package/src/js/plugins/ads.js +647 -0
  43. package/src/js/plugins/preview-thumbnails.js +706 -0
  44. package/src/js/plugins/vimeo.js +443 -0
  45. package/src/js/plugins/youtube.js +451 -0
  46. package/src/js/plyr.d.ts +729 -0
  47. package/src/js/plyr.js +1291 -0
  48. package/src/js/plyr.polyfilled.js +13 -0
  49. package/src/js/source.js +155 -0
  50. package/src/js/storage.js +70 -0
  51. package/src/js/support.js +100 -0
  52. package/src/js/ui.js +297 -0
  53. package/src/js/utils/animation.js +33 -0
  54. package/src/js/utils/arrays.js +23 -0
  55. package/src/js/utils/browser.js +21 -0
  56. package/src/js/utils/elements.js +263 -0
  57. package/src/js/utils/events.js +116 -0
  58. package/src/js/utils/fetch.js +45 -0
  59. package/src/js/utils/i18n.js +47 -0
  60. package/src/js/utils/is.js +81 -0
  61. package/src/js/utils/load-image.js +19 -0
  62. package/src/js/utils/load-script.js +14 -0
  63. package/src/js/utils/load-sprite.js +77 -0
  64. package/src/js/utils/numbers.js +17 -0
  65. package/src/js/utils/objects.js +43 -0
  66. package/src/js/utils/promise.js +14 -0
  67. package/src/js/utils/strings.js +80 -0
  68. package/src/js/utils/style.js +148 -0
  69. package/src/js/utils/time.js +36 -0
  70. package/src/js/utils/urls.js +40 -0
  71. package/src/sass/base.scss +69 -0
  72. package/src/sass/components/badges.scss +12 -0
  73. package/src/sass/components/captions.scss +58 -0
  74. package/src/sass/components/control.scss +52 -0
  75. package/src/sass/components/controls.scss +65 -0
  76. package/src/sass/components/menus.scss +205 -0
  77. package/src/sass/components/poster.scss +27 -0
  78. package/src/sass/components/progress.scss +107 -0
  79. package/src/sass/components/sliders.scss +99 -0
  80. package/src/sass/components/times.scss +20 -0
  81. package/src/sass/components/tooltips.scss +91 -0
  82. package/src/sass/components/volume.scss +18 -0
  83. package/src/sass/lib/animation.scss +31 -0
  84. package/src/sass/lib/css-vars.scss +103 -0
  85. package/src/sass/lib/functions.scss +3 -0
  86. package/src/sass/lib/mixins.scss +82 -0
  87. package/src/sass/plugins/ads.scss +53 -0
  88. package/src/sass/plugins/preview-thumbnails/index.scss +121 -0
  89. package/src/sass/plugins/preview-thumbnails/settings.scss +17 -0
  90. package/src/sass/plyr.scss +46 -0
  91. package/src/sass/settings/badges.scss +7 -0
  92. package/src/sass/settings/breakpoints.scss +9 -0
  93. package/src/sass/settings/captions.scss +10 -0
  94. package/src/sass/settings/colors.scss +18 -0
  95. package/src/sass/settings/controls.scss +30 -0
  96. package/src/sass/settings/cosmetics.scss +5 -0
  97. package/src/sass/settings/helpers.scss +7 -0
  98. package/src/sass/settings/menus.scss +13 -0
  99. package/src/sass/settings/progress.scss +18 -0
  100. package/src/sass/settings/sliders.scss +39 -0
  101. package/src/sass/settings/tooltips.scss +11 -0
  102. package/src/sass/settings/type.scss +16 -0
  103. package/src/sass/states/fullscreen.scss +15 -0
  104. package/src/sass/types/audio.scss +61 -0
  105. package/src/sass/types/video.scss +170 -0
  106. package/src/sass/utils/animation.scss +7 -0
  107. package/src/sass/utils/hidden.scss +28 -0
  108. package/src/sprite/plyr-airplay.svg +8 -0
  109. package/src/sprite/plyr-captions-off.svg +7 -0
  110. package/src/sprite/plyr-captions-on.svg +7 -0
  111. package/src/sprite/plyr-download.svg +8 -0
  112. package/src/sprite/plyr-enter-fullscreen.svg +4 -0
  113. package/src/sprite/plyr-exit-fullscreen.svg +4 -0
  114. package/src/sprite/plyr-fast-forward.svg +3 -0
  115. package/src/sprite/plyr-logo-vimeo.svg +6 -0
  116. package/src/sprite/plyr-logo-youtube.svg +6 -0
  117. package/src/sprite/plyr-muted.svg +8 -0
  118. package/src/sprite/plyr-pause.svg +8 -0
  119. package/src/sprite/plyr-pip.svg +6 -0
  120. package/src/sprite/plyr-play.svg +5 -0
  121. package/src/sprite/plyr-restart.svg +5 -0
  122. package/src/sprite/plyr-rewind.svg +3 -0
  123. package/src/sprite/plyr-settings.svg +5 -0
  124. package/src/sprite/plyr-volume.svg +11 -0
  125. package/tasks/build.js +226 -0
  126. package/tasks/deploy.js +216 -0
  127. package/tasks/utils/publish.js +34 -0
@@ -0,0 +1,305 @@
1
+ // ==========================================================================
2
+ // Fullscreen wrapper
3
+ // https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
4
+ // https://webkit.org/blog/7929/designing-websites-for-iphone-x/
5
+ // ==========================================================================
6
+
7
+ import browser from './utils/browser';
8
+ import { closest, getElements, hasClass, toggleClass } from './utils/elements';
9
+ import { on, triggerEvent } from './utils/events';
10
+ import is from './utils/is';
11
+ import { silencePromise } from './utils/promise';
12
+
13
+ class Fullscreen {
14
+ constructor(player) {
15
+ // Keep reference to parent
16
+ this.player = player;
17
+
18
+ // Get prefix
19
+ this.prefix = Fullscreen.prefix;
20
+ this.property = Fullscreen.property;
21
+
22
+ // Scroll position
23
+ this.scrollPosition = { x: 0, y: 0 };
24
+
25
+ // Force the use of 'full window/browser' rather than fullscreen
26
+ this.forceFallback = player.config.fullscreen.fallback === 'force';
27
+
28
+ // Get the fullscreen element
29
+ // Checks container is an ancestor, defaults to null
30
+ this.player.elements.fullscreen
31
+ = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container);
32
+
33
+ // Register event listeners
34
+ // Handle event (incase user presses escape etc)
35
+ on.call(
36
+ this.player,
37
+ document,
38
+ this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
39
+ () => {
40
+ // TODO: Filter for target??
41
+ this.onChange();
42
+ },
43
+ );
44
+
45
+ // Fullscreen toggle on double click
46
+ on.call(this.player, this.player.elements.container, 'dblclick', (event) => {
47
+ // Ignore double click in controls
48
+ if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
49
+ return;
50
+ }
51
+
52
+ this.player.listeners.proxy(event, this.toggle, 'fullscreen');
53
+ });
54
+
55
+ // Tap focus when in fullscreen
56
+ on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
57
+
58
+ // Update the UI
59
+ this.update();
60
+ }
61
+
62
+ // Determine if native supported
63
+ static get nativeSupported() {
64
+ return !!(
65
+ document.fullscreenEnabled
66
+ || document.webkitFullscreenEnabled
67
+ || document.mozFullScreenEnabled
68
+ || document.msFullscreenEnabled
69
+ );
70
+ }
71
+
72
+ // If we're actually using native
73
+ get useNative() {
74
+ return Fullscreen.nativeSupported && !this.forceFallback;
75
+ }
76
+
77
+ // Get the prefix for handlers
78
+ static get prefix() {
79
+ // No prefix
80
+ if (is.function(document.exitFullscreen)) return '';
81
+
82
+ // Check for fullscreen support by vendor prefix
83
+ let value = '';
84
+ const prefixes = ['webkit', 'moz', 'ms'];
85
+
86
+ prefixes.some((pre) => {
87
+ if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {
88
+ value = pre;
89
+ return true;
90
+ }
91
+
92
+ return false;
93
+ });
94
+
95
+ return value;
96
+ }
97
+
98
+ static get property() {
99
+ return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
100
+ }
101
+
102
+ // Determine if fullscreen is supported
103
+ get supported() {
104
+ return [
105
+ // Fullscreen is enabled in config
106
+ this.player.config.fullscreen.enabled,
107
+ // Must be a video
108
+ this.player.isVideo,
109
+ // Either native is supported or fallback enabled
110
+ Fullscreen.nativeSupported || this.player.config.fullscreen.fallback,
111
+ // YouTube has no way to trigger fullscreen, so on devices with no native support, playsinline
112
+ // must be enabled and iosNative fullscreen must be disabled to offer the fullscreen fallback
113
+ !this.player.isYouTube
114
+ || Fullscreen.nativeSupported
115
+ || !browser.isIos
116
+ || (this.player.config.playsinline && !this.player.config.fullscreen.iosNative),
117
+ ].every(Boolean);
118
+ }
119
+
120
+ // Get active state
121
+ get active() {
122
+ if (!this.supported) return false;
123
+
124
+ // Fallback using classname
125
+ if (!Fullscreen.nativeSupported || this.forceFallback) {
126
+ return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
127
+ }
128
+
129
+ const element = !this.prefix
130
+ ? this.target.getRootNode().fullscreenElement
131
+ : this.target.getRootNode()[`${this.prefix}${this.property}Element`];
132
+
133
+ return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
134
+ }
135
+
136
+ // Get target element
137
+ get target() {
138
+ return browser.isIos && this.player.config.fullscreen.iosNative
139
+ ? this.player.media
140
+ : this.player.elements.fullscreen ?? this.player.elements.container;
141
+ }
142
+
143
+ onChange = () => {
144
+ if (!this.supported) return;
145
+
146
+ // Update toggle button
147
+ const button = this.player.elements.buttons.fullscreen;
148
+ if (is.element(button)) {
149
+ button.pressed = this.active;
150
+ }
151
+
152
+ // Always trigger events on the plyr / media element (not a fullscreen container) and let them bubble up
153
+ const target = this.target === this.player.media ? this.target : this.player.elements.container;
154
+ // Trigger an event
155
+ triggerEvent.call(this.player, target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
156
+ };
157
+
158
+ toggleFallback = (toggle = false) => {
159
+ // Store or restore scroll position
160
+ if (toggle) {
161
+ this.scrollPosition = {
162
+ x: window.scrollX ?? 0,
163
+ y: window.scrollY ?? 0,
164
+ };
165
+ }
166
+ else {
167
+ window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
168
+ }
169
+
170
+ // Toggle scroll
171
+ document.body.style.overflow = toggle ? 'hidden' : '';
172
+
173
+ // Toggle class hook
174
+ toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
175
+
176
+ // Force full viewport on iPhone X+
177
+ if (browser.isIos) {
178
+ let viewport = document.head.querySelector('meta[name="viewport"]');
179
+ const property = 'viewport-fit=cover';
180
+
181
+ // Inject the viewport meta if required
182
+ if (!viewport) {
183
+ viewport = document.createElement('meta');
184
+ viewport.setAttribute('name', 'viewport');
185
+ }
186
+
187
+ // Check if the property already exists
188
+ const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
189
+
190
+ if (toggle) {
191
+ this.cleanupViewport = !hasProperty;
192
+ if (!hasProperty) viewport.content += `,${property}`;
193
+ }
194
+ else if (this.cleanupViewport) {
195
+ viewport.content = viewport.content
196
+ .split(',')
197
+ .filter(part => part.trim() !== property)
198
+ .join(',');
199
+ }
200
+ }
201
+
202
+ // Toggle button and fire events
203
+ this.onChange();
204
+ };
205
+
206
+ // Trap focus inside container
207
+ trapFocus = (event) => {
208
+ // Bail if iOS/iPadOS, not active, not the tab key
209
+ if (browser.isIos || browser.isIPadOS || !this.active || event.key !== 'Tab') return;
210
+
211
+ // Get the current focused element
212
+ const focused = document.activeElement;
213
+ const focusable = getElements.call(this.player, 'a[href], button:not(:disabled), input:not(:disabled), [tabindex]');
214
+ const [first] = focusable;
215
+ const last = focusable[focusable.length - 1];
216
+
217
+ if (focused === last && !event.shiftKey) {
218
+ // Move focus to first element that can be tabbed if Shift isn't used
219
+ first.focus();
220
+ event.preventDefault();
221
+ }
222
+ else if (focused === first && event.shiftKey) {
223
+ // Move focus to last element that can be tabbed if Shift is used
224
+ last.focus();
225
+ event.preventDefault();
226
+ }
227
+ };
228
+
229
+ // Update UI
230
+ update = () => {
231
+ if (this.supported) {
232
+ let mode;
233
+
234
+ if (this.forceFallback) mode = 'Fallback (forced)';
235
+ else if (Fullscreen.nativeSupported) mode = 'Native';
236
+ else mode = 'Fallback';
237
+
238
+ this.player.debug.log(`${mode} fullscreen enabled`);
239
+ }
240
+ else {
241
+ this.player.debug.log('Fullscreen not supported and fallback disabled');
242
+ }
243
+
244
+ // Add styling hook to show button
245
+ toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.supported);
246
+ };
247
+
248
+ // Make an element fullscreen
249
+ enter = () => {
250
+ if (!this.supported) return;
251
+
252
+ // iOS native fullscreen doesn't need the request step
253
+ if (browser.isIos && this.player.config.fullscreen.iosNative) {
254
+ if (this.player.isVimeo) {
255
+ this.player.embed.requestFullscreen();
256
+ }
257
+ else {
258
+ this.target.webkitEnterFullscreen();
259
+ }
260
+ }
261
+ else if (!Fullscreen.nativeSupported || this.forceFallback) {
262
+ this.toggleFallback(true);
263
+ }
264
+ else if (!this.prefix) {
265
+ this.target.requestFullscreen({ navigationUI: 'hide' });
266
+ }
267
+ else if (!is.empty(this.prefix)) {
268
+ this.target[`${this.prefix}Request${this.property}`]();
269
+ }
270
+ };
271
+
272
+ // Bail from fullscreen
273
+ exit = () => {
274
+ if (!this.supported) return;
275
+
276
+ // iOS native fullscreen
277
+ if (browser.isIos && this.player.config.fullscreen.iosNative) {
278
+ if (this.player.isVimeo) {
279
+ this.player.embed.exitFullscreen();
280
+ }
281
+ else {
282
+ this.target.webkitEnterFullscreen();
283
+ }
284
+ silencePromise(this.player.play());
285
+ }
286
+ else if (!Fullscreen.nativeSupported || this.forceFallback) {
287
+ this.toggleFallback(false);
288
+ }
289
+ else if (!this.prefix) {
290
+ (document.cancelFullScreen || document.exitFullscreen).call(document);
291
+ }
292
+ else if (!is.empty(this.prefix)) {
293
+ const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
294
+ document[`${this.prefix}${action}${this.property}`]();
295
+ }
296
+ };
297
+
298
+ // Toggle state
299
+ toggle = () => {
300
+ if (!this.active) this.enter();
301
+ else this.exit();
302
+ };
303
+ }
304
+
305
+ export default Fullscreen;
@@ -0,0 +1,148 @@
1
+ // ==========================================================================
2
+ // Plyr HTML5 helpers
3
+ // ==========================================================================
4
+
5
+ import support from './support';
6
+ import { removeElement } from './utils/elements';
7
+ import { triggerEvent } from './utils/events';
8
+ import is from './utils/is';
9
+ import { silencePromise } from './utils/promise';
10
+ import { setAspectRatio } from './utils/style';
11
+
12
+ const html5 = {
13
+ getSources() {
14
+ if (!this.isHTML5) {
15
+ return [];
16
+ }
17
+
18
+ const sources = Array.from(this.media.querySelectorAll('source'));
19
+
20
+ // Filter out unsupported sources (if type is specified)
21
+ return sources.filter((source) => {
22
+ const type = source.getAttribute('type');
23
+
24
+ if (is.empty(type)) {
25
+ return true;
26
+ }
27
+
28
+ return support.mime.call(this, type);
29
+ });
30
+ },
31
+
32
+ // Get quality levels
33
+ getQualityOptions() {
34
+ // Whether we're forcing all options (e.g. for streaming)
35
+ if (this.config.quality.forced) {
36
+ return this.config.quality.options;
37
+ }
38
+
39
+ // Get sizes from <source> elements
40
+ return html5.getSources
41
+ .call(this)
42
+ .map(source => Number(source.getAttribute('size')))
43
+ .filter(Boolean);
44
+ },
45
+
46
+ setup() {
47
+ if (!this.isHTML5) {
48
+ return;
49
+ }
50
+
51
+ const player = this;
52
+
53
+ // Set speed options from config
54
+ player.options.speed = player.config.speed.options;
55
+
56
+ // Set aspect ratio if fixed
57
+ if (!is.empty(this.config.ratio)) {
58
+ setAspectRatio.call(player);
59
+ }
60
+
61
+ // Quality
62
+ Object.defineProperty(player.media, 'quality', {
63
+ get() {
64
+ // Get sources
65
+ const sources = html5.getSources.call(player);
66
+ const source = sources.find(s => s.getAttribute('src') === player.source);
67
+
68
+ // Return size, if match is found
69
+ return source && Number(source.getAttribute('size'));
70
+ },
71
+ set(input) {
72
+ if (player.quality === input) {
73
+ return;
74
+ }
75
+
76
+ // If we're using an external handler...
77
+ if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
78
+ player.config.quality.onChange(input);
79
+ }
80
+ else {
81
+ // Get sources
82
+ const sources = html5.getSources.call(player);
83
+ // Get first match for requested size
84
+ const source = sources.find(s => Number(s.getAttribute('size')) === input);
85
+
86
+ // No matching source found
87
+ if (!source) {
88
+ return;
89
+ }
90
+
91
+ // Get current state
92
+ const { currentTime, paused, preload, readyState, playbackRate } = player.media;
93
+
94
+ // Set new source
95
+ player.media.src = source.getAttribute('src');
96
+
97
+ // Prevent loading if preload="none" and the current source isn't loaded (#1044)
98
+ if (preload !== 'none' || readyState) {
99
+ // Restore time
100
+ player.once('loadedmetadata', () => {
101
+ player.speed = playbackRate;
102
+ player.currentTime = currentTime;
103
+
104
+ // Resume playing
105
+ if (!paused) {
106
+ silencePromise(player.play());
107
+ }
108
+ });
109
+
110
+ // Load new source
111
+ player.media.load();
112
+ }
113
+ }
114
+
115
+ // Trigger change event
116
+ triggerEvent.call(player, player.media, 'qualitychange', false, {
117
+ quality: input,
118
+ });
119
+ },
120
+ });
121
+ },
122
+
123
+ // Cancel current network requests
124
+ // See https://github.com/xgauravyaduvanshii/redxplyr/issues/174
125
+ cancelRequests() {
126
+ if (!this.isHTML5) {
127
+ return;
128
+ }
129
+
130
+ // Remove child sources
131
+ removeElement(html5.getSources.call(this));
132
+
133
+ // Set blank video src attribute
134
+ // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
135
+ // Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
136
+ this.media.setAttribute('src', this.config.blankVideo);
137
+
138
+ // Load the new empty source
139
+ // This will cancel existing requests
140
+ // See https://github.com/xgauravyaduvanshii/redxplyr/issues/174
141
+ this.media.load();
142
+
143
+ // Debugging
144
+ this.debug.log('Cancelled network requests');
145
+ },
146
+ };
147
+
148
+ export default html5;