@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.
- package/dist/mediaplayer/players/html5.js +44 -18
- package/dist/mediaplayer/utils/crossOrigin.js +64 -0
- package/dist/mediaplayer.js +7 -1
- package/package.json +1 -1
- package/src/mediaplayer/players/html5.js +17 -7
- package/src/mediaplayer/readme.md +17 -0
- package/src/mediaplayer/tpl/audio.tpl +1 -1
- package/src/mediaplayer/tpl/video.tpl +1 -1
- package/src/mediaplayer/utils/crossOrigin.js +63 -0
- package/src/mediaplayer.js +10 -0
|
@@ -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
|
|
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.
|
|
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(
|
|
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
|
|
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.
|
|
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(
|
|
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.,
|
|
178
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
152
179
|
*
|
|
153
|
-
* Copyright (c) 2015-
|
|
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
|
-
|
|
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
|
+
});
|
package/dist/mediaplayer.js
CHANGED
|
@@ -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
|
@@ -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.,
|
|
14
|
+
* Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
|
|
15
15
|
*
|
|
16
|
-
* Copyright (c) 2015-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
};
|
package/src/mediaplayer.js
CHANGED
|
@@ -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)
|