fcad-core-dragon 2.0.0-beta.3 → 2.0.0-beta.4

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 (95) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/.eslintrc.cjs +81 -81
  4. package/CHANGELOG +373 -364
  5. package/README.md +71 -71
  6. package/bk.scss +117 -117
  7. package/package.json +61 -61
  8. package/src/$locales/en.json +143 -143
  9. package/src/$locales/fr.json +105 -105
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1147 -1054
  12. package/src/components/AppBaseButton.vue +87 -87
  13. package/src/components/AppBaseErrorDisplay.vue +438 -438
  14. package/src/components/AppBaseFlipCard.vue +84 -84
  15. package/src/components/AppBaseModule.vue +1636 -1673
  16. package/src/components/AppBasePage.vue +779 -779
  17. package/src/components/AppBasePopover.vue +41 -41
  18. package/src/components/AppCompAudio.vue +234 -234
  19. package/src/components/AppCompBranchButtons.vue +552 -552
  20. package/src/components/AppCompButtonProgress.vue +126 -126
  21. package/src/components/AppCompCarousel.vue +298 -298
  22. package/src/components/AppCompInputCheckBoxNext.vue +195 -195
  23. package/src/components/AppCompInputDropdownNext.vue +159 -159
  24. package/src/components/AppCompInputRadioNext.vue +152 -152
  25. package/src/components/AppCompInputTextNext.vue +106 -106
  26. package/src/components/AppCompInputTextTableNext.vue +141 -141
  27. package/src/components/AppCompInputTextToFillDropdownNext.vue +230 -230
  28. package/src/components/AppCompInputTextToFillNext.vue +171 -171
  29. package/src/components/AppCompJauge.vue +74 -74
  30. package/src/components/AppCompMenu.vue +423 -413
  31. package/src/components/AppCompMenuItem.vue +228 -228
  32. package/src/components/AppCompNavigation.vue +959 -960
  33. package/src/components/AppCompNoteCall.vue +133 -133
  34. package/src/components/AppCompNoteCredit.vue +292 -292
  35. package/src/components/AppCompPlayBar.vue +1218 -1218
  36. package/src/components/AppCompPlayBarNext.vue +2052 -2052
  37. package/src/components/AppCompPlayBarProgress.vue +82 -82
  38. package/src/components/AppCompPopUpNext.vue +503 -503
  39. package/src/components/AppCompQuizNext.vue +2904 -2904
  40. package/src/components/AppCompQuizRecall.vue +276 -276
  41. package/src/components/AppCompSVGNext.vue +347 -347
  42. package/src/components/AppCompSettingsMenu.vue +172 -172
  43. package/src/components/AppCompTableOfContent.vue +387 -387
  44. package/src/components/AppCompTranscript.vue +24 -24
  45. package/src/components/AppCompVideoPlayer.vue +368 -368
  46. package/src/components/AppCompViewDisplay.vue +6 -6
  47. package/src/components/BaseModule.vue +72 -72
  48. package/src/composables/useQuiz.js +206 -206
  49. package/src/externalComps/ModuleView.vue +22 -22
  50. package/src/externalComps/SummaryView.vue +91 -91
  51. package/src/main.js +272 -272
  52. package/src/mixins/$mediaMixins.js +819 -819
  53. package/src/mixins/timerMixin.js +155 -155
  54. package/src/module/stores/appStore.js +901 -893
  55. package/src/module/xapi/ADL.js +380 -376
  56. package/src/module/xapi/Crypto/Hasher.js +241 -241
  57. package/src/module/xapi/Crypto/WordArray.js +278 -278
  58. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  59. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  60. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  61. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  62. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  63. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  64. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  65. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  66. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  67. package/src/module/xapi/Crypto/index.js +53 -53
  68. package/src/module/xapi/Statement/activity.js +47 -47
  69. package/src/module/xapi/Statement/agent.js +55 -55
  70. package/src/module/xapi/Statement/group.js +26 -26
  71. package/src/module/xapi/Statement/index.js +259 -259
  72. package/src/module/xapi/Statement/statement.js +253 -253
  73. package/src/module/xapi/Statement/statementRef.js +23 -23
  74. package/src/module/xapi/Statement/substatement.js +22 -22
  75. package/src/module/xapi/Statement/verb.js +36 -36
  76. package/src/module/xapi/activitytypes.js +17 -17
  77. package/src/module/xapi/launch.js +157 -157
  78. package/src/module/xapi/utils.js +167 -167
  79. package/src/module/xapi/verbs.js +294 -294
  80. package/src/module/xapi/wrapper.js +1963 -1963
  81. package/src/module/xapi/xapiStatement.js +444 -444
  82. package/src/plugins/bus.js +8 -8
  83. package/src/plugins/gsap.js +14 -14
  84. package/src/plugins/helper.js +314 -308
  85. package/src/plugins/i18n.js +44 -44
  86. package/src/plugins/idb.js +227 -219
  87. package/src/plugins/save.js +37 -37
  88. package/src/plugins/scorm.js +287 -287
  89. package/src/plugins/xapi.js +11 -11
  90. package/src/public/index.html +33 -33
  91. package/src/router/index.js +43 -43
  92. package/src/router/routes.js +312 -312
  93. package/src/shared/generalfuncs.js +210 -210
  94. package/src/shared/validators.js +1069 -1069
  95. package/vite.config.js +0 -27
