fcad-core-dragon 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.editorconfig +33 -0
  2. package/.eslintignore +29 -0
  3. package/.eslintrc.js +86 -0
  4. package/.prettierrc.js +5 -0
  5. package/CHANGELOG +364 -0
  6. package/README.md +72 -0
  7. package/babel.config.js +3 -0
  8. package/package.json +65 -0
  9. package/src/$locales/en.json +155 -0
  10. package/src/$locales/fr.json +156 -0
  11. package/src/assets/data/onboardingMessages.json +47 -0
  12. package/src/assets/img/BeanEater-1s-200px.svg +25 -0
  13. package/src/components/AppBase.vue +635 -0
  14. package/src/components/AppBaseButton.vue +63 -0
  15. package/src/components/AppBaseDragChoice.vue +91 -0
  16. package/src/components/AppBaseDropZone.vue +112 -0
  17. package/src/components/AppBaseErrorDisplay.vue +391 -0
  18. package/src/components/AppBaseFlipCard.vue +83 -0
  19. package/src/components/AppBaseModule.vue +1589 -0
  20. package/src/components/AppBasePage.vue +338 -0
  21. package/src/components/AppCompBranchButtons.vue +581 -0
  22. package/src/components/AppCompButtonProgress.vue +158 -0
  23. package/src/components/AppCompCarousel.vue +285 -0
  24. package/src/components/AppCompDragAndDrop.vue +339 -0
  25. package/src/components/AppCompInputAssociation.vue +332 -0
  26. package/src/components/AppCompInputCheckBox.vue +227 -0
  27. package/src/components/AppCompInputDropdown.vue +184 -0
  28. package/src/components/AppCompInputRadio.vue +169 -0
  29. package/src/components/AppCompInputTextBox.vue +91 -0
  30. package/src/components/AppCompInputTextTable.vue +155 -0
  31. package/src/components/AppCompInputTextToFillDropdown.vue +255 -0
  32. package/src/components/AppCompInputTextToFillText.vue +164 -0
  33. package/src/components/AppCompJauge.vue +56 -0
  34. package/src/components/AppCompMediaPlayer.vue +365 -0
  35. package/src/components/AppCompMenu.vue +203 -0
  36. package/src/components/AppCompMenuItem.vue +216 -0
  37. package/src/components/AppCompNavigationFull.vue +1791 -0
  38. package/src/components/AppCompPlayBar.vue +1540 -0
  39. package/src/components/AppCompPopUp.vue +523 -0
  40. package/src/components/AppCompQuiz.vue +2998 -0
  41. package/src/components/AppCompSettingsMenu.vue +170 -0
  42. package/src/components/AppCompTableOfContent.vue +209 -0
  43. package/src/components/AppCompToolTip.vue +94 -0
  44. package/src/components/AppCompViewDisplay.vue +6 -0
  45. package/src/components/BaseModule.vue +148 -0
  46. package/src/main.js +218 -0
  47. package/src/mixins/$pageMixins.js +381 -0
  48. package/src/mixins/$quizMixins.js +456 -0
  49. package/src/mixins/timerMixin.js +132 -0
  50. package/src/module/store.js +874 -0
  51. package/src/module/xapi/ADL.js +339 -0
  52. package/src/module/xapi/Crypto/Hasher.js +241 -0
  53. package/src/module/xapi/Crypto/WordArray.js +278 -0
  54. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -0
  55. package/src/module/xapi/Crypto/algorithms/C_algo.js +319 -0
  56. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -0
  57. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -0
  58. package/src/module/xapi/Crypto/encoders/Base.js +105 -0
  59. package/src/module/xapi/Crypto/encoders/Base64.js +99 -0
  60. package/src/module/xapi/Crypto/encoders/Hex.js +60 -0
  61. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -0
  62. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -0
  63. package/src/module/xapi/Crypto/index.js +53 -0
  64. package/src/module/xapi/Statement/activity.js +47 -0
  65. package/src/module/xapi/Statement/agent.js +55 -0
  66. package/src/module/xapi/Statement/group.js +26 -0
  67. package/src/module/xapi/Statement/index.js +259 -0
  68. package/src/module/xapi/Statement/statement.js +253 -0
  69. package/src/module/xapi/Statement/statementRef.js +23 -0
  70. package/src/module/xapi/Statement/substatement.js +22 -0
  71. package/src/module/xapi/Statement/verb.js +36 -0
  72. package/src/module/xapi/activitytypes.js +17 -0
  73. package/src/module/xapi/launch.js +157 -0
  74. package/src/module/xapi/utils.js +167 -0
  75. package/src/module/xapi/verbs.js +294 -0
  76. package/src/module/xapi/wrapper.js +1890 -0
  77. package/src/module/xapi/xapiStatement.js +444 -0
  78. package/src/plugins/bus.js +3 -0
  79. package/src/plugins/gsap.js +14 -0
  80. package/src/plugins/helper.js +260 -0
  81. package/src/plugins/i18n.js +31 -0
  82. package/src/plugins/idb.js +211 -0
  83. package/src/plugins/save.js +37 -0
  84. package/src/plugins/scorm.js +287 -0
  85. package/src/plugins/timeManager.js +77 -0
  86. package/src/plugins/xapi.js +11 -0
  87. package/src/public/index.html +21 -0
  88. package/src/routes.js +734 -0
  89. package/src/shared/generalfuncs.js +113 -0
  90. package/vue.config.js +83 -0
