@oat-sa/tao-core-ui 3.22.0 → 3.23.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.
@@ -1,4 +1,4 @@
1
- define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support', 'handlebars', 'lib/handlebars/helpers', 'ui/mediaplayer/utils/reminder', 'ui/mediaplayer/utils/timeObserver'], function ($$1, UrlParser, eventifier, support, Handlebars, Helpers0, reminderManagerFactory, timeObserverFactory) { 'use strict';
1
+ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support', 'handlebars', 'lib/handlebars/helpers', 'ui/mediaplayer/utils/reminder', 'ui/mediaplayer/utils/timeObserver', 'ui/mediaplayer/utils/crossOrigin'], function ($$1, UrlParser, eventifier, support, Handlebars, Helpers0, reminderManagerFactory, timeObserverFactory, crossOriginUtil) { 'use strict';
2
2
 
3
3
  $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
4
4
  UrlParser = UrlParser && Object.prototype.hasOwnProperty.call(UrlParser, 'default') ? UrlParser['default'] : UrlParser;
@@ -8,6 +8,7 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
8
8
  Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
9
9
  reminderManagerFactory = reminderManagerFactory && Object.prototype.hasOwnProperty.call(reminderManagerFactory, 'default') ? reminderManagerFactory['default'] : reminderManagerFactory;
10
10
  timeObserverFactory = timeObserverFactory && Object.prototype.hasOwnProperty.call(timeObserverFactory, 'default') ? timeObserverFactory['default'] : timeObserverFactory;
11
+ crossOriginUtil = crossOriginUtil && Object.prototype.hasOwnProperty.call(crossOriginUtil, 'default') ? crossOriginUtil['default'] : crossOriginUtil;
11
12
 
12
13
  if (!Helpers0.__initialized) {
13
14
  Helpers0(Handlebars);
@@ -16,16 +17,29 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
16
17
  var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
17
18
  this.compilerInfo = [4,'>= 1.0.0'];
18
19
  helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
19
- var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;
20
+ var buffer = "", stack1, helper, options, self=this, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;
20
21
 
21
22
  function program1(depth0,data) {
22
23
 
23
24
 
24
- return "crossorigin";
25
+ return " crossorigin=\"use-credentials\"";
25
26
  }
26
27
 
27
28
  function program3(depth0,data) {
28
29
 
30
+ var stack1;
31
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOrigin), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
32
+ if(stack1 || stack1 === 0) { return stack1; }
33
+ else { return ''; }
34
+ }
35
+ function program4(depth0,data) {
36
+
37
+
38
+ return " crossorigin";
39
+ }
40
+
41
+ function program6(depth0,data) {
42
+
29
43
  var buffer = "", stack1, helper, options;
30
44
  buffer += "\n <a href=\"";
31
45
  if (helper = helpers.link) { stack1 = helper.call(depth0, {hash:{},data:data}); }
@@ -41,13 +55,13 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
41
55
  if (helper = helpers.preload) { stack1 = helper.call(depth0, {hash:{},data:data}); }
42
56
  else { helper = (depth0 && depth0.preload); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
43
57
  buffer += escapeExpression(stack1)
44
- + "\" controls ";
45
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.cors), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
58
+ + "\" controls";
59
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOriginUseCredentials), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
46
60
  if(stack1 || stack1 === 0) { buffer += stack1; }
47
61
  buffer += ">\n "
48
62
  + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Your browser doesn’t support the audio player.", options) : helperMissing.call(depth0, "__", "Your browser doesn’t support the audio player.", options)))
49
63
  + "\n";
50
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
64
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
51
65
  if(stack1 || stack1 === 0) { buffer += stack1; }
52
66
  buffer += "\n</audio>\n";
53
67
  return buffer;
@@ -64,16 +78,29 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
64
78
  var Template$1 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
65
79
  this.compilerInfo = [4,'>= 1.0.0'];
66
80
  helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
67
- var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;
81
+ var buffer = "", stack1, helper, options, self=this, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;
68
82
 
69
83
  function program1(depth0,data) {
70
84
 
71
85
 
72
- return "crossorigin";
86
+ return " crossorigin=\"use-credentials\"";
73
87
  }
74
88
 