@@ -1,819 +1,819 @@
1
- //import { mapState } from 'pinia'
2
- /*
3
- @ Description: Mixins to extends general fonctionnalities in a media component (audio, video)
4
- @ Note: Must be used along component AppCompVideoPlayer, AppCompAudio and AppCompPlaybar or the components will not work
5
- */
6
- import { mapState, mapActions } from 'pinia'
7
- import { useAppStore } from '../module/stores/appStore'
8
- const $extendsMedia = {
9
- data: () => {
10
- return {
11
- //Container Playbar
12
- pbContainer: null,
13
- //ShowControls (show the playbar or not, default is true)
14
- showControlsValue: true,
15
- //Player
16
- isPlaying: false,
17
- canReplay: false,
18
- savedIsPlaying: false, //To get the playing status on mousemove on the progressBar
19
- //ProgressBar
20
- currentTime: 0,
21
- progressArea: null,
22
- progressBar: null,
23
- progressIndicator: null,
24
- progressThumb: null,
25
- progressThumbDown: false,
26
- progressThumbHover: false,
27
- //Volume
28
- volumeBtn: null,
29
- volumeSlider: null,
30
- volumeIndicator: null,
31
- currentVolume: null,
32
- savedVolume: 0.5, //Saved volume when muted
33
- //muted
34
- muted: false,
35
-
36
- //Labels
37
- //Strings for screenreaders (duration et time)
38
- mediaA11Y: {
39
- label: 'progress',
40
- valMax: null,
41
- valNow: null,
42
- valueText: null,
43
- timeCode: 0,
44
- duration: 0
45
- },
46
- //buttons label (all the buttons that need labels management in the playbar)
47
- btnsLabelDisplay: {
48
- 'btn-subtitles': false,
49
- 'btn-transcript': false,
50
- 'btn-fullscreen': false,
51
- 'playbar-play': false,
52
- 'playbar-volume': false,
53
- 'volume-slider': false
54
- },
55
- //FocusState to manage arrows keydown on the 2 sliders (progress and volume)
56
- focusState: {
57
- progressBar: false,
58
- volumeSlider: false
59
- }
60
- }
61
- },
62
- computed: {
63
- ...mapState(useAppStore, [
64
- 'getCurrentPage',
65
- 'getModuleChildren',
66
- 'hasMediaElOrTimeline',
67
- 'getCurrentMediaDuration',
68
- 'getCurrentBrowser',
69
- 'getDataFromServer',
70
- 'getAutoplayEnabled',
71
- 'getMediaVolume',
72
- 'getModuleInfo',
73
- 'getMediaSubtitles',
74
- 'getMediaPlaybarValues',
75
- 'getUserInteraction',
76
- 'getMediaMuted'
77
- ]),
78
- //MediaElement
79
- mediaElement() {
80
- if (!this.mediaToPlay) return null
81
- const { mElement } = this.mediaToPlay
82
- return mElement ? mElement : null
83
- },
84
- //Media Progress
85
- //Time display : duration
86
- mediaDurationTime() {
87
- if (this.mediaDuration > 0)
88
- return this.$helper.formatTime(this.mediaDuration)
89
- else return '00:00'
90
- },
91
- //Time display : timecode
92
- timecode() {
93
- if (this.currentTime && typeof (this.currentTime === Number)) {
94
- return this.$helper.formatTime(this.currentTime)
95
- } else return '00:00'
96
- },
97
- //ProgressPercentage for progressBarIndicator width
98
- progressBarPercentage() {
99
- if (this.currentTime && this.mediaDuration)
100
- return (this.currentTime / this.mediaDuration) * 100
101
- else return 0
102
- },
103
- progressBarEnded() {
104
- return this.currentTime >= Math.floor(this.mediaDuration)
105
- },
106
- //Volume
107
- volumeState() {
108
- let state = null
109
- switch (true) {
110
- case this.muted || this.currentVolume <= 0:
111
- state = 'muted'
112
- break
113
-
114
- case !this.muted && this.currentVolume <= 0.5:
115
- state = 'low'
116
- break
117
- case !this.muted && this.currentVolume > 0.5:
118
- state = 'high'
119
- }
120
-
121
- return state
122
- },
123
- //Volume progress for slider background
124
- volumeSliderBackground() {
125
- return (this.currentVolume * 100).toFixed(0).toString() + '%'
126
- },
127
- //Volume from store
128
- mediaVolume() {
129
- // return this.getMediaVolume
130
- return this.getMediaPlaybarValues('volume')
131
- },
132
-
133
- // mediaVolume() {
134
- // let vol = 0.5
135
- // if (!this.getDataFromServer) return vol
136
-
137
- // const {pbValues} = this.getDataFromServer
138
- // if(pbValues && pbValues.volume) vol = parseFloat(pbValues.volume)
139
- // console.log("My Vol...", vol)
140
- // return vol
141
- // },
142
- //media from store
143
- mediaMuted() {
144
- return this.getMediaMuted
145
- },
146
- //Labels
147
- playLabel() {
148
- let label = null
149
- switch (true) {
150
- case this.isPlaying:
151
- label = this.$t('button.pause')
152
- break
153
-
154
- case this.canReplay:
155
- label = this.$t('button.replay')
156
- break
157
-
158
- default:
159
- label = this.$t('button.play')
160
- }
161
- return label
162
- },
163
- volumeLabel() {
164
- let label = null
165
- switch (true) {
166
- case this.muted:
167
- label = `${this.$t('button.unmute')}`
168
- break
169
- case !this.muted:
170
- label = `${this.$t('button.mute')}`
171
- break
172
- }
173
- return label
174
- },
175
- volumeLevelA11Y() {
176
- const w = this.$i18n.locale === 'en' ? 'at' : 'à'
177
- const level = Math.round(this.currentVolume * 100)
178
- let txtA11Y = `Volume ${w} ${level}%`
179
-
180
- return txtA11Y
181
- }
182
- },
183
- watch: {
184
- getUserInteraction: {
185
- immediate: true,
186
- deep: true,
187
- handler() {
188
- if (!this.getUserInteraction) return
189
- this.getViewedSatus()
190
- this.setMediaVolume()
191
- }
192
- }
193
- },
194
-
195
- methods: {
196
- ...mapActions(useAppStore, ['setMediaMuted', 'setMediaPlaybarValues']),
197
- /**
198
- * @description - Set all the DOM elements for medias
199
- */
200
- initializeMediaElm() {
201
- this.pbContainer = this.$refs['$pb-container']
202
- //ProgressBar
203
- this.progressArea = this.$refs['$progress-area']
204
- this.progressBar = this.$refs['$progress-bar']
205
- this.progressIndicator = this.$refs['$progress-indicator']
206
- this.progressThumb = this.$refs['$progress-thumb']
207
- //SeekTooltip
208
- this.seekTooltip = this.$refs['$seek-tooltip']
209
- //Volume//
210
- this.volumeBtn = this.$refs['$btn-volume']
211
- this.volumeSlider = this.$refs['$volume-slider']
212
- this.volumeIndicator = this.$refs['$volume-progress']
213
- },
214
- /**
215
- * @description - handle all the listeners for medias
216
- */
217
- setMediaHandlers() {
218
- if (this.mediaElement) {
219
- //Prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
220
- this.pbContainer.addEventListener('keydown', this.keysPreventDefault)
221
- //progressBar events
222
- this.progressArea.addEventListener('click', this.seekingProgress)
223
- //window handlers for playbar progress
224
- window.addEventListener('mousemove', this.progressWindowMove)
225
- window.addEventListener('mouseup', this.progressWindowUp)
226
- //EndprogressBar
227
- //Update data when media as a timeupdate/ended
228
- this.mediaElement.addEventListener(
229
- 'timeupdate',
230
- this.updateProgressBarTime
231
- )
232
- this.mediaElement.addEventListener('ended', this.mediaEnded)
233
- //Volume
234
- this.volumeSlider.addEventListener('input', this.updateVolumeLevel)
235
- //Hotkeys
236
- this.pbContainer.addEventListener('keydown', this.handleMediaControls)
237
-
238
- //Bus
239
- //this.$bus.$on('play-media', this.handleMediaControls)
240
- }
241
- },
242
-
243
- /**
244
- * @description - Unset all the listeners (medias)
245
- */
246
- removeMediaHandlers() {
247
- if (this.mediaElement) {
248
- //progressBar events
249
- this.progressArea.removeEventListener('click', this.seekingProgress)
250
- //window handlers for playbar progress
251
- window.removeEventListener('mousemove', this.progressWindowMove)
252
- window.removeEventListener('mouseup', this.progressWindowUp)
253
-
254
- this.mediaElement.removeEventListener(
255
- 'timeupdate',
256
- this.updateProgressBarTime
257
- )
258
- this.mediaElement.removeEventListener('ended', this.mediaEnded)
259
-
260
- //Volume
261
- this.volumeSlider.removeEventListener('input', this.updateVolumeLevel)
262
- //Keys
263
- this.pbContainer.removeEventListener('keydown', this.keysPreventDefault)
264
- this.pbContainer.removeEventListener(
265
- 'keydown',
266
- this.handleMediaControls
267
- )
268
- }
269
- },
270
- /**
271
- * @description - play or pause the media
272
- * @fires manage-media-players - to the PAge the media in play
273
- */
274
- togglePlay() {
275
- //If the progressBar is at the end, restart the media
276
- if (this.progressBarEnded) {
277
- this.mediaElement.currentTime = 0
278
- this.currentTime = 0
279
- }
280
- //MediaElement
281
- this.isPlaying ? this.mediaElement.pause() : this.mediaElement.play()
282
- //Data
283
- if (!this.isPlaying) {
284
- //Signal to set this mediaElement as the last playing
285
- this.$bus.$emit('manage-media-players', this.mediaToPlay)
286
- }
287
-
288
- this.isPlaying = !this.isPlaying
289
- this.canReplay = false
290
- },
291
- /**
292
- * @description - Set the media to a new time (if its a time between 0 and max)
293
- */
294
- setMediaTime(time) {
295
- if (time <= 0) this.mediaElement.currentTime = 0
296
- else if (time > this.mediaDuration)
297
- this.mediaElement.currentTime = this.mediaDuration
298
- else this.mediaElement.currentTime = time
299
- //Handle finish Media if its set to max duration
300
- if (this.progressBarEnded) {
301
- this.mediaEnded()
302
- }
303
- },
304
- /**
305
- * @description - Set the media Ended if it is set to the end of the progressBar by the user or if it plays until the end
306
- */
307
-
308
- mediaEnded() {
309
- //Don't make the media play when release progressBar thumb and the media is Ended
310
- this.savedIsPlaying = false
311
- //Pausing mediaElement is isPlaying
312
- if (this.mediaElement && this.isPlaying) {
313
- this.mediaElement.pause()
314
- this.isPlaying = false
315
- }
316
- this.$bus.$emit('media-viewed', this.mediaToPlay.id)
317
- this.canReplay = true
318
- if (this.mediaToPlay.mType === 'video') this.showControls()
319
- },
320
-
321
- /**
322
- * @description get the view Status of the current media from UserInteraction
323
- */
324
- getViewedSatus() {
325
- //Should get the viewed Status from UserInteraction
326
- let { activityRef, id } = this.getCurrentPage
327
- if (
328
- !this.getUserInteraction[activityRef] ||
329
- !this.getUserInteraction[activityRef][id]
330
- )
331
- return
332
- const { userInteraction } = this.getUserInteraction[activityRef][id]
333
- if (!userInteraction.mediasViewed || !userInteraction.mediasViewed.length)
334
- return
335
- const { mediasViewed } = userInteraction
336
- //Should update the can replay state of media
337
- this.canReplay = mediasViewed.includes(this.mediaElement.id)
338
- },
339
- /**
340
- * @description controls the level Of the media volume
341
- * @param d string, optional volUp or volDown to Add or Remove 0.05 from volume value
342
- */
343
- updateVolumeLevel(d = null) {
344
- if (!this.mediaElement) return
345
- if (this.mediaElement.muted) {
346
- this.mediaElement.muted = this.muted = false
347
- //Send muted state to the store
348
- this.setMutedState(this.muted)
349
- }
350
- switch (true) {
351
- case d == 'volUP':
352
- //Set volume Up
353
- if (this.mediaElement.volume >= 1) return
354
- if (this.mediaElement.volume >= 0.95) {
355
- this.mediaElement.volume = 1
356
- } else {
357
- this.mediaElement.volume = (
358
- this.mediaElement.volume + 0.05
359
- ).toFixed(2)
360
- }
361
- this.volumeSlider.value = this.currentVolume =
362
- this.mediaElement.volume
363
- break
364
-
365
- case d == 'volDOWN':
366
- //Set volume Down
367
- if (this.mediaElement.volume <= 0) return
368
- if (this.mediaElement.volume <= 0.05) {
369
- this.mediaElement.volume = 0
370
- } else {
371
- this.mediaElement.volume = (
372
- this.mediaElement.volume - 0.05
373
- ).toFixed(2)
374
- }
375
-
376
- this.volumeSlider.value = this.currentVolume =
377
- this.mediaElement.volume
378
- break
379
-
380
- default: {
381
- //set volume
382
- this.currentVolume = this.mediaElement.volume =
383
- this.volumeSlider.value
384
- this.currentVolume = parseFloat(this.currentVolume).toFixed(2)
385
- }
386
- }
387
- if (!this.muted) this.savedVolume = this.currentVolume
388
- this.setMediaPlaybarValues({
389
- volume: this.savedVolume
390
- }) // Save the volume in the Store
391
- },
392
- /**
393
- * @description - Toggle the state fo the sound
394
- */
395
- toggleMute() {
396
- //Check first the volume of the media
397
- if (this.mediaElement.volume == 0) this.muted = true
398
-
399
- this.muted = !this.muted
400
- this.mediaElement.muted = this.muted // set the muted state of the media
401
-
402
- //Send mutedstate to the store
403
- this.setMutedState(this.muted)
404
-
405
- if (this.muted) {
406
- this.mediaElement.volume = 0
407
- this.savedVolume = this.currentVolume
408
- this.volumeSlider.value = 0
409
- this.currentVolume = 0
410
- }
411
- if (!this.muted) {
412
- this.volumeSlider.value = this.savedVolume
413
- this.currentVolume = this.mediaElement.volume = this.volumeSlider.value
414
- }
415
- },
416
-
417
- /** @description Save the muted in the Store */
418
- setMutedState() {
419
- this.setMediaMuted(this.muted)
420
- },
421
- //Keyboard event methods
422
- /**
423
- * @description Method to prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
424
- * */
425
- keysPreventDefault(evt) {
426
- if (
427
- ['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(
428
- evt.key
429
- ) > -1
430
- ) {
431
- evt.preventDefault()
432
- }
433
- },
434
-
435
- /**
436
- * @description Method to handle the keyboard input to controle the media player
437
- *@summary Keyboard actions:
438
- * *** Left-Arrow/ Right-Arrow : move foward/Backward media by 5s
439
- * *** Up-Arrow/ Down-Arrow: Increase/Decrease media volume by 5%
440
- * *** c : show/ hide subtitle
441
- * *** f : enter/ exit fullscreen
442
- * *** k : play/ pause media
443
- * *** m : Mute/ Unmute volume
444
- * *** t : show transcript
445
- */
446
-
447
- handleMediaControls(e) {
448
- e = e.code
449
- this.showControls()
450
- switch (e) {
451
- case 'ArrowLeft':
452
- //If focus is on Volume, VolDown
453
- if (this.focusState.volumeSlider) {
454
- //VolDown
455
- this.updateVolumeLevel('volDOWN')
456
- break
457
- } else {
458
- //Go back 5s in media timeline
459
- this.cursorLeft()
460
- break
461
- }
462
-
463
- case 'ArrowRight':
464
- //If focus is on Volume, VolUp
465
- if (this.focusState.volumeSlider) {
466
- //VolUp
467
- this.updateVolumeLevel('volUP')
468
- break
469
- } else {
470
- //Move forward 5s in media timeline
471
- this.cursorRight()
472
- break
473
- }
474
-
475
- case 'ArrowUp':
476
- //If focus is on progressBar, cursorRight
477
- if (this.focusState.progressBar) {
478
- //Move forward 5s in media timeline
479
- this.cursorRight()
480
- break
481
- } else {
482
- //VolUp
483
- this.updateVolumeLevel('volUP')
484
- break
485
- }
486
-
487
- case 'ArrowDown':
488
- //If focus is on progressBar, cursorRight
489
- if (this.focusState.progressBar) {
490
- //Go back 5s in media timeline
491
- this.cursorLeft()
492
- break
493
- } else {
494
- //Voldown
495
- this.updateVolumeLevel('volDOWN')
496
- break
497
- }
498
-
499
- case 'KeyM':
500
- this.toggleMute()
501
- break
502
-
503
- case 'KeyF':
504
- if (this.mediaToPlay.mType == 'audio') break
505
- //Toggle fullScreen
506
- this.toggleFullScreen()
507
- break
508
-
509
- case 'KeyT':
510
- if (this.mediaToPlay.mType == 'audio') break
511
- //Toggle transcript view
512
- this.toggleViewTranscript()
513
- break
514
-
515
- case 'KeyC':
516
- if (this.mediaToPlay.mType == 'audio') break
517
- //Toggle subtitle view
518
- this.toggleViewSubtitle()
519
- break
520
-
521
- case 'KeyK':
522
- //Play Pause on k bar pressed
523
- if (this.mediaToPlay.mType == 'audio') this.togglePlay()
524
- //Play/pause video with the playBackAnim if it is triggerd with KeyK
525
- else this.handlePlayVideo('playBackAnim')
526
- break
527
- }
528
- },
529
- /**
530
- * @description - Activate the label of a button include in btnsLabelDisplay on Hover/Focus on the button. Deactivate the label in all the others btns
531
- */
532
- activateLabel(elm) {
533
- const id = elm.id
534
- this.btnsLabelDisplay[id] = true
535
- for (const property in this.btnsLabelDisplay) {
536
- if (property !== id) {
537
- this.btnsLabelDisplay[property] = false
538
- }
539
- }
540
- },
541
- /**
542
- * @description - Get currenttime from media
543
- */
544
-
545
- getCurrentTime() {
546
- this.currentTime = this.mediaElement.currentTime
547
- return this.mediaElement.currentTime
548
- },
549
- /**
550
- * @description - controls to move the media timeline backward
551
- */
552
- cursorLeft() {
553
- this.setMediaTime(this.getCurrentTime() - 5)
554
- },
555
- /**
556
- * @description - control to move the media timeline forwrad
557
- */
558
- cursorRight() {
559
- this.setMediaTime(this.getCurrentTime() + 5)
560
- },
561
- //ProgressBar Methods
562
-
563
- /**
564
- * @description - Update currentTime and timing strings when media is playing
565
- */
566
- updateProgressBarTime(e) {
567
- //Get currentTime from the event
568
- this.currentTime = e.target.currentTime
569
- //Update strings
570
- this.setProgressBarA11Y()
571
- //If replay is true and the progressIndicator is not at the end, set canReplay to false
572
- if (this.canReplay) this.canReplay = false
573
- //If replay is false and progressIndicator is at the end, set canReplay to true
574
- if (
575
- !this.canReplay &&
576
- this.currentTime >= Math.floor(this.mediaDuration)
577
- ) {
578
- this.canReplay = true
579
- }
580
- },
581
-
582
- /**
583
- * @description - Set mediatime depending on progressBar click
584
- */
585
- seekingProgress(e) {
586
- if (!this.progressThumbDown && !this.progressThumbHover) {
587
- const updatedTime =
588
- (e.offsetX / this.progressBar.offsetWidth) * this.mediaDuration
589
- this.setMediaTime(updatedTime)
590
- }
591
- },
592
-
593
- /**
594
- * @description - Set mediatime depending on mousemove on the screen (when progress thumb is mousedown)
595
- */
596
- progressWindowMove(ev) {
597
- if (this.progressThumbDown) {
598
- if (this.isPlaying) this.togglePlay()
599
- this.getTimeFromClickPos(ev)
600
- let timeToSet = Math.floor(this.getTimeFromClickPos(ev))
601
- if (timeToSet < 0) timeToSet = 0
602
- else if (timeToSet > this.duration) timeToSet = this.duration
603
- this.currentTime = timeToSet
604
- }
605
- },
606
-
607
- /**
608
- * @description - Update thumb state (mouseup) when the mouse is up on the screen
609
- */
610
- progressWindowUp(ev) {
611
- if (this.progressThumbDown) {
612
- this.setMediaTime(this.currentTime)
613
- this.progressThumbDown = false
614
- //savedIsPlaying is the playing status saved on mousedown event on the thumb (the video is paused when moving on the progressBar)
615
- if (this.isPlaying !== this.savedIsPlaying) this.togglePlay()
616
- }
617
- },
618
- /**
619
- * @description - Get the time corresponding of the position of the click on the progressBar
620
- */
621
- getTimeFromClickPos(ev) {
622
- let timeToSet = 0
623
- //La progress Bar
624
- const progressBar = this.progressBar
625
- //La position de la progressBar dans la page
626
- const left = progressBar.getBoundingClientRect().left
627
- //Position du click par rapport à la progressBar
628
- let clickPos = ev.clientX - left
629
- //Si la position est à gauche de la progressBar, retourner 0s
630
- if (clickPos < 0) return 0
631
- //Obtenir le width de la progressBar
632
- const width = progressBar.offsetWidth
633
- //Obtenir le ratio de la position du click vs width de la progressBar
634
- let percentage = clickPos / width
635
- //Si > que 1, veux dire que le clique est à droite de la progressBar (mettre le curseur à la fin)
636
- percentage = percentage > 1 ? 1 : percentage
637
- //Ratio * temps total du media donne la progressiob
638
- timeToSet = percentage * this.mediaDuration
639
- return timeToSet
640
- },
641
-
642
- //End progressBar methods
643
-
644
- //Labels
645
-
646
- /** @description - methode to set A11Y support for screen reader on progress bar element (video/audio/) */
647
- setProgressBarA11Y() {
648
- const w = this.$i18n.locale === 'en' ? 'at' : 'à'
649
- const dm =
650
- this.$i18n.locale === 'en'
651
- ? `${this.mediaToPlay.mType} duration`
652
- : `durée ${this.mediaToPlay.mType}`
653
-
654
- this.mediaA11Y.valMax = Math.round(this.mediaElement.duration)
655
- this.mediaA11Y.valNow = Math.round(this.currentTime)
656
-
657
- //format text output for screen reader
658
- let currentTimeArray = this.timecode.split(':')
659
- let fullTimeArray = this.mediaDurationTime.split(':')
660
- let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
661
- const mediaTitle = this.mediaToPlay.mTitle || this.mediaToPlay.mType
662
- this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
663
-
664
- //format string to remove leading 0 digit for time
665
- const formatNum = (n) => {
666
- return n[0] == '0' ? n.substring(1).trim() : n.trim()
667
- }
668
-
669
- const formatMsg = (x, y) => {
670
- let formatStr = this.$t('a11y_sr.range_expression')
671
- .replace('{x}', x)
672
- .replace('{y}', y)
673
-
674
- return formatStr
675
- }
676
- switch (fullTimeArray.length) {
677
- case 3:
678
- if (!currentTimeArray.length) return
679
-
680
- //format current hours text text with plurialization apply
681
- hrsTxt =
682
- currentTimeArray.length == 3 && parseInt(currentTimeArray[0]) > 0
683
- ? `${formatNum(currentTimeArray[0])} ${this.$tc(
684
- 'a11y_sr.time.hours',
685
- formatNum(currentTimeArray[0])
686
- )}`
687
- : ''
688
-
689
- //format minutes text text with plurialization apply
690
- mnTxt =
691
- parseInt(currentTimeArray[1]) > 0
692
- ? `${formatNum(currentTimeArray[1])} ${this.$tc(
693
- 'a11y_sr.time.minute',
694
- formatNum(currentTimeArray[1])
695
- )}`
696
- : ''
697
-
698
- //format seconds text with plurialization apply
699
- ssTxt =
700
- parseInt(currentTimeArray[2]) > 0
701
- ? `${formatNum(currentTimeArray[2])} ${this.$tc(
702
- 'a11y_sr.time.second',
703
- formatNum(currentTimeArray[2])
704
- )}`
705
- : `0 ${this.$tc('a11y_sr.time.second')}`
706
-
707
- HRSTxt =
708
- parseInt(fullTimeArray[0]) > 0
709
- ? `${formatNum(fullTimeArray[0])} ${this.$tc(
710
- 'a11y_sr.time.hours',
711
- formatNum(fullTimeArray[0])
712
- )}`
713
- : ''
714
-
715
- MNTxt =
716
- parseInt(fullTimeArray[1]) > 0
717
- ? `${formatNum(fullTimeArray[1])} ${this.$tc(
718
- 'a11y_sr.time.minute',
719
- formatNum(fullTimeArray[1])
720
- )}`
721
- : ''
722
-
723
- SSTxt =
724
- parseInt(fullTimeArray[2]) > 0
725
- ? `${formatNum(fullTimeArray[2])} ${this.$tc(
726
- 'a11y_sr.time.second',
727
- formatNum(fullTimeArray[2])
728
- )}`
729
- : ''
730
-
731
- cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
732
- fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
733
-
734
- //Format to show 0 when no value is present
735
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
736
-
737
- // Set sr text for timeCode && duration
738
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
739
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
740
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
741
-
742
- break
743
- case 2:
744
- if (!currentTimeArray.length) return
745
-
746
- //format current time text
747
- mnTxt =
748
- parseInt(currentTimeArray[0]) > 0
749
- ? `${formatNum(currentTimeArray[0])} ${this.$tc(
750
- 'a11y_sr.time.minute',
751
- formatNum(currentTimeArray[0])
752
- )}`
753
- : ''
754
- ssTxt =
755
- parseInt(currentTimeArray[1]) > 0
756
- ? `${formatNum(currentTimeArray[1])} ${this.$tc(
757
- 'a11y_sr.time.second',
758
- formatNum(currentTimeArray[1])
759
- )}`
760
- : `0 ${this.$tc('a11y_sr.time.second')}`
761
-
762
- // format full time text
763
- MNTxt =
764
- parseInt(fullTimeArray[0]) > 0
765
- ? `${formatNum(fullTimeArray[0])} ${this.$tc(
766
- 'a11y_sr.time.minute',
767
- formatNum(fullTimeArray[0])
768
- )}`
769
- : ''
770
- SSTxt =
771
- parseInt(fullTimeArray[1]) > 0
772
- ? `${formatNum(fullTimeArray[1])} ${this.$tc(
773
- 'a11y_sr.time.second',
774
- formatNum(fullTimeArray[1])
775
- )}`
776
- : ''
777
-
778
- cTimeTxt = `${mnTxt} ${ssTxt}`
779
- fTimeTxt = `${MNTxt} ${SSTxt}`
780
-
781
- //Format to show 0 when no value is present
782
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
783
-
784
- // Set sr text for timeCode && duration
785
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
786
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
787
-
788
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
789
-
790
- break
791
- }
792
- },
793
- /**
794
- * @description - Deactivate the label of a button include in btnsLabelDisplay on mouseout/blur of this button
795
- */
796
- deactivateLabel(elm) {
797
- const id = elm.id
798
- this.btnsLabelDisplay[id] = false
799
- },
800
- /**
801
- * @description - Update the state of the elm describe by the string (focus or not) for keydown arrow management in handleMediaControls method (volume | progress bar can use arrow up-down and left-right only on focus)
802
- */
803
- changeFocusState(string, bool) {
804
- this.focusState[string] = bool
805
- },
806
- /**
807
- * @description - handle initial volume level
808
- */
809
- setMediaVolume() {
810
- //Set media muted or with the savedVolume
811
- this.muted = this.mediaMuted
812
- if (!this.muted)
813
- return (this.currentVolume = this.savedVolume = this.mediaVolume)
814
- this.savedVolume = this.mediaVolume
815
- this.currentVolume = 0
816
- }
817
- }
818
- }
819
- export default $extendsMedia
1
+ //import { mapState } from 'pinia'
2
+ /*
3
+ @ Description: Mixins to extends general fonctionnalities in a media component (audio, video)
4
+ @ Note: Must be used along component AppCompVideoPlayer, AppCompAudio and AppCompPlaybar or the components will not work
5
+ */
6
+ import { mapState, mapActions } from 'pinia'
7
+ import { useAppStore } from '../module/stores/appStore'
8
+ const $extendsMedia = {
9
+ data: () => {
10
+ return {
11
+ //Container Playbar
12
+ pbContainer: null,
13
+ //ShowControls (show the playbar or not, default is true)
14
+ showControlsValue: true,
15
+ //Player
16
+ isPlaying: false,
17
+ canReplay: false,
18
+ savedIsPlaying: false, //To get the playing status on mousemove on the progressBar
19
+ //ProgressBar
20
+ currentTime: 0,
21
+ progressArea: null,
22
+ progressBar: null,
23
+ progressIndicator: null,
24
+ progressThumb: null,
25
+ progressThumbDown: false,
26
+ progressThumbHover: false,
27
+ //Volume
28
+ volumeBtn: null,
29
+ volumeSlider: null,
30
+ volumeIndicator: null,
31
+ currentVolume: null,
32
+ savedVolume: 0.5, //Saved volume when muted
33
+ //muted
34
+ muted: false,
35
+
36
+ //Labels
37
+ //Strings for screenreaders (duration et time)
38
+ mediaA11Y: {
39
+ label: 'progress',
40
+ valMax: null,
41
+ valNow: null,
42
+ valueText: null,
43
+ timeCode: 0,
44
+ duration: 0
45
+ },
46
+ //buttons label (all the buttons that need labels management in the playbar)
47
+ btnsLabelDisplay: {
48
+ 'btn-subtitles': false,
49
+ 'btn-transcript': false,
50
+ 'btn-fullscreen': false,
51
+ 'playbar-play': false,
52
+ 'playbar-volume': false,
53
+ 'volume-slider': false
54
+ },
55
+ //FocusState to manage arrows keydown on the 2 sliders (progress and volume)
56
+ focusState: {
57
+ progressBar: false,
58
+ volumeSlider: false
59
+ }
60
+ }
61
+ },
62
+ computed: {
63
+ ...mapState(useAppStore, [
64
+ 'getCurrentPage',
65
+ 'getModuleChildren',
66
+ 'hasMediaElOrTimeline',
67
+ 'getCurrentMediaDuration',
68
+ 'getCurrentBrowser',
69
+ 'getDataFromServer',
70
+ 'getAutoplayEnabled',
71
+ 'getMediaVolume',
72
+ 'getModuleInfo',
73
+ 'getMediaSubtitles',
74
+ 'getMediaPlaybarValues',
75
+ 'getUserInteraction',
76
+ 'getMediaMuted'
77
+ ]),
78
+ //MediaElement
79
+ mediaElement() {
80
+ if (!this.mediaToPlay) return null
81
+ const { mElement } = this.mediaToPlay
82
+ return mElement ? mElement : null
83
+ },
84
+ //Media Progress
85
+ //Time display : duration
86
+ mediaDurationTime() {
87
+ if (this.mediaDuration > 0)
88
+ return this.$helper.formatTime(this.mediaDuration)
89
+ else return '00:00'
90
+ },
91
+ //Time display : timecode
92
+ timecode() {
93
+ if (this.currentTime && typeof (this.currentTime === Number)) {
94
+ return this.$helper.formatTime(this.currentTime)
95
+ } else return '00:00'
96
+ },
97
+ //ProgressPercentage for progressBarIndicator width
98
+ progressBarPercentage() {
99
+ if (this.currentTime && this.mediaDuration)
100
+ return (this.currentTime / this.mediaDuration) * 100
101
+ else return 0
102
+ },
103
+ progressBarEnded() {
104
+ return this.currentTime >= Math.floor(this.mediaDuration)
105
+ },
106
+ //Volume
107
+ volumeState() {
108
+ let state = null
109
+ switch (true) {
110
+ case this.muted || this.currentVolume <= 0:
111
+ state = 'muted'
112
+ break
113
+
114
+ case !this.muted && this.currentVolume <= 0.5:
115
+ state = 'low'
116
+ break
117
+ case !this.muted && this.currentVolume > 0.5:
118
+ state = 'high'
119
+ }
120
+
121
+ return state
122
+ },
123
+ //Volume progress for slider background
124
+ volumeSliderBackground() {
125
+ return (this.currentVolume * 100).toFixed(0).toString() + '%'
126
+ },
127
+ //Volume from store
128
+ mediaVolume() {
129
+ // return this.getMediaVolume
130
+ return this.getMediaPlaybarValues('volume')
131
+ },
132
+
133
+ // mediaVolume() {
134
+ // let vol = 0.5
135
+ // if (!this.getDataFromServer) return vol
136
+
137
+ // const {pbValues} = this.getDataFromServer
138
+ // if(pbValues && pbValues.volume) vol = parseFloat(pbValues.volume)
139
+ // console.log("My Vol...", vol)
140
+ // return vol
141
+ // },
142
+ //media from store
143
+ mediaMuted() {
144
+ return this.getMediaMuted
145
+ },
146
+ //Labels
147
+ playLabel() {
148
+ let label = null
149
+ switch (true) {
150
+ case this.isPlaying:
151
+ label = this.$t('button.pause')
152
+ break
153
+
154
+ case this.canReplay:
155
+ label = this.$t('button.replay')
156
+ break
157
+
158
+ default:
159
+ label = this.$t('button.play')
160
+ }
161
+ return label
162
+ },
163
+ volumeLabel() {
164
+ let label = null
165
+ switch (true) {
166
+ case this.muted:
167
+ label = `${this.$t('button.unmute')}`
168
+ break
169
+ case !this.muted:
170
+ label = `${this.$t('button.mute')}`
171
+ break
172
+ }
173
+ return label
174
+ },
175
+ volumeLevelA11Y() {
176
+ const w = this.$i18n.locale === 'en' ? 'at' : 'à'
177
+ const level = Math.round(this.currentVolume * 100)
178
+ let txtA11Y = `Volume ${w} ${level}%`
179
+
180
+ return txtA11Y
181
+ }
182
+ },
183
+ watch: {
184
+ getUserInteraction: {
185
+ immediate: true,
186
+ deep: true,
187
+ handler() {
188
+ if (!this.getUserInteraction) return
189
+ this.getViewedSatus()
190
+ this.setMediaVolume()
191
+ }
192
+ }
193
+ },
194
+
195
+ methods: {
196
+ ...mapActions(useAppStore, ['setMediaMuted', 'setMediaPlaybarValues']),
197
+ /**
198
+ * @description - Set all the DOM elements for medias
199
+ */
200
+ initializeMediaElm() {
201
+ this.pbContainer = this.$refs['$pb-container']
202
+ //ProgressBar
203
+ this.progressArea = this.$refs['$progress-area']
204
+ this.progressBar = this.$refs['$progress-bar']
205
+ this.progressIndicator = this.$refs['$progress-indicator']
206
+ this.progressThumb = this.$refs['$progress-thumb']
207
+ //SeekTooltip
208
+ this.seekTooltip = this.$refs['$seek-tooltip']
209
+ //Volume//
210
+ this.volumeBtn = this.$refs['$btn-volume']
211
+ this.volumeSlider = this.$refs['$volume-slider']
212
+ this.volumeIndicator = this.$refs['$volume-progress']
213
+ },
214
+ /**
215
+ * @description - handle all the listeners for medias
216
+ */
217
+ setMediaHandlers() {
218
+ if (this.mediaElement) {
219
+ //Prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
220
+ this.pbContainer.addEventListener('keydown', this.keysPreventDefault)
221
+ //progressBar events
222
+ this.progressArea.addEventListener('click', this.seekingProgress)
223
+ //window handlers for playbar progress
224
+ window.addEventListener('mousemove', this.progressWindowMove)
225
+ window.addEventListener('mouseup', this.progressWindowUp)
226
+ //EndprogressBar
227
+ //Update data when media as a timeupdate/ended
228
+ this.mediaElement.addEventListener(
229
+ 'timeupdate',
230
+ this.updateProgressBarTime
231
+ )
232
+ this.mediaElement.addEventListener('ended', this.mediaEnded)
233
+ //Volume
234
+ this.volumeSlider.addEventListener('input', this.updateVolumeLevel)
235
+ //Hotkeys
236
+ this.pbContainer.addEventListener('keydown', this.handleMediaControls)
237
+
238
+ //Bus
239
+ //this.$bus.$on('play-media', this.handleMediaControls)
240
+ }
241
+ },
242
+
243
+ /**
244
+ * @description - Unset all the listeners (medias)
245
+ */
246
+ removeMediaHandlers() {
247
+ if (this.mediaElement) {
248
+ //progressBar events
249
+ this.progressArea.removeEventListener('click', this.seekingProgress)
250
+ //window handlers for playbar progress
251
+ window.removeEventListener('mousemove', this.progressWindowMove)
252
+ window.removeEventListener('mouseup', this.progressWindowUp)
253
+
254
+ this.mediaElement.removeEventListener(
255
+ 'timeupdate',
256
+ this.updateProgressBarTime
257
+ )
258
+ this.mediaElement.removeEventListener('ended', this.mediaEnded)
259
+
260
+ //Volume
261
+ this.volumeSlider.removeEventListener('input', this.updateVolumeLevel)
262
+ //Keys
263
+ this.pbContainer.removeEventListener('keydown', this.keysPreventDefault)
264
+ this.pbContainer.removeEventListener(
265
+ 'keydown',
266
+ this.handleMediaControls
267
+ )
268
+ }
269
+ },
270
+ /**
271
+ * @description - play or pause the media
272
+ * @fires manage-media-players - to the PAge the media in play
273
+ */
274
+ togglePlay() {
275
+ //If the progressBar is at the end, restart the media
276
+ if (this.progressBarEnded) {
277
+ this.mediaElement.currentTime = 0
278
+ this.currentTime = 0
279
+ }
280
+ //MediaElement
281
+ this.isPlaying ? this.mediaElement.pause() : this.mediaElement.play()
282
+ //Data
283
+ if (!this.isPlaying) {
284
+ //Signal to set this mediaElement as the last playing
285
+ this.$bus.$emit('manage-media-players', this.mediaToPlay)
286
+ }
287
+
288
+ this.isPlaying = !this.isPlaying
289
+ this.canReplay = false
290
+ },
291
+ /**
292
+ * @description - Set the media to a new time (if its a time between 0 and max)
293
+ */
294
+ setMediaTime(time) {
295
+ if (time <= 0) this.mediaElement.currentTime = 0
296
+ else if (time > this.mediaDuration)
297
+ this.mediaElement.currentTime = this.mediaDuration
298
+ else this.mediaElement.currentTime = time
299
+ //Handle finish Media if its set to max duration
300
+ if (this.progressBarEnded) {
301
+ this.mediaEnded()
302
+ }
303
+ },
304
+ /**
305
+ * @description - Set the media Ended if it is set to the end of the progressBar by the user or if it plays until the end
306
+ */
307
+
308
+ mediaEnded() {
309
+ //Don't make the media play when release progressBar thumb and the media is Ended
310
+ this.savedIsPlaying = false
311
+ //Pausing mediaElement is isPlaying
312
+ if (this.mediaElement && this.isPlaying) {
313
+ this.mediaElement.pause()
314
+ this.isPlaying = false
315
+ }
316
+ this.$bus.$emit('media-viewed', this.mediaToPlay.id)
317
+ this.canReplay = true
318
+ if (this.mediaToPlay.mType === 'video') this.showControls()
319
+ },
320
+
321
+ /**
322
+ * @description get the view Status of the current media from UserInteraction
323
+ */
324
+ getViewedSatus() {
325
+ //Should get the viewed Status from UserInteraction
326
+ let { activityRef, id } = this.getCurrentPage
327
+ if (
328
+ !this.getUserInteraction[activityRef] ||
329
+ !this.getUserInteraction[activityRef][id]
330
+ )
331
+ return
332
+ const { userInteraction } = this.getUserInteraction[activityRef][id]
333
+ if (!userInteraction.mediasViewed || !userInteraction.mediasViewed.length)
334
+ return
335
+ const { mediasViewed } = userInteraction
336
+ //Should update the can replay state of media
337
+ this.canReplay = mediasViewed.includes(this.mediaElement.id)
338
+ },
339
+ /**
340
+ * @description controls the level Of the media volume
341
+ * @param d string, optional volUp or volDown to Add or Remove 0.05 from volume value
342
+ */
343
+ updateVolumeLevel(d = null) {
344
+ if (!this.mediaElement) return
345
+ if (this.mediaElement.muted) {
346
+ this.mediaElement.muted = this.muted = false
347
+ //Send muted state to the store
348
+ this.setMutedState(this.muted)
349
+ }
350
+ switch (true) {
351
+ case d == 'volUP':
352
+ //Set volume Up
353
+ if (this.mediaElement.volume >= 1) return
354
+ if (this.mediaElement.volume >= 0.95) {
355
+ this.mediaElement.volume = 1
356
+ } else {
357
+ this.mediaElement.volume = (
358
+ this.mediaElement.volume + 0.05
359
+ ).toFixed(2)
360
+ }
361
+ this.volumeSlider.value = this.currentVolume =
362
+ this.mediaElement.volume
363
+ break
364
+
365
+ case d == 'volDOWN':
366
+ //Set volume Down
367
+ if (this.mediaElement.volume <= 0) return
368
+ if (this.mediaElement.volume <= 0.05) {
369
+ this.mediaElement.volume = 0
370
+ } else {
371
+ this.mediaElement.volume = (
372
+ this.mediaElement.volume - 0.05
373
+ ).toFixed(2)
374
+ }
375
+
376
+ this.volumeSlider.value = this.currentVolume =
377
+ this.mediaElement.volume
378
+ break
379
+
380
+ default: {
381
+ //set volume
382
+ this.currentVolume = this.mediaElement.volume =
383
+ this.volumeSlider.value
384
+ this.currentVolume = parseFloat(this.currentVolume).toFixed(2)
385
+ }
386
+ }
387
+ if (!this.muted) this.savedVolume = this.currentVolume
388
+ this.setMediaPlaybarValues({
389
+ volume: this.savedVolume
390
+ }) // Save the volume in the Store
391
+ },
392
+ /**
393
+ * @description - Toggle the state fo the sound
394
+ */
395
+ toggleMute() {
396
+ //Check first the volume of the media
397
+ if (this.mediaElement.volume == 0) this.muted = true
398
+
399
+ this.muted = !this.muted
400
+ this.mediaElement.muted = this.muted // set the muted state of the media
401
+
402
+ //Send mutedstate to the store
403
+ this.setMutedState(this.muted)
404
+
405
+ if (this.muted) {
406
+ this.mediaElement.volume = 0
407
+ this.savedVolume = this.currentVolume
408
+ this.volumeSlider.value = 0
409
+ this.currentVolume = 0
410
+ }
411
+ if (!this.muted) {
412
+ this.volumeSlider.value = this.savedVolume
413
+ this.currentVolume = this.mediaElement.volume = this.volumeSlider.value
414
+ }
415
+ },
416
+
417
+ /** @description Save the muted in the Store */
418
+ setMutedState() {
419
+ this.setMediaMuted(this.muted)
420
+ },
421
+ //Keyboard event methods
422
+ /**
423
+ * @description Method to prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
424
+ * */
425
+ keysPreventDefault(evt) {
426
+ if (
427
+ ['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(
428
+ evt.key
429
+ ) > -1
430
+ ) {
431
+ evt.preventDefault()
432
+ }
433
+ },
434
+
435
+ /**
436
+ * @description Method to handle the keyboard input to controle the media player
437
+ *@summary Keyboard actions:
438
+ * *** Left-Arrow/ Right-Arrow : move foward/Backward media by 5s
439
+ * *** Up-Arrow/ Down-Arrow: Increase/Decrease media volume by 5%
440
+ * *** c : show/ hide subtitle
441
+ * *** f : enter/ exit fullscreen
442
+ * *** k : play/ pause media
443
+ * *** m : Mute/ Unmute volume
444
+ * *** t : show transcript
445
+ */
446
+
447
+ handleMediaControls(e) {
448
+ e = e.code
449
+ this.showControls()
450
+ switch (e) {
451
+ case 'ArrowLeft':
452
+ //If focus is on Volume, VolDown
453
+ if (this.focusState.volumeSlider) {
454
+ //VolDown
455
+ this.updateVolumeLevel('volDOWN')
456
+ break
457
+ } else {
458
+ //Go back 5s in media timeline
459
+ this.cursorLeft()
460
+ break
461
+ }
462
+
463
+ case 'ArrowRight':
464
+ //If focus is on Volume, VolUp
465
+ if (this.focusState.volumeSlider) {
466
+ //VolUp
467
+ this.updateVolumeLevel('volUP')
468
+ break
469
+ } else {
470
+ //Move forward 5s in media timeline
471
+ this.cursorRight()
472
+ break
473
+ }
474
+
475
+ case 'ArrowUp':
476
+ //If focus is on progressBar, cursorRight
477
+ if (this.focusState.progressBar) {
478
+ //Move forward 5s in media timeline
479
+ this.cursorRight()
480
+ break
481
+ } else {
482
+ //VolUp
483
+ this.updateVolumeLevel('volUP')
484
+ break
485
+ }
486
+
487
+ case 'ArrowDown':
488
+ //If focus is on progressBar, cursorRight
489
+ if (this.focusState.progressBar) {
490
+ //Go back 5s in media timeline
491
+ this.cursorLeft()
492
+ break
493
+ } else {
494
+ //Voldown
495
+ this.updateVolumeLevel('volDOWN')
496
+ break
497
+ }
498
+
499
+ case 'KeyM':
500
+ this.toggleMute()
501
+ break
502
+
503
+ case 'KeyF':
504
+ if (this.mediaToPlay.mType == 'audio') break
505
+ //Toggle fullScreen
506
+ this.toggleFullScreen()
507
+ break
508
+
509
+ case 'KeyT':
510
+ if (this.mediaToPlay.mType == 'audio') break
511
+ //Toggle transcript view
512
+ this.toggleViewTranscript()
513
+ break
514
+
515
+ case 'KeyC':
516
+ if (this.mediaToPlay.mType == 'audio') break
517
+ //Toggle subtitle view
518
+ this.toggleViewSubtitle()
519
+ break
520
+
521
+ case 'KeyK':
522
+ //Play Pause on k bar pressed
523
+ if (this.mediaToPlay.mType == 'audio') this.togglePlay()
524
+ //Play/pause video with the playBackAnim if it is triggerd with KeyK
525
+ else this.handlePlayVideo('playBackAnim')
526
+ break
527
+ }
528
+ },
529
+ /**
530
+ * @description - Activate the label of a button include in btnsLabelDisplay on Hover/Focus on the button. Deactivate the label in all the others btns
531
+ */
532
+ activateLabel(elm) {
533
+ const id = elm.id
534
+ this.btnsLabelDisplay[id] = true
535
+ for (const property in this.btnsLabelDisplay) {
536
+ if (property !== id) {
537
+ this.btnsLabelDisplay[property] = false
538
+ }
539
+ }
540
+ },
541
+ /**
542
+ * @description - Get currenttime from media
543
+ */
544
+
545
+ getCurrentTime() {
546
+ this.currentTime = this.mediaElement.currentTime
547
+ return this.mediaElement.currentTime
548
+ },
549
+ /**
550
+ * @description - controls to move the media timeline backward
551
+ */
552
+ cursorLeft() {
553
+ this.setMediaTime(this.getCurrentTime() - 5)
554
+ },
555
+ /**
556
+ * @description - control to move the media timeline forwrad
557
+ */
558
+ cursorRight() {
559
+ this.setMediaTime(this.getCurrentTime() + 5)
560
+ },
561
+ //ProgressBar Methods
562
+
563
+ /**
564
+ * @description - Update currentTime and timing strings when media is playing
565
+ */
566
+ updateProgressBarTime(e) {
567
+ //Get currentTime from the event
568
+ this.currentTime = e.target.currentTime
569
+ //Update strings
570
+ this.setProgressBarA11Y()
571
+ //If replay is true and the progressIndicator is not at the end, set canReplay to false
572
+ if (this.canReplay) this.canReplay = false
573
+ //If replay is false and progressIndicator is at the end, set canReplay to true
574
+ if (
575
+ !this.canReplay &&
576
+ this.currentTime >= Math.floor(this.mediaDuration)
577
+ ) {
578
+ this.canReplay = true
579
+ }
580
+ },
581
+
582
+ /**
583
+ * @description - Set mediatime depending on progressBar click
584
+ */
585
+ seekingProgress(e) {
586
+ if (!this.progressThumbDown && !this.progressThumbHover) {
587
+ const updatedTime =
588
+ (e.offsetX / this.progressBar.offsetWidth) * this.mediaDuration
589
+ this.setMediaTime(updatedTime)
590
+ }
591
+ },
592
+
593
+ /**
594
+ * @description - Set mediatime depending on mousemove on the screen (when progress thumb is mousedown)
595
+ */
596
+ progressWindowMove(ev) {
597
+ if (this.progressThumbDown) {
598
+ if (this.isPlaying) this.togglePlay()
599
+ this.getTimeFromClickPos(ev)
600
+ let timeToSet = Math.floor(this.getTimeFromClickPos(ev))
601
+ if (timeToSet < 0) timeToSet = 0
602
+ else if (timeToSet > this.duration) timeToSet = this.duration
603
+ this.currentTime = timeToSet
604
+ }
605
+ },
606
+
607
+ /**
608
+ * @description - Update thumb state (mouseup) when the mouse is up on the screen
609
+ */
610
+ progressWindowUp(ev) {
611
+ if (this.progressThumbDown) {
612
+ this.setMediaTime(this.currentTime)
613
+ this.progressThumbDown = false
614
+ //savedIsPlaying is the playing status saved on mousedown event on the thumb (the video is paused when moving on the progressBar)
615
+ if (this.isPlaying !== this.savedIsPlaying) this.togglePlay()
616
+ }
617
+ },
618
+ /**
619
+ * @description - Get the time corresponding of the position of the click on the progressBar
620
+ */
621
+ getTimeFromClickPos(ev) {
622
+ let timeToSet = 0
623
+ //La progress Bar
624
+ const progressBar = this.progressBar
625
+ //La position de la progressBar dans la page
626
+ const left = progressBar.getBoundingClientRect().left
627
+ //Position du click par rapport à la progressBar
628
+ let clickPos = ev.clientX - left
629
+ //Si la position est à gauche de la progressBar, retourner 0s
630
+ if (clickPos < 0) return 0
631
+ //Obtenir le width de la progressBar
632
+ const width = progressBar.offsetWidth
633
+ //Obtenir le ratio de la position du click vs width de la progressBar
634
+ let percentage = clickPos / width
635
+ //Si > que 1, veux dire que le clique est à droite de la progressBar (mettre le curseur à la fin)
636
+ percentage = percentage > 1 ? 1 : percentage
637
+ //Ratio * temps total du media donne la progressiob
638
+ timeToSet = percentage * this.mediaDuration
639
+ return timeToSet
640
+ },
641
+
642
+ //End progressBar methods
643
+
644
+ //Labels
645
+
646
+ /** @description - methode to set A11Y support for screen reader on progress bar element (video/audio/) */
647
+ setProgressBarA11Y() {
648
+ const w = this.$i18n.locale === 'en' ? 'at' : 'à'
649
+ const dm =
650
+ this.$i18n.locale === 'en'
651
+ ? `${this.mediaToPlay.mType} duration`
652
+ : `durée ${this.mediaToPlay.mType}`
653
+
654
+ this.mediaA11Y.valMax = Math.round(this.mediaElement.duration)
655
+ this.mediaA11Y.valNow = Math.round(this.currentTime)
656
+
657
+ //format text output for screen reader
658
+ let currentTimeArray = this.timecode.split(':')
659
+ let fullTimeArray = this.mediaDurationTime.split(':')
660
+ let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
661
+ const mediaTitle = this.mediaToPlay.mTitle || this.mediaToPlay.mType
662
+ this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
663
+
664
+ //format string to remove leading 0 digit for time
665
+ const formatNum = (n) => {
666
+ return n[0] == '0' ? n.substring(1).trim() : n.trim()
667
+ }
668
+
669
+ const formatMsg = (x, y) => {
670
+ let formatStr = this.$t('a11y_sr.range_expression')
671
+ .replace('{x}', x)
672
+ .replace('{y}', y)
673
+
674
+ return formatStr
675
+ }
676
+ switch (fullTimeArray.length) {
677
+ case 3:
678
+ if (!currentTimeArray.length) return
679
+
680
+ //format current hours text text with plurialization apply
681
+ hrsTxt =
682
+ currentTimeArray.length == 3 && parseInt(currentTimeArray[0]) > 0
683
+ ? `${formatNum(currentTimeArray[0])} ${this.$tc(
684
+ 'a11y_sr.time.hours',
685
+ formatNum(currentTimeArray[0])
686
+ )}`
687
+ : ''
688
+
689
+ //format minutes text text with plurialization apply
690
+ mnTxt =
691
+ parseInt(currentTimeArray[1]) > 0
692
+ ? `${formatNum(currentTimeArray[1])} ${this.$tc(
693
+ 'a11y_sr.time.minute',
694
+ formatNum(currentTimeArray[1])
695
+ )}`
696
+ : ''
697
+
698
+ //format seconds text with plurialization apply
699
+ ssTxt =
700
+ parseInt(currentTimeArray[2]) > 0
701
+ ? `${formatNum(currentTimeArray[2])} ${this.$tc(
702
+ 'a11y_sr.time.second',
703
+ formatNum(currentTimeArray[2])
704
+ )}`
705
+ : `0 ${this.$tc('a11y_sr.time.second')}`
706
+
707
+ HRSTxt =
708
+ parseInt(fullTimeArray[0]) > 0
709
+ ? `${formatNum(fullTimeArray[0])} ${this.$tc(
710
+ 'a11y_sr.time.hours',
711
+ formatNum(fullTimeArray[0])
712
+ )}`
713
+ : ''
714
+
715
+ MNTxt =
716
+ parseInt(fullTimeArray[1]) > 0
717
+ ? `${formatNum(fullTimeArray[1])} ${this.$tc(
718
+ 'a11y_sr.time.minute',
719
+ formatNum(fullTimeArray[1])
720
+ )}`
721
+ : ''
722
+
723
+ SSTxt =
724
+ parseInt(fullTimeArray[2]) > 0
725
+ ? `${formatNum(fullTimeArray[2])} ${this.$tc(
726
+ 'a11y_sr.time.second',
727
+ formatNum(fullTimeArray[2])
728
+ )}`
729
+ : ''
730
+
731
+ cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
732
+ fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
733
+
734
+ //Format to show 0 when no value is present
735
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
736
+
737
+ // Set sr text for timeCode && duration
738
+ this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
739
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
740
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
741
+
742
+ break
743
+ case 2:
744
+ if (!currentTimeArray.length) return
745
+
746
+ //format current time text
747
+ mnTxt =
748
+ parseInt(currentTimeArray[0]) > 0
749
+ ? `${formatNum(currentTimeArray[0])} ${this.$tc(
750
+ 'a11y_sr.time.minute',
751
+ formatNum(currentTimeArray[0])
752
+ )}`
753
+ : ''
754
+ ssTxt =
755
+ parseInt(currentTimeArray[1]) > 0
756
+ ? `${formatNum(currentTimeArray[1])} ${this.$tc(
757
+ 'a11y_sr.time.second',
758
+ formatNum(currentTimeArray[1])
759
+ )}`
760
+ : `0 ${this.$tc('a11y_sr.time.second')}`
761
+
762
+ // format full time text
763
+ MNTxt =
764
+ parseInt(fullTimeArray[0]) > 0
765
+ ? `${formatNum(fullTimeArray[0])} ${this.$tc(
766
+ 'a11y_sr.time.minute',
767
+ formatNum(fullTimeArray[0])
768
+ )}`
769
+ : ''
770
+ SSTxt =
771
+ parseInt(fullTimeArray[1]) > 0
772
+ ? `${formatNum(fullTimeArray[1])} ${this.$tc(
773
+ 'a11y_sr.time.second',
774
+ formatNum(fullTimeArray[1])
775
+ )}`
776
+ : ''
777
+
778
+ cTimeTxt = `${mnTxt} ${ssTxt}`
779
+ fTimeTxt = `${MNTxt} ${SSTxt}`
780
+
781
+ //Format to show 0 when no value is present
782
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
783
+
784
+ // Set sr text for timeCode && duration
785
+ this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
786
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
787
+
788
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
789
+
790
+ break
791
+ }
792
+ },
793
+ /**
794
+ * @description - Deactivate the label of a button include in btnsLabelDisplay on mouseout/blur of this button
795
+ */
796
+ deactivateLabel(elm) {
797
+ const id = elm.id
798
+ this.btnsLabelDisplay[id] = false
799
+ },
800
+ /**
801
+ * @description - Update the state of the elm describe by the string (focus or not) for keydown arrow management in handleMediaControls method (volume | progress bar can use arrow up-down and left-right only on focus)
802
+ */
803
+ changeFocusState(string, bool) {
804
+ this.focusState[string] = bool
805
+ },
806
+ /**
807
+ * @description - handle initial volume level
808
+ */
809
+ setMediaVolume() {
810
+ //Set media muted or with the savedVolume
811
+ this.muted = this.mediaMuted
812
+ if (!this.muted)
813
+ return (this.currentVolume = this.savedVolume = this.mediaVolume)
814
+ this.savedVolume = this.mediaVolume
815
+ this.currentVolume = 0
816
+ }
817
+ }
818
+ }
819
+ export default $extendsMedia