@@ -0,0 +1,1540 @@
1
+ <!-- Add Bref Description of what component does and need
2
+ -->
3
+ <template>
4
+ <div
5
+ id="playbar-wrapper"
6
+ :class="{ minimized: barMinimized }"
7
+ @mouseover="playbarOver"
8
+ >
9
+ <div id="playbar-btns">
10
+ <div id="playbar-flex-wrap">
11
+ <div id="playbar-current-time">
12
+ <span aria-hidden="true">
13
+ {{ timecode }}
14
+ </span>
15
+ <span class="sr-only">{{ mediaA11Y.timeCode }}</span>
16
+ </div>
17
+ <div
18
+ id="playbar-volume"
19
+ ref="volumeButton"
20
+ class="playbar-btn btn"
21
+ role="button"
22
+ tabindex="0"
23
+ :aria-label="volumeLabel"
24
+ @mouseover="volumeOver"
25
+ @mouseleave="volumeLeave"
26
+ @click="volumeClick"
27
+ @keyup.space="volumeClick"
28
+ @keyup.enter="volumeClick"
29
+ @focus="playbarFocus"
30
+ >
31
+ <div v-show="volumeSliderOpen" id="playbar-volume-slider-zone">
32
+ <div id="playbar-volume-slider">
33
+ <div class="range-slider" :style="volumeStyle" tabindex="0">
34
+ <input
35
+ ref="volume-range"
36
+ type="range"
37
+ min="0"
38
+ max="1"
39
+ step="0.05"
40
+ aria-label="Volume"
41
+ :aria-valuenow="displayVol"
42
+ aria-valuemin="0"
43
+ aria-valuemax="100"
44
+ :aria-valuetext="`${displayVol}% volume`"
45
+ tabindex="0"
46
+ :value="volDefault"
47
+ :name="$t('button.volume')"
48
+ @input="volumeInputChange"
49
+ />
50
+ <output>{{ displayVol }} %</output>
51
+
52
+ <div class="range-slider__progress"></div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ <svg
57
+ v-if="volDefault > 0"
58
+ id="volume_up_black_24dp"
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ width="24"
61
+ height="24"
62
+ viewBox="0 0 24 24"
63
+ >
64
+ <path
65
+ id="Path_3896"
66
+ data-name="Path 3896"
67
+ d="M0,0H24V24H0Z"
68
+ fill="none"
69
+ />
70
+ <path
71
+ id="Path_3897"
72
+ data-name="Path 3897"
73
+ d="M3,9v6H7l5,5V4L7,9Zm13.5,3A4.5,4.5,0,0,0,14,7.97v8.05A4.474,4.474,0,0,0,16.5,12ZM14,3.23V5.29a7,7,0,0,1,0,13.42v2.06A8.994,8.994,0,0,0,14,3.23Z"
74
+ />
75
+ </svg>
76
+ <svg
77
+ v-if="volDefault <= 0"
78
+ xmlns="http://www.w3.org/2000/svg"
79
+ width="18"
80
+ height="18"
81
+ viewBox="0 0 18 18"
82
+ >
83
+ <path
84
+ id="Path_3893"
85
+ data-name="Path 3893"
86
+ d="M16.5,12A4.5,4.5,0,0,0,14,7.97v2.21l2.45,2.45A4.232,4.232,0,0,0,16.5,12ZM19,12a6.843,6.843,0,0,1-.54,2.64l1.51,1.51A8.8,8.8,0,0,0,21,12a9,9,0,0,0-7-8.77V5.29A7.005,7.005,0,0,1,19,12ZM4.27,3,3,4.27,7.73,9H3v6H7l5,5V13.27l4.25,4.25A6.924,6.924,0,0,1,14,18.7v2.06a8.99,8.99,0,0,0,3.69-1.81L19.73,21,21,19.73l-9-9ZM12,4,9.91,6.09,12,8.18Z"
87
+ transform="translate(-3 -3)"
88
+ />
89
+ </svg>
90
+ </div>
91
+ <app-base-button
92
+ id="playbar-play"
93
+ ref="playButton"
94
+ class="playbar-btn"
95
+ :aria-label="label"
96
+ :title="label"
97
+ @click="playClick"
98
+ @focus="playbarFocus"
99
+ >
100
+ <!--------------------------------Pause SVG------------------------------------>
101
+ <svg
102
+ v-if="isPlaying"
103
+ id="pause-icon"
104
+ xmlns="http://www.w3.org/2000/svg"
105
+ width="12"
106
+ height="14"
107
+ viewBox="0 0 12 14"
108
+ >
109
+ <path
110
+ id="Path_3887"
111
+ data-name="Path 3887"
112
+ d="M6,19h4V5H6ZM14,5V19h4V5Z"
113
+ transform="translate(-6 -5)"
114
+ />
115
+ </svg>
116
+ <!--------------------------------Replay SVG------------------------------------>
117
+ <svg
118
+ v-if="canReplay && !isPlaying"
119
+ id="replay-icon"
120
+ xmlns="http://www.w3.org/2000/svg"
121
+ xmlns:xlink="http://www.w3.org/1999/xlink"
122
+ width="24"
123
+ height="24"
124
+ viewBox="0 0 16 20"
125
+ xml:space="preserve"
126
+ >
127
+ <path
128
+ class="st0"
129
+ d="M8,4V0L3,5l5,5V6c3.3,0,6,2.7,6,6s-2.7,6-6,6s-6-2.7-6-6H0c0,4.4,3.6,8,8,8s8-3.6,8-8 S12.4,4,8,4z"
130
+ />
131
+ </svg>
132
+ <!--------------------------------- Play SVG ----------------------------------->
133
+ <svg
134
+ v-if="!isPlaying && !canReplay"
135
+ id="play-icon"
136
+ xmlns="http://www.w3.org/2000/svg"
137
+ width="15.278"
138
+ height="19.444"
139
+ viewBox="0 0 15.278 19.444"
140
+ >
141
+ <path
142
+ id="Path_3858"
143
+ data-name="Path 3858"
144
+ d="M8,5V24.444l15.278-9.722Z"
145
+ transform="translate(-8 -5)"
146
+ />
147
+ </svg>
148
+ </app-base-button>
149
+ <app-base-button
150
+ id="acessMenu"
151
+ ref="subButton"
152
+ class="playbar-btn"
153
+ :aria-label="$t('button.accessibility')"
154
+ :title="$t('button.accessibility')"
155
+ @click="toggleSubtitleMenu"
156
+ @focus="playbarFocus"
157
+ >
158
+ <svg
159
+ v-if="!subtitleMenuOpen"
160
+ id="Component_86_3"
161
+ data-name="Component 86 – 3"
162
+ xmlns="http://www.w3.org/2000/svg"
163
+ width="21.642"
164
+ height="16"
165
+ viewBox="0 0 21.642 16"
166
+ >
167
+ <path
168
+ id="Path_3989"
169
+ data-name="Path 3989"
170
+ d="M690,385a4.991,4.991,0,0,1-4.578-7H673a2.006,2.006,0,0,0-2,2v12a2.006,2.006,0,0,0,2,2h16a2.006,2.006,0,0,0,2-2v-7.1A4.987,4.987,0,0,1,690,385Zm-17,1h4v2h-4Zm10,6H673v-2h10Zm6,0h-4v-2h4Zm0-4H679v-2h10Z"
171
+ transform="translate(-671 -378)"
172
+ />
173
+ <path
174
+ id="Path_3902"
175
+ data-name="Path 3902"
176
+ d="M688.061,379l-.609.61,2.595,2.6,2.6-2.6-.61-.61-1.985,1.981Z"
177
+ transform="translate(-671 -378)"
178
+ />
179
+ </svg>
180
+
181
+ <svg
182
+ v-if="subtitleMenuOpen"
183
+ id="Component_86_5"
184
+ data-name="Component 86 – 5"
185
+ xmlns="http://www.w3.org/2000/svg"
186
+ width="21.642"
187
+ height="16"
188
+ viewBox="0 0 21.642 16"
189
+ >
190
+ <path
191
+ id="Path_3989"
192
+ data-name="Path 3989"
193
+ d="M690,385a4.991,4.991,0,0,1-4.578-7H673a2.006,2.006,0,0,0-2,2v12a2.006,2.006,0,0,0,2,2h16a2.006,2.006,0,0,0,2-2v-7.1A4.987,4.987,0,0,1,690,385Zm-17,1h4v2h-4Zm10,6H673v-2h10Zm6,0h-4v-2h4Zm0-4H679v-2h10Z"
194
+ transform="translate(-671 -378)"
195
+ />
196
+ <path
197
+ id="Path_3902"
198
+ data-name="Path 3902"
199
+ d="M688.061,379l-.609.61,2.595,2.6,2.6-2.6-.61-.61-1.985,1.981Z"
200
+ transform="translate(709.094 383.205) rotate(180)"
201
+ />
202
+ </svg>
203
+ </app-base-button>
204
+ <div id="playbar-cc" :class="{ submenu_active: subtitleMenuOpen }">
205
+ <div v-show="subtitleMenuOpen" id="subtitleMenuWrapper">
206
+ <app-base-button
207
+ id="btn-transcript"
208
+ class="subtitleBtns"
209
+ :aria-label="$t('button.download_transcript')"
210
+ :title="$t('button.download_transcript')"
211
+ :disabled="!hasTranscript"
212
+ :class="{ md_disabled: !hasTranscript }"
213
+ @click="downloadTranscript"
214
+ @focus="playbarFocus"
215
+ >
216
+ <svg
217
+ xmlns="http://www.w3.org/2000/svg"
218
+ width="18"
219
+ height="18"
220
+ viewBox="0 0 18 18"
221
+ >
222
+ <path
223
+ id="Path_3846"
224
+ data-name="Path 3846"
225
+ d="M19,12v7H5V12H3v7a2.006,2.006,0,0,0,2,2H19a2.006,2.006,0,0,0,2-2V12Zm-6,.67,2.59-2.58L17,11.5l-5,5-5-5,1.41-1.41L11,12.67V3h2Z"
226
+ transform="translate(-3 -3)"
227
+ />
228
+ </svg>
229
+ <span>{{ $t('button.download_transcript') }}</span>
230
+ </app-base-button>
231
+ <app-base-button
232
+ v-if="!subtitlesEnabled"
233
+ id="btn-subtitles"
234
+ class="btn subtitleBtns"
235
+ :aria-label="$t('button.show_subtitle')"
236
+ :title="$t('button.show_subtitle')"
237
+ :disabled="!hasSubtitle"
238
+ :class="{ md_disabled: !hasSubtitle }"
239
+ @click="showSubtitles"
240
+ @focus="playbarFocus"
241
+ >
242
+ <svg
243
+ xmlns="http://www.w3.org/2000/svg"
244
+ width="20"
245
+ height="16"
246
+ viewBox="0 0 20 16"
247
+ >
248
+ <path
249
+ id="Path_3854"
250
+ data-name="Path 3854"
251
+ d="M20,4H4A2.006,2.006,0,0,0,2,6V18a2.006,2.006,0,0,0,2,2H20a2.006,2.006,0,0,0,2-2V6A2.006,2.006,0,0,0,20,4ZM4,12H8v2H4Zm10,6H4V16H14Zm6,0H16V16h4Zm0-4H10V12H20Z"
252
+ transform="translate(-2 -4)"
253
+ />
254
+ </svg>
255
+ <span>{{ $t('button.show_subtitle') }}</span>
256
+ </app-base-button>
257
+ <app-base-button
258
+ v-if="subtitlesEnabled"
259
+ id="btn-hide-subtitles"
260
+ class="subtitleBtns"
261
+ :aria-label="$t('button.hide_subtitle')"
262
+ :title="$t('button.hide_subtitle')"
263
+ @click="hideSubtitles"
264
+ >
265
+ <svg
266
+ id="sub-off-icon"
267
+ xmlns="http://www.w3.org/2000/svg"
268
+ width="20.96"
269
+ height="20.51"
270
+ viewBox="0 0 20.96 20.51"
271
+ >
272
+ <g
273
+ id="Group_6950"
274
+ data-name="Group 6950"
275
+ transform="translate(-1.04 -2.45)"
276
+ >
277
+ <g id="Group_6949" data-name="Group 6949">
278
+ <path
279
+ id="Path_3855"
280
+ data-name="Path 3855"
281
+ d="M20,4H6.83l8,8H20v2H16.83l4.93,4.93A1.949,1.949,0,0,0,22,18V6A2.006,2.006,0,0,0,20,4Z"
282
+ />
283
+ <path
284
+ id="Path_3856"
285
+ data-name="Path 3856"
286
+ d="M1.04,3.87l1.2,1.2A1.949,1.949,0,0,0,2,6V18a2.006,2.006,0,0,0,2,2H17.17l2.96,2.96,1.41-1.41L2.45,2.45ZM8,12v2H4V12Zm6,4.83V18H4V16h9.17Z"
287
+ />
288
+ </g>
289
+ </g>
290
+ </svg>
291
+ <span>{{ $t('button.hide_subtitle') }}</span>
292
+ </app-base-button>
293
+ </div>
294
+ </div>
295
+ <div id="playbar-total-time">
296
+ <!-- <time datetime="PT2H30M"> -->
297
+ <span aria-hidden="true">
298
+ {{ duration }}
299
+ </span>
300
+ <span class="sr-only">{{ mediaA11Y.duration }}</span>
301
+ </div>
302
+ </div>
303
+ </div>
304
+ <div
305
+ id="playbar-progress"
306
+ ref="playbar-progress"
307
+ role="progressbar"
308
+ aria-valuemin="0"
309
+ :aria-label="mediaA11Y.label"
310
+ :aria-valuenow="mediaA11Y.valNow"
311
+ :aria-valuemax="mediaA11Y.valMax"
312
+ :aria-valuetext="mediaA11Y.valueText"
313
+ @mouseover="progressOver"
314
+ @mouseleave="progressLeave"
315
+ @mousemove="progressMove"
316
+ @click="progressClick"
317
+ >
318
+ <div id="playbar-buffer" ref="playbar-buffer"></div>
319
+ <div id="playbar-playback" ref="playbar-playback">
320
+ <div
321
+ id="playbar-cursor"
322
+ tabindex="0"
323
+ @mousedown="cursorDown"
324
+ @focus="playbarFocus"
325
+ ></div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </template>
330
+
331
+ <script>
332
+ import { mapGetters } from 'vuex'
333
+
334
+ export default {
335
+ props: {
336
+ appStatus: { type: Boolean, required: true },
337
+ mediaToShow: { type: [Object, Boolean], default: false }
338
+ },
339
+
340
+ data() {
341
+ return {
342
+ previousBtn: null,
343
+ menuBtn: null,
344
+ nextBtn: null,
345
+ mediaIs: null,
346
+ playBtn: null,
347
+ stopBtn: null,
348
+ muteBtn: null,
349
+ progressBar: null,
350
+ progressShaddow: null,
351
+ progress: null,
352
+ progressTime: null,
353
+ mediaDuration: '--:--',
354
+ navigation: {},
355
+ currentPath: null,
356
+ isBranching: false,
357
+ //bCounter: null,
358
+ index: 0,
359
+ navigationRoutes: null,
360
+ hasMedia: false,
361
+ hasSound: false,
362
+ media: null,
363
+ nextActivity: null,
364
+ previousActivity: null,
365
+ cursorDragged: false,
366
+ bufferDomElem: '',
367
+ playbackDomElem: '',
368
+ isPlaying: false,
369
+ mediaHandlersSet: false,
370
+ duration: '',
371
+ timecode: '',
372
+ subtitlesEnabled: false,
373
+ volumeSliderOpen: false,
374
+ volumeBtn: null,
375
+ volDefault: 1,
376
+ hover: false,
377
+ lastVolume: '',
378
+ muted: false,
379
+ isAnimation: false,
380
+ subtitleMenuOpen: false,
381
+ hasTranscript: false,
382
+ hasSubtitle: false,
383
+ canReplay: false,
384
+ focusTimeout: null,
385
+ delayUntilHide: 3000,
386
+ playbarLrs: [],
387
+ mediaA11Y: {
388
+ label: 'progress',
389
+ valMax: null,
390
+ valNow: null,
391
+ valueText: null,
392
+ timeCode: 0,
393
+ duration: 0
394
+ }
395
+ }
396
+ },
397
+ computed: {
398
+ ...mapGetters([
399
+ 'getCurrentPage',
400
+ 'getModuleChildren',
401
+ 'hasMediaElOrTimeline',
402
+ 'getCurrentMediaDuration',
403
+ 'getIntroStatus',
404
+ 'getCurrentBrowser',
405
+ 'getAutoplayEnabled',
406
+ 'getMediaVolume',
407
+ 'getModuleInfo',
408
+ 'getMediaSubtitles',
409
+ 'getUserInteraction'
410
+ ]),
411
+ isViewed() {
412
+ let userData = this.getUserInteraction
413
+ let activityId = this.getCurrentPage.activityRef
414
+ let pageId = this.getCurrentPage.id
415
+ let viewed = false
416
+ if (
417
+ userData &&
418
+ userData[activityId] &&
419
+ userData[activityId][pageId] &&
420
+ userData[activityId][pageId].userInteraction &&
421
+ userData[activityId][pageId].userInteraction.mediaIsViewed
422
+ ) {
423
+ viewed = true
424
+ }
425
+
426
+ return viewed
427
+ },
428
+ barMinimized() {
429
+ //si la vidéo est en pause: ne pas minimiser la playbar
430
+ if (!this.isPlaying) {
431
+ return false
432
+ } else {
433
+ //si la vidéo joue, minimiser seulement si la souris ne la survole pas.
434
+ return !this.hover
435
+ }
436
+ },
437
+ volumeStyle() {
438
+ return (
439
+ '--min:0; --max:1; --step:0.05; --value:' +
440
+ this.volDefault +
441
+ '; --text-value:"' +
442
+ this.volDefault +
443
+ '";'
444
+ )
445
+ },
446
+ label() {
447
+ let label = null
448
+ if (this.isPlaying) {
449
+ label = `${this.$t('button.pause')}`
450
+ } else {
451
+ label = `${this.$t('button.play')}`
452
+ }
453
+ return label
454
+ },
455
+ volumeLabel() {
456
+ let label = null
457
+ if (this.muted) {
458
+ label = `${this.$t('button.unmute')}`
459
+ } else {
460
+ label = `${this.$t('button.mute')}`
461
+ }
462
+ return label
463
+ },
464
+ displayVol() {
465
+ let test = Math.floor(this.volDefault * 100)
466
+ return test
467
+ }
468
+ },
469
+
470
+ watch: {
471
+ /* animation duration is 0:00 on load, we watch the media object for a change and update duration display accordingly
472
+ */
473
+ media() {
474
+ this.updateDurationDisplay()
475
+ },
476
+ /*
477
+ * Why this watcher:
478
+ * When the component is created : the HMTLMediaElement (mElement in the mediaToshow)doesn't exist
479
+ * This compenent need to ensure that the data is available in the store before it will try o access and attach the event listeners (click, seek, stop etc.)
480
+ */
481
+ mediaToShow: {
482
+ handler() {
483
+ if (this.mediaToShow) {
484
+ const { type, mElement } = this.mediaToShow
485
+
486
+ switch (type) {
487
+ case 'pg_animation':
488
+ this.isAnimation = true
489
+ this.setMedia()
490
+ if (this.mediaHandlersSet === false && this.hasMedia) {
491
+ if (this.getAutoplayEnabled) {
492
+ this.playClick()
493
+ }
494
+ //animation was viewed previously
495
+ if (this.isViewed) {
496
+ //set animation timeline at the end
497
+ this.setMediaTime(this.mediaToShow.timeline.totalDuration())
498
+ this.canReplay = true
499
+ }
500
+ this.setMediaHandlers()
501
+ }
502
+
503
+ this.updateTimecodeDisplay()
504
+
505
+ break
506
+
507
+ case 'pg_media':
508
+ if (mElement) {
509
+ this.setMedia()
510
+ this.updateDurationDisplay()
511
+ this.setMediaA11Y()
512
+ if (this.mediaHandlersSet === false && this.hasMedia) {
513
+ this.setVolume()
514
+ if (this.subtitlesEnabled) {
515
+ this.showSubtitles()
516
+ }
517
+
518
+ this.updateTimecodeDisplay()
519
+
520
+ if (this.getAutoplayEnabled) {
521
+ this.playClick()
522
+ }
523
+ //video was viewed previously
524
+ if (this.isViewed) {
525
+ //set video timecode at the end
526
+ this.setMediaTime(this.media.mElement.duration)
527
+ this.canReplay = true
528
+ }
529
+ this.setMediaHandlers()
530
+ }
531
+ }
532
+ break
533
+ }
534
+ }
535
+ },
536
+ immediate: true
537
+ },
538
+
539
+ $route() {
540
+ setTimeout(() => {
541
+ this.$scorm.getDataValues()
542
+ }, 200)
543
+ },
544
+ /*
545
+ * In some cases (when the page has media ) the media metadata is not ready and total duration is not available.
546
+ * Watching getCurrentMediaDuration allow to track changes in the value of the duration and call updateDurationDisplay()
547
+ */
548
+ getCurrentMediaDuration() {
549
+ this.updateDurationDisplay()
550
+ }
551
+ },
552
+ beforeDestroy() {
553
+ this.saveToLrs()
554
+ if (this.isPlaying) {
555
+ this.playClick()
556
+ }
557
+ this.cancelTimeout()
558
+ if (this.mediaHandlersSet) {
559
+ this.removeMediaHandlers()
560
+ }
561
+ },
562
+
563
+ mounted() {
564
+ this.volumeBtn = this.$refs['volume-range']
565
+ this.bufferDomElem = this.$refs['playbar-buffer']
566
+ this.playbackDomElem = this.$refs['playbar-playback']
567
+ //get volume from the store
568
+ this.volDefault = this.getMediaVolume
569
+ this.subtitlesEnabled = this.getMediaSubtitles
570
+
571
+ //TODO: Trying to get the focus on the volume slider to them be able to inscrease/descrease volume with key up/key down
572
+
573
+ // if (!this.$refs || !this.$refs.volumeButton) return
574
+
575
+ // this.$refs.volumeButton.addEventListener('focus', () => {
576
+ // this.volumeSliderOpen = true
577
+ // // this.$refs.volume-range
578
+ // this.volumeBtn.addEventListener('keydown', (e) => {
579
+ // var key = e.key || e.keyCode
580
+ // if (key === 'ArrowUp' || key === 38) {
581
+ // // console.log('VolumeUP')
582
+ // }
583
+
584
+ // if (key === 'ArrowDown' || key === 40) {
585
+ // //console.log('VolumeDOWN')
586
+ // }
587
+ // })
588
+ // })
589
+ },
590
+ methods: {
591
+ setIsViewed() {
592
+ let userData = this.getUserInteraction
593
+ let activityId = this.getCurrentPage.activityRef
594
+ let pageId = this.getCurrentPage.id
595
+
596
+ this.$set(
597
+ userData[activityId][pageId].userInteraction,
598
+ 'mediaIsViewed',
599
+ true
600
+ )
601
+ },
602
+ saveToLrs() {
603
+ let text
604
+ //Defining the text to display for stmt description and definition
605
+ switch (this.$i18n.locale) {
606
+ case 'fr':
607
+ if (this.getModuleInfo.courseID)
608
+ text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
609
+ else text = `Le ${this.getModuleInfo.id}`
610
+ break
611
+ case 'en':
612
+ if (this.getModuleInfo.courseID)
613
+ text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
614
+ else text = `The ${this.getModuleInfo.id}`
615
+ break
616
+ }
617
+ //Creating custom statement
618
+ const stmt = {
619
+ id: (() => {
620
+ if (this.getModuleInfo.courseID) return this.getModuleInfo.courseID
621
+ else return null
622
+ })(),
623
+ verb: 'played',
624
+ definition: text,
625
+ description: text,
626
+ extension: [
627
+ {
628
+ id: 'playbar-values',
629
+ content: {
630
+ volume: this.volDefault,
631
+ subtitlesEnabled: this.subtitlesEnabled
632
+ }
633
+ }
634
+ ]
635
+ }
636
+ setTimeout(() => {
637
+ this.$bus.$emit('send-xapi-statement', stmt) //send xapi statement
638
+ }, 1000)
639
+ },
640
+
641
+ /*TODO: Need to refactor the control key mecanique to take in account Arrow Keys UP/DOWN
642
+ * ISSUE: Not possible to controle volume with Arrow keys UP/DOWN
643
+ */
644
+ windowKeydown(e) {
645
+ var key = e.key || e.keyCode
646
+ // Listen to keyEvent only on media Elements are on focus
647
+ if (
648
+ !this.media.mElement ||
649
+ this.media.mElement !== document.activeElement
650
+ )
651
+ return
652
+
653
+ //when focus is on the video element, spacebar = play/pause
654
+ if (key === ' ' || key === 'Esc' || key === 32) this.playClick()
655
+
656
+ //left and right arrows to seek video
657
+ if (key === 'ArrowLeft' || key === 37) {
658
+ this.cursorLeft()
659
+ }
660
+ if (key === 'ArrowRight' || key === 39) {
661
+ this.cursorRight()
662
+ }
663
+
664
+ // if (key === 'ArrowUp' || key === 38) {
665
+ // // this.cursorRight()
666
+ // console.log('VolumeUP')
667
+ // }
668
+
669
+ // if (key === 'ArrowDown' || key === 40) {
670
+ // console.log('VolumeDOWN')
671
+ // //this.cursorRight()
672
+ // }
673
+ },
674
+ getCurrentTime() {
675
+ return this.isAnimation
676
+ ? this.media.timeline.time()
677
+ : this.media.mElement.currentTime
678
+ },
679
+ cursorLeft() {
680
+ this.playbarFocus()
681
+ this.setMediaTime(this.getCurrentTime() - 10)
682
+ },
683
+ cursorRight() {
684
+ this.playbarFocus()
685
+ this.setMediaTime(this.getCurrentTime() + 10)
686
+ },
687
+ playbarFocus() {
688
+ this.playbarOver()
689
+ },
690
+ startTimeout() {
691
+ this.focusTimeout = setTimeout(this.playbarLeave, this.delayUntilHide)
692
+ },
693
+ cancelTimeout() {
694
+ if (this.focusTimeout) {
695
+ clearTimeout(this.focusTimeout)
696
+ }
697
+ },
698
+ playbarOver() {
699
+ this.hover = true
700
+ this.cancelTimeout()
701
+ this.startTimeout()
702
+ },
703
+ playbarLeave() {
704
+ this.hover = this.cursorDragged ? true : false
705
+ this.volumeLeave()
706
+ if (this.subtitleMenuOpen) {
707
+ this.toggleSubtitleMenu()
708
+ }
709
+ },
710
+ volumeOver() {
711
+ this.volumeSliderOpen = true
712
+ },
713
+ volumeLeave() {
714
+ this.volumeSliderOpen = false
715
+ },
716
+ volumeClick(ev) {
717
+ if (ev.target.id !== 'playbar-volume') {
718
+ return false
719
+ }
720
+
721
+ if (this.muted) {
722
+ this.volumeBtn.value = this.lastVolume
723
+ if (this.media.mElement) this.media.mElement.muted = false
724
+ this.muted = false
725
+ } else {
726
+ this.lastVolume = this.volumeBtn.value
727
+ this.volumeBtn.value = 0
728
+ this.muted = true
729
+ }
730
+ this.playbarFocus()
731
+ this.setVolume()
732
+ },
733
+ volumeInputChange() {
734
+ if (this.muted) {
735
+ this.muted = false
736
+ }
737
+ this.setVolume()
738
+ },
739
+ updateDurationDisplay() {
740
+ if (this.isAnimation) {
741
+ this.duration = this.$helper.formatTime(
742
+ this.mediaToShow.timeline.totalDuration()
743
+ )
744
+ } else {
745
+ this.duration = Number.isNaN(this.media.mElement.duration)
746
+ ? '--:--'
747
+ : this.$helper.formatTime(this.media.mElement.duration)
748
+ }
749
+ },
750
+ updateTimecodeDisplay() {
751
+ this.timecode = this.isAnimation
752
+ ? this.$helper.formatTime(this.media.timeline.time())
753
+ : this.$helper.formatTime(this.media.mElement.currentTime)
754
+ },
755
+
756
+ updateCursorPosition() {
757
+ const currentTime = this.isAnimation
758
+ ? this.media.timeline.time()
759
+ : this.media.mElement.currentTime
760
+ const duration = this.isAnimation
761
+ ? this.mediaToShow.timeline.totalDuration()
762
+ : this.media.mElement.duration
763
+ const cursorPos = this.getCursorPosFromTime(currentTime, duration)
764
+ this.setCursorPosition(cursorPos)
765
+ },
766
+ setMediaHandlers() {
767
+ if (this.media && (this.media.mElement || this.media.timeline)) {
768
+ //window handlers for playbar progress
769
+ window.addEventListener('mousemove', this.windowMove)
770
+ window.addEventListener('mouseup', this.windowUp)
771
+ window.addEventListener('resize', this.updateCursorPosition)
772
+ window.addEventListener('keydown', this.windowKeydown)
773
+ if (this.isAnimation) {
774
+ this.media.timeline.eventCallback('onUpdate', this.mediaTimeupdate)
775
+ this.media.timeline.eventCallback('onComplete', this.mediaEnded)
776
+ } else {
777
+ this.media.mElement.addEventListener(
778
+ 'timeupdate',
779
+ this.mediaTimeupdate
780
+ )
781
+ this.media.mElement.addEventListener('ended', this.mediaEnded)
782
+ /// hide the this.playbar()
783
+
784
+ this.media.mElement.addEventListener('click', () => {
785
+ if (this.focusTimeout) {
786
+ this.playbarLeave()
787
+ this.cancelTimeout()
788
+ }
789
+ })
790
+ this.media.mElement.focus()
791
+ //There is a timeline animation link to media set the callback event for timeline
792
+ if (this.media.timeline) {
793
+ this.media.timeline.eventCallback('onUpdate', this.mediaTimeupdate)
794
+ this.media.timeline.eventCallback('onComplete', this.mediaEnded)
795
+ }
796
+ }
797
+ }
798
+ this.mediaHandlersSet = true
799
+ },
800
+ removeMediaHandlers() {
801
+ if (this.isAnimation || this.media.timeline) {
802
+ this.media.timeline.eventCallback('onUpdate', null)
803
+ this.media.timeline.eventCallback('onComplete', null)
804
+ this.media.timeline.kill()
805
+ }
806
+ if (this.media.mElement) {
807
+ this.media.mElement.removeEventListener(
808
+ 'timeupdate',
809
+ this.mediaTimeupdate
810
+ )
811
+ this.media.mElement.removeEventListener('ended', this.mediaEnded)
812
+ }
813
+ //window handlers for playbar progress
814
+ window.removeEventListener('mousemove', this.windowMove)
815
+ window.removeEventListener('mouseup', this.windowUp)
816
+ window.removeEventListener('resize', this.updateCursorPosition)
817
+ window.removeEventListener('keydown', this.windowKeydown)
818
+
819
+ this.mediaHandlersSet = false
820
+ },
821
+ playClick() {
822
+ if (this.canReplay) {
823
+ this.setMediaTime(0)
824
+ }
825
+ if (this.isAnimation) {
826
+ this.playAnimation()
827
+ } else {
828
+ this.playVideo()
829
+ // play also the animation if there is one link to the media
830
+ if (this.media.timeline) this.playAnimation()
831
+ }
832
+ this.isPlaying = !this.isPlaying
833
+ this.canReplay = false
834
+ this.playbarFocus()
835
+ },
836
+
837
+ playVideo() {
838
+ if (this.isPlaying) {
839
+ this.media.mElement.pause()
840
+ } else {
841
+ this.media.mElement.play()
842
+ }
843
+ },
844
+
845
+ playAnimation() {
846
+ if (this.isPlaying) {
847
+ this.media.timeline.pause()
848
+ this.media.timeline.paused(true)
849
+ } else {
850
+ this.media.timeline.paused(false)
851
+ this.media.timeline.play()
852
+ }
853
+ },
854
+ setCursorPosition(xPos) {
855
+ this.playbackDomElem.style.width = xPos + 'px'
856
+ },
857
+ setMediaTime(time) {
858
+ if (this.isAnimation) {
859
+ this.media.timeline.time(time)
860
+ } else {
861
+ this.media.mElement.currentTime = time
862
+ if (this.media.timeline) this.media.timeline.time(time)
863
+ }
864
+ },
865
+ mediaTimeupdate() {
866
+ if (!this.cursorDragged) {
867
+ this.updateCursorPosition()
868
+ }
869
+ this.updateTimecodeDisplay()
870
+ this.setMediaA11Y()
871
+ },
872
+
873
+ /**
874
+ * @fires mediaPlaybackEnded to AppCompNavigationFull.vue
875
+ */
876
+ mediaEnded() {
877
+ if ((this.media.timeline || this.isAnimation) && this.isPlaying) {
878
+ this.media.timeline.pause()
879
+ this.media.timeline.paused(true)
880
+ }
881
+
882
+ this.isPlaying = false
883
+ this.canReplay = true
884
+ this.$bus.$emit('mediaPlaybackEnded')
885
+ //save the viewed status in userInteraction
886
+ if (this.isViewed === false) {
887
+ this.setIsViewed()
888
+ }
889
+ },
890
+ windowMove(ev) {
891
+ if (this.cursorDragged) {
892
+ this.setCursorPosition(ev.clientX)
893
+ }
894
+ },
895
+ windowUp(ev) {
896
+ if (this.cursorDragged) {
897
+ this.setCursorDragging(false)
898
+ const timeToSet = this.getTimeFromClickPos(ev)
899
+ this.setMediaTime(timeToSet)
900
+ }
901
+ },
902
+ setCursorDragging(bool) {
903
+ this.cursorDragged = bool
904
+ },
905
+ cursorDown() {
906
+ this.setCursorDragging(true)
907
+ },
908
+ progressOver() {
909
+ this.bufferDomElem.style.opacity = '1'
910
+ },
911
+ progressLeave() {
912
+ this.bufferDomElem.style.opacity = '0'
913
+ },
914
+ progressMove(ev) {
915
+ this.bufferDomElem.style.width = ev.clientX + 'px'
916
+ },
917
+ progressClick(ev) {
918
+ if (!this.cursorDragged) {
919
+ this.bufferDomElem.style.width = ev.clientX + 'px'
920
+ const timeToSet = this.getTimeFromClickPos(ev)
921
+ this.setMediaTime(timeToSet)
922
+ }
923
+ },
924
+ getCursorPosFromTime(currentTime, totalTime) {
925
+ let cursorPosition = 0
926
+ const domElem = this.$refs['playbar-progress']
927
+ const width = domElem.offsetWidth
928
+ const percentageTime = currentTime / totalTime
929
+ cursorPosition = width * percentageTime
930
+ return cursorPosition
931
+ },
932
+ getTimeFromClickPos(ev) {
933
+ let timeToSet = 0
934
+ const domElem = this.$refs['playbar-progress']
935
+ const clickPos = ev.clientX
936
+ const width = domElem.offsetWidth
937
+ const percentage = (clickPos / width) * 100
938
+ if (this.media && (this.media.timeline || this.media.mElement))
939
+ timeToSet = this.isAnimation
940
+ ? (this.media.timeline.totalDuration() / 100) * percentage
941
+ : (this.media.mElement.duration / 100) * percentage
942
+ else console.warn('media undefined')
943
+ return Math.floor(timeToSet)
944
+ },
945
+
946
+ /* Controle the Volume of the Media */
947
+ setVolume() {
948
+ if (this.media.mElement && this.volumeBtn) {
949
+ this.media.mElement.volume = this.volDefault = this.volumeBtn.value
950
+ this.$store.dispatch('setMediaVolume', this.volumeBtn.value)
951
+ }
952
+ },
953
+
954
+ toggleSubtitleMenu() {
955
+ this.subtitleMenuOpen = !this.subtitleMenuOpen
956
+ },
957
+ /**
958
+ * @description method to download transcription.
959
+ * @summary Create a tag with downloadable link and download the element to user PC. remove a element after download
960
+ */
961
+ async downloadTranscript() {
962
+ try {
963
+ const tFile = this.media.mTranscript
964
+ const fileUrl = `./${tFile}`
965
+ let atype = tFile.split('.')[1]
966
+
967
+ const res = await this.axios.get(fileUrl, { responseType: 'blob' })
968
+ const blob = new Blob([res.data], {
969
+ type: this.$helper.mimeTypeFor(atype)
970
+ })
971
+ const alink = document.createElement('a') //create a element
972
+
973
+ alink.href = URL.createObjectURL(blob) //create url reference for the object
974
+ alink.download = `${tFile}` // set download name
975
+ alink.click() //symulate click event to download the object
976
+ URL.revokeObjectURL(alink.href) //release the url referrence of the object
977
+ } catch (err) {
978
+ console.warn(this.$t('err_download_transcript'))
979
+ }
980
+ },
981
+ showSubtitles() {
982
+ this.media.mElement.textTracks[0].mode = 'showing'
983
+ this.subtitlesEnabled = true
984
+ this.$store.dispatch('setMediaSubtitles', true)
985
+ if (this.getCurrentBrowser == 'Firefox') {
986
+ let video = document.getElementsByTagName('video')[0]
987
+ let tracks = video.textTracks
988
+ let track = tracks[0]
989
+
990
+ track.addEventListener('cuechange', function fireFoxMoveCC() {
991
+ if (track.activeCues[0]) {
992
+ track.activeCues[0].line = 12
993
+ }
994
+ })
995
+ }
996
+ },
997
+ hideSubtitles() {
998
+ this.media.mElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
999
+ this.subtitlesEnabled = false
1000
+ this.$store.dispatch('setMediaSubtitles', false)
1001
+ },
1002
+
1003
+ /** ******************* Rendering Methods****************************** */
1004
+
1005
+ // TODO: Integrate customization of the subtitle (size, position ect...)
1006
+ setSubtitle() {},
1007
+ setMedia() {
1008
+ this.media = this.mediaToShow
1009
+
1010
+ // Setting Video / audio listeners
1011
+ if (this.media && this.media.type === 'pg_media') {
1012
+ this.hasMedia = true
1013
+ this.hasSound = true
1014
+
1015
+ if (this.media.mSubtitle) this.hasSubtitle = true
1016
+
1017
+ if (this.media.mTranscript) this.hasTranscript = true
1018
+ } else if (this.media && this.media.type === 'pg_animation') {
1019
+ this.hasMedia = true
1020
+ this.hasSound = false
1021
+ }
1022
+ if (this.media && Object.keys(this.media).length < 1) {
1023
+ this.hasMedia = false
1024
+ this.hasSound = false
1025
+ this.hasTranscript = false
1026
+ this.hasSubtitle = false
1027
+ }
1028
+ }, // END setMedia
1029
+
1030
+ /** @description - methode to set A11Y support for screen reader on media element (video/audio/animation) */
1031
+ setMediaA11Y() {
1032
+ if (!this.hasMedia) return
1033
+
1034
+ const w = this.$i18n.locale === 'en' ? 'at' : 'à'
1035
+ const dm =
1036
+ this.$i18n.locale === 'en'
1037
+ ? `${this.media.mType} duration`
1038
+ : `durée ${this.media.mType}`
1039
+
1040
+ this.mediaA11Y.valMax = this.isAnimation
1041
+ ? Math.round(this.media.timeline.totalDuration())
1042
+ : Math.round(this.media.mElement.duration) // media duration in floating-point
1043
+
1044
+ //get media Current time
1045
+ this.mediaA11Y.valNow = this.isAnimation
1046
+ ? Math.round(this.media.timeline.time())
1047
+ : Math.round(this.media.mElement.currentTime)
1048
+
1049
+ //format text output for screen reader
1050
+ let currentTime = this.timecode.split(':')
1051
+ let fullTime = this.duration.split(':')
1052
+ let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
1053
+
1054
+ const mediaTitle = this.media.mTitle || this.media.mType
1055
+
1056
+ this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
1057
+
1058
+ //format string to remove leading 0 digit for time
1059
+ const formatNum = (n) => {
1060
+ return n[0] == '0' ? n.substring(1).trim() : n.trim()
1061
+ }
1062
+
1063
+ const formatMsg = (x, y) => {
1064
+ let formatStr = this.$t('a11y_sr.range_expression')
1065
+ .replace('{x}', x)
1066
+ .replace('{y}', y)
1067
+
1068
+ return formatStr
1069
+ }
1070
+
1071
+ switch (fullTime.length) {
1072
+ case 3:
1073
+ if (!currentTime.length) return
1074
+
1075
+ //format current hours text text with plurialization apply
1076
+ hrsTxt =
1077
+ currentTime.length == 3 && parseInt(currentTime[0]) > 0
1078
+ ? `${formatNum(currentTime[0])} ${this.$tc(
1079
+ 'a11y_sr.time.hours',
1080
+ formatNum(currentTime[0])
1081
+ )}`
1082
+ : ''
1083
+
1084
+ //format minutes text text with plurialization apply
1085
+ mnTxt =
1086
+ parseInt(currentTime[1]) > 0
1087
+ ? `${formatNum(currentTime[1])} ${this.$tc(
1088
+ 'a11y_sr.time.minute',
1089
+ formatNum(currentTime[1])
1090
+ )}`
1091
+ : ''
1092
+
1093
+ //format seconds text with plurialization apply
1094
+ ssTxt =
1095
+ parseInt(currentTime[2]) > 0
1096
+ ? `${formatNum(currentTime[2])} ${this.$tc(
1097
+ 'a11y_sr.time.second',
1098
+ formatNum(currentTime[2])
1099
+ )}`
1100
+ : `0 ${this.$tc('a11y_sr.time.second')}`
1101
+
1102
+ HRSTxt =
1103
+ parseInt(fullTime[0]) > 0
1104
+ ? `${formatNum(fullTime[0])} ${this.$tc(
1105
+ 'a11y_sr.time.hours',
1106
+ formatNum(fullTime[0])
1107
+ )}`
1108
+ : ''
1109
+
1110
+ MNTxt =
1111
+ parseInt(fullTime[1]) > 0
1112
+ ? `${formatNum(fullTime[1])} ${this.$tc(
1113
+ 'a11y_sr.time.minute',
1114
+ formatNum(fullTime[1])
1115
+ )}`
1116
+ : ''
1117
+
1118
+ SSTxt =
1119
+ parseInt(fullTime[2]) > 0
1120
+ ? `${formatNum(fullTime[2])} ${this.$tc(
1121
+ 'a11y_sr.time.second',
1122
+ formatNum(fullTime[2])
1123
+ )}`
1124
+ : ''
1125
+
1126
+ cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
1127
+ fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
1128
+
1129
+ //Format to show 0 when no value is present
1130
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1131
+
1132
+ // Set sr text for timeCode && duration
1133
+ this.mediaA11Y.timeCode = `${this.media.mType} ${w} ${cTimeTxt}`
1134
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1135
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(
1136
+ cTimeTxt,
1137
+ fTimeTxt
1138
+ )}`
1139
+
1140
+ break
1141
+ case 2:
1142
+ if (!currentTime.length) return
1143
+
1144
+ //format current time text
1145
+ mnTxt =
1146
+ parseInt(currentTime[0]) > 0
1147
+ ? `${formatNum(currentTime[0])} ${this.$tc(
1148
+ 'a11y_sr.time.minute',
1149
+ formatNum(currentTime[0])
1150
+ )}`
1151
+ : ''
1152
+ ssTxt =
1153
+ parseInt(currentTime[1]) > 0
1154
+ ? `${formatNum(currentTime[1])} ${this.$tc(
1155
+ 'a11y_sr.time.second',
1156
+ formatNum(currentTime[1])
1157
+ )}`
1158
+ : `0 ${this.$tc('a11y_sr.time.second')}`
1159
+
1160
+ // format full time text
1161
+ MNTxt =
1162
+ parseInt(fullTime[0]) > 0
1163
+ ? `${formatNum(fullTime[0])} ${this.$tc(
1164
+ 'a11y_sr.time.minute',
1165
+ formatNum(fullTime[0])
1166
+ )}`
1167
+ : ''
1168
+ SSTxt =
1169
+ parseInt(fullTime[1]) > 0
1170
+ ? `${formatNum(fullTime[1])} ${this.$tc(
1171
+ 'a11y_sr.time.second',
1172
+ formatNum(fullTime[1])
1173
+ )}`
1174
+ : ''
1175
+
1176
+ cTimeTxt = `${mnTxt} ${ssTxt}`
1177
+ fTimeTxt = `${MNTxt} ${SSTxt}`
1178
+
1179
+ //Format to show 0 when no value is present
1180
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1181
+
1182
+ // Set sr text for timeCode && duration
1183
+ this.mediaA11Y.timeCode = `${this.media.mType} ${w} ${cTimeTxt}`
1184
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1185
+
1186
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(
1187
+ cTimeTxt,
1188
+ fTimeTxt
1189
+ )}`
1190
+
1191
+ break
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ </script>
1197
+ <style lang="scss">
1198
+ /*
1199
+ Playbar hover/leave transition
1200
+ */
1201
+ #playbar-wrapper {
1202
+ width: 100%;
1203
+ position: absolute;
1204
+ left: 0;
1205
+ position: fixed;
1206
+ //z-index: 99999;
1207
+ z-index: 10;
1208
+ bottom: 0;
1209
+
1210
+ * {
1211
+ -webkit-user-select: none; /* Safari */
1212
+ -ms-user-select: none; /* IE 10 and IE 11 */
1213
+ user-select: none; /* Standard syntax */
1214
+ }
1215
+ &.minimized {
1216
+ bottom: -7px;
1217
+ #playbar-btns {
1218
+ #playbar-flex-wrap {
1219
+ .playbar-btn {
1220
+ background-color: transparent;
1221
+ border: none;
1222
+ padding: 0px;
1223
+ }
1224
+ }
1225
+ }
1226
+ }
1227
+
1228
+ #playbar-btns {
1229
+ width: 55%;
1230
+ max-width: 485px;
1231
+ min-width: 290px;
1232
+ height: 70px;
1233
+ margin: 0 auto;
1234
+
1235
+ #playbar-flex-wrap {
1236
+ display: flex;
1237
+ flex-direction: row;
1238
+ justify-content: space-around;
1239
+ align-items: center;
1240
+ height: 100%;
1241
+ position: relative;
1242
+ #playbar-current-time {
1243
+ flex: 0 0 45px;
1244
+ }
1245
+
1246
+ .playbar-btn {
1247
+ cursor: pointer;
1248
+ display: flex;
1249
+ align-items: center;
1250
+ padding: 0px;
1251
+
1252
+ svg {
1253
+ pointer-events: none;
1254
+ margin: 0 auto;
1255
+ display: block;
1256
+ }
1257
+
1258
+ &#playbar-volume {
1259
+ position: relative;
1260
+ #playbar-volume-slider-zone {
1261
+ background-color: transparent;
1262
+ width: 90px;
1263
+ height: 120px;
1264
+ position: absolute;
1265
+ top: -120px;
1266
+ left: -27px;
1267
+
1268
+ #playbar-volume-slider {
1269
+ width: 30px;
1270
+ height: 100px;
1271
+ margin: 6px auto;
1272
+ padding-bottom: 100px;
1273
+ position: relative;
1274
+
1275
+ .range-slider {
1276
+ --progress-radius: 20px;
1277
+ --track-height: calc(var(--thumb-size) / 2);
1278
+ --thumb-size: 20px;
1279
+
1280
+ --value-a: Clamp(
1281
+ var(--min),
1282
+ var(--value, 0),
1283
+ var(--max)
1284
+ ); // default value ("--value" is used in single-range markup)
1285
+ --value-b: var(--value, 0); // default value
1286
+
1287
+ --completed-a: calc(
1288
+ (var(--value-a) - var(--min)) / (var(--max) - var(--min)) *
1289
+ 100
1290
+ );
1291
+ --completed-b: calc(
1292
+ (var(--value-b) - var(--min)) / (var(--max) - var(--min)) *
1293
+ 100
1294
+ );
1295
+ --ca: Min(var(--completed-a), var(--completed-b));
1296
+ --cb: Max(var(--completed-a), var(--completed-b));
1297
+
1298
+ // breakdown of the below super-complex brain-breaking CSS math:
1299
+ // "clamp" is used to ensure either "-1" or "1"
1300
+ // "calc" is used to inflat the outcome into a huge number, to get rid of any value between -1 & 1
1301
+ // if absolute diff of both completed % is above "5" (%)
1302
+ // ".001" bumps the value just a bit, to avoid a scenario where calc resulted in "0" (then clamp will also be "0")
1303
+ --thumbs-too-close: Clamp(
1304
+ -1,
1305
+ 1000 * (Min(1, Max(var(--cb) - var(--ca) - 5, -1)) + 0.001),
1306
+ 1
1307
+ );
1308
+ --thumb-close-to-min: Min(
1309
+ 1,
1310
+ Max(var(--ca) - 2, 0)
1311
+ ); // 2% threshold
1312
+ --thumb-close-to-max: Min(
1313
+ 1,
1314
+ Max(98 - var(--cb), 0)
1315
+ ); // 2% threshold
1316
+
1317
+ @mixin thumb {
1318
+ appearance: none;
1319
+ height: var(--thumb-size);
1320
+ width: var(--thumb-size);
1321
+ pointer-events: auto;
1322
+ }
1323
+
1324
+ display: inline-block;
1325
+ height: Max(var(--track-height), var(--thumb-size));
1326
+ position: absolute;
1327
+ z-index: 1;
1328
+ top: 40px;
1329
+ left: -32px;
1330
+ z-index: 1;
1331
+ width: 95px;
1332
+ transform: rotate(270deg);
1333
+
1334
+ &__progress {
1335
+ --start-end: calc(var(--thumb-size) / 2);
1336
+ --clip-end: calc(100% - (var(--cb)) * 1%);
1337
+ --clip-start: calc(var(--ca) * 1%);
1338
+ --clip: inset(-20px var(--clip-end) -20px var(--clip-start));
1339
+ position: absolute;
1340
+ left: var(--start-end);
1341
+ right: var(--start-end);
1342
+ top: calc(var(--thumb-size) / 2 - var(--track-height) / 2);
1343
+ height: calc(var(--track-height));
1344
+ background: var(--progress-background, #eee);
1345
+ border: 2px solid;
1346
+ pointer-events: none;
1347
+ z-index: -1;
1348
+ border-radius: var(--progress-radius);
1349
+
1350
+ // fill area
1351
+ &::before {
1352
+ content: '';
1353
+ position: absolute;
1354
+ left: 0;
1355
+ right: 0;
1356
+ clip-path: var(--clip);
1357
+ top: 0;
1358
+ bottom: 0;
1359
+ z-index: 1;
1360
+ border-radius: inherit;
1361
+ }
1362
+
1363
+ // shadow-effect
1364
+ &::after {
1365
+ content: '';
1366
+ position: absolute;
1367
+ top: 0;
1368
+ right: 0;
1369
+ bottom: 0;
1370
+ left: 0;
1371
+ pointer-events: none;
1372
+ border-radius: inherit;
1373
+ }
1374
+ }
1375
+
1376
+ & > input {
1377
+ -webkit-appearance: none;
1378
+ width: 100%;
1379
+ height: var(--thumb-size);
1380
+ margin: 0;
1381
+ position: absolute;
1382
+ left: 0;
1383
+ cursor: -webkit-grab;
1384
+ cursor: grab;
1385
+ outline: none;
1386
+ background: none;
1387
+
1388
+ &:not(:only-of-type) {
1389
+ pointer-events: none;
1390
+ }
1391
+
1392
+ &::-webkit-slider-thumb {
1393
+ @include thumb;
1394
+ }
1395
+ &::-moz-range-thumb {
1396
+ @include thumb;
1397
+ }
1398
+ &::-ms-thumb {
1399
+ @include thumb;
1400
+ }
1401
+
1402
+ &:active {
1403
+ cursor: grabbing;
1404
+ z-index: 2; // when sliding left thumb over the right or vice-versa, make sure the moved thumb is on top
1405
+ }
1406
+
1407
+ &:nth-of-type(1) {
1408
+ --is-left-most: Clamp(
1409
+ 0,
1410
+ (var(--value-a) - var(--value-b)) * 99999,
1411
+ 1
1412
+ );
1413
+ & + output {
1414
+ &:not(:only-of-type) {
1415
+ --flip: calc(var(--thumbs-too-close) * -1);
1416
+ }
1417
+ }
1418
+ }
1419
+
1420
+ &:nth-of-type(2) {
1421
+ --is-left-most: Clamp(
1422
+ 0,
1423
+ (var(--value-b) - var(--value-a)) * 99999,
1424
+ 1
1425
+ );
1426
+ & + output {
1427
+ --value: var(--value-b);
1428
+ }
1429
+ }
1430
+
1431
+ // non-multiple range should not clip start of progress bar
1432
+ &:only-of-type {
1433
+ ~ .range-slider__progress {
1434
+ --clip-start: 0;
1435
+ }
1436
+ }
1437
+
1438
+ & + output {
1439
+ --flip: -1;
1440
+ --x-offset: calc(var(--completed-b) * -1%);
1441
+ --pos: calc(
1442
+ ((var(--value) - var(--min)) / (var(--max) - var(--min))) *
1443
+ 68%
1444
+ );
1445
+
1446
+ opacity: 0;
1447
+ pointer-events: none;
1448
+ position: absolute;
1449
+ z-index: 5;
1450
+ left: var(--pos);
1451
+ transform: translate(
1452
+ var(--x-offset),
1453
+ calc(
1454
+ 150% * var(--flip) -
1455
+ (var(--y-offset, 0px) + var(--value-offset-y)) *
1456
+ var(--flip)
1457
+ )
1458
+ );
1459
+ margin-left: -5px;
1460
+ margin-top: 45px;
1461
+ line-height: 0;
1462
+ transform: rotate(90deg);
1463
+ transition: all 0.12s ease-out, left 0s;
1464
+ }
1465
+ }
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+
1472
+ #playbar-cc {
1473
+ position: absolute;
1474
+ top: -83px;
1475
+ right: 170px;
1476
+
1477
+ &.submenu_active {
1478
+ box-sizing: border-box;
1479
+ padding: 4px;
1480
+ }
1481
+ #subtitleMenuWrapper {
1482
+ position: absolute;
1483
+ width: 294px;
1484
+
1485
+ .subtitleBtns {
1486
+ width: 100%;
1487
+ height: 40px;
1488
+ padding: 0 24px;
1489
+ line-height: 40px;
1490
+ border: none;
1491
+ text-align: left;
1492
+
1493
+ svg {
1494
+ display: inline-block;
1495
+ margin-right: 20px;
1496
+ transform: translateY(-3px);
1497
+ }
1498
+ }
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ #playbar-progress {
1505
+ -webkit-user-select: none; /* Safari */
1506
+ -ms-user-select: none; /* IE 10 and IE 11 */
1507
+ user-select: none; /* Standard syntax */
1508
+ width: 100%;
1509
+ height: 12px;
1510
+ position: relative;
1511
+ cursor: pointer;
1512
+ & > div {
1513
+ height: 100%;
1514
+ position: absolute;
1515
+ }
1516
+ #playbar-playback {
1517
+ min-width: 8px;
1518
+ #playbar-cursor {
1519
+ width: 16px;
1520
+ height: 16px;
1521
+ position: absolute;
1522
+ right: -8px;
1523
+ top: -2px;
1524
+ &:focus {
1525
+ transform: scale(1.1);
1526
+ }
1527
+ }
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ #acessMenu {
1533
+ &:hover {
1534
+ svg {
1535
+ width: 21.642px !important;
1536
+ height: 16px !important;
1537
+ }
1538
+ }
1539
+ }
1540
+ </style>