75
89
  function program3(depth0,data) {
76
90
 
91
+ var stack1;
92
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOrigin), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
93
+ if(stack1 || stack1 === 0) { return stack1; }
94
+ else { return ''; }
95
+ }
96
+ function program4(depth0,data) {
97
+
98
+
99
+ return " crossorigin";
100
+ }
101
+
102
+ function program6(depth0,data) {
103
+
77
104
  var buffer = "", stack1, helper, options;
78
105
  buffer += "\n <a href=\"";
79
106
  if (helper = helpers.link) { stack1 = helper.call(depth0, {hash:{},data:data}); }
@@ -93,13 +120,13 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
93
120
  if (helper = helpers.poster) { stack1 = helper.call(depth0, {hash:{},data:data}); }
94
121
  else { helper = (depth0 && depth0.poster); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
95
122
  buffer += escapeExpression(stack1)
96
- + "\" controls ";
97
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.cors), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
123
+ + "\" controls";
124
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.crossOriginUseCredentials), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
98
125
  if(stack1 || stack1 === 0) { buffer += stack1; }
99
126
  buffer += ">\n "
100
127
  + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Your browser doesn’t support the video player.", options) : helperMissing.call(depth0, "__", "Your browser doesn’t support the video player.", options)))
101
128
  + "\n";
102
- stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
129
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.link), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
103
130
  if(stack1 || stack1 === 0) { buffer += stack1; }
104
131
  buffer += "\n</video>\n";
105
132
  return buffer;
@@ -148,9 +175,9 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
148
175
  *
149
176
  * You should have received a copy of the GNU General Public License
150
177
  * along with this program; if not, write to the Free Software
151
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
178
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
152
179
  *
153
- * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
180
+ * Copyright (c) 2015-2026 (original work) Open Assessment Technologies SA ;
154
181
  */
155
182
 
156
183
  /**
@@ -191,6 +218,7 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
191
218
  * @param {string} [config.type] - The type of player (video or audio) (default: video)
192
219
  * @param {boolean} [config.preview] - Enables the media preview (load media metadata)
193
220
  * @param {boolean} [config.debug] - Enables the debug mode
221
+ * @param {'anonymous'|'use-credentials'} [config.crossOriginMode] - CORS credentials mode for cross-domain sources
194
222
  * @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
195
223
  * @returns {object} player
196
224
  */
