@oat-sa/tao-core-ui 1.5.4 → 1.6.3
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/ckeditor/ckConfigurator.js +9 -1
- package/dist/mediaEditor/mediaEditorComponent.js +5 -3
- package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +46 -25
- package/dist/mediaplayer/css/player.css +104 -14
- package/dist/mediaplayer/css/player.css.map +1 -1
- package/dist/mediaplayer/players/html5.js +767 -0
- package/dist/mediaplayer/players/youtube.js +470 -0
- package/dist/mediaplayer/players.js +35 -0
- package/dist/mediaplayer/support.js +134 -0
- package/dist/mediaplayer/utils/reminder.js +198 -0
- package/dist/mediaplayer/utils/timeObserver.js +149 -0
- package/dist/mediaplayer/youtubeManager.js +177 -0
- package/dist/mediaplayer.js +1251 -1912
- package/dist/previewer.js +25 -19
- package/package.json +1 -1
- package/scss/basic.scss +1 -0
- package/scss/inc/_jquery.nouislider.scss +254 -0
- package/src/ckeditor/ckConfigurator.js +10 -1
- package/src/itemButtonList/css/item-button-list.css +225 -0
- package/src/itemButtonList/css/item-button-list.css.map +1 -0
- package/src/mediaEditor/mediaEditorComponent.js +25 -26
- package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +83 -63
- package/src/mediaplayer/css/player.css +104 -14
- package/src/mediaplayer/css/player.css.map +1 -1
- package/src/mediaplayer/players/html5.js +564 -0
- package/src/mediaplayer/players/youtube.js +323 -0
- package/src/mediaplayer/players.js +29 -0
- package/src/mediaplayer/scss/player.scss +125 -16
- package/src/mediaplayer/support.js +126 -0
- package/src/mediaplayer/tpl/audio.tpl +6 -0
- package/src/mediaplayer/tpl/player.tpl +11 -32
- package/src/mediaplayer/tpl/source.tpl +1 -0
- package/src/mediaplayer/tpl/video.tpl +6 -0
- package/src/mediaplayer/tpl/youtube.tpl +1 -0
- package/src/mediaplayer/utils/reminder.js +184 -0
- package/src/mediaplayer/utils/timeObserver.js +143 -0
- package/src/mediaplayer/youtubeManager.js +161 -0
- package/src/mediaplayer.js +1217 -1901
- package/src/previewer.js +40 -33
- package/src/searchModal/css/advancedSearch.css +190 -0
- package/src/searchModal/css/advancedSearch.css.map +1 -0
- package/src/searchModal/css/searchModal.css +506 -0
- package/src/searchModal/css/searchModal.css.map +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
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) 2015-2021 (original work) Open Assessment Technologies SA ;
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A Regex to detect Apple mobile browsers
|
|
21
|
+
* @type {RegExp}
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
const reAppleMobiles = /ip(hone|od)/i;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A list of MIME types with codec declaration
|
|
28
|
+
* @type {Object}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
const supportedMimeTypes = {
|
|
32
|
+
// video
|
|
33
|
+
'video/webm': 'video/webm; codecs="vp8, vorbis"',
|
|
34
|
+
'video/mp4': 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
|
35
|
+
'video/ogg': 'video/ogg; codecs="theora, vorbis"',
|
|
36
|
+
// audio
|
|
37
|
+
'audio/mpeg': 'audio/mpeg;',
|
|
38
|
+
'audio/mp4': 'audio/mp4; codecs="mp4a.40.5"',
|
|
39
|
+
'audio/ogg': 'audio/ogg; codecs="vorbis"',
|
|
40
|
+
'audio/wav': 'audio/wav; codecs="1"'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Checks support for a MIME type.
|
|
45
|
+
* @param {HTMLMediaElement} media The media element on which check support
|
|
46
|
+
* @param {String} mimeType A MIME type to check the support for
|
|
47
|
+
* @returns {string}
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
const findSupport = (media, mimeType) => media.canPlayType(mimeType).replace(/no/, '');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Support detection
|
|
54
|
+
* @type {Object}
|
|
55
|
+
*/
|
|
56
|
+
export default {
|
|
57
|
+
/**
|
|
58
|
+
* Checks if the browser can play media
|
|
59
|
+
* @param {HTMLMediaElement} media The media element on which check support
|
|
60
|
+
* @param {String} [mimeType] An optional MIME type to precise the support
|
|
61
|
+
* @returns {Boolean}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
checkSupport(media, mimeType) {
|
|
65
|
+
const support = media.canPlayType;
|
|
66
|
+
if (support && mimeType) {
|
|
67
|
+
return !!(
|
|
68
|
+
(supportedMimeTypes[mimeType] && findSupport(media, supportedMimeTypes[mimeType])) ||
|
|
69
|
+
findSupport(media, mimeType)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return !!support;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Checks if the browser can play video and audio
|
|
78
|
+
* @param {String} [type] The type of media (audio or video)
|
|
79
|
+
* @param {String} [mime] A media MIME type to check
|
|
80
|
+
* @returns {Boolean}
|
|
81
|
+
*/
|
|
82
|
+
canPlay(type, mime) {
|
|
83
|
+
if (type) {
|
|
84
|
+
switch (type.toLowerCase()) {
|
|
85
|
+
case 'audio':
|
|
86
|
+
return this.canPlayAudio(mime);
|
|
87
|
+
|
|
88
|
+
case 'youtube':
|
|
89
|
+
return this.canPlayVideo();
|
|
90
|
+
|
|
91
|
+
case 'video':
|
|
92
|
+
return this.canPlayVideo(mime);
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return this.canPlayAudio() && this.canPlayVideo();
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if the browser can play audio
|
|
103
|
+
* @param {String} [mime] A media MIME type to check
|
|
104
|
+
* @returns {Boolean}
|
|
105
|
+
*/
|
|
106
|
+
canPlayAudio(mime) {
|
|
107
|
+
return this.checkSupport(document.createElement('audio'), mime);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Checks if the browser can play video
|
|
112
|
+
* @param {String} [mime] A media MIME type to check
|
|
113
|
+
* @returns {Boolean}
|
|
114
|
+
*/
|
|
115
|
+
canPlayVideo(mime) {
|
|
116
|
+
return this.checkSupport(document.createElement('video'), mime);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Checks if the browser allows to control the media playback
|
|
121
|
+
* @returns {Boolean}
|
|
122
|
+
*/
|
|
123
|
+
canControl() {
|
|
124
|
+
return !reAppleMobiles.test(window.navigator.userAgent);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
<div class="mediaplayer {{type}}">
|
|
2
2
|
<div class="player">
|
|
3
|
-
|
|
4
|
-
{{#if is.youtube}}
|
|
5
|
-
{{#each sources}}
|
|
6
|
-
<div class="media video youtube" data-video-src="{{src}}" data-video-id="{{id}}" data-type="youtube"></div>
|
|
7
|
-
{{/each}}
|
|
8
|
-
{{else}}
|
|
9
|
-
<video class="media video" poster="{{poster}}" controls {{#if is.cors}}crossorigin{{/if}}>
|
|
10
|
-
{{#each sources}}
|
|
11
|
-
<source src="{{src}}" type="{{type}}">
|
|
12
|
-
{{/each}}
|
|
13
|
-
|
|
14
|
-
{{__ 'Your browser doesn’t support the video player.'}}
|
|
15
|
-
{{#if link}}
|
|
16
|
-
<a href="{{link}}">{{__ 'Please download the video and view offline.'}}</a>
|
|
17
|
-
{{/if}}
|
|
18
|
-
</video>
|
|
19
|
-
{{/if}}
|
|
20
|
-
{{else}}
|
|
21
|
-
<audio class="media audio" controls {{#if is.cors}}crossorigin{{/if}}>
|
|
22
|
-
{{#each sources}}
|
|
23
|
-
<source src="{{src}}" type="{{type}}">
|
|
24
|
-
{{/each}}
|
|
25
|
-
|
|
26
|
-
{{__ 'Your browser doesn’t support the audio player.'}}
|
|
27
|
-
{{#if link}}
|
|
28
|
-
<a href="{{link}}">{{__ 'Please download the track and listen offline.'}}</a>
|
|
29
|
-
{{/if}}
|
|
30
|
-
</audio>
|
|
31
|
-
{{/if}}
|
|
32
|
-
<div class="overlay">
|
|
3
|
+
<div class="player-overlay">
|
|
33
4
|
<a class="action play" data-control="play"><span class="icon icon-play" title="{{__ 'Play'}}"></span></a>
|
|
34
5
|
<a class="action play" data-control="pause"><span class="icon icon-pause" title="{{__ 'Pause'}}"></span></a>
|
|
6
|
+
<a class="action reload" data-control="start">
|
|
7
|
+
<span class="icon icon-play" title="{{__ 'Start'}}"></span>
|
|
8
|
+
<div class="message">{{__ 'Click to start'}}</div>
|
|
9
|
+
</a>
|
|
10
|
+
<a class="action reload" data-control="reload">
|
|
11
|
+
<div class="icon icon-reload" title="{{__ 'Reload'}}"></div>
|
|
12
|
+
<div class="message">{{__ 'You are encountering a prolonged connectivity loss.'}} {{__ 'Click to reload.'}}</div>
|
|
13
|
+
</a>
|
|
35
14
|
</div>
|
|
36
15
|
</div>
|
|
37
16
|
<div class="controls">
|
|
@@ -42,8 +21,8 @@
|
|
|
42
21
|
</div>
|
|
43
22
|
<div class="control seek"><div class="slider"></div></div>
|
|
44
23
|
<div class="control infos timer">
|
|
45
|
-
<span class="info time" data-control="time-cur" title="{{__ 'Current playback position'}}"
|
|
46
|
-
<span class="info time" data-control="time-end" title="{{__ 'Total duration'}}"
|
|
24
|
+
<span class="info time" data-control="time-cur" title="{{__ 'Current playback position'}}">--:--</span>
|
|
25
|
+
<span class="info time" data-control="time-end" title="{{__ 'Total duration'}}">--:--</span>
|
|
47
26
|
</div>
|
|
48
27
|
<div class="control actions sound">
|
|
49
28
|
<div class="volume"><div class="slider"></div></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<source src="{{src}}" type="{{type}}">
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<video class="media video" preload="{{preload}}" poster="{{poster}}" controls {{#if cors}}crossorigin{{/if}}>
|
|
2
|
+
{{__ 'Your browser doesn’t support the video player.'}}
|
|
3
|
+
{{#if link}}
|
|
4
|
+
<a href="{{link}}">{{__ 'Please download the video and view offline.'}}</a>
|
|
5
|
+
{{/if}}
|
|
6
|
+
</video>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="media video youtube" data-video-src="{{src}}" data-video-id="{{id}}" data-type="youtube"></div>
|
|
@@ -0,0 +1,184 @@
|
|
|
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) 2021 (original work) Open Assessment Technologies SA ;
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a reminder manager.
|
|
21
|
+
*
|
|
22
|
+
* A reminder manager allows to register callback functions that will be called after a particular amount of time.
|
|
23
|
+
* The schedule can be created and cancelled at any time.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Create a reminder manager
|
|
27
|
+
* const manager = reminderManagerFactory();
|
|
28
|
+
*
|
|
29
|
+
* // Add a reminder that will be called after 2s (delay is given in milliseconds)
|
|
30
|
+
* manager.remind(() => console.log('Hello!'), 2000);
|
|
31
|
+
*
|
|
32
|
+
* // Start the schedule
|
|
33
|
+
* manager.start();
|
|
34
|
+
*
|
|
35
|
+
* // We can know how many time elapsed since the last schedule
|
|
36
|
+
* const elapsed = manager.elapsed;
|
|
37
|
+
*
|
|
38
|
+
* // The schedule can be cancelled
|
|
39
|
+
* if (needToCancel) {
|
|
40
|
+
* manager.stop();
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // The schedule should be cancelled
|
|
44
|
+
* console.log('schedule running:', manager.running)
|
|
45
|
+
*
|
|
46
|
+
* @returns {reminderManager}
|
|
47
|
+
*/
|
|
48
|
+
export default function reminderManagerFactory() {
|
|
49
|
+
// Keep track of the running state
|
|
50
|
+
let running = false;
|
|
51
|
+
|
|
52
|
+
// Timestamp of the last start
|
|
53
|
+
let last = 0;
|
|
54
|
+
|
|
55
|
+
// A list of reminders to callback
|
|
56
|
+
const reminders = new Map();
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cancels a schedule for a particular reminder.
|
|
60
|
+
* @param {object} state - A sate object containing the timeout handler for the reminder.
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
const stopReminder = state => {
|
|
64
|
+
if (state && state.timeout) {
|
|
65
|
+
clearTimeout(state.timeout);
|
|
66
|
+
state.timeout = null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Cancel the schedule for all reminders.
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
const stopAllReminders = () => reminders.forEach(stopReminder);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Schedule all reminders.
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
const startAllReminders = () => {
|
|
81
|
+
reminders.forEach((state, reminder) => {
|
|
82
|
+
stopReminder(state);
|
|
83
|
+
state.timeout = setTimeout(reminder, state.delay);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Defines the API of a reminder manager.
|
|
89
|
+
*
|
|
90
|
+
* A reminder manager allows to register callback functions that will be called after a particular amount of time.
|
|
91
|
+
* The schedule can be created and cancelled at any time.
|
|
92
|
+
*
|
|
93
|
+
* @namespace reminderManager
|
|
94
|
+
*/
|
|
95
|
+
return {
|
|
96
|
+
/**
|
|
97
|
+
* Tells whether or not the schedule is running.
|
|
98
|
+
* @type {boolean}
|
|
99
|
+
* @member running
|
|
100
|
+
* @memberOf reminderManager
|
|
101
|
+
*/
|
|
102
|
+
get running() {
|
|
103
|
+
return running;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gives the amount of time elapsed since the start of the schedule. It is given in milliseconds.
|
|
108
|
+
* If the schedule is not running, it will always be 0.
|
|
109
|
+
* @type {number}
|
|
110
|
+
* @member running
|
|
111
|
+
* @memberOf reminderManager
|
|
112
|
+
*/
|
|
113
|
+
get elapsed() {
|
|
114
|
+
if (!running) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
return performance.now() - last;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Schedules all reminders from now on.
|
|
122
|
+
*
|
|
123
|
+
* @returns {reminderManager}
|
|
124
|
+
* @function start
|
|
125
|
+
* @memberOf reminderManager
|
|
126
|
+
*/
|
|
127
|
+
start() {
|
|
128
|
+
running = true;
|
|
129
|
+
last = performance.now();
|
|
130
|
+
startAllReminders();
|
|
131
|
+
return this;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Cancels all scheduled reminders.
|
|
136
|
+
*
|
|
137
|
+
* @returns {reminderManager}
|
|
138
|
+
* @function stop
|
|
139
|
+
* @memberOf reminderManager
|
|
140
|
+
*/
|
|
141
|
+
stop() {
|
|
142
|
+
running = false;
|
|
143
|
+
stopAllReminders();
|
|
144
|
+
return this;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Adds a callback to be scheduled.
|
|
149
|
+
* It won't be scheduled until the schedule is restarted.
|
|
150
|
+
*
|
|
151
|
+
* @param {Function} cb - A function to call after the delay elapsed.
|
|
152
|
+
* @param {number} delay - The delay after what call back the reminder. It is given in milliseconds.
|
|
153
|
+
* @returns {reminderManager}
|
|
154
|
+
* @function remind
|
|
155
|
+
* @memberOf reminderManager
|
|
156
|
+
*/
|
|
157
|
+
remind(cb, delay) {
|
|
158
|
+
if ('function' === typeof cb && delay) {
|
|
159
|
+
stopReminder(reminders.get(cb));
|
|
160
|
+
reminders.set(cb, { delay });
|
|
161
|
+
}
|
|
162
|
+
return this;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Removes a scheduled callback. If a schedule was running, it will be cancelled first.
|
|
167
|
+
*
|
|
168
|
+
* @param {Function} [cb] - The callback function to remove. If omitted, all reminders will be removed.
|
|
169
|
+
* @returns {reminderManager}
|
|
170
|
+
* @function forget
|
|
171
|
+
* @memberOf reminderManager
|
|
172
|
+
*/
|
|
173
|
+
forget(cb) {
|
|
174
|
+
if ('undefined' !== typeof cb) {
|
|
175
|
+
stopReminder(reminders.get(cb));
|
|
176
|
+
reminders.delete(cb);
|
|
177
|
+
} else {
|
|
178
|
+
stopAllReminders();
|
|
179
|
+
reminders.clear();
|
|
180
|
+
}
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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) 2021 (original work) Open Assessment Technologies SA ;
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import eventifier from 'core/eventifier';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a time observer.
|
|
23
|
+
*
|
|
24
|
+
* It observes the updates applied to a timeline, raising a flag when an irregularity occurs.
|
|
25
|
+
*
|
|
26
|
+
* It works as follow:
|
|
27
|
+
* - an initial state is defined (example: current: 0, duration: 100)
|
|
28
|
+
* - each time a position is forced (say the position is changed outside of the regular time update), the observer needs
|
|
29
|
+
* to be notified.
|
|
30
|
+
* - each time the position is updated (say regular time update), the observer needs to be called.
|
|
31
|
+
* - if the difference between the last regular update and the last one is too high, an event is triggered
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Create a time observer with an expected interval of 2 seconds
|
|
35
|
+
* const observer = timeObserverFactory(2);
|
|
36
|
+
*
|
|
37
|
+
* // Init the state
|
|
38
|
+
* observer.start(player.position, player.duration);
|
|
39
|
+
*
|
|
40
|
+
* // Update on a regular basis
|
|
41
|
+
* player.on('timeupdate', () => observer.update(player.position));
|
|
42
|
+
*
|
|
43
|
+
* // Notify any position change outside of the regular update
|
|
44
|
+
* player.on('seek', () => observer.seek(player.position));
|
|
45
|
+
*
|
|
46
|
+
* // Gets informed from any irregularity
|
|
47
|
+
* observer.on('irregularity', () => console.log('irregular jump in time');
|
|
48
|
+
*
|
|
49
|
+
* @param {number} interval - The typical interval expected between two updates. It is given in seconds.
|
|
50
|
+
* @returns {timeObserver}
|
|
51
|
+
*/
|
|
52
|
+
export default function timeObserverFactory(interval = 1) {
|
|
53
|
+
// Current time position
|
|
54
|
+
let position = 0;
|
|
55
|
+
|
|
56
|
+
// Total duration expected
|
|
57
|
+
let duration = 0;
|
|
58
|
+
|
|
59
|
+
// Last position forced
|
|
60
|
+
let seek = 0;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Defines the API of a time observer.
|
|
64
|
+
*
|
|
65
|
+
* It observes the updates applied to a timeline, raising a flag when an irregularity occurs.
|
|
66
|
+
*
|
|
67
|
+
* @namespace timeObserver
|
|
68
|
+
*/
|
|
69
|
+
return eventifier({
|
|
70
|
+
/**
|
|
71
|
+
* Gets the current time position reported to the observer.
|
|
72
|
+
*
|
|
73
|
+
* @returns {number}
|
|
74
|
+
* @type {number}
|
|
75
|
+
* @member position
|
|
76
|
+
* @memberOf timeObserver
|
|
77
|
+
*/
|
|
78
|
+
get position() {
|
|
79
|
+
return position;
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Gets the total duration reported to the observer.
|
|
84
|
+
*
|
|
85
|
+
* @returns {number}
|
|
86
|
+
* @type {number}
|
|
87
|
+
* @member duration
|
|
88
|
+
* @memberOf timeObserver
|
|
89
|
+
*/
|
|
90
|
+
get duration() {
|
|
91
|
+
return duration;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Initialises the time state.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} initPosition - The initial time position
|
|
98
|
+
* @param {number} initDuration - The total duration expected
|
|
99
|
+
* @returns {timeObserver}
|
|
100
|
+
* @function init
|
|
101
|
+
* @memberOf timeObserver
|
|
102
|
+
*/
|
|
103
|
+
init(initPosition, initDuration) {
|
|
104
|
+
position = seek = initPosition;
|
|
105
|
+
duration = initDuration;
|
|
106
|
+
return this;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Updates the time position. If the difference with the previous update is too high, an `irregularity` event
|
|
111
|
+
* will be emitted.
|
|
112
|
+
*
|
|
113
|
+
* @param {number} newPosition - The new time position
|
|
114
|
+
* @returns {timeObserver}
|
|
115
|
+
*
|
|
116
|
+
* @fires irregularity
|
|
117
|
+
*/
|
|
118
|
+
update(newPosition) {
|
|
119
|
+
if (newPosition > seek && newPosition - position > interval) {
|
|
120
|
+
/**
|
|
121
|
+
* Notifies an irregularity in the time update
|
|
122
|
+
* @event irregularity
|
|
123
|
+
* @param {number} position - last regular position
|
|
124
|
+
* @param {number} newPosition - new irregular position
|
|
125
|
+
*/
|
|
126
|
+
this.trigger('irregularity', position, newPosition);
|
|
127
|
+
}
|
|
128
|
+
position = newPosition;
|
|
129
|
+
return this;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Notifies the observer about a change in the position outside of the regular update.
|
|
134
|
+
*
|
|
135
|
+
* @param {number} seekPosition
|
|
136
|
+
* @returns {timeObserver}
|
|
137
|
+
*/
|
|
138
|
+
seek(seekPosition) {
|
|
139
|
+
position = seek = seekPosition;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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) 2015-2021 (original work) Open Assessment Technologies SA ;
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import $ from 'jquery';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CDN for the YouTube API
|
|
23
|
+
* @type {String}
|
|
24
|
+
*/
|
|
25
|
+
const youtubeApi = 'https://www.youtube.com/iframe_api';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A Regex to extract ID from Youtube URLs
|
|
29
|
+
* @type {RegExp}
|
|
30
|
+
*/
|
|
31
|
+
const reYoutube = /([?&\/]v[=\/])([\w-]+)([&\/]?)/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Installs a Youtube player. The Youtube API must be ready
|
|
35
|
+
* @param {String|jQuery|HTMLElement} elem
|
|
36
|
+
* @param {Object} player
|
|
37
|
+
* @param {Object} [options]
|
|
38
|
+
* @param {Boolean} [options.controls]
|
|
39
|
+
*/
|
|
40
|
+
function addYoutubePlayer(elem, player, options = {}) {
|
|
41
|
+
const $elem = $(elem);
|
|
42
|
+
|
|
43
|
+
new window.YT.Player($elem.get(0), {
|
|
44
|
+
height: '360',
|
|
45
|
+
width: '640',
|
|
46
|
+
videoId: $elem.data('videoId'),
|
|
47
|
+
playerVars: {
|
|
48
|
+
//hd: true,
|
|
49
|
+
autoplay: 0,
|
|
50
|
+
controls: options && options.controls ? 1 : 0,
|
|
51
|
+
rel: 0,
|
|
52
|
+
showinfo: 0,
|
|
53
|
+
wmode: 'transparent',
|
|
54
|
+
modestbranding: 1,
|
|
55
|
+
disablekb: 1,
|
|
56
|
+
playsinline: 1,
|
|
57
|
+
enablejsapi: 1,
|
|
58
|
+
origin: location.hostname
|
|
59
|
+
},
|
|
60
|
+
events: {
|
|
61
|
+
onReady: ev => player.onReady(ev),
|
|
62
|
+
onStateChange: ev => player.onStateChange(ev)
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A local manager for Youtube players.
|
|
69
|
+
* Relies on https://developers.google.com/youtube/iframe_api_reference
|
|
70
|
+
* @type {Object}
|
|
71
|
+
*/
|
|
72
|
+
export default function youtubeManagerFactory() {
|
|
73
|
+
// The Youtube API injection state
|
|
74
|
+
let injected = false;
|
|
75
|
+
|
|
76
|
+
// The Youtube API ready state
|
|
77
|
+
let ready = false;
|
|
78
|
+
|
|
79
|
+
// A list of pending players
|
|
80
|
+
let pending = [];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if the Youtube API is ready to use
|
|
84
|
+
* @returns {Boolean}
|
|
85
|
+
*/
|
|
86
|
+
function isApiReady() {
|
|
87
|
+
const apiReady = typeof window.YT !== 'undefined' && typeof window.YT.Player !== 'undefined';
|
|
88
|
+
if (apiReady && !ready) {
|
|
89
|
+
ready = true;
|
|
90
|
+
pending.forEach(args => {
|
|
91
|
+
if (args) {
|
|
92
|
+
addYoutubePlayer(...args);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
pending = [];
|
|
96
|
+
}
|
|
97
|
+
return apiReady;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Injects the Youtube API into the page
|
|
102
|
+
*/
|
|
103
|
+
function injectApi() {
|
|
104
|
+
if (!isApiReady()) {
|
|
105
|
+
window.require([youtubeApi], () => {
|
|
106
|
+
const check = () => {
|
|
107
|
+
if (!isApiReady()) {
|
|
108
|
+
setTimeout(check, 100);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
check();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
injected = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
/**
|
|
120
|
+
* Adds a Youtube player
|
|
121
|
+
* @param {String|jQuery|HTMLElement} elem
|
|
122
|
+
* @param {Object} player
|
|
123
|
+
* @param {Object} [options]
|
|
124
|
+
* @param {Boolean} [options.controls]
|
|
125
|
+
*/
|
|
126
|
+
add(elem, player, options) {
|
|
127
|
+
if (ready) {
|
|
128
|
+
addYoutubePlayer(elem, player, options);
|
|
129
|
+
} else {
|
|
130
|
+
pending.push([elem, player, options]);
|
|
131
|
+
|
|
132
|
+
if (!injected) {
|
|
133
|
+
injectApi();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Removes a pending Youtube player
|
|
140
|
+
* @param {String|jQuery|HTMLElement} elem
|
|
141
|
+
* @param {Object} player
|
|
142
|
+
*/
|
|
143
|
+
remove(elem, player) {
|
|
144
|
+
pending.forEach((args, idx) => {
|
|
145
|
+
if (args && elem === args[0] && player === args[1]) {
|
|
146
|
+
pending[idx] = null;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Extracts the ID of a Youtube video from an URL
|
|
153
|
+
* @param {String} url
|
|
154
|
+
* @returns {String}
|
|
155
|
+
*/
|
|
156
|
+
extractYoutubeId(url) {
|
|
157
|
+
const res = reYoutube.exec(url);
|
|
158
|
+
return (res && res[2]) || url;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|