@@ -253,16 +281,12 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
253
281
  init() {
254
282
  const tpl = 'audio' === type ? audioTpl : videoTpl;
255
283
  const page = new UrlParser(window.location);
256
- let cors = false;
257
284
  let preload = config.preview ? 'metadata' : 'none';
258
285
  let poster = '';
259
286
  let link = '';
260
287
  let result = false;
261
288
  state = {};
262
289
  sources.forEach(source => {
263
- if (!page.sameDomain(source.src)) {
264
- cors = true;
265
- }
266
290
  if (source.poster) {
267
291
  poster = source.poster;
268
292
  }
@@ -270,8 +294,10 @@ define(['jquery', 'util/urlParser', 'core/eventifier', 'ui/mediaplayer/support',
270
294
  link = source.link;
271
295
  }
272
296
  });
297
+ const crossOriginAttr = crossOriginUtil.resolveCrossOriginAttribute(sources, config.crossOriginMode, page);
273
298
  $media = $$1(tpl({
274
- cors,
299
+ crossOrigin: crossOriginAttr === 'anonymous',
300
+ crossOriginUseCredentials: crossOriginAttr === 'use-credentials',
275
301
  preload,
276
302
  poster,
277
303
  link
@@ -0,0 +1,64 @@
1
+ define(function () { 'use strict';
2
+
3
+ /**
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License
6
+ * as published by the Free Software Foundation; under version 2
7
+ * of the License (non-upgradable).
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+ *
18
+ * Copyright (c) 2026 (original work) Open Assessment Technologies SA ;
19
+ */
20
+
21
+ /** @typedef {'anonymous' | 'use-credentials'} CrossOriginMode */
22
+
23
+ const VALID_CROSS_ORIGIN_MODES = new Set(['anonymous', 'use-credentials']);
24
+
25
+ /**
26
+ * Normalises cross-origin mode from instance or RequireJS module config.
27
+ * @param {CrossOriginMode | string | undefined | null} instanceMode
28
+ * @param {CrossOriginMode | string | undefined | null} [moduleMode]
29
+ * @returns {CrossOriginMode}
30
+ */
31
+ function resolveCrossOriginMode(instanceMode, moduleMode) {
32
+ let mode = instanceMode;
33
+ if (typeof mode === 'undefined' || mode === null) {
34
+ mode = moduleMode;
35
+ }
36
+ if (typeof mode === 'undefined' || mode === null) {
37
+ mode = 'anonymous';
38
+ }
39
+ return VALID_CROSS_ORIGIN_MODES.has(mode) ? /** @type {CrossOriginMode} */mode : 'anonymous';
40
+ }
41
+
42
+ /**
43
+ * Resolves the crossorigin attribute for HTML5 media when sources are cross-domain.
44
+ * @param {Array<{ src?: string }>} sources
45
+ * @param {CrossOriginMode} crossOriginMode
46
+ * @param {{ sameDomain: (url: string) => boolean }} page
47
+ * @returns {CrossOriginMode | null} null when attribute should be omitted
48
+ */
49
+ function resolveCrossOriginAttribute(sources, crossOriginMode, page) {
50
+ const isCrossDomain = sources.some(source => source && source.src && !page.sameDomain(source.src));
51
+ if (!isCrossDomain) {
52
+ return null;
53
+ }
54
+ return resolveCrossOriginMode(crossOriginMode);
55
+ }
56
+ var crossOrigin = {
57
+ VALID_CROSS_ORIGIN_MODES,
58
+ resolveCrossOriginMode,
59
+ resolveCrossOriginAttribute
60
+ };
61
+
62
+ return crossOrigin;
63
+
64
+ });
@@ -1,4 +1,4 @@
1
- define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/request', 'core/eventifier', 'core/mimetype', 'core/store', 'ui/mediaplayer/support', 'ui/mediaplayer/players', 'handlebars', 'lib/handlebars/helpers', 'css!ui/mediaplayer/css/player.css', 'nouislider'], function ($$1, _, async, UrlParser, request, eventifier, mimetype, store, support, players, Handlebars, Helpers0, player_css, nouislider) { 'use strict';
1
+ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/request', 'core/eventifier', 'core/mimetype', 'core/store', 'ui/mediaplayer/support', 'ui/mediaplayer/players', 'handlebars', 'lib/handlebars/helpers', 'ui/mediaplayer/utils/crossOrigin', 'module', 'css!ui/mediaplayer/css/player.css', 'nouislider'], function ($$1, _, async, UrlParser, request, eventifier, mimetype, store, support, players, Handlebars, Helpers0, crossOriginUtil, module, player_css, nouislider) { 'use strict';
2
2
 
3
3
  $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
4
4
  _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
@@ -12,6 +12,8 @@ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/reques
12
12
  players = players && Object.prototype.hasOwnProperty.call(players, 'default') ? players['default'] : players;
13
13
  Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
14
14
  Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
15
+ crossOriginUtil = crossOriginUtil && Object.prototype.hasOwnProperty.call(crossOriginUtil, 'default') ? crossOriginUtil['default'] : crossOriginUtil;
16
+ module = module && Object.prototype.hasOwnProperty.call(module, 'default') ? module['default'] : module;
15
17
 
16
18
  if (!Helpers0.__initialized) {
17
19
  Helpers0(Handlebars);
@@ -80,6 +82,7 @@ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/reques
80
82
  *
81
83
  * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
82
84
  */
85
+ const moduleConfig = module.config ? module.config() : {};
83
86
 
84
87
  /**
85
88
  * CSS namespace
@@ -258,6 +261,7 @@ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/reques
258
261
  * @param {Number} [config.height] - Sets the height of the player (default: depends on media type)
259
262
  * @param {Boolean} [config.preview] - Enables the media preview (load media metadata)
260
263
  * @param {Boolean} [config.debug] - Enables the debug mode
264
+ * @param {'anonymous'|'use-credentials'} [config.crossOriginMode] - CORS credentials mode for cross-domain HTML5 sources (default: module config, then anonymous)
261
265
  * @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
262
266
  * @event render - Event triggered when the player is rendering
263
267
  * @event error - Event triggered when the player throws an unrecoverable error
@@ -285,6 +289,7 @@ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/reques
285
289
  // load the config set, discard null values in order to allow defaults to be set
286
290
  this.config = _.omitBy(mediaPlayerConfig || {}, value => typeof value === 'undefined' || value === null);
287
291
  _.defaults(this.config, defaults.options);
292
+ this.config.crossOriginMode = crossOriginUtil.resolveCrossOriginMode(this.config.crossOriginMode, moduleConfig.crossOriginMode);
288
293
  if (!this.config.mimeType && 'string' === typeof this.config.type && this.config.type.indexOf('/') > 0) {
289
294
  this.config.mimeType = this.config.type;
290
295
  }
@@ -834,6 +839,7 @@ define(['jquery', 'lodash', 'async', 'util/urlParser', 'core/dataProvider/reques
834
839
  sources: this.getSources(),
835
840
  preview: this.config.preview,
836
841
  debug: this.config.debug,
842
+ crossOriginMode: this.config.crossOriginMode,
837
843
  stalledDetectionDelay: this.config.stalledDetectionDelay
838
844
  };
839
845
  this.player = playerFactory(this.$player, playerConfig).on('resize', (width, height) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oat-sa/tao-core-ui",
3
- "version": "3.22.0",
3
+ "version": "3.23.0",
4
4
  "displayName": "TAO Core UI",
5
5
  "description": "UI libraries of TAO",
6
6
  "scripts": {
@@ -11,9 +11,9 @@
11
11
  *
12
12
  * You should have received a copy of the GNU General Public License
13
13
  * along with this program; if not, write to the Free Software
14
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
15
15
  *
16
- * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
16
+ * Copyright (c) 2015-2026 (original work) Open Assessment Technologies SA ;
17
17
  */
18
18
 
19
19
  import $ from 'jquery';
@@ -25,6 +25,7 @@ import videoTpl from 'ui/mediaplayer/tpl/video';
25
25
  import sourceTpl from 'ui/mediaplayer/tpl/source';
26
26
  import reminderManagerFactory from 'ui/mediaplayer/utils/reminder';
27
27
  import timeObserverFactory from 'ui/mediaplayer/utils/timeObserver';
28
+ import crossOriginUtil from 'ui/mediaplayer/utils/crossOrigin';
28
29
 
29
30
  /**
30
31
  * CSS namespace
@@ -91,6 +92,7 @@ const playerEvents = ['end', 'error', 'pause', 'play', 'playing', 'ready', 'resi
91
92
  * @param {string} [config.type] - The type of player (video or audio) (default: video)
92
93
  * @param {boolean} [config.preview] - Enables the media preview (load media metadata)
93
94
  * @param {boolean} [config.debug] - Enables the debug mode
95
+ * @param {'anonymous'|'use-credentials'} [config.crossOriginMode] - CORS credentials mode for cross-domain sources
94
96
  * @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
95
97
  * @returns {object} player
96
98
  */
@@ -161,7 +163,6 @@ export default function html5PlayerFactory($container, config = {}) {
161
163
  init() {
162
164
  const tpl = 'audio' === type ? audioTpl : videoTpl;
163
165
  const page = new UrlParser(window.location);
164
- let cors = false;
165
166
  let preload = config.preview ? 'metadata' : 'none';
166
167
  let poster = '';
167
168
  let link = '';
@@ -170,9 +171,6 @@ export default function html5PlayerFactory($container, config = {}) {
170
171
  state = {};
171
172
 
172
173
  sources.forEach(source => {
173
- if (!page.sameDomain(source.src)) {
174
- cors = true;
175
- }
176
174
  if (source.poster) {
177
175
  poster = source.poster;
178
176
  }
@@ -181,7 +179,19 @@ export default function html5PlayerFactory($container, config = {}) {
181
179
  }
182
180
  });
183
181
 
184
- $media = $(tpl({ cors, preload, poster, link }));
182
+ const crossOriginAttr = crossOriginUtil.resolveCrossOriginAttribute(
183
+ sources,
184
+ config.crossOriginMode,
185
+ page
186
+ );
187
+
188
+ $media = $(tpl({
189
+ crossOrigin: crossOriginAttr === 'anonymous',
190
+ crossOriginUseCredentials: crossOriginAttr === 'use-credentials',
191
+ preload,
192
+ poster,
193
+ link
194
+ }));
185
195
  $container.append($media);
186
196
 
187
197
  applyVideoSizing();
@@ -24,6 +24,7 @@ require(['ui/mediaplayer'], function(mediaplayer) {
24
24
  - *Number* **volume** - Sets the sound volume (default: 80)
25
25
  - *Number* **width** - Sets the width of the player (default: depends on media type)
26
26
  - *Number* **height** - Sets the height of the player (default: depends on media type)
27
+ - *String* **crossOriginMode** - CORS mode for cross-domain HTML5 sources: `anonymous` (default, bare `crossorigin`) or `use-credentials` (`crossorigin="use-credentials"`)
27
28
  - *Function* **onrender** - Event listener called when the player is rendering
28
29
  - *Function* **onready** - Event listener called when the player is fully ready
29
30
  - *Function* **onplay** - Event listener called when the playback is starting
@@ -33,6 +34,22 @@ require(['ui/mediaplayer'], function(mediaplayer) {
33
34
  - *Function* **onlimitreached** - Event listener called when the play limit has been reached
34
35
  - *Function* **ondestroy** - Event listener called when the player is destroying
35
36
 
37
+ ### Module configuration
38
+
39
+ Set a default `crossOriginMode` for all player instances via RequireJS:
40
+
41
+ ```javascript
42
+ require.config({
43
+ config: {
44
+ 'ui/mediaplayer': {
45
+ crossOriginMode: 'use-credentials'
46
+ }
47
+ }
48
+ });
49
+ ```
50
+
51
+ Per-instance `crossOriginMode` overrides the module default.
52
+
36
53
  ### Events
37
54
 
38
55
  Some events are triggered both on the component using `eventifier` and on the DOM through the component wrapper.
@@ -1,4 +1,4 @@
1
- <audio class="media audio" preload="{{preload}}" controls {{#if cors}}crossorigin{{/if}}>
1
+ <audio class="media audio" preload="{{preload}}" controls{{#if crossOriginUseCredentials}} crossorigin="use-credentials"{{else}}{{#if crossOrigin}} crossorigin{{/if}}{{/if}}>
2
2
  {{__ 'Your browser doesn’t support the audio player.'}}
3
3
  {{#if link}}
4
4
  <a href="{{link}}">{{__ 'Please download the track and listen offline.'}}</a>
@@ -1,4 +1,4 @@
1
- <video class="media video" preload="{{preload}}" poster="{{poster}}" controls {{#if cors}}crossorigin{{/if}}>
1
+ <video class="media video" preload="{{preload}}" poster="{{poster}}" controls{{#if crossOriginUseCredentials}} crossorigin="use-credentials"{{else}}{{#if crossOrigin}} crossorigin{{/if}}{{/if}}>
2
2
  {{__ 'Your browser doesn’t support the video player.'}}
3
3
  {{#if link}}
4
4
  <a href="{{link}}">{{__ 'Please download the video and view offline.'}}</a>
@@ -0,0 +1,63 @@
1
+ /**
2
+ * This program is free software; you can redistribute it and/or
3
+ * modify it under the terms of the GNU General Public License
4
+ * as published by the Free Software Foundation; under version 2
5
+ * of the License (non-upgradable).
6
+ *
7
+ * This program is distributed in the hope that it will be useful,
8
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ * GNU General Public License for more details.
11
+ *
12
+ * You should have received a copy of the GNU General Public License
13
+ * along with this program; if not, write to the Free Software
14
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
+ *
16
+ * Copyright (c) 2026 (original work) Open Assessment Technologies SA ;
17
+ */
18
+
19
+ /** @typedef {'anonymous' | 'use-credentials'} CrossOriginMode */
20
+
21
+ const VALID_CROSS_ORIGIN_MODES = new Set(['anonymous', 'use-credentials']);
22
+
23
+ /**
24
+ * Normalises cross-origin mode from instance or RequireJS module config.
25
+ * @param {CrossOriginMode | string | undefined | null} instanceMode
26
+ * @param {CrossOriginMode | string | undefined | null} [moduleMode]
27
+ * @returns {CrossOriginMode}
28
+ */
29
+ function resolveCrossOriginMode(instanceMode, moduleMode) {
30
+ let mode = instanceMode;
31
+
32
+ if (typeof mode === 'undefined' || mode === null) {
33
+ mode = moduleMode;
34
+ }
35
+ if (typeof mode === 'undefined' || mode === null) {
36
+ mode = 'anonymous';
37
+ }
38
+
39
+ return VALID_CROSS_ORIGIN_MODES.has(mode) ? /** @type {CrossOriginMode} */ (mode) : 'anonymous';
40
+ }
41
+
42
+ /**
43
+ * Resolves the crossorigin attribute for HTML5 media when sources are cross-domain.
44
+ * @param {Array<{ src?: string }>} sources
45
+ * @param {CrossOriginMode} crossOriginMode
46
+ * @param {{ sameDomain: (url: string) => boolean }} page
47
+ * @returns {CrossOriginMode | null} null when attribute should be omitted
48
+ */
49
+ function resolveCrossOriginAttribute(sources, crossOriginMode, page) {
50
+ const isCrossDomain = sources.some(source => source && source.src && !page.sameDomain(source.src));
51
+
52
+ if (!isCrossDomain) {
53
+ return null;
54
+ }
55
+
56
+ return resolveCrossOriginMode(crossOriginMode);
57
+ }
58
+
59
+ export default {
60
+ VALID_CROSS_ORIGIN_MODES,
61
+ resolveCrossOriginMode,
62
+ resolveCrossOriginAttribute
63
+ };
@@ -27,9 +27,13 @@ import store from 'core/store';
27
27
  import support from 'ui/mediaplayer/support';
28
28
  import players from 'ui/mediaplayer/players';
29
29
  import playerTpl from 'ui/mediaplayer/tpl/player';
30
+ import crossOriginUtil from 'ui/mediaplayer/utils/crossOrigin';
31
+ import module from 'module';
30
32
  import 'ui/mediaplayer/css/player.css';
31
33
  import 'nouislider';
32
34
 
35
+ const moduleConfig = module.config ? module.config() : {};
36
+
33
37
  /**
34
38
  * CSS namespace
35
39
  * @type {String}
@@ -212,6 +216,7 @@ const isResponsiveSize = sizeProps => {
212
216
  * @param {Number} [config.height] - Sets the height of the player (default: depends on media type)
213
217
  * @param {Boolean} [config.preview] - Enables the media preview (load media metadata)
214
218
  * @param {Boolean} [config.debug] - Enables the debug mode
219
+ * @param {'anonymous'|'use-credentials'} [config.crossOriginMode] - CORS credentials mode for cross-domain HTML5 sources (default: module config, then anonymous)
215
220
  * @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
216
221
  * @event render - Event triggered when the player is rendering
217
222
  * @event error - Event triggered when the player throws an unrecoverable error
@@ -239,6 +244,10 @@ function mediaplayerFactory(config) {
239
244
  // load the config set, discard null values in order to allow defaults to be set
240
245
  this.config = _.omitBy(mediaPlayerConfig || {}, value => typeof value === 'undefined' || value === null);
241
246
  _.defaults(this.config, defaults.options);
247
+ this.config.crossOriginMode = crossOriginUtil.resolveCrossOriginMode(
248
+ this.config.crossOriginMode,
249
+ moduleConfig.crossOriginMode
250
+ );
242
251
  if (!this.config.mimeType && 'string' === typeof this.config.type && this.config.type.indexOf('/') > 0) {
243
252
  this.config.mimeType = this.config.type;
244
253
  }
@@ -882,6 +891,7 @@ function mediaplayerFactory(config) {
882
891
  sources: this.getSources(),
883
892
  preview: this.config.preview,
884
893
  debug: this.config.debug,
894
+ crossOriginMode: this.config.crossOriginMode,
885
895
  stalledDetectionDelay: this.config.stalledDetectionDelay
886
896
  };
887
897
  this.player = playerFactory(this.$player, playerConfig)