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,2052 +1,2052 @@
1
- <!--@ Description: This component is used to display the playbar associate with the videoPlayer
2
- @ What it does: The component is used to interacted with the videoPlayer or audioPlayer via buttons.
3
- mediaMixins is used for all the methods/data shared between audio and video. In the appCompPlayBar component, there is also all the methods/data specific to video-->
4
- <template>
5
- <div
6
- ref="$pb-container"
7
- class="pb-container"
8
- :class="{ video: mediaToPlay.mType === 'video' }"
9
- >
10
- <!--------------------------------- Btn Play Principal (video only) -------------------------------------->
11
- <app-base-button
12
- v-if="mediaToPlay.mType === 'video'"
13
- ref="$btn-play-playback"
14
- class="playback-button"
15
- :class="{
16
- initial: !playClicked,
17
- 'playback-animation': playBackAnim && playClicked
18
- }"
19
- :aria-label="playLabel + ' principal'"
20
- :data-title="`${playLabel}`"
21
- @click="
22
- () => {
23
- mediaToPlay.mType === 'video'
24
- ? handlePlayVideo('playBackAnim')
25
- : togglePlay()
26
- }
27
- "
28
- >
29
- <div
30
- v-show="(!playClicked && !canReplay) || (!canReplay && isPlaying)"
31
- class="playback-wrapper"
32
- >
33
- <svg class="app-icons-svg play-icon">
34
- <use href="#play-icon" />
35
- </svg>
36
- </div>
37
- <div
38
- v-show="playClicked && !isPlaying && !canReplay"
39
- class="playback-wrapper"
40
- >
41
- <svg class="app-icons-svg pause-icon">
42
- <use href="#pause-icon" />
43
- </svg>
44
- </div>
45
- <div v-show="!playClicked && canReplay" class="playback-wrapper">
46
- <svg class="app-icons-svg replay-icon">
47
- <use href="#replay-icon" />
48
- </svg>
49
- </div>
50
- </app-base-button>
51
-
52
- <div
53
- v-if="mediaToPlay"
54
- class="pb-wrapper"
55
- :class="{
56
- 'show-controls': showControlsValue || mediaToPlay.mType === 'audio',
57
- audio: mediaToPlay.mType === 'audio',
58
- video: mediaToPlay.mType === 'video'
59
- }"
60
- >
61
- <!--------------------------------- PLAY-BAR Progress (video only) -------------------------------------->
62
- <div
63
- v-if="mediaToPlay.mType === 'video'"
64
- ref="$playbar-timeline"
65
- class="pb-timeline"
66
- >
67
- <div ref="$progress-area" class="progress-area">
68
- <div
69
- ref="$progress-bar"
70
- draggable="false"
71
- tabindex="0"
72
- class="pb-progress-bar"
73
- role="slider"
74
- aria-valuemin="0"
75
- :aria-label="mediaA11Y.label"
76
- :aria-valuenow="mediaA11Y.valNow"
77
- :aria-valuemax="mediaA11Y.valMax"
78
- :aria-valuetext="mediaA11Y.valueText"
79
- @focus="changeFocusState('progressBar', true)"
80
- @blur="changeFocusState('progressBar', false)"
81
- >
82
- <!--Class progress-animation is apply when we are not using the thumb to change the progression-->
83
- <div
84
- ref="$progress-indicator"
85
- draggable="false"
86
- class="progress-indicator"
87
- :class="{ 'progress-animation': !progressThumbDown }"
88
- :style="{ width: progressBarPercentage + '%' }"
89
- >
90
- <!--Mousedown validate if the thumb is cliqued (if yes, the mouse is follow to modify the progress)
91
- MouseOver/MouseOut deactivate click event on progressBar if the mouse hovered the thumb-->
92
- <div
93
- ref="$progress-thumb"
94
- draggable="false"
95
- class="progress-thumb"
96
- style="z-index: 9"
97
- @mousedown="
98
- function () {
99
- savedIsPlaying = isPlaying
100
- progressThumbDown = true
101
- }
102
- "
103
- @mouseover="progressThumbHover = true"
104
- @mouseleave="progressThumbHover = false"
105
- ></div>
106
- </div>
107
- </div>
108
- <!--------------------------------- PLAY-BAR ToolTip (video only) ----------------------------------->
109
- <div
110
- v-if="mediaToPlay.mType === 'video'"
111
- ref="$seek-tooltip"
112
- aria-hidden="true"
113
- class="seek-tooltip"
114
- >
115
- {{ tooTipTimeCode }}
116
- </div>
117
- </div>
118
- </div>
119
- <div class="pb-controls">
120
- <div class="pb-areas left">
121
- <!--------------------------------- PLAY-BAR Play ------------------------------------>
122
- <app-base-button
123
- ref="$btn-play"
124
- class="playbar-btn playbar-play"
125
- :class="{ displayLabel: btnsLabelDisplay['playbar-play'] === true }"
126
- :aria-label="
127
- playLabel + (mediaToPlay.mType === 'video' ? ' secondaire' : '')
128
- "
129
- :data-title="`${playLabel} (k)`"
130
- @click="
131
- () => {
132
- mediaToPlay.mType === 'video' ? handlePlayVideo() : togglePlay()
133
- }
134
- "
135
- @mouseenter="activateLabel($event)"
136
- @mouseleave="deactivateLabel($event)"
137
- @focus="activateLabel($event)"
138
- @blur="deactivateLabel($event)"
139
- >
140
- <svg
141
- v-show="!canReplay && !isPlaying"
142
- class="app-icons-svg play-icon"
143
- >
144
- <use href="#play-icon" />
145
- </svg>
146
- <svg
147
- v-show="isPlaying && !canReplay"
148
- class="app-icons-svg pause-icon"
149
- >
150
- <use href="#pause-icon" />
151
- </svg>
152
- <svg v-show="canReplay" class="app-icons-svg replay-icon">
153
- <use href="#replay-icon" />
154
- </svg>
155
- </app-base-button>
156
- <!--------------------------------- PLAY-BAR Timer ------------------------------------>
157
- <div class="pb-timer">
158
- <span
159
- aria-hidden="true"
160
- style="-webkit-user-select: none"
161
- draggable="false"
162
- >
163
- {{ timecode }} / {{ mediaDurationTime }}
164
- </span>
165
- </div>
166
- <!--------------------------------- PLAY-BAR Progress (audio only) -------------------------------------->
167
- <div
168
- v-if="mediaToPlay.mType === 'audio'"
169
- ref="$playbar-timeline"
170
- class="pb-timeline"
171
- >
172
- <div ref="$progress-area" class="progress-area">
173
- <div
174
- id="progress-bar"
175
- ref="$progress-bar"
176
- draggable="false"
177
- tabindex="0"
178
- class="pb-progress-bar"
179
- role="slider"
180
- aria-valuemin="0"
181
- :aria-label="mediaA11Y.label"
182
- :aria-valuenow="mediaA11Y.valNow"
183
- :aria-valuemax="mediaA11Y.valMax"
184
- :aria-valuetext="mediaA11Y.valueText"
185
- @focus="changeFocusState('progressBar', true)"
186
- @blur="changeFocusState('progressBar', false)"
187
- >
188
- <!--Class progress-animation is apply when we are not using the thumb to change the progression-->
189
- <div
190
- id="progress-indicator"
191
- ref="$progress-indicator"
192
- draggable="false"
193
- class="progress-indicator"
194
- :class="{ 'progress-animation': !progressThumbDown }"
195
- :style="{ width: progressBarPercentage + '%' }"
196
- >
197
- <!--Mousedown validate if the thumb is cliqued (if yes, the mouse is follow to modify the progress)
198
- MouseOver/MouseOut deactivate click event on progressBar if the mouse hovered the thumb-->
199
- <div
200
- ref="$progress-thumb"
201
- draggable="false"
202
- class="progress-thumb"
203
- style="z-index: 9"
204
- @mousedown="
205
- function () {
206
- savedIsPlaying = isPlaying
207
- progressThumbDown = true
208
- }
209
- "
210
- @mouseover="progressThumbHover = true"
211
- @mouseleave="progressThumbHover = false"
212
- ></div>
213
- </div>
214
- </div>
215
- </div>
216
- </div>
217
- <!--------------------------------- PLAY-BAR Volume -------------------------------------->
218
- <app-base-button
219
- id="playbar-volume"
220
- ref="$btn-volume"
221
- class="volume-btn"
222
- :class="{
223
- displayLabel: btnsLabelDisplay['playbar-volume'] === true
224
- }"
225
- tabindex="0"
226
- :aria-label="volumeLabel"
227
- :data-title="`${volumeLabel} (m)`"
228
- @click="toggleMute"
229
- @mouseenter="activateLabel($event)"
230
- @mouseleave="deactivateLabel($event)"
231
- @focus="activateLabel($event)"
232
- @blur="deactivateLabel($event)"
233
- >
234
- <svg v-show="volumeState == 'muted'" class="app-icons-svg">
235
- <use href="#volume-mute-icon" />
236
- </svg>
237
- <svg v-show="volumeState == 'high'" class="app-icons-svg">
238
- <use href="#volume-high-icon" />
239
- </svg>
240
- <svg
241
- v-show="volumeState == 'low'"
242
- class="app-icons-svg volume-low-icon"
243
- >
244
- <use href="#volume-low-icon" />
245
- </svg>
246
- </app-base-button>
247
- <div ref="zone-volume" class="volume-controls">
248
- <input
249
- ref="$volume-slider"
250
- class="volume-slider"
251
- :class="{
252
- displayLabel: btnsLabelDisplay['volume-slider'] === true
253
- }"
254
- :value="currentVolume"
255
- :aria-valuetext="volumeLevelA11Y"
256
- type="range"
257
- max="1"
258
- min="0"
259
- step="0.01"
260
- tabindex="0"
261
- :aria-label="`${$t('button.volume')}`"
262
- @click="updateVolumeLevel"
263
- @mouseenter="activateLabel($event.currentTarget)"
264
- @mouseleave="deactivateLabel($event.currentTarget)"
265
- @focus="
266
- activateLabel($event.currentTarget),
267
- changeFocusState('volumeSlider', true)
268
- "
269
- @blur="
270
- deactivateLabel($event.currentTarget),
271
- changeFocusState('volumeSlider', false)
272
- "
273
- />
274
- <span class="volume-label" aria-hidden="true">
275
- {{ $t('button.volume') }}
276
- </span>
277
- <!--using span because ::before on input is not supported by firefox-->
278
- <span
279
- ref="$volume-progress"
280
- class="volume-progress"
281
- :style="{
282
- '--background-size-vs': `calc(${volumeSliderBackground} - 2px)`
283
- }"
284
- ></span>
285
- </div>
286
- </div>
287
- <!--------------------------------- PLAY-BAR CC (video only) ------------------------------------------>
288
- <div v-if="mediaToPlay.mType === 'video'" class="pb-areas right">
289
- <app-base-button
290
- id="btn-subtitles"
291
- ref="$btn-subtitle"
292
- class="btn subtitleBtns"
293
- :aria-label="ccLabel"
294
- :data-title="`${ccLabel} (c)`"
295
- :disabled="!hasSubtitle"
296
- :class="{
297
- md_disabled: !hasSubtitle,
298
- displayLabel: btnsLabelDisplay['btn-subtitles'] === true
299
- }"
300
- @click="toggleViewSubtitle()"
301
- @mouseenter="activateLabel($event)"
302
- @mouseleave="deactivateLabel($event)"
303
- @focus="activateLabel($event)"
304
- @blur="deactivateLabel($event)"
305
- >
306
- <svg
307
- v-show="(hasSubtitle && !subtitlesEnabled) || !hasSubtitle"
308
- class="app-icons-svg"
309
- >
310
- <use href="#subtitle-off-icon" />
311
- </svg>
312
- <svg v-show="hasSubtitle && subtitlesEnabled" class="app-icons-svg">
313
- <use href="#subtitle-on-icon" />
314
- </svg>
315
- </app-base-button>
316
- <!--------------------------------- PLAY-BAR Transcript ----------------------------------->
317
- <app-base-button
318
- id="btn-transcript"
319
- ref="$btn-transcript"
320
- class="btn-transcript"
321
- :aria-label="transcriptLabel"
322
- :data-title="`${transcriptLabel} (t)`"
323
- :disabled="transcriptBtnDisabled"
324
- :class="{
325
- md_disabled: transcriptBtnDisabled,
326
- displayLabel: btnsLabelDisplay['btn-transcript'] === true
327
- }"
328
- @click="toggleViewTranscript($event)"
329
- @mouseenter="activateLabel($event)"
330
- @mouseleave="deactivateLabel($event)"
331
- @focus="activateLabel($event)"
332
- @blur="deactivateLabel($event)"
333
- >
334
- <svg
335
- v-show="(hasTranscript && !transcriptEnabled) || !hasTranscript"
336
- class="app-icons-svg"
337
- >
338
- <use href="#transcript-off-icon" />
339
- </svg>
340
- <svg
341
- v-show="hasTranscript && transcriptEnabled"
342
- class="app-icons-svg"
343
- >
344
- <use href="#transcript-on-icon" />
345
- </svg>
346
- </app-base-button>
347
- <!--------------------------------- PLAY-BAR FullScreen ----------------------------------->
348
- <app-base-button
349
- id="btn-fullscreen"
350
- ref="$btn-fullscreen"
351
- class="fullscreenBtns"
352
- :aria-label="fullscreenLabel"
353
- :disabled="fullscreenBtnDisabled"
354
- :class="{
355
- md_disabled: fullscreenBtnDisabled,
356
- displayLabel: btnsLabelDisplay['btn-fullscreen'] === true
357
- }"
358
- :data-title="`${fullscreenLabel} (f)`"
359
- @click="toggleFullScreen()"
360
- @mouseenter="activateLabel($event)"
361
- @mouseleave="deactivateLabel($event)"
362
- @focus="activateLabel($event)"
363
- @blur="deactivateLabel($event)"
364
- >
365
- <svg v-show="!fullscreenOn" class="app-icons-svg">
366
- <use href="#fullscreen-icon" />
367
- </svg>
368
- <svg v-show="fullscreenOn" class="app-icons-svg">
369
- <use href="#fullscreen-exit-icon" />
370
- </svg>
371
- </app-base-button>
372
- </div>
373
- </div>
374
- </div>
375
- </div>
376
- </template>
377
-
378
- <script>
379
- import { mapState, mapActions } from 'pinia'
380
- import { useAppStore } from '../module/stores/appStore'
381
- import axios from 'axios'
382
-
383
- export default {
384
- props: {
385
- mediaToPlay: { type: [Object, Boolean], default: false }
386
- },
387
-
388
- emits: ['resize-video'],
389
- setup() {
390
- const store = useAppStore()
391
- const { activityRef, id } = store.getCurrentPage
392
- const { userInteraction } = store.getPageInteraction(activityRef, id)
393
- return { store, userInteraction }
394
- },
395
- data() {
396
- return {
397
- id: `plyr_${this.mediaToPlay.id}`,
398
- //Playback animation
399
- playClicked: false,
400
- playBackAnim: true,
401
- //ProgressSeek
402
- progressSeek: null,
403
- //Tooltip
404
- seekTooltip: null,
405
- tooTipTimeCode: '00:00',
406
- //Hiding playbar animation
407
- focusTimeout: null,
408
- delayUntilHide: 5000,
409
- hideTimer: null,
410
- //Transcript
411
- transcriptEnabled: false,
412
- transcriptToShow: null,
413
- //Fullscreen
414
- fullscreenOn: false,
415
- //==========================================================
416
- //Container Playbar
417
- pbContainer: null,
418
- //ShowControls (show the playbar or not, default is true)
419
- showControlsValue: true,
420
- //Player
421
- isPlaying: false,
422
- canReplay: false,
423
- savedIsPlaying: false, //To get the playing status on mousemove on the progressBar
424
- //ProgressBar
425
- currentTime: 0,
426
- progressArea: null,
427
- progressBar: null,
428
- progressIndicator: null,
429
- progressThumb: null,
430
- progressThumbDown: false,
431
- progressThumbHover: false,
432
- //Volume
433
- volumeBtn: null,
434
- volumeSlider: null,
435
- volumeIndicator: null,
436
- currentVolume: null,
437
- savedVolume: 0.5, //Saved volume when muted
438
- //muted
439
- muted: false,
440
-
441
- //Labels
442
- //Strings for screenreaders (duration et time)
443
- mediaA11Y: {
444
- label: 'progress',
445
- valMax: null,
446
- valNow: null,
447
- valueText: null,
448
- timeCode: 0,
449
- duration: 0
450
- },
451
- //buttons label (all the buttons that need labels management in the playbar)
452
- btnsLabelDisplay: {
453
- 'btn-subtitles': false,
454
- 'btn-transcript': false,
455
- 'btn-fullscreen': false,
456
- 'playbar-play': false,
457
- 'playbar-volume': false,
458
- 'volume-slider': false
459
- },
460
- //FocusState to manage arrows keydown on the 2 sliders (progress and volume)
461
- focusState: {
462
- progressBar: false,
463
- volumeSlider: false
464
- },
465
- otherVideoTranscriptShown: false
466
- //==========================================================
467
- }
468
- },
469
- computed: {
470
- ...mapState(useAppStore, [
471
- 'getCurrentBrowser',
472
- 'getMediaSubtitles',
473
- //=====================================================
474
- 'getCurrentPage',
475
- 'getModuleChildren',
476
- 'hasMediaElOrTimeline',
477
- 'getCurrentMediaDuration',
478
- 'getDataFromServer',
479
- 'getAutoplayEnabled',
480
- 'getMediaVolume',
481
- 'getModuleInfo',
482
- 'getMediaPlaybarValues',
483
- 'getPageInteraction',
484
- 'getMediaMuted'
485
- //=====================================================
486
- ]),
487
- //==========================================================
488
- //MediaElement
489
- mediaElement() {
490
- if (!this.mediaToPlay) return null
491
- const { mElement } = this.mediaToPlay
492
- return mElement ? mElement : null
493
- },
494
- //Media Progress
495
- //Time display : duration
496
- mediaDurationTime() {
497
- if (this.mediaDuration > 0)
498
- return this.$helper.formatTime(this.mediaDuration)
499
- else return '00:00'
500
- },
501
- //Time display : timecode
502
- timecode() {
503
- if (this.currentTime && typeof (this.currentTime === Number)) {
504
- return this.$helper.formatTime(this.currentTime)
505
- } else return '00:00'
506
- },
507
- //ProgressPercentage for progressBarIndicator width
508
- progressBarPercentage() {
509
- if (this.currentTime && this.mediaDuration)
510
- return (this.currentTime / this.mediaDuration) * 100
511
- else return 0
512
- },
513
- progressBarEnded() {
514
- return this.currentTime >= Math.floor(this.mediaDuration)
515
- },
516
- //Volume
517
- volumeState() {
518
- let state = null
519
- switch (true) {
520
- case this.muted || this.currentVolume <= 0:
521
- state = 'muted'
522
- break
523
-
524
- case !this.muted && this.currentVolume <= 0.5:
525
- state = 'low'
526
- break
527
- case !this.muted && this.currentVolume > 0.5:
528
- state = 'high'
529
- }
530
-
531
- return state
532
- },
533
- //Volume progress for slider background
534
- volumeSliderBackground() {
535
- return (this.currentVolume * 100).toFixed(0).toString() + '%'
536
- },
537
- //Volume from store
538
- mediaVolume() {
539
- // return this.getMediaVolume
540
- return this.getMediaPlaybarValues('volume')
541
- },
542
- //media from store
543
- mediaMuted() {
544
- return this.getMediaMuted
545
- },
546
- //Labels
547
- playLabel() {
548
- let label = null
549
- switch (true) {
550
- case this.isPlaying:
551
- label = this.$t('button.pause')
552
- break
553
-
554
- case this.canReplay:
555
- label = this.$t('button.replay')
556
- break
557
-
558
- default:
559
- label = this.$t('button.play')
560
- }
561
- return label
562
- },
563
- volumeLabel() {
564
- let label = null
565
- switch (true) {
566
- case this.muted:
567
- label = `${this.$t('button.unmute')}`
568
- break
569
- case !this.muted:
570
- label = `${this.$t('button.mute')}`
571
- break
572
- }
573
- return label
574
- },
575
- volumeLevelA11Y() {
576
- const w = this.$i18n.locale === 'en' ? 'at' : 'à'
577
- const level = Math.round(this.currentVolume * 100)
578
- let txtA11Y = `Volume ${w} ${level}%`
579
-
580
- return txtA11Y
581
- },
582
- //==========================================================
583
- //Transcript
584
- hasTranscript() {
585
- if (!this.mediaToPlay) return
586
- return this.mediaToPlay.mTranscript || false
587
- },
588
- //Subtitle
589
- hasSubtitle() {
590
- if (!this.mediaToPlay) return
591
- return this.mediaToPlay.mSubtitles || false
592
- },
593
- subtitlesEnabled() {
594
- if (!this.hasSubtitle) return false
595
- else return this.getMediaSubtitles
596
- },
597
- //mediaContainer (only used for video)
598
- mediaContainer() {
599
- if (!this.mediaToPlay) return
600
- return this.mediaToPlay.mMediaContainer
601
- },
602
-
603
- //MediaDuration :Number (used for tooltip)
604
- mediaDuration() {
605
- return this.mediaToPlay.mElement.duration
606
- ? this.mediaToPlay.mElement.duration
607
- : 0
608
- },
609
- //Labels
610
- fullscreenLabel() {
611
- let label = null
612
- if (this.fullscreenOn) label = `${this.$t('button.full_screen_off')}`
613
- else label = `${this.$t('button.full_screen_on')}`
614
- return label
615
- },
616
- transcriptLabel() {
617
- let label = null
618
- if (this.transcriptEnabled) label = `${this.$t('button.transcript_off')}`
619
- else label = `${this.$t('button.transcript_on')}`
620
- return label
621
- },
622
- ccLabel() {
623
- let label = null
624
- if (this.subtitlesEnabled) label = `${this.$t('button.subtitle_off')}`
625
- else label = `${this.$t('button.subtitle_on')}`
626
- return label
627
- },
628
- transcriptBtnDisabled() {
629
- return (
630
- !this.hasTranscript ||
631
- this.fullscreenOn ||
632
- this.otherVideoTranscriptShown
633
- )
634
- },
635
- fullscreenBtnDisabled() {
636
- return this.transcriptEnabled || this.otherVideoTranscriptShown
637
- }
638
- },
639
- watch: {
640
- userInteraction: {
641
- immediate: true,
642
- deep: true,
643
- handler() {
644
- if (!this.userInteraction || !Object.keys(this.userInteraction).length)
645
- return
646
- this.getViewedSatus()
647
- this.setMediaVolume()
648
- }
649
- }
650
- },
651
-
652
- mounted() {
653
- //initialize media $refs
654
- this.initializeMediaElm()
655
- //initialize video $refs && set eventlistener for video
656
- if (this.mediaToPlay.mType === 'video') {
657
- this.initializeVideoElm()
658
- this.setVideoHandlers()
659
- }
660
- //Set eventlistener for media
661
- this.setMediaHandlers()
662
-
663
- //Set label for ProgressBar
664
- this.setProgressBarA11Y()
665
- //Set initial volume and duration
666
- this.updateVolumeLevel()
667
-
668
- //update the the information of the media element linked to this play bar. playbar instance will be added to the media info
669
- this.updateCurrentMediaElements({
670
- id: this.mediaToPlay.id,
671
- ...this.$parent.$refs
672
- })
673
- },
674
- beforeUnmount() {
675
- if (this.isPlaying) {
676
- this.togglePlay()
677
- }
678
- this.removeMediaHandlers()
679
-
680
- if (this.mediaToPlay.mType === 'video') this.removeVideoHandlers()
681
-
682
- if (this.firefoxTrack) {
683
- this.firefoxTrack.removeEventListener('cuechange', this.fireFoxMoveCC)
684
- }
685
- //this.$bus.$off('play-media', this.handleMediaControls)
686
- this.$bus.$off('transcript-hidden', this.resetTranscript)
687
- return this.$bus.$off('close-sidebar', 'ctxTranscript')
688
- },
689
- methods: {
690
- ...mapActions(useAppStore, [
691
- 'setMediaSubtitles',
692
- 'updateCurrentMediaElements',
693
- 'setMediaMuted',
694
- 'setMediaPlaybarValues'
695
- ]),
696
-
697
- //======================================================================
698
- /**
699
- * @description - Set all the DOM elements for medias
700
- */
701
- initializeMediaElm() {
702
- this.pbContainer = this.$refs['$pb-container']
703
- //ProgressBar
704
- this.progressArea = this.$refs['$progress-area']
705
- this.progressBar = this.$refs['$progress-bar']
706
- this.progressIndicator = this.$refs['$progress-indicator']
707
- this.progressThumb = this.$refs['$progress-thumb']
708
- //SeekTooltip
709
- this.seekTooltip = this.$refs['$seek-tooltip']
710
- //Volume//
711
- this.volumeBtn = this.$refs['$btn-volume']
712
- this.volumeSlider = this.$refs['$volume-slider']
713
- this.volumeIndicator = this.$refs['$volume-progress']
714
- },
715
- /**
716
- * @description - handle all the listeners for medias
717
- */
718
- setMediaHandlers() {
719
- if (this.mediaElement) {
720
- //Prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
721
- this.pbContainer.addEventListener('keydown', this.keysPreventDefault)
722
- //progressBar events
723
- this.progressArea.addEventListener('click', this.seekingProgress)
724
- //window handlers for playbar progress
725
- window.addEventListener('mousemove', this.progressWindowMove)
726
- window.addEventListener('mouseup', this.progressWindowUp)
727
- //EndprogressBar
728
- //Update data when media as a timeupdate/ended
729
- this.mediaElement.addEventListener(
730
- 'timeupdate',
731
- this.updateProgressBarTime
732
- )
733
- this.mediaElement.addEventListener('ended', this.mediaEnded)
734
- //Volume
735
- this.volumeSlider.addEventListener('input', this.updateVolumeLevel)
736
- //Hotkeys
737
- this.pbContainer.addEventListener('keydown', this.handleMediaControls)
738
-
739
- //Bus
740
- //this.$bus.$on('play-media', this.handleMediaControls)
741
- }
742
- },
743
-
744
- /**
745
- * @description - Unset all the listeners (medias)
746
- */
747
- removeMediaHandlers() {
748
- if (this.mediaElement) {
749
- //progressBar events
750
- this.progressArea.removeEventListener('click', this.seekingProgress)
751
- //window handlers for playbar progress
752
- window.removeEventListener('mousemove', this.progressWindowMove)
753
- window.removeEventListener('mouseup', this.progressWindowUp)
754
-
755
- this.mediaElement.removeEventListener(
756
- 'timeupdate',
757
- this.updateProgressBarTime
758
- )
759
- this.mediaElement.removeEventListener('ended', this.mediaEnded)
760
-
761
- //Volume
762
- this.volumeSlider.removeEventListener('input', this.updateVolumeLevel)
763
- //Keys
764
- this.pbContainer.removeEventListener('keydown', this.keysPreventDefault)
765
- this.pbContainer.removeEventListener(
766
- 'keydown',
767
- this.handleMediaControls
768
- )
769
- }
770
- },
771
- /**
772
- * @description - play or pause the media
773
- * @fires manage-media-players - to the PAge the media in play
774
- */
775
- togglePlay() {
776
- //If the progressBar is at the end, restart the media
777
- if (this.progressBarEnded) {
778
- this.mediaElement.currentTime = 0
779
- this.currentTime = 0
780
- }
781
- //MediaElement
782
- this.isPlaying ? this.mediaElement.pause() : this.mediaElement.play()
783
- //Data
784
- if (!this.isPlaying) {
785
- //Signal to set this mediaElement as the last playing
786
- this.$bus.$emit('manage-media-players', this.mediaToPlay)
787
- }
788
-
789
- this.isPlaying = !this.isPlaying
790
- this.canReplay = false
791
- },
792
- /**
793
- * @description - Set the media to a new time (if its a time between 0 and max)
794
- */
795
- setMediaTime(time) {
796
- if (time <= 0) this.mediaElement.currentTime = 0
797
- else if (time > this.mediaDuration)
798
- this.mediaElement.currentTime = this.mediaDuration
799
- else this.mediaElement.currentTime = time
800
- //Handle finish Media if its set to max duration
801
- if (this.progressBarEnded) {
802
- this.mediaEnded()
803
- }
804
- },
805
- /**
806
- * @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
807
- */
808
-
809
- mediaEnded() {
810
- //Don't make the media play when release progressBar thumb and the media is Ended
811
- this.savedIsPlaying = false
812
- //Pausing mediaElement is isPlaying
813
- if (this.mediaElement && this.isPlaying) {
814
- this.mediaElement.pause()
815
- this.isPlaying = false
816
- }
817
- this.$bus.$emit('media-viewed', this.mediaToPlay.id)
818
- this.canReplay = true
819
- if (this.mediaToPlay.mType === 'video') this.showControls()
820
- },
821
-
822
- /**
823
- * @description get the view Status of the current media from UserInteraction
824
- */
825
- getViewedSatus() {
826
- //Should get the viewed Status from UserInteraction
827
- // let { activityRef, id } = this.getCurrentPage
828
- // const { userInteraction = null } = this.getPageInteraction(
829
- // activityRef,
830
- // id
831
- // )
832
- if (
833
- !this.userInteraction ||
834
- !this.userInteraction.mediasViewed ||
835
- !this.userInteraction.mediasViewed.length
836
- )
837
- return
838
-
839
- const { mediasViewed } = this.userInteraction
840
-
841
- //Should update the can replay state of media
842
- this.canReplay = mediasViewed.includes(this.mediaElement.id)
843
- },
844
- /**
845
- * @description controls the level Of the media volume
846
- * @param d string, optional volUp or volDown to Add or Remove 0.05 from volume value
847
- */
848
- updateVolumeLevel(d = null) {
849
- if (!this.mediaElement) return
850
- if (this.mediaElement.muted) {
851
- this.mediaElement.muted = this.muted = false
852
- //Send muted state to the store
853
- this.setMutedState(this.muted)
854
- }
855
- switch (true) {
856
- case d == 'volUP':
857
- //Set volume Up
858
- if (this.mediaElement.volume >= 1) return
859
- if (this.mediaElement.volume >= 0.95) {
860
- this.mediaElement.volume = 1
861
- } else {
862
- this.mediaElement.volume = (
863
- this.mediaElement.volume + 0.05
864
- ).toFixed(2)
865
- }
866
- this.volumeSlider.value = this.currentVolume =
867
- this.mediaElement.volume
868
- break
869
-
870
- case d == 'volDOWN':
871
- //Set volume Down
872
- if (this.mediaElement.volume <= 0) return
873
- if (this.mediaElement.volume <= 0.05) {
874
- this.mediaElement.volume = 0
875
- } else {
876
- this.mediaElement.volume = (
877
- this.mediaElement.volume - 0.05
878
- ).toFixed(2)
879
- }
880
-
881
- this.volumeSlider.value = this.currentVolume =
882
- this.mediaElement.volume
883
- break
884
-
885
- default: {
886
- //set volume
887
- this.currentVolume = this.mediaElement.volume =
888
- this.volumeSlider.value
889
- this.currentVolume = parseFloat(this.currentVolume).toFixed(2)
890
- }
891
- }
892
- if (!this.muted) this.savedVolume = this.currentVolume
893
-
894
- this.setMediaPlaybarValues({
895
- volume: this.savedVolume
896
- }) // Save the volume in the Store
897
- },
898
- /**
899
- * @description - Toggle the state fo the sound
900
- */
901
- toggleMute() {
902
- //Check first the volume of the media
903
- if (this.mediaElement.volume == 0) this.muted = true
904
-
905
- this.muted = !this.muted
906
- this.mediaElement.muted = this.muted // set the muted state of the media
907
-
908
- //Send mutedstate to the store
909
- this.setMutedState(this.muted)
910
-
911
- if (this.muted) {
912
- this.mediaElement.volume = 0
913
- this.savedVolume = this.currentVolume
914
- this.volumeSlider.value = 0
915
- this.currentVolume = 0
916
- }
917
- if (!this.muted) {
918
- this.volumeSlider.value = this.savedVolume
919
- this.currentVolume = this.mediaElement.volume = this.volumeSlider.value
920
- }
921
- },
922
-
923
- /** @description Save the muted in the Store */
924
- setMutedState() {
925
- this.setMediaMuted(this.muted)
926
- },
927
- //Keyboard event methods
928
- /**
929
- * @description Method to prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
930
- * */
931
- keysPreventDefault(evt) {
932
- if (
933
- ['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(
934
- evt.key
935
- ) > -1
936
- ) {
937
- evt.preventDefault()
938
- }
939
- },
940
-
941
- /**
942
- * @description Method to handle the keyboard input to controle the media player
943
- *@summary Keyboard actions:
944
- * *** Left-Arrow/ Right-Arrow : move foward/Backward media by 5s
945
- * *** Up-Arrow/ Down-Arrow: Increase/Decrease media volume by 5%
946
- * *** c : show/ hide subtitle
947
- * *** f : enter/ exit fullscreen
948
- * *** k : play/ pause media
949
- * *** m : Mute/ Unmute volume
950
- * *** t : show transcript
951
- */
952
-
953
- handleMediaControls(e) {
954
- e = e.code
955
- this.showControls()
956
- switch (e) {
957
- case 'ArrowLeft':
958
- //If focus is on Volume, VolDown
959
- if (this.focusState.volumeSlider) {
960
- //VolDown
961
- this.updateVolumeLevel('volDOWN')
962
- break
963
- } else {
964
- //Go back 5s in media timeline
965
- this.cursorLeft()
966
- break
967
- }
968
-
969
- case 'ArrowRight':
970
- //If focus is on Volume, VolUp
971
- if (this.focusState.volumeSlider) {
972
- //VolUp
973
- this.updateVolumeLevel('volUP')
974
- break
975
- } else {
976
- //Move forward 5s in media timeline
977
- this.cursorRight()
978
- break
979
- }
980
-
981
- case 'ArrowUp':
982
- //If focus is on progressBar, cursorRight
983
- if (this.focusState.progressBar) {
984
- //Move forward 5s in media timeline
985
- this.cursorRight()
986
- break
987
- } else {
988
- //VolUp
989
- this.updateVolumeLevel('volUP')
990
- break
991
- }
992
-
993
- case 'ArrowDown':
994
- //If focus is on progressBar, cursorRight
995
- if (this.focusState.progressBar) {
996
- //Go back 5s in media timeline
997
- this.cursorLeft()
998
- break
999
- } else {
1000
- //Voldown
1001
- this.updateVolumeLevel('volDOWN')
1002
- break
1003
- }
1004
-
1005
- case 'KeyM':
1006
- this.toggleMute()
1007
- break
1008
-
1009
- case 'KeyF':
1010
- if (this.mediaToPlay.mType == 'audio') break
1011
- //Toggle fullScreen
1012
- this.toggleFullScreen()
1013
- break
1014
-
1015
- case 'KeyT':
1016
- if (this.mediaToPlay.mType == 'audio') break
1017
- //Toggle transcript view
1018
- this.toggleViewTranscript(e)
1019
- break
1020
-
1021
- case 'KeyC':
1022
- if (this.mediaToPlay.mType == 'audio') break
1023
- //Toggle subtitle view
1024
- this.toggleViewSubtitle()
1025
- break
1026
-
1027
- case 'KeyK':
1028
- //Play Pause on k bar pressed
1029
- if (this.mediaToPlay.mType == 'audio') this.togglePlay()
1030
- //Play/pause video with the playBackAnim if it is triggerd with KeyK
1031
- else this.handlePlayVideo('playBackAnim')
1032
- break
1033
- }
1034
- },
1035
- /**
1036
- * @description - Activate the label of a button include in btnsLabelDisplay on Hover/Focus on the button. Deactivate the label in all the others btns
1037
- */
1038
- activateLabel(elm) {
1039
- const id = elm.id
1040
- this.btnsLabelDisplay[id] = true
1041
- for (const property in this.btnsLabelDisplay) {
1042
- if (property !== id) {
1043
- this.btnsLabelDisplay[property] = false
1044
- }
1045
- }
1046
- },
1047
- /**
1048
- * @description - Get currenttime from media
1049
- */
1050
-
1051
- getCurrentTime() {
1052
- this.currentTime = this.mediaElement.currentTime
1053
- return this.mediaElement.currentTime
1054
- },
1055
- /**
1056
- * @description - controls to move the media timeline backward
1057
- */
1058
- cursorLeft() {
1059
- this.setMediaTime(this.getCurrentTime() - 5)
1060
- },
1061
- /**
1062
- * @description - control to move the media timeline forwrad
1063
- */
1064
- cursorRight() {
1065
- this.setMediaTime(this.getCurrentTime() + 5)
1066
- },
1067
- //ProgressBar Methods
1068
-
1069
- /**
1070
- * @description - Update currentTime and timing strings when media is playing
1071
- */
1072
- updateProgressBarTime(e) {
1073
- //Get currentTime from the event
1074
- this.currentTime = e.target.currentTime
1075
- //Update strings
1076
- this.setProgressBarA11Y()
1077
- //If replay is true and the progressIndicator is not at the end, set canReplay to false
1078
- if (this.canReplay) this.canReplay = false
1079
- //If replay is false and progressIndicator is at the end, set canReplay to true
1080
- if (
1081
- !this.canReplay &&
1082
- this.currentTime >= Math.floor(this.mediaDuration)
1083
- ) {
1084
- this.canReplay = true
1085
- }
1086
- },
1087
-
1088
- /**
1089
- * @description - Set mediatime depending on progressBar click
1090
- */
1091
- seekingProgress(e) {
1092
- if (!this.progressThumbDown && !this.progressThumbHover) {
1093
- const updatedTime =
1094
- (e.offsetX / this.progressBar.offsetWidth) * this.mediaDuration
1095
- this.setMediaTime(updatedTime)
1096
- }
1097
- },
1098
-
1099
- /**
1100
- * @description - Set mediatime depending on mousemove on the screen (when progress thumb is mousedown)
1101
- */
1102
- progressWindowMove(ev) {
1103
- if (this.progressThumbDown) {
1104
- if (this.isPlaying) this.togglePlay()
1105
- this.getTimeFromClickPos(ev)
1106
- let timeToSet = Math.floor(this.getTimeFromClickPos(ev))
1107
- if (timeToSet < 0) timeToSet = 0
1108
- else if (timeToSet > this.duration) timeToSet = this.duration
1109
- this.currentTime = timeToSet
1110
- }
1111
- },
1112
-
1113
- /**
1114
- * @description - Update thumb state (mouseup) when the mouse is up on the screen
1115
- */
1116
- progressWindowUp(ev) {
1117
- if (this.progressThumbDown) {
1118
- this.setMediaTime(this.currentTime)
1119
- this.progressThumbDown = false
1120
- //savedIsPlaying is the playing status saved on mousedown event on the thumb (the video is paused when moving on the progressBar)
1121
- if (this.isPlaying !== this.savedIsPlaying) this.togglePlay()
1122
- }
1123
- },
1124
- /**
1125
- * @description - Get the time corresponding of the position of the click on the progressBar
1126
- */
1127
- getTimeFromClickPos(ev) {
1128
- let timeToSet = 0
1129
- //La progress Bar
1130
- const progressBar = this.progressBar
1131
- //La position de la progressBar dans la page
1132
- const left = progressBar.getBoundingClientRect().left
1133
- //Position du click par rapport à la progressBar
1134
- let clickPos = ev.clientX - left
1135
- //Si la position est à gauche de la progressBar, retourner 0s
1136
- if (clickPos < 0) return 0
1137
- //Obtenir le width de la progressBar
1138
- const width = progressBar.offsetWidth
1139
- //Obtenir le ratio de la position du click vs width de la progressBar
1140
- let percentage = clickPos / width
1141
- //Si > que 1, veux dire que le clique est à droite de la progressBar (mettre le curseur à la fin)
1142
- percentage = percentage > 1 ? 1 : percentage
1143
- //Ratio * temps total du media donne la progressiob
1144
- timeToSet = percentage * this.mediaDuration
1145
- return timeToSet
1146
- },
1147
-
1148
- /** @description - methode to set A11Y support for screen reader on progress bar element (video/audio/) */
1149
- setProgressBarA11Y() {
1150
- const w = this.$i18n.locale === 'en' ? 'at' : 'à'
1151
- const dm =
1152
- this.$i18n.locale === 'en'
1153
- ? `${this.mediaToPlay.mType} duration`
1154
- : `durée ${this.mediaToPlay.mType}`
1155
-
1156
- this.mediaA11Y.valMax = Math.round(this.mediaElement.duration)
1157
- this.mediaA11Y.valNow = Math.round(this.currentTime)
1158
-
1159
- //format text output for screen reader
1160
- let currentTimeArray = this.timecode.split(':')
1161
- let fullTimeArray = this.mediaDurationTime.split(':')
1162
- let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
1163
- const mediaTitle = this.mediaToPlay.mTitle || this.mediaToPlay.mType
1164
- this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
1165
-
1166
- //format string to remove leading 0 digit for time
1167
- const formatNum = (n) => {
1168
- return n[0] == '0' ? n.substring(1).trim() : n.trim()
1169
- }
1170
-
1171
- const formatMsg = (x, y) => {
1172
- const reg = /%x|%y/g
1173
- let formatStr = this.$t('a11y_sr.range_expression').replace(
1174
- reg,
1175
- (e) => {
1176
- if (e == '%x') return x
1177
- if (e == '%y') return y
1178
- }
1179
- )
1180
-
1181
- return formatStr
1182
- }
1183
-
1184
- switch (fullTimeArray.length) {
1185
- case 3:
1186
- if (!currentTimeArray.length) return
1187
- //format current hours text text with plurialization apply
1188
- hrsTxt =
1189
- currentTimeArray.length == 3 && parseInt(currentTimeArray[0]) > 0
1190
- ? `${formatNum(currentTimeArray[0])} ${this.$tc(
1191
- 'a11y_sr.time.hours',
1192
- parseInt(formatNum(currentTimeArray[0]))
1193
- )}`
1194
- : ''
1195
-
1196
- //format minutes text text with plurialization apply
1197
- mnTxt =
1198
- parseInt(currentTimeArray[1]) > 0
1199
- ? `${formatNum(currentTimeArray[1])} ${this.$tc(
1200
- 'a11y_sr.time.minute',
1201
- parseInt(formatNum(currentTimeArray[1]))
1202
- )}`
1203
- : ''
1204
-
1205
- //format seconds text with plurialization apply
1206
- ssTxt =
1207
- parseInt(currentTimeArray[2]) > 0
1208
- ? `${formatNum(currentTimeArray[2])} ${this.$tc(
1209
- 'a11y_sr.time.second',
1210
- parseInt(formatNum(currentTimeArray[2]))
1211
- )}`
1212
- : `0 ${this.$tc('a11y_sr.time.second')}`
1213
-
1214
- HRSTxt =
1215
- parseInt(fullTimeArray[0]) > 0
1216
- ? `${formatNum(fullTimeArray[0])} ${this.$tc(
1217
- 'a11y_sr.time.hours',
1218
- parseInt(formatNum(fullTimeArray[0]))
1219
- )}`
1220
- : ''
1221
-
1222
- MNTxt =
1223
- parseInt(fullTimeArray[1]) > 0
1224
- ? `${formatNum(fullTimeArray[1])} ${this.$tc(
1225
- 'a11y_sr.time.minute',
1226
- parseInt(formatNum(fullTimeArray[1]))
1227
- )}`
1228
- : ''
1229
-
1230
- SSTxt =
1231
- parseInt(fullTimeArray[2]) > 0
1232
- ? `${formatNum(fullTimeArray[2])} ${this.$tc(
1233
- 'a11y_sr.time.second',
1234
- parseInt(formatNum(fullTimeArray[2]))
1235
- )}`
1236
- : ''
1237
-
1238
- cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
1239
- fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
1240
-
1241
- //Format to show 0 when no value is present
1242
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1243
- // Set sr text for timeCode && duration
1244
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1245
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1246
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
1247
-
1248
- break
1249
- case 2:
1250
- if (!currentTimeArray.length) return
1251
-
1252
- //format current time text
1253
- mnTxt =
1254
- parseInt(currentTimeArray[0]) > 0
1255
- ? `${formatNum(currentTimeArray[0])} ${this.$tc(
1256
- 'a11y_sr.time.minute',
1257
- parseInt(formatNum(currentTimeArray[0]))
1258
- )}`
1259
- : ''
1260
-
1261
- ssTxt =
1262
- parseInt(currentTimeArray[1]) > 0
1263
- ? `${formatNum(currentTimeArray[1])} ${this.$tc(
1264
- 'a11y_sr.time.second',
1265
- parseInt(formatNum(currentTimeArray[1]))
1266
- )}`
1267
- : `0 ${this.$tc('a11y_sr.time.second')}`
1268
-
1269
- // format full time text
1270
- MNTxt =
1271
- parseInt(fullTimeArray[0]) > 0
1272
- ? `${formatNum(fullTimeArray[0])} ${this.$tc(
1273
- 'a11y_sr.time.minute',
1274
- parseInt(formatNum(fullTimeArray[0]))
1275
- )}`
1276
- : ''
1277
- SSTxt =
1278
- parseInt(fullTimeArray[1]) > 0
1279
- ? `${formatNum(fullTimeArray[1])} ${this.$tc(
1280
- 'a11y_sr.time.second',
1281
- parseInt(formatNum(fullTimeArray[1]))
1282
- )}`
1283
- : ''
1284
-
1285
- cTimeTxt = `${mnTxt} ${ssTxt}`
1286
- fTimeTxt = `${MNTxt} ${SSTxt}`
1287
-
1288
- //Format to show 0 when no value is present
1289
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1290
- // Set sr text for timeCode && duration
1291
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1292
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1293
-
1294
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
1295
-
1296
- break
1297
- }
1298
- },
1299
- /**
1300
- * @description - Deactivate the label of a button include in btnsLabelDisplay on mouseout/blur of this button
1301
- */
1302
- deactivateLabel(elm) {
1303
- const id = elm.id
1304
- this.btnsLabelDisplay[id] = false
1305
- },
1306
- /**
1307
- * @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)
1308
- */
1309
- changeFocusState(string, bool) {
1310
- this.focusState[string] = bool
1311
- },
1312
- /**
1313
- * @description - handle initial volume level
1314
- */
1315
- setMediaVolume() {
1316
- //Set media muted or with the savedVolume
1317
- this.muted = this.mediaMuted
1318
- if (!this.muted)
1319
- return (this.currentVolume = this.savedVolume = this.mediaVolume)
1320
- this.savedVolume = this.mediaVolume
1321
- this.currentVolume = 0
1322
- },
1323
- //======================================================================
1324
- /**
1325
- * @description - Set the DOM elements specific for video
1326
- */
1327
- initializeVideoElm() {
1328
- //SeekTooltip
1329
- this.seekTooltip = this.$refs['$seek-tooltip']
1330
- //Transcript
1331
- this.$bus.$on('transcript-hidden', this.resetTranscript)
1332
- this.setTranscript()
1333
- },
1334
-
1335
- /** "@description -update the information of the seek tooltip*/
1336
- updateSeekTooltip(evt) {
1337
- //Elm progress Bar
1338
- const progressBar = this.progressBar
1339
- //progressBar position on the page
1340
- const left = progressBar.getBoundingClientRect().left
1341
- //Mouse position versus progressBar
1342
- const hoverPos = evt.clientX - left
1343
- //Get progressBar offsetWidth
1344
- const width = progressBar.offsetWidth
1345
-
1346
- //Define the value of the skip tooltip position
1347
- let offsetX = null
1348
-
1349
- switch (true) {
1350
- case hoverPos < 15:
1351
- offsetX = 20
1352
- break
1353
- case hoverPos > width - 55:
1354
- offsetX = width - 55
1355
- break
1356
- default:
1357
- offsetX = hoverPos
1358
- }
1359
-
1360
- this.seekTooltip.style.left = `${offsetX}px` // update visual position of tooltip
1361
-
1362
- //Get % of the cursor hover position/progressBar width
1363
- let percentage = 0
1364
- if (hoverPos > width) percentage = 100
1365
- if (hoverPos < 0) percentage = 0
1366
- else {
1367
- percentage = hoverPos / width
1368
- }
1369
- let skipTo = percentage * this.mediaDuration
1370
- //Update the Tooltip time
1371
- this.tooTipTimeCode = this.$helper.formatTime(skipTo)
1372
- },
1373
- /**
1374
- * @description - handle all the listeners for videos
1375
- */
1376
- setVideoHandlers() {
1377
- //Tooltip
1378
- this.progressArea.addEventListener('mousemove', this.updateSeekTooltip)
1379
- //Fullscreen
1380
- this.mediaContainer.addEventListener(
1381
- 'fullscreenchange',
1382
- this.updateFullScreenState
1383
- )
1384
- //Show controls
1385
- this.mediaContainer.addEventListener('mousemove', this.showControls)
1386
- },
1387
- /**
1388
- * @description - Unset all the listeners (video)
1389
- */
1390
- removeVideoHandlers() {
1391
- //Tooltip
1392
- this.progressArea.removeEventListener('mousemove', this.updateSeekTooltip)
1393
- //Fullscreen
1394
- this.mediaContainer.removeEventListener(
1395
- 'fullscreenchange',
1396
- this.updateFullScreenState
1397
- )
1398
- //Show controls
1399
- this.mediaContainer.removeEventListener('mousemove', this.showControls)
1400
- },
1401
- /**
1402
- /* @description - handle the play /pause of the media and the playback video animation
1403
- */
1404
- handlePlayVideo(type = null) {
1405
- //Play/pause
1406
- this.togglePlay()
1407
- //Playbar animation (show/hide)
1408
- this.isPlaying ? this.hideControls() : this.showControls()
1409
-
1410
- //Playback animation (only the first play click)
1411
- if (this.playClicked && !type) {
1412
- this.playBackAnim = false
1413
- return
1414
- }
1415
- //this.runPlaybackAnimation()
1416
- this.playBackAnim = true
1417
- this.playClicked = true
1418
- // Should indicate with media is playing and set it in the store
1419
- },
1420
- /**
1421
- * @description - Toggle the state of the screen
1422
- * Set or unset the media in full screen
1423
- */
1424
- toggleFullScreen() {
1425
- const fullscreenElement = this.mediaContainer
1426
- if (document.fullscreenElement) {
1427
- // exitFullscreen is only available on the Document object.
1428
- document.exitFullscreen()
1429
- } else {
1430
- //fullscreen is available on Element.
1431
- fullscreenElement.requestFullscreen()
1432
- }
1433
- },
1434
- /**
1435
- * @description update the value of fulls screen state
1436
- */
1437
- updateFullScreenState() {
1438
- this.fullscreenOn = !this.fullscreenOn
1439
- },
1440
-
1441
- fireFoxMoveCC() {
1442
- if (this.firefoxTrack.activeCues[0]) {
1443
- this.firefoxTrack.activeCues[0].line = 12
1444
- }
1445
- },
1446
- /**
1447
- * @description show the subtitle
1448
- */
1449
- showSubtitles() {
1450
- this.mediaElement.textTracks[0].mode = 'showing'
1451
- this.setMediaSubtitles(true)
1452
- if (this.getCurrentBrowser == 'Firefox') {
1453
- let video = document.getElementsByTagName('video')[0]
1454
- let tracks = video.textTracks
1455
- this.firefoxTrack = tracks[0]
1456
-
1457
- this.firefoxTrack.addEventListener('cuechange', this.fireFoxMoveCC)
1458
- }
1459
- },
1460
-
1461
- hideSubtitles() {
1462
- this.mediaElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
1463
- this.setMediaSubtitles(false)
1464
- },
1465
- toggleViewSubtitle() {
1466
- if (this.subtitlesEnabled) return this.hideSubtitles()
1467
- if (!this.subtitlesEnabled) return this.showSubtitles()
1468
- },
1469
- /**
1470
- * @description method to show or hide the transcipt.
1471
- * @summary get toggle the value of transcriptEnabled and get content from the transcript file
1472
- * and transfer it to Module for display.
1473
- * @fires 'open-sidebar' to AppBaseModule
1474
- * @fires 'close-sidebar' to AppBaseModule
1475
- */
1476
- toggleViewTranscript(event) {
1477
- this.transcriptEnabled = !this.transcriptEnabled
1478
- //When the function is called after a user interaction (mouse or keyboard), emit event
1479
- if (event) {
1480
- //emit transcript toggle event, listener in AppBasePage
1481
- this.$bus.$emit(
1482
- 'video-transcript-toggle',
1483
- this.mediaToPlay,
1484
- this.transcriptEnabled
1485
- )
1486
- }
1487
-
1488
- if (this.transcriptEnabled && this.transcriptToShow) {
1489
- //this.$bus.$emit('resize-media', 'sm')
1490
- //Resize video container
1491
- this.$emit('resize-video', 'sm')
1492
- //Open sidebar with the transcript
1493
- return this.$bus.$emit('open-sidebar', {
1494
- ctx: 'ctxTranscript',
1495
- e: this.transcriptToShow,
1496
- w: this.pbContainer
1497
- })
1498
- }
1499
-
1500
- // Send close signal for the side bar when transcipt state is not enabled
1501
- if (!this.transcriptEnabled) {
1502
- //this.$bus.$emit('resize-media', 'lg')
1503
- //Resize video container
1504
- this.$emit('resize-video', 'lg')
1505
- //Open sidebar with the transcript
1506
- return this.$bus.$emit('close-sidebar', 'ctxTranscript')
1507
- }
1508
- },
1509
- /**
1510
- * @description Fetching method for transcript
1511
- * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
1512
- * and return its content for display
1513
- * @return {String} HTML string
1514
- */
1515
- async fetchTranscript() {
1516
- try {
1517
- //const tFile = 'exemple_transcript2.html'
1518
- const { mTranscript } = this.mediaToPlay
1519
- const tFile = mTranscript
1520
- if (!tFile) throw new Error('Missing transcript File!')
1521
-
1522
- // validate file types. Only .html are allowed
1523
- if (!tFile.endsWith('.html'))
1524
- throw new Error(
1525
- 'Invalid valid transcript file. \n Expecting .html file'
1526
- )
1527
-
1528
- const fileUrl = !tFile.includes('/') ? `./${tFile}` : tFile //allow passing file in Public folder
1529
- // const fileUrl = new URL(tFile, import.meta.url))
1530
- const res = await axios.get(fileUrl, { responseType: 'blob' })
1531
- const content = await res.data.text()
1532
-
1533
- return content
1534
- } catch (err) {
1535
- console.warn("YOU'VE GOT AN ERROR!\n", err)
1536
- }
1537
- },
1538
- /** ******************* Rendering Methods****************************** */
1539
-
1540
- /**
1541
- * @Description Method to set the transcription that need to be displayed
1542
- */
1543
- async setTranscript() {
1544
- if (!this.hasTranscript) return (this.transcriptToShow = null)
1545
-
1546
- this.transcriptToShow = await this.fetchTranscript()
1547
- },
1548
-
1549
- /**
1550
- * @description Method to reset the transcript state
1551
- */
1552
- resetTranscript(content) {
1553
- this.transcriptEnabled = false
1554
- this.otherVideoTranscriptShown = false
1555
- },
1556
-
1557
- /**
1558
- * @description Show the media controler */
1559
- showControls() {
1560
- this.showControlsValue = true
1561
- if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
1562
-
1563
- this.hideControls()
1564
- },
1565
- /**
1566
- * @description Hide the media after the video start playing
1567
- */
1568
-
1569
- hideControls() {
1570
- if (this.mediaElement.paused) return
1571
-
1572
- this.hideTimer = setTimeout(() => {
1573
- this.showControlsValue = false
1574
- }, this.delayUntilHide)
1575
- }
1576
- }
1577
- }
1578
- </script>
1579
- <style lang="scss" scoped>
1580
- .pb-container.video {
1581
- position: absolute;
1582
- height: 100%;
1583
- width: 100%;
1584
- justify-content: center;
1585
- display: flex;
1586
- }
1587
-
1588
- .playback-button {
1589
- position: absolute;
1590
- top: 0;
1591
- left: 0;
1592
- width: 100%;
1593
- height: 100%;
1594
- display: flex;
1595
- flex-flow: column wrap;
1596
- justify-content: center;
1597
- align-items: center;
1598
- background-color: transparent;
1599
- .playback-wrapper {
1600
- height: 64px;
1601
- width: 64px;
1602
- display: flex;
1603
- justify-content: center;
1604
- align-items: center;
1605
- border-radius: 50%;
1606
-
1607
- svg {
1608
- height: 32px;
1609
- width: 26.29px;
1610
- &.play-icon {
1611
- margin-left: 11%;
1612
- }
1613
- }
1614
- }
1615
- }
1616
- //Wrapper
1617
- .pb-wrapper {
1618
- width: 100%;
1619
- display: flex;
1620
- flex-direction: column;
1621
- position: absolute;
1622
- bottom: 0;
1623
- opacity: 0;
1624
- z-index: 2;
1625
- &.show-controls {
1626
- opacity: 1;
1627
- transition: opacity 0.35s ease-in-out;
1628
- }
1629
-
1630
- //Wrapper timeline
1631
- .pb-timeline {
1632
- --progress-bar-height: 6px;
1633
- position: relative;
1634
- width: 100%;
1635
- height: var(--progress-bar-height);
1636
- .progress-area {
1637
- width: 100%;
1638
- height: 400%; //Add contact surface of click on progressBar
1639
- position: relative;
1640
- top: -150%;
1641
- display: flex;
1642
- cursor: pointer;
1643
- align-items: center;
1644
- z-index: 8; //z-index on top of the video elm
1645
- &:hover {
1646
- .seek-tooltip {
1647
- opacity: 1;
1648
- }
1649
- }
1650
-
1651
- //Progress bar
1652
- .pb-progress-bar {
1653
- --progress-bar-border-height: 1px;
1654
- cursor: pointer;
1655
- width: 100%;
1656
- height: var(--progress-bar-height);
1657
- //background-color: transparent;
1658
- overflow: visible;
1659
- &:focus {
1660
- .progress-thumb {
1661
- }
1662
- }
1663
- }
1664
-
1665
- .progress-indicator {
1666
- width: 0;
1667
- height: 100%;
1668
- position: relative;
1669
- user-select: none;
1670
-
1671
- &.progress-animation {
1672
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1673
- transition-duration: 0.01s;
1674
- transition-property: all;
1675
- transition: width 0.1s linear;
1676
- }
1677
- }
1678
- //Custom thumb
1679
- .progress-thumb {
1680
- //Cliquable thumb
1681
- --progress-thumb-size: 30px;
1682
- width: var(--progress-thumb-size);
1683
- height: var(--progress-thumb-size);
1684
- position: absolute;
1685
-
1686
- right: calc(var(--progress-thumb-size) / -2);
1687
- top: calc(
1688
- -1 * (
1689
- var(--progress-thumb-size) / 2 - var(--progress-bar-height) / 2
1690
- ) - var(--progress-bar-border-height) / 2
1691
- );
1692
- display: flex;
1693
- align-items: center;
1694
- justify-content: center;
1695
- &::after {
1696
- content: '';
1697
- //Visible thumb
1698
- height: 16px;
1699
- width: 16px;
1700
- //Colors and styling
1701
- border-radius: 50%;
1702
- //background-color: var(--primary700);
1703
- }
1704
- }
1705
-
1706
- &:hover {
1707
- .seek-tooltip {
1708
- //tooltip visible only when hovering progress-area
1709
- opacity: 1;
1710
- }
1711
- }
1712
-
1713
- .seek-tooltip {
1714
- position: absolute;
1715
- top: -30px;
1716
- opacity: 0;
1717
- user-select: none;
1718
- border-radius: 1px;
1719
- padding: 2px;
1720
- }
1721
- }
1722
- }
1723
-
1724
- //Controls
1725
- .pb-controls {
1726
- padding: 12px 32px;
1727
- @media screen and (max-width: 800px) {
1728
- padding: 12px 12px;
1729
- }
1730
- & {
1731
- width: 100%;
1732
- //height: 76px;
1733
- height: auto;
1734
- display: flex;
1735
- flex-flow: row wrap;
1736
- justify-content: space-between;
1737
- -webkit-justify-content: space-between;
1738
- align-items: center;
1739
- }
1740
- .pb-areas {
1741
- display: flex;
1742
- flex-flow: row nowrap;
1743
- justify-content: center;
1744
- align-items: center;
1745
-
1746
- &.right {
1747
- .btn {
1748
- margin-right: 24px;
1749
-
1750
- &:last-child {
1751
- margin-right: 0;
1752
- }
1753
- }
1754
- }
1755
-
1756
- .btn {
1757
- position: relative;
1758
-
1759
- &::before {
1760
- content: attr(data-title);
1761
- position: absolute;
1762
- top: -50px;
1763
- right: 0;
1764
- margin-left: -35px;
1765
- word-break: keep-all;
1766
- white-space: pre;
1767
- display: none;
1768
- opacity: 0;
1769
- width: fit-content;
1770
- border-radius: 1px;
1771
- padding: 2px;
1772
- }
1773
- //Icons btn specific
1774
- &.playbar-btn {
1775
- &::before {
1776
- left: 0 !important;
1777
- margin-left: 0 !important;
1778
- }
1779
- & {
1780
- margin-right: 16px;
1781
- border-radius: 50%;
1782
- }
1783
- &:hover {
1784
- &::before {
1785
- display: block;
1786
- opacity: 1;
1787
- }
1788
- }
1789
- @media screen and (max-width: 800px) {
1790
- height: 32px;
1791
- width: 32px;
1792
- }
1793
- svg.play-icon {
1794
- margin-left: 11.875%; // 11.8% off center on the design
1795
- }
1796
- }
1797
- &.volume-btn {
1798
- margin: 0 20px;
1799
- svg.volume-low-icon {
1800
- height: 17.75px;
1801
- }
1802
- }
1803
- }
1804
- //Manage displayLabel (dynamic class)
1805
- button.displayLabel {
1806
- &:hover,
1807
- &:focus {
1808
- &::before {
1809
- display: block;
1810
- opacity: 1;
1811
- }
1812
- }
1813
- }
1814
-
1815
- .pb-timer {
1816
- width: max-content;
1817
- }
1818
- //Volume
1819
- .volume-controls {
1820
- --volumecontrols-height: 22px;
1821
- --volume-thumb-size: 16px;
1822
- display: flex;
1823
- flex-flow: row wrap;
1824
- justify-content: center;
1825
- position: relative;
1826
- height: var(--volumecontrols-height);
1827
- &:hover {
1828
- //button title //DisplayLabel is a dynamic class
1829
- .volume-slider.displayLabel + span {
1830
- display: block;
1831
- opacity: 1;
1832
- }
1833
- }
1834
- .volume-slider {
1835
- -webkit-appearance: none;
1836
- width: 100px;
1837
- //Responsive
1838
- @media screen and (max-width: 800px) {
1839
- width: 80px;
1840
- }
1841
- & {
1842
- border-radius: 24px;
1843
- cursor: pointer;
1844
- position: relative;
1845
- z-index: 2;
1846
- }
1847
- &::-webkit-slider-runnable-track {
1848
- background: transparent;
1849
- }
1850
- //Input slider thumb
1851
- @mixin volume-thumb {
1852
- -webkit-appearance: none;
1853
- width: var(--volume-thumb-size);
1854
- height: var(--volume-thumb-size);
1855
- margin-top: 12px;
1856
- border-radius: 50%;
1857
- position: relative;
1858
- cursor: pointer;
1859
- }
1860
- &::-webkit-slider-thumb {
1861
- @include volume-thumb;
1862
- }
1863
- //button title
1864
- & + span {
1865
- position: absolute;
1866
- top: -62px;
1867
- right: 0;
1868
- margin-left: -35px;
1869
- word-break: keep-all;
1870
- white-space: pre;
1871
- display: none;
1872
- opacity: 0;
1873
- user-select: none;
1874
- //Colors
1875
- //background-color: var(--primary100);
1876
- //color: var(--primary700);
1877
- border-radius: 1px;
1878
- padding: 2px;
1879
- }
1880
- //DisplayLabel is a dynamic class
1881
- &.displayLabel:focus {
1882
- & + span {
1883
- display: block;
1884
- opacity: 1;
1885
- }
1886
- }
1887
- }
1888
-
1889
- .volume-progress {
1890
- --volumeIndicator-height: 9px;
1891
- height: var(--volumeIndicator-height);
1892
- top: calc(
1893
- var(--volumecontrols-height) / 2 - var(--volumeIndicator-height) / 2
1894
- ); //centrer en hauteur
1895
- width: calc(
1896
- 100% - var(--volume-thumb-size) / 2
1897
- ); //Width smaller than the range width to be sure the progress width don't exceed the range width
1898
- position: absolute;
1899
- border-radius: 20px;
1900
- background-repeat: no-repeat;
1901
- background-size: var(--background-size-vs, 0%) 100%; //Js dynamic variable
1902
- //colors
1903
- }
1904
- }
1905
- }
1906
- }
1907
-
1908
- svg {
1909
- //width: 19px;
1910
- //height: 19px;
1911
- //colors
1912
- //stroke: var(--primary700);
1913
- }
1914
-
1915
- button {
1916
- height: 48px;
1917
- width: 48px;
1918
- background-color: transparent;
1919
- padding: 0;
1920
- display: flex;
1921
- justify-content: center;
1922
- align-items: center;
1923
- svg {
1924
- height: 24px;
1925
- @media screen and (max-width: 800px) {
1926
- height: 18px;
1927
- }
1928
- }
1929
- }
1930
- }
1931
-
1932
- //CSS for audio only
1933
- .audio.pb-wrapper {
1934
- border-radius: 4px;
1935
- position: initial;
1936
- .pb-controls {
1937
- padding: 8px 16px;
1938
- .volume-controls {
1939
- margin-left: 8px;
1940
- width: 100%;
1941
- --volume-thumb-size: 12px;
1942
- .volume-slider {
1943
- width: 50px;
1944
- }
1945
- .volume-progress {
1946
- --volumeIndicator-height: 6px;
1947
- }
1948
- }
1949
- }
1950
- .pb-timer {
1951
- margin-right: 20px;
1952
- }
1953
- .pb-timeline {
1954
- .progress-area {
1955
- justify-content: center;
1956
- }
1957
- .pb-progress-bar {
1958
- border-radius: 4px;
1959
- //max-width: 90%;
1960
- }
1961
- }
1962
- .volume-btn {
1963
- margin-left: 16px;
1964
- }
1965
- .pb-areas {
1966
- width: 100%;
1967
- display: grid;
1968
- grid-template-columns:
1969
- min-content
1970
- min-content
1971
- 1fr
1972
- min-content
1973
- min-content;
1974
- align-items: center;
1975
- justify-content: center;
1976
- button {
1977
- height: 24px;
1978
- width: 24px;
1979
- &.playbar-btn {
1980
- margin-right: 8px;
1981
- }
1982
- svg {
1983
- height: 12.25px;
1984
- @media screen and (max-width: 800px) {
1985
- height: 18px;
1986
- }
1987
- }
1988
- &.volume-btn {
1989
- svg {
1990
- height: 16px;
1991
- }
1992
- svg.volume-low-icon {
1993
- height: 14px;
1994
- }
1995
- }
1996
- }
1997
- }
1998
- }
1999
- //Playback button video (to finish)
2000
- .playback-button {
2001
- .playback-wrapper {
2002
- opacity: 0;
2003
- }
2004
- &.initial {
2005
- .playback-wrapper {
2006
- animation-name: initial;
2007
- opacity: 1;
2008
- }
2009
- }
2010
- &.playback-animation {
2011
- .playback-wrapper {
2012
- opacity: 0;
2013
- animation-name: handlePlayBack;
2014
- animation-duration: 0.8s;
2015
- animation-timing-function: ease-in-out;
2016
- }
2017
- }
2018
- }
2019
-
2020
- .audio-media-player {
2021
- .pb-areas {
2022
- .btn {
2023
- &::before {
2024
- top: inherit !important;
2025
- bottom: -50px;
2026
- }
2027
- }
2028
- }
2029
- }
2030
-
2031
- @keyframes handlePlayBack {
2032
- 0% {
2033
- opacity: 0;
2034
- }
2035
- 50% {
2036
- opacity: 1;
2037
- transform: scale(0.95);
2038
- }
2039
- 100% {
2040
- opacity: 0;
2041
- }
2042
- }
2043
-
2044
- @keyframes disappear {
2045
- 0% {
2046
- opacity: 1;
2047
- }
2048
- 100% {
2049
- opacity: 0;
2050
- }
2051
- }
2052
- </style>
1
+ <!--@ Description: This component is used to display the playbar associate with the videoPlayer
2
+ @ What it does: The component is used to interacted with the videoPlayer or audioPlayer via buttons.
3
+ mediaMixins is used for all the methods/data shared between audio and video. In the appCompPlayBar component, there is also all the methods/data specific to video-->
4
+ <template>
5
+ <div
6
+ ref="$pb-container"
7
+ class="pb-container"
8
+ :class="{ video: mediaToPlay.mType === 'video' }"
9
+ >
10
+ <!--------------------------------- Btn Play Principal (video only) -------------------------------------->
11
+ <app-base-button
12
+ v-if="mediaToPlay.mType === 'video'"
13
+ ref="$btn-play-playback"
14
+ class="playback-button"
15
+ :class="{
16
+ initial: !playClicked,
17
+ 'playback-animation': playBackAnim && playClicked
18
+ }"
19
+ :aria-label="playLabel + ' principal'"
20
+ :data-title="`${playLabel}`"
21
+ @click="
22
+ () => {
23
+ mediaToPlay.mType === 'video'
24
+ ? handlePlayVideo('playBackAnim')
25
+ : togglePlay()
26
+ }
27
+ "
28
+ >
29
+ <div
30
+ v-show="(!playClicked && !canReplay) || (!canReplay && isPlaying)"
31
+ class="playback-wrapper"
32
+ >
33
+ <svg class="app-icons-svg play-icon">
34
+ <use href="#play-icon" />
35
+ </svg>
36
+ </div>
37
+ <div
38
+ v-show="playClicked && !isPlaying && !canReplay"
39
+ class="playback-wrapper"
40
+ >
41
+ <svg class="app-icons-svg pause-icon">
42
+ <use href="#pause-icon" />
43
+ </svg>
44
+ </div>
45
+ <div v-show="!playClicked && canReplay" class="playback-wrapper">
46
+ <svg class="app-icons-svg replay-icon">
47
+ <use href="#replay-icon" />
48
+ </svg>
49
+ </div>
50
+ </app-base-button>
51
+
52
+ <div
53
+ v-if="mediaToPlay"
54
+ class="pb-wrapper"
55
+ :class="{
56
+ 'show-controls': showControlsValue || mediaToPlay.mType === 'audio',
57
+ audio: mediaToPlay.mType === 'audio',
58
+ video: mediaToPlay.mType === 'video'
59
+ }"
60
+ >
61
+ <!--------------------------------- PLAY-BAR Progress (video only) -------------------------------------->
62
+ <div
63
+ v-if="mediaToPlay.mType === 'video'"
64
+ ref="$playbar-timeline"
65
+ class="pb-timeline"
66
+ >
67
+ <div ref="$progress-area" class="progress-area">
68
+ <div
69
+ ref="$progress-bar"
70
+ draggable="false"
71
+ tabindex="0"
72
+ class="pb-progress-bar"
73
+ role="slider"
74
+ aria-valuemin="0"
75
+ :aria-label="mediaA11Y.label"
76
+ :aria-valuenow="mediaA11Y.valNow"
77
+ :aria-valuemax="mediaA11Y.valMax"
78
+ :aria-valuetext="mediaA11Y.valueText"
79
+ @focus="changeFocusState('progressBar', true)"
80
+ @blur="changeFocusState('progressBar', false)"
81
+ >
82
+ <!--Class progress-animation is apply when we are not using the thumb to change the progression-->
83
+ <div
84
+ ref="$progress-indicator"
85
+ draggable="false"
86
+ class="progress-indicator"
87
+ :class="{ 'progress-animation': !progressThumbDown }"
88
+ :style="{ width: progressBarPercentage + '%' }"
89
+ >
90
+ <!--Mousedown validate if the thumb is cliqued (if yes, the mouse is follow to modify the progress)
91
+ MouseOver/MouseOut deactivate click event on progressBar if the mouse hovered the thumb-->
92
+ <div
93
+ ref="$progress-thumb"
94
+ draggable="false"
95
+ class="progress-thumb"
96
+ style="z-index: 9"
97
+ @mousedown="
98
+ function () {
99
+ savedIsPlaying = isPlaying
100
+ progressThumbDown = true
101
+ }
102
+ "
103
+ @mouseover="progressThumbHover = true"
104
+ @mouseleave="progressThumbHover = false"
105
+ ></div>
106
+ </div>
107
+ </div>
108
+ <!--------------------------------- PLAY-BAR ToolTip (video only) ----------------------------------->
109
+ <div
110
+ v-if="mediaToPlay.mType === 'video'"
111
+ ref="$seek-tooltip"
112
+ aria-hidden="true"
113
+ class="seek-tooltip"
114
+ >
115
+ {{ tooTipTimeCode }}
116
+ </div>
117
+ </div>
118
+ </div>
119
+ <div class="pb-controls">
120
+ <div class="pb-areas left">
121
+ <!--------------------------------- PLAY-BAR Play ------------------------------------>
122
+ <app-base-button
123
+ ref="$btn-play"
124
+ class="playbar-btn playbar-play"
125
+ :class="{ displayLabel: btnsLabelDisplay['playbar-play'] === true }"
126
+ :aria-label="
127
+ playLabel + (mediaToPlay.mType === 'video' ? ' secondaire' : '')
128
+ "
129
+ :data-title="`${playLabel} (k)`"
130
+ @click="
131
+ () => {
132
+ mediaToPlay.mType === 'video' ? handlePlayVideo() : togglePlay()
133
+ }
134
+ "
135
+ @mouseenter="activateLabel($event)"
136
+ @mouseleave="deactivateLabel($event)"
137
+ @focus="activateLabel($event)"
138
+ @blur="deactivateLabel($event)"
139
+ >
140
+ <svg
141
+ v-show="!canReplay && !isPlaying"
142
+ class="app-icons-svg play-icon"
143
+ >
144
+ <use href="#play-icon" />
145
+ </svg>
146
+ <svg
147
+ v-show="isPlaying && !canReplay"
148
+ class="app-icons-svg pause-icon"
149
+ >
150
+ <use href="#pause-icon" />
151
+ </svg>
152
+ <svg v-show="canReplay" class="app-icons-svg replay-icon">
153
+ <use href="#replay-icon" />
154
+ </svg>
155
+ </app-base-button>
156
+ <!--------------------------------- PLAY-BAR Timer ------------------------------------>
157
+ <div class="pb-timer">
158
+ <span
159
+ aria-hidden="true"
160
+ style="-webkit-user-select: none"
161
+ draggable="false"
162
+ >
163
+ {{ timecode }} / {{ mediaDurationTime }}
164
+ </span>
165
+ </div>
166
+ <!--------------------------------- PLAY-BAR Progress (audio only) -------------------------------------->
167
+ <div
168
+ v-if="mediaToPlay.mType === 'audio'"
169
+ ref="$playbar-timeline"
170
+ class="pb-timeline"
171
+ >
172
+ <div ref="$progress-area" class="progress-area">
173
+ <div
174
+ id="progress-bar"
175
+ ref="$progress-bar"
176
+ draggable="false"
177
+ tabindex="0"
178
+ class="pb-progress-bar"
179
+ role="slider"
180
+ aria-valuemin="0"
181
+ :aria-label="mediaA11Y.label"
182
+ :aria-valuenow="mediaA11Y.valNow"
183
+ :aria-valuemax="mediaA11Y.valMax"
184
+ :aria-valuetext="mediaA11Y.valueText"
185
+ @focus="changeFocusState('progressBar', true)"
186
+ @blur="changeFocusState('progressBar', false)"
187
+ >
188
+ <!--Class progress-animation is apply when we are not using the thumb to change the progression-->
189
+ <div
190
+ id="progress-indicator"
191
+ ref="$progress-indicator"
192
+ draggable="false"
193
+ class="progress-indicator"
194
+ :class="{ 'progress-animation': !progressThumbDown }"
195
+ :style="{ width: progressBarPercentage + '%' }"
196
+ >
197
+ <!--Mousedown validate if the thumb is cliqued (if yes, the mouse is follow to modify the progress)
198
+ MouseOver/MouseOut deactivate click event on progressBar if the mouse hovered the thumb-->
199
+ <div
200
+ ref="$progress-thumb"
201
+ draggable="false"
202
+ class="progress-thumb"
203
+ style="z-index: 9"
204
+ @mousedown="
205
+ function () {
206
+ savedIsPlaying = isPlaying
207
+ progressThumbDown = true
208
+ }
209
+ "
210
+ @mouseover="progressThumbHover = true"
211
+ @mouseleave="progressThumbHover = false"
212
+ ></div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+ <!--------------------------------- PLAY-BAR Volume -------------------------------------->
218
+ <app-base-button
219
+ id="playbar-volume"
220
+ ref="$btn-volume"
221
+ class="volume-btn"
222
+ :class="{
223
+ displayLabel: btnsLabelDisplay['playbar-volume'] === true
224
+ }"
225
+ tabindex="0"
226
+ :aria-label="volumeLabel"
227
+ :data-title="`${volumeLabel} (m)`"
228
+ @click="toggleMute"
229
+ @mouseenter="activateLabel($event)"
230
+ @mouseleave="deactivateLabel($event)"
231
+ @focus="activateLabel($event)"
232
+ @blur="deactivateLabel($event)"
233
+ >
234
+ <svg v-show="volumeState == 'muted'" class="app-icons-svg">
235
+ <use href="#volume-mute-icon" />
236
+ </svg>
237
+ <svg v-show="volumeState == 'high'" class="app-icons-svg">
238
+ <use href="#volume-high-icon" />
239
+ </svg>
240
+ <svg
241
+ v-show="volumeState == 'low'"
242
+ class="app-icons-svg volume-low-icon"
243
+ >
244
+ <use href="#volume-low-icon" />
245
+ </svg>
246
+ </app-base-button>
247
+ <div ref="zone-volume" class="volume-controls">
248
+ <input
249
+ ref="$volume-slider"
250
+ class="volume-slider"
251
+ :class="{
252
+ displayLabel: btnsLabelDisplay['volume-slider'] === true
253
+ }"
254
+ :value="currentVolume"
255
+ :aria-valuetext="volumeLevelA11Y"
256
+ type="range"
257
+ max="1"
258
+ min="0"
259
+ step="0.01"
260
+ tabindex="0"
261
+ :aria-label="`${$t('button.volume')}`"
262
+ @click="updateVolumeLevel"
263
+ @mouseenter="activateLabel($event.currentTarget)"
264
+ @mouseleave="deactivateLabel($event.currentTarget)"
265
+ @focus="
266
+ activateLabel($event.currentTarget),
267
+ changeFocusState('volumeSlider', true)
268
+ "
269
+ @blur="
270
+ deactivateLabel($event.currentTarget),
271
+ changeFocusState('volumeSlider', false)
272
+ "
273
+ />
274
+ <span class="volume-label" aria-hidden="true">
275
+ {{ $t('button.volume') }}
276
+ </span>
277
+ <!--using span because ::before on input is not supported by firefox-->
278
+ <span
279
+ ref="$volume-progress"
280
+ class="volume-progress"
281
+ :style="{
282
+ '--background-size-vs': `calc(${volumeSliderBackground} - 2px)`
283
+ }"
284
+ ></span>
285
+ </div>
286
+ </div>
287
+ <!--------------------------------- PLAY-BAR CC (video only) ------------------------------------------>
288
+ <div v-if="mediaToPlay.mType === 'video'" class="pb-areas right">
289
+ <app-base-button
290
+ id="btn-subtitles"
291
+ ref="$btn-subtitle"
292
+ class="btn subtitleBtns"
293
+ :aria-label="ccLabel"
294
+ :data-title="`${ccLabel} (c)`"
295
+ :disabled="!hasSubtitle"
296
+ :class="{
297
+ md_disabled: !hasSubtitle,
298
+ displayLabel: btnsLabelDisplay['btn-subtitles'] === true
299
+ }"
300
+ @click="toggleViewSubtitle()"
301
+ @mouseenter="activateLabel($event)"
302
+ @mouseleave="deactivateLabel($event)"
303
+ @focus="activateLabel($event)"
304
+ @blur="deactivateLabel($event)"
305
+ >
306
+ <svg
307
+ v-show="(hasSubtitle && !subtitlesEnabled) || !hasSubtitle"
308
+ class="app-icons-svg"
309
+ >
310
+ <use href="#subtitle-off-icon" />
311
+ </svg>
312
+ <svg v-show="hasSubtitle && subtitlesEnabled" class="app-icons-svg">
313
+ <use href="#subtitle-on-icon" />
314
+ </svg>
315
+ </app-base-button>
316
+ <!--------------------------------- PLAY-BAR Transcript ----------------------------------->
317
+ <app-base-button
318
+ id="btn-transcript"
319
+ ref="$btn-transcript"
320
+ class="btn-transcript"
321
+ :aria-label="transcriptLabel"
322
+ :data-title="`${transcriptLabel} (t)`"
323
+ :disabled="transcriptBtnDisabled"
324
+ :class="{
325
+ md_disabled: transcriptBtnDisabled,
326
+ displayLabel: btnsLabelDisplay['btn-transcript'] === true
327
+ }"
328
+ @click="toggleViewTranscript($event)"
329
+ @mouseenter="activateLabel($event)"
330
+ @mouseleave="deactivateLabel($event)"
331
+ @focus="activateLabel($event)"
332
+ @blur="deactivateLabel($event)"
333
+ >
334
+ <svg
335
+ v-show="(hasTranscript && !transcriptEnabled) || !hasTranscript"
336
+ class="app-icons-svg"
337
+ >
338
+ <use href="#transcript-off-icon" />
339
+ </svg>
340
+ <svg
341
+ v-show="hasTranscript && transcriptEnabled"
342
+ class="app-icons-svg"
343
+ >
344
+ <use href="#transcript-on-icon" />
345
+ </svg>
346
+ </app-base-button>
347
+ <!--------------------------------- PLAY-BAR FullScreen ----------------------------------->
348
+ <app-base-button
349
+ id="btn-fullscreen"
350
+ ref="$btn-fullscreen"
351
+ class="fullscreenBtns"
352
+ :aria-label="fullscreenLabel"
353
+ :disabled="fullscreenBtnDisabled"
354
+ :class="{
355
+ md_disabled: fullscreenBtnDisabled,
356
+ displayLabel: btnsLabelDisplay['btn-fullscreen'] === true
357
+ }"
358
+ :data-title="`${fullscreenLabel} (f)`"
359
+ @click="toggleFullScreen()"
360
+ @mouseenter="activateLabel($event)"
361
+ @mouseleave="deactivateLabel($event)"
362
+ @focus="activateLabel($event)"
363
+ @blur="deactivateLabel($event)"
364
+ >
365
+ <svg v-show="!fullscreenOn" class="app-icons-svg">
366
+ <use href="#fullscreen-icon" />
367
+ </svg>
368
+ <svg v-show="fullscreenOn" class="app-icons-svg">
369
+ <use href="#fullscreen-exit-icon" />
370
+ </svg>
371
+ </app-base-button>
372
+ </div>
373
+ </div>
374
+ </div>
375
+ </div>
376
+ </template>
377
+
378
+ <script>
379
+ import { mapState, mapActions } from 'pinia'
380
+ import { useAppStore } from '../module/stores/appStore'
381
+ import axios from 'axios'
382
+
383
+ export default {
384
+ props: {
385
+ mediaToPlay: { type: [Object, Boolean], default: false }
386
+ },
387
+
388
+ emits: ['resize-video'],
389
+ setup() {
390
+ const store = useAppStore()
391
+ const { activityRef, id } = store.getCurrentPage
392
+ const { userInteraction } = store.getPageInteraction(activityRef, id)
393
+ return { store, userInteraction }
394
+ },
395
+ data() {
396
+ return {
397
+ id: `plyr_${this.mediaToPlay.id}`,
398
+ //Playback animation
399
+ playClicked: false,
400
+ playBackAnim: true,
401
+ //ProgressSeek
402
+ progressSeek: null,
403
+ //Tooltip
404
+ seekTooltip: null,
405
+ tooTipTimeCode: '00:00',
406
+ //Hiding playbar animation
407
+ focusTimeout: null,
408
+ delayUntilHide: 5000,
409
+ hideTimer: null,
410
+ //Transcript
411
+ transcriptEnabled: false,
412
+ transcriptToShow: null,
413
+ //Fullscreen
414
+ fullscreenOn: false,
415
+ //==========================================================
416
+ //Container Playbar
417
+ pbContainer: null,
418
+ //ShowControls (show the playbar or not, default is true)
419
+ showControlsValue: true,
420
+ //Player
421
+ isPlaying: false,
422
+ canReplay: false,
423
+ savedIsPlaying: false, //To get the playing status on mousemove on the progressBar
424
+ //ProgressBar
425
+ currentTime: 0,
426
+ progressArea: null,
427
+ progressBar: null,
428
+ progressIndicator: null,
429
+ progressThumb: null,
430
+ progressThumbDown: false,
431
+ progressThumbHover: false,
432
+ //Volume
433
+ volumeBtn: null,
434
+ volumeSlider: null,
435
+ volumeIndicator: null,
436
+ currentVolume: null,
437
+ savedVolume: 0.5, //Saved volume when muted
438
+ //muted
439
+ muted: false,
440
+
441
+ //Labels
442
+ //Strings for screenreaders (duration et time)
443
+ mediaA11Y: {
444
+ label: 'progress',
445
+ valMax: null,
446
+ valNow: null,
447
+ valueText: null,
448
+ timeCode: 0,
449
+ duration: 0
450
+ },
451
+ //buttons label (all the buttons that need labels management in the playbar)
452
+ btnsLabelDisplay: {
453
+ 'btn-subtitles': false,
454
+ 'btn-transcript': false,
455
+ 'btn-fullscreen': false,
456
+ 'playbar-play': false,
457
+ 'playbar-volume': false,
458
+ 'volume-slider': false
459
+ },
460
+ //FocusState to manage arrows keydown on the 2 sliders (progress and volume)
461
+ focusState: {
462
+ progressBar: false,
463
+ volumeSlider: false
464
+ },
465
+ otherVideoTranscriptShown: false
466
+ //==========================================================
467
+ }
468
+ },
469
+ computed: {
470
+ ...mapState(useAppStore, [
471
+ 'getCurrentBrowser',
472
+ 'getMediaSubtitles',
473
+ //=====================================================
474
+ 'getCurrentPage',
475
+ 'getModuleChildren',
476
+ 'hasMediaElOrTimeline',
477
+ 'getCurrentMediaDuration',
478
+ 'getDataFromServer',
479
+ 'getAutoplayEnabled',
480
+ 'getMediaVolume',
481
+ 'getModuleInfo',
482
+ 'getMediaPlaybarValues',
483
+ 'getPageInteraction',
484
+ 'getMediaMuted'
485
+ //=====================================================
486
+ ]),
487
+ //==========================================================
488
+ //MediaElement
489
+ mediaElement() {
490
+ if (!this.mediaToPlay) return null
491
+ const { mElement } = this.mediaToPlay
492
+ return mElement ? mElement : null
493
+ },
494
+ //Media Progress
495
+ //Time display : duration
496
+ mediaDurationTime() {
497
+ if (this.mediaDuration > 0)
498
+ return this.$helper.formatTime(this.mediaDuration)
499
+ else return '00:00'
500
+ },
501
+ //Time display : timecode
502
+ timecode() {
503
+ if (this.currentTime && typeof (this.currentTime === Number)) {
504
+ return this.$helper.formatTime(this.currentTime)
505
+ } else return '00:00'
506
+ },
507
+ //ProgressPercentage for progressBarIndicator width
508
+ progressBarPercentage() {
509
+ if (this.currentTime && this.mediaDuration)
510
+ return (this.currentTime / this.mediaDuration) * 100
511
+ else return 0
512
+ },
513
+ progressBarEnded() {
514
+ return this.currentTime >= Math.floor(this.mediaDuration)
515
+ },
516
+ //Volume
517
+ volumeState() {
518
+ let state = null
519
+ switch (true) {
520
+ case this.muted || this.currentVolume <= 0:
521
+ state = 'muted'
522
+ break
523
+
524
+ case !this.muted && this.currentVolume <= 0.5:
525
+ state = 'low'
526
+ break
527
+ case !this.muted && this.currentVolume > 0.5:
528
+ state = 'high'
529
+ }
530
+
531
+ return state
532
+ },
533
+ //Volume progress for slider background
534
+ volumeSliderBackground() {
535
+ return (this.currentVolume * 100).toFixed(0).toString() + '%'
536
+ },
537
+ //Volume from store
538
+ mediaVolume() {
539
+ // return this.getMediaVolume
540
+ return this.getMediaPlaybarValues('volume')
541
+ },
542
+ //media from store
543
+ mediaMuted() {
544
+ return this.getMediaMuted
545
+ },
546
+ //Labels
547
+ playLabel() {
548
+ let label = null
549
+ switch (true) {
550
+ case this.isPlaying:
551
+ label = this.$t('button.pause')
552
+ break
553
+
554
+ case this.canReplay:
555
+ label = this.$t('button.replay')
556
+ break
557
+
558
+ default:
559
+ label = this.$t('button.play')
560
+ }
561
+ return label
562
+ },
563
+ volumeLabel() {
564
+ let label = null
565
+ switch (true) {
566
+ case this.muted:
567
+ label = `${this.$t('button.unmute')}`
568
+ break
569
+ case !this.muted:
570
+ label = `${this.$t('button.mute')}`
571
+ break
572
+ }
573
+ return label
574
+ },
575
+ volumeLevelA11Y() {
576
+ const w = this.$i18n.locale === 'en' ? 'at' : 'à'
577
+ const level = Math.round(this.currentVolume * 100)
578
+ let txtA11Y = `Volume ${w} ${level}%`
579
+
580
+ return txtA11Y
581
+ },
582
+ //==========================================================
583
+ //Transcript
584
+ hasTranscript() {
585
+ if (!this.mediaToPlay) return
586
+ return this.mediaToPlay.mTranscript || false
587
+ },
588
+ //Subtitle
589
+ hasSubtitle() {
590
+ if (!this.mediaToPlay) return
591
+ return this.mediaToPlay.mSubtitles || false
592
+ },
593
+ subtitlesEnabled() {
594
+ if (!this.hasSubtitle) return false
595
+ else return this.getMediaSubtitles
596
+ },
597
+ //mediaContainer (only used for video)
598
+ mediaContainer() {
599
+ if (!this.mediaToPlay) return
600
+ return this.mediaToPlay.mMediaContainer
601
+ },
602
+
603
+ //MediaDuration :Number (used for tooltip)
604
+ mediaDuration() {
605
+ return this.mediaToPlay.mElement.duration
606
+ ? this.mediaToPlay.mElement.duration
607
+ : 0
608
+ },
609
+ //Labels
610
+ fullscreenLabel() {
611
+ let label = null
612
+ if (this.fullscreenOn) label = `${this.$t('button.full_screen_off')}`
613
+ else label = `${this.$t('button.full_screen_on')}`
614
+ return label
615
+ },
616
+ transcriptLabel() {
617
+ let label = null
618
+ if (this.transcriptEnabled) label = `${this.$t('button.transcript_off')}`
619
+ else label = `${this.$t('button.transcript_on')}`
620
+ return label
621
+ },
622
+ ccLabel() {
623
+ let label = null
624
+ if (this.subtitlesEnabled) label = `${this.$t('button.subtitle_off')}`
625
+ else label = `${this.$t('button.subtitle_on')}`
626
+ return label
627
+ },
628
+ transcriptBtnDisabled() {
629
+ return (
630
+ !this.hasTranscript ||
631
+ this.fullscreenOn ||
632
+ this.otherVideoTranscriptShown
633
+ )
634
+ },
635
+ fullscreenBtnDisabled() {
636
+ return this.transcriptEnabled || this.otherVideoTranscriptShown
637
+ }
638
+ },
639
+ watch: {
640
+ userInteraction: {
641
+ immediate: true,
642
+ deep: true,
643
+ handler() {
644
+ if (!this.userInteraction || !Object.keys(this.userInteraction).length)
645
+ return
646
+ this.getViewedSatus()
647
+ this.setMediaVolume()
648
+ }
649
+ }
650
+ },
651
+
652
+ mounted() {
653
+ //initialize media $refs
654
+ this.initializeMediaElm()
655
+ //initialize video $refs && set eventlistener for video
656
+ if (this.mediaToPlay.mType === 'video') {
657
+ this.initializeVideoElm()
658
+ this.setVideoHandlers()
659
+ }
660
+ //Set eventlistener for media
661
+ this.setMediaHandlers()
662
+
663
+ //Set label for ProgressBar
664
+ this.setProgressBarA11Y()
665
+ //Set initial volume and duration
666
+ this.updateVolumeLevel()
667
+
668
+ //update the the information of the media element linked to this play bar. playbar instance will be added to the media info
669
+ this.updateCurrentMediaElements({
670
+ id: this.mediaToPlay.id,
671
+ ...this.$parent.$refs
672
+ })
673
+ },
674
+ beforeUnmount() {
675
+ if (this.isPlaying) {
676
+ this.togglePlay()
677
+ }
678
+ this.removeMediaHandlers()
679
+
680
+ if (this.mediaToPlay.mType === 'video') this.removeVideoHandlers()
681
+
682
+ if (this.firefoxTrack) {
683
+ this.firefoxTrack.removeEventListener('cuechange', this.fireFoxMoveCC)
684
+ }
685
+ //this.$bus.$off('play-media', this.handleMediaControls)
686
+ this.$bus.$off('transcript-hidden', this.resetTranscript)
687
+ return this.$bus.$off('close-sidebar', 'ctxTranscript')
688
+ },
689
+ methods: {
690
+ ...mapActions(useAppStore, [
691
+ 'setMediaSubtitles',
692
+ 'updateCurrentMediaElements',
693
+ 'setMediaMuted',
694
+ 'setMediaPlaybarValues'
695
+ ]),
696
+
697
+ //======================================================================
698
+ /**
699
+ * @description - Set all the DOM elements for medias
700
+ */
701
+ initializeMediaElm() {
702
+ this.pbContainer = this.$refs['$pb-container']
703
+ //ProgressBar
704
+ this.progressArea = this.$refs['$progress-area']
705
+ this.progressBar = this.$refs['$progress-bar']
706
+ this.progressIndicator = this.$refs['$progress-indicator']
707
+ this.progressThumb = this.$refs['$progress-thumb']
708
+ //SeekTooltip
709
+ this.seekTooltip = this.$refs['$seek-tooltip']
710
+ //Volume//
711
+ this.volumeBtn = this.$refs['$btn-volume']
712
+ this.volumeSlider = this.$refs['$volume-slider']
713
+ this.volumeIndicator = this.$refs['$volume-progress']
714
+ },
715
+ /**
716
+ * @description - handle all the listeners for medias
717
+ */
718
+ setMediaHandlers() {
719
+ if (this.mediaElement) {
720
+ //Prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
721
+ this.pbContainer.addEventListener('keydown', this.keysPreventDefault)
722
+ //progressBar events
723
+ this.progressArea.addEventListener('click', this.seekingProgress)
724
+ //window handlers for playbar progress
725
+ window.addEventListener('mousemove', this.progressWindowMove)
726
+ window.addEventListener('mouseup', this.progressWindowUp)
727
+ //EndprogressBar
728
+ //Update data when media as a timeupdate/ended
729
+ this.mediaElement.addEventListener(
730
+ 'timeupdate',
731
+ this.updateProgressBarTime
732
+ )
733
+ this.mediaElement.addEventListener('ended', this.mediaEnded)
734
+ //Volume
735
+ this.volumeSlider.addEventListener('input', this.updateVolumeLevel)
736
+ //Hotkeys
737
+ this.pbContainer.addEventListener('keydown', this.handleMediaControls)
738
+
739
+ //Bus
740
+ //this.$bus.$on('play-media', this.handleMediaControls)
741
+ }
742
+ },
743
+
744
+ /**
745
+ * @description - Unset all the listeners (medias)
746
+ */
747
+ removeMediaHandlers() {
748
+ if (this.mediaElement) {
749
+ //progressBar events
750
+ this.progressArea.removeEventListener('click', this.seekingProgress)
751
+ //window handlers for playbar progress
752
+ window.removeEventListener('mousemove', this.progressWindowMove)
753
+ window.removeEventListener('mouseup', this.progressWindowUp)
754
+
755
+ this.mediaElement.removeEventListener(
756
+ 'timeupdate',
757
+ this.updateProgressBarTime
758
+ )
759
+ this.mediaElement.removeEventListener('ended', this.mediaEnded)
760
+
761
+ //Volume
762
+ this.volumeSlider.removeEventListener('input', this.updateVolumeLevel)
763
+ //Keys
764
+ this.pbContainer.removeEventListener('keydown', this.keysPreventDefault)
765
+ this.pbContainer.removeEventListener(
766
+ 'keydown',
767
+ this.handleMediaControls
768
+ )
769
+ }
770
+ },
771
+ /**
772
+ * @description - play or pause the media
773
+ * @fires manage-media-players - to the PAge the media in play
774
+ */
775
+ togglePlay() {
776
+ //If the progressBar is at the end, restart the media
777
+ if (this.progressBarEnded) {
778
+ this.mediaElement.currentTime = 0
779
+ this.currentTime = 0
780
+ }
781
+ //MediaElement
782
+ this.isPlaying ? this.mediaElement.pause() : this.mediaElement.play()
783
+ //Data
784
+ if (!this.isPlaying) {
785
+ //Signal to set this mediaElement as the last playing
786
+ this.$bus.$emit('manage-media-players', this.mediaToPlay)
787
+ }
788
+
789
+ this.isPlaying = !this.isPlaying
790
+ this.canReplay = false
791
+ },
792
+ /**
793
+ * @description - Set the media to a new time (if its a time between 0 and max)
794
+ */
795
+ setMediaTime(time) {
796
+ if (time <= 0) this.mediaElement.currentTime = 0
797
+ else if (time > this.mediaDuration)
798
+ this.mediaElement.currentTime = this.mediaDuration
799
+ else this.mediaElement.currentTime = time
800
+ //Handle finish Media if its set to max duration
801
+ if (this.progressBarEnded) {
802
+ this.mediaEnded()
803
+ }
804
+ },
805
+ /**
806
+ * @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
807
+ */
808
+
809
+ mediaEnded() {
810
+ //Don't make the media play when release progressBar thumb and the media is Ended
811
+ this.savedIsPlaying = false
812
+ //Pausing mediaElement is isPlaying
813
+ if (this.mediaElement && this.isPlaying) {
814
+ this.mediaElement.pause()
815
+ this.isPlaying = false
816
+ }
817
+ this.$bus.$emit('media-viewed', this.mediaToPlay.id)
818
+ this.canReplay = true
819
+ if (this.mediaToPlay.mType === 'video') this.showControls()
820
+ },
821
+
822
+ /**
823
+ * @description get the view Status of the current media from UserInteraction
824
+ */
825
+ getViewedSatus() {
826
+ //Should get the viewed Status from UserInteraction
827
+ // let { activityRef, id } = this.getCurrentPage
828
+ // const { userInteraction = null } = this.getPageInteraction(
829
+ // activityRef,
830
+ // id
831
+ // )
832
+ if (
833
+ !this.userInteraction ||
834
+ !this.userInteraction.mediasViewed ||
835
+ !this.userInteraction.mediasViewed.length
836
+ )
837
+ return
838
+
839
+ const { mediasViewed } = this.userInteraction
840
+
841
+ //Should update the can replay state of media
842
+ this.canReplay = mediasViewed.includes(this.mediaElement.id)
843
+ },
844
+ /**
845
+ * @description controls the level Of the media volume
846
+ * @param d string, optional volUp or volDown to Add or Remove 0.05 from volume value
847
+ */
848
+ updateVolumeLevel(d = null) {
849
+ if (!this.mediaElement) return
850
+ if (this.mediaElement.muted) {
851
+ this.mediaElement.muted = this.muted = false
852
+ //Send muted state to the store
853
+ this.setMutedState(this.muted)
854
+ }
855
+ switch (true) {
856
+ case d == 'volUP':
857
+ //Set volume Up
858
+ if (this.mediaElement.volume >= 1) return
859
+ if (this.mediaElement.volume >= 0.95) {
860
+ this.mediaElement.volume = 1
861
+ } else {
862
+ this.mediaElement.volume = (
863
+ this.mediaElement.volume + 0.05
864
+ ).toFixed(2)
865
+ }
866
+ this.volumeSlider.value = this.currentVolume =
867
+ this.mediaElement.volume
868
+ break
869
+
870
+ case d == 'volDOWN':
871
+ //Set volume Down
872
+ if (this.mediaElement.volume <= 0) return
873
+ if (this.mediaElement.volume <= 0.05) {
874
+ this.mediaElement.volume = 0
875
+ } else {
876
+ this.mediaElement.volume = (
877
+ this.mediaElement.volume - 0.05
878
+ ).toFixed(2)
879
+ }
880
+
881
+ this.volumeSlider.value = this.currentVolume =
882
+ this.mediaElement.volume
883
+ break
884
+
885
+ default: {
886
+ //set volume
887
+ this.currentVolume = this.mediaElement.volume =
888
+ this.volumeSlider.value
889
+ this.currentVolume = parseFloat(this.currentVolume).toFixed(2)
890
+ }
891
+ }
892
+ if (!this.muted) this.savedVolume = this.currentVolume
893
+
894
+ this.setMediaPlaybarValues({
895
+ volume: this.savedVolume
896
+ }) // Save the volume in the Store
897
+ },
898
+ /**
899
+ * @description - Toggle the state fo the sound
900
+ */
901
+ toggleMute() {
902
+ //Check first the volume of the media
903
+ if (this.mediaElement.volume == 0) this.muted = true
904
+
905
+ this.muted = !this.muted
906
+ this.mediaElement.muted = this.muted // set the muted state of the media
907
+
908
+ //Send mutedstate to the store
909
+ this.setMutedState(this.muted)
910
+
911
+ if (this.muted) {
912
+ this.mediaElement.volume = 0
913
+ this.savedVolume = this.currentVolume
914
+ this.volumeSlider.value = 0
915
+ this.currentVolume = 0
916
+ }
917
+ if (!this.muted) {
918
+ this.volumeSlider.value = this.savedVolume
919
+ this.currentVolume = this.mediaElement.volume = this.volumeSlider.value
920
+ }
921
+ },
922
+
923
+ /** @description Save the muted in the Store */
924
+ setMutedState() {
925
+ this.setMediaMuted(this.muted)
926
+ },
927
+ //Keyboard event methods
928
+ /**
929
+ * @description Method to prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
930
+ * */
931
+ keysPreventDefault(evt) {
932
+ if (
933
+ ['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(
934
+ evt.key
935
+ ) > -1
936
+ ) {
937
+ evt.preventDefault()
938
+ }
939
+ },
940
+
941
+ /**
942
+ * @description Method to handle the keyboard input to controle the media player
943
+ *@summary Keyboard actions:
944
+ * *** Left-Arrow/ Right-Arrow : move foward/Backward media by 5s
945
+ * *** Up-Arrow/ Down-Arrow: Increase/Decrease media volume by 5%
946
+ * *** c : show/ hide subtitle
947
+ * *** f : enter/ exit fullscreen
948
+ * *** k : play/ pause media
949
+ * *** m : Mute/ Unmute volume
950
+ * *** t : show transcript
951
+ */
952
+
953
+ handleMediaControls(e) {
954
+ e = e.code
955
+ this.showControls()
956
+ switch (e) {
957
+ case 'ArrowLeft':
958
+ //If focus is on Volume, VolDown
959
+ if (this.focusState.volumeSlider) {
960
+ //VolDown
961
+ this.updateVolumeLevel('volDOWN')
962
+ break
963
+ } else {
964
+ //Go back 5s in media timeline
965
+ this.cursorLeft()
966
+ break
967
+ }
968
+
969
+ case 'ArrowRight':
970
+ //If focus is on Volume, VolUp
971
+ if (this.focusState.volumeSlider) {
972
+ //VolUp
973
+ this.updateVolumeLevel('volUP')
974
+ break
975
+ } else {
976
+ //Move forward 5s in media timeline
977
+ this.cursorRight()
978
+ break
979
+ }
980
+
981
+ case 'ArrowUp':
982
+ //If focus is on progressBar, cursorRight
983
+ if (this.focusState.progressBar) {
984
+ //Move forward 5s in media timeline
985
+ this.cursorRight()
986
+ break
987
+ } else {
988
+ //VolUp
989
+ this.updateVolumeLevel('volUP')
990
+ break
991
+ }
992
+
993
+ case 'ArrowDown':
994
+ //If focus is on progressBar, cursorRight
995
+ if (this.focusState.progressBar) {
996
+ //Go back 5s in media timeline
997
+ this.cursorLeft()
998
+ break
999
+ } else {
1000
+ //Voldown
1001
+ this.updateVolumeLevel('volDOWN')
1002
+ break
1003
+ }
1004
+
1005
+ case 'KeyM':
1006
+ this.toggleMute()
1007
+ break
1008
+
1009
+ case 'KeyF':
1010
+ if (this.mediaToPlay.mType == 'audio') break
1011
+ //Toggle fullScreen
1012
+ this.toggleFullScreen()
1013
+ break
1014
+
1015
+ case 'KeyT':
1016
+ if (this.mediaToPlay.mType == 'audio') break
1017
+ //Toggle transcript view
1018
+ this.toggleViewTranscript(e)
1019
+ break
1020
+
1021
+ case 'KeyC':
1022
+ if (this.mediaToPlay.mType == 'audio') break
1023
+ //Toggle subtitle view
1024
+ this.toggleViewSubtitle()
1025
+ break
1026
+
1027
+ case 'KeyK':
1028
+ //Play Pause on k bar pressed
1029
+ if (this.mediaToPlay.mType == 'audio') this.togglePlay()
1030
+ //Play/pause video with the playBackAnim if it is triggerd with KeyK
1031
+ else this.handlePlayVideo('playBackAnim')
1032
+ break
1033
+ }
1034
+ },
1035
+ /**
1036
+ * @description - Activate the label of a button include in btnsLabelDisplay on Hover/Focus on the button. Deactivate the label in all the others btns
1037
+ */
1038
+ activateLabel(elm) {
1039
+ const id = elm.id
1040
+ this.btnsLabelDisplay[id] = true
1041
+ for (const property in this.btnsLabelDisplay) {
1042
+ if (property !== id) {
1043
+ this.btnsLabelDisplay[property] = false
1044
+ }
1045
+ }
1046
+ },
1047
+ /**
1048
+ * @description - Get currenttime from media
1049
+ */
1050
+
1051
+ getCurrentTime() {
1052
+ this.currentTime = this.mediaElement.currentTime
1053
+ return this.mediaElement.currentTime
1054
+ },
1055
+ /**
1056
+ * @description - controls to move the media timeline backward
1057
+ */
1058
+ cursorLeft() {
1059
+ this.setMediaTime(this.getCurrentTime() - 5)
1060
+ },
1061
+ /**
1062
+ * @description - control to move the media timeline forwrad
1063
+ */
1064
+ cursorRight() {
1065
+ this.setMediaTime(this.getCurrentTime() + 5)
1066
+ },
1067
+ //ProgressBar Methods
1068
+
1069
+ /**
1070
+ * @description - Update currentTime and timing strings when media is playing
1071
+ */
1072
+ updateProgressBarTime(e) {
1073
+ //Get currentTime from the event
1074
+ this.currentTime = e.target.currentTime
1075
+ //Update strings
1076
+ this.setProgressBarA11Y()
1077
+ //If replay is true and the progressIndicator is not at the end, set canReplay to false
1078
+ if (this.canReplay) this.canReplay = false
1079
+ //If replay is false and progressIndicator is at the end, set canReplay to true
1080
+ if (
1081
+ !this.canReplay &&
1082
+ this.currentTime >= Math.floor(this.mediaDuration)
1083
+ ) {
1084
+ this.canReplay = true
1085
+ }
1086
+ },
1087
+
1088
+ /**
1089
+ * @description - Set mediatime depending on progressBar click
1090
+ */
1091
+ seekingProgress(e) {
1092
+ if (!this.progressThumbDown && !this.progressThumbHover) {
1093
+ const updatedTime =
1094
+ (e.offsetX / this.progressBar.offsetWidth) * this.mediaDuration
1095
+ this.setMediaTime(updatedTime)
1096
+ }
1097
+ },
1098
+
1099
+ /**
1100
+ * @description - Set mediatime depending on mousemove on the screen (when progress thumb is mousedown)
1101
+ */
1102
+ progressWindowMove(ev) {
1103
+ if (this.progressThumbDown) {
1104
+ if (this.isPlaying) this.togglePlay()
1105
+ this.getTimeFromClickPos(ev)
1106
+ let timeToSet = Math.floor(this.getTimeFromClickPos(ev))
1107
+ if (timeToSet < 0) timeToSet = 0
1108
+ else if (timeToSet > this.duration) timeToSet = this.duration
1109
+ this.currentTime = timeToSet
1110
+ }
1111
+ },
1112
+
1113
+ /**
1114
+ * @description - Update thumb state (mouseup) when the mouse is up on the screen
1115
+ */
1116
+ progressWindowUp(ev) {
1117
+ if (this.progressThumbDown) {
1118
+ this.setMediaTime(this.currentTime)
1119
+ this.progressThumbDown = false
1120
+ //savedIsPlaying is the playing status saved on mousedown event on the thumb (the video is paused when moving on the progressBar)
1121
+ if (this.isPlaying !== this.savedIsPlaying) this.togglePlay()
1122
+ }
1123
+ },
1124
+ /**
1125
+ * @description - Get the time corresponding of the position of the click on the progressBar
1126
+ */
1127
+ getTimeFromClickPos(ev) {
1128
+ let timeToSet = 0
1129
+ //La progress Bar
1130
+ const progressBar = this.progressBar
1131
+ //La position de la progressBar dans la page
1132
+ const left = progressBar.getBoundingClientRect().left
1133
+ //Position du click par rapport à la progressBar
1134
+ let clickPos = ev.clientX - left
1135
+ //Si la position est à gauche de la progressBar, retourner 0s
1136
+ if (clickPos < 0) return 0
1137
+ //Obtenir le width de la progressBar
1138
+ const width = progressBar.offsetWidth
1139
+ //Obtenir le ratio de la position du click vs width de la progressBar
1140
+ let percentage = clickPos / width
1141
+ //Si > que 1, veux dire que le clique est à droite de la progressBar (mettre le curseur à la fin)
1142
+ percentage = percentage > 1 ? 1 : percentage
1143
+ //Ratio * temps total du media donne la progressiob
1144
+ timeToSet = percentage * this.mediaDuration
1145
+ return timeToSet
1146
+ },
1147
+
1148
+ /** @description - methode to set A11Y support for screen reader on progress bar element (video/audio/) */
1149
+ setProgressBarA11Y() {
1150
+ const w = this.$i18n.locale === 'en' ? 'at' : 'à'
1151
+ const dm =
1152
+ this.$i18n.locale === 'en'
1153
+ ? `${this.mediaToPlay.mType} duration`
1154
+ : `durée ${this.mediaToPlay.mType}`
1155
+
1156
+ this.mediaA11Y.valMax = Math.round(this.mediaElement.duration)
1157
+ this.mediaA11Y.valNow = Math.round(this.currentTime)
1158
+
1159
+ //format text output for screen reader
1160
+ let currentTimeArray = this.timecode.split(':')
1161
+ let fullTimeArray = this.mediaDurationTime.split(':')
1162
+ let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
1163
+ const mediaTitle = this.mediaToPlay.mTitle || this.mediaToPlay.mType
1164
+ this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
1165
+
1166
+ //format string to remove leading 0 digit for time
1167
+ const formatNum = (n) => {
1168
+ return n[0] == '0' ? n.substring(1).trim() : n.trim()
1169
+ }
1170
+
1171
+ const formatMsg = (x, y) => {
1172
+ const reg = /%x|%y/g
1173
+ let formatStr = this.$t('a11y_sr.range_expression').replace(
1174
+ reg,
1175
+ (e) => {
1176
+ if (e == '%x') return x
1177
+ if (e == '%y') return y
1178
+ }
1179
+ )
1180
+
1181
+ return formatStr
1182
+ }
1183
+
1184
+ switch (fullTimeArray.length) {
1185
+ case 3:
1186
+ if (!currentTimeArray.length) return
1187
+ //format current hours text text with plurialization apply
1188
+ hrsTxt =
1189
+ currentTimeArray.length == 3 && parseInt(currentTimeArray[0]) > 0
1190
+ ? `${formatNum(currentTimeArray[0])} ${this.$tc(
1191
+ 'a11y_sr.time.hours',
1192
+ parseInt(formatNum(currentTimeArray[0]))
1193
+ )}`
1194
+ : ''
1195
+
1196
+ //format minutes text text with plurialization apply
1197
+ mnTxt =
1198
+ parseInt(currentTimeArray[1]) > 0
1199
+ ? `${formatNum(currentTimeArray[1])} ${this.$tc(
1200
+ 'a11y_sr.time.minute',
1201
+ parseInt(formatNum(currentTimeArray[1]))
1202
+ )}`
1203
+ : ''
1204
+
1205
+ //format seconds text with plurialization apply
1206
+ ssTxt =
1207
+ parseInt(currentTimeArray[2]) > 0
1208
+ ? `${formatNum(currentTimeArray[2])} ${this.$tc(
1209
+ 'a11y_sr.time.second',
1210
+ parseInt(formatNum(currentTimeArray[2]))
1211
+ )}`
1212
+ : `0 ${this.$tc('a11y_sr.time.second')}`
1213
+
1214
+ HRSTxt =
1215
+ parseInt(fullTimeArray[0]) > 0
1216
+ ? `${formatNum(fullTimeArray[0])} ${this.$tc(
1217
+ 'a11y_sr.time.hours',
1218
+ parseInt(formatNum(fullTimeArray[0]))
1219
+ )}`
1220
+ : ''
1221
+
1222
+ MNTxt =
1223
+ parseInt(fullTimeArray[1]) > 0
1224
+ ? `${formatNum(fullTimeArray[1])} ${this.$tc(
1225
+ 'a11y_sr.time.minute',
1226
+ parseInt(formatNum(fullTimeArray[1]))
1227
+ )}`
1228
+ : ''
1229
+
1230
+ SSTxt =
1231
+ parseInt(fullTimeArray[2]) > 0
1232
+ ? `${formatNum(fullTimeArray[2])} ${this.$tc(
1233
+ 'a11y_sr.time.second',
1234
+ parseInt(formatNum(fullTimeArray[2]))
1235
+ )}`
1236
+ : ''
1237
+
1238
+ cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
1239
+ fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
1240
+
1241
+ //Format to show 0 when no value is present
1242
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1243
+ // Set sr text for timeCode && duration
1244
+ this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1245
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1246
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
1247
+
1248
+ break
1249
+ case 2:
1250
+ if (!currentTimeArray.length) return
1251
+
1252
+ //format current time text
1253
+ mnTxt =
1254
+ parseInt(currentTimeArray[0]) > 0
1255
+ ? `${formatNum(currentTimeArray[0])} ${this.$tc(
1256
+ 'a11y_sr.time.minute',
1257
+ parseInt(formatNum(currentTimeArray[0]))
1258
+ )}`
1259
+ : ''
1260
+
1261
+ ssTxt =
1262
+ parseInt(currentTimeArray[1]) > 0
1263
+ ? `${formatNum(currentTimeArray[1])} ${this.$tc(
1264
+ 'a11y_sr.time.second',
1265
+ parseInt(formatNum(currentTimeArray[1]))
1266
+ )}`
1267
+ : `0 ${this.$tc('a11y_sr.time.second')}`
1268
+
1269
+ // format full time text
1270
+ MNTxt =
1271
+ parseInt(fullTimeArray[0]) > 0
1272
+ ? `${formatNum(fullTimeArray[0])} ${this.$tc(
1273
+ 'a11y_sr.time.minute',
1274
+ parseInt(formatNum(fullTimeArray[0]))
1275
+ )}`
1276
+ : ''
1277
+ SSTxt =
1278
+ parseInt(fullTimeArray[1]) > 0
1279
+ ? `${formatNum(fullTimeArray[1])} ${this.$tc(
1280
+ 'a11y_sr.time.second',
1281
+ parseInt(formatNum(fullTimeArray[1]))
1282
+ )}`
1283
+ : ''
1284
+
1285
+ cTimeTxt = `${mnTxt} ${ssTxt}`
1286
+ fTimeTxt = `${MNTxt} ${SSTxt}`
1287
+
1288
+ //Format to show 0 when no value is present
1289
+ cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1290
+ // Set sr text for timeCode && duration
1291
+ this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1292
+ this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1293
+
1294
+ this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(cTimeTxt, fTimeTxt)}`
1295
+
1296
+ break
1297
+ }
1298
+ },
1299
+ /**
1300
+ * @description - Deactivate the label of a button include in btnsLabelDisplay on mouseout/blur of this button
1301
+ */
1302
+ deactivateLabel(elm) {
1303
+ const id = elm.id
1304
+ this.btnsLabelDisplay[id] = false
1305
+ },
1306
+ /**
1307
+ * @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)
1308
+ */
1309
+ changeFocusState(string, bool) {
1310
+ this.focusState[string] = bool
1311
+ },
1312
+ /**
1313
+ * @description - handle initial volume level
1314
+ */
1315
+ setMediaVolume() {
1316
+ //Set media muted or with the savedVolume
1317
+ this.muted = this.mediaMuted
1318
+ if (!this.muted)
1319
+ return (this.currentVolume = this.savedVolume = this.mediaVolume)
1320
+ this.savedVolume = this.mediaVolume
1321
+ this.currentVolume = 0
1322
+ },
1323
+ //======================================================================
1324
+ /**
1325
+ * @description - Set the DOM elements specific for video
1326
+ */
1327
+ initializeVideoElm() {
1328
+ //SeekTooltip
1329
+ this.seekTooltip = this.$refs['$seek-tooltip']
1330
+ //Transcript
1331
+ this.$bus.$on('transcript-hidden', this.resetTranscript)
1332
+ this.setTranscript()
1333
+ },
1334
+
1335
+ /** "@description -update the information of the seek tooltip*/
1336
+ updateSeekTooltip(evt) {
1337
+ //Elm progress Bar
1338
+ const progressBar = this.progressBar
1339
+ //progressBar position on the page
1340
+ const left = progressBar.getBoundingClientRect().left
1341
+ //Mouse position versus progressBar
1342
+ const hoverPos = evt.clientX - left
1343
+ //Get progressBar offsetWidth
1344
+ const width = progressBar.offsetWidth
1345
+
1346
+ //Define the value of the skip tooltip position
1347
+ let offsetX = null
1348
+
1349
+ switch (true) {
1350
+ case hoverPos < 15:
1351
+ offsetX = 20
1352
+ break
1353
+ case hoverPos > width - 55:
1354
+ offsetX = width - 55
1355
+ break
1356
+ default:
1357
+ offsetX = hoverPos
1358
+ }
1359
+
1360
+ this.seekTooltip.style.left = `${offsetX}px` // update visual position of tooltip
1361
+
1362
+ //Get % of the cursor hover position/progressBar width
1363
+ let percentage = 0
1364
+ if (hoverPos > width) percentage = 100
1365
+ if (hoverPos < 0) percentage = 0
1366
+ else {
1367
+ percentage = hoverPos / width
1368
+ }
1369
+ let skipTo = percentage * this.mediaDuration
1370
+ //Update the Tooltip time
1371
+ this.tooTipTimeCode = this.$helper.formatTime(skipTo)
1372
+ },
1373
+ /**
1374
+ * @description - handle all the listeners for videos
1375
+ */
1376
+ setVideoHandlers() {
1377
+ //Tooltip
1378
+ this.progressArea.addEventListener('mousemove', this.updateSeekTooltip)
1379
+ //Fullscreen
1380
+ this.mediaContainer.addEventListener(
1381
+ 'fullscreenchange',
1382
+ this.updateFullScreenState
1383
+ )
1384
+ //Show controls
1385
+ this.mediaContainer.addEventListener('mousemove', this.showControls)
1386
+ },
1387
+ /**
1388
+ * @description - Unset all the listeners (video)
1389
+ */
1390
+ removeVideoHandlers() {
1391
+ //Tooltip
1392
+ this.progressArea.removeEventListener('mousemove', this.updateSeekTooltip)
1393
+ //Fullscreen
1394
+ this.mediaContainer.removeEventListener(
1395
+ 'fullscreenchange',
1396
+ this.updateFullScreenState
1397
+ )
1398
+ //Show controls
1399
+ this.mediaContainer.removeEventListener('mousemove', this.showControls)
1400
+ },
1401
+ /**
1402
+ /* @description - handle the play /pause of the media and the playback video animation
1403
+ */
1404
+ handlePlayVideo(type = null) {
1405
+ //Play/pause
1406
+ this.togglePlay()
1407
+ //Playbar animation (show/hide)
1408
+ this.isPlaying ? this.hideControls() : this.showControls()
1409
+
1410
+ //Playback animation (only the first play click)
1411
+ if (this.playClicked && !type) {
1412
+ this.playBackAnim = false
1413
+ return
1414
+ }
1415
+ //this.runPlaybackAnimation()
1416
+ this.playBackAnim = true
1417
+ this.playClicked = true
1418
+ // Should indicate with media is playing and set it in the store
1419
+ },
1420
+ /**
1421
+ * @description - Toggle the state of the screen
1422
+ * Set or unset the media in full screen
1423
+ */
1424
+ toggleFullScreen() {
1425
+ const fullscreenElement = this.mediaContainer
1426
+ if (document.fullscreenElement) {
1427
+ // exitFullscreen is only available on the Document object.
1428
+ document.exitFullscreen()
1429
+ } else {
1430
+ //fullscreen is available on Element.
1431
+ fullscreenElement.requestFullscreen()
1432
+ }
1433
+ },
1434
+ /**
1435
+ * @description update the value of fulls screen state
1436
+ */
1437
+ updateFullScreenState() {
1438
+ this.fullscreenOn = !this.fullscreenOn
1439
+ },
1440
+
1441
+ fireFoxMoveCC() {
1442
+ if (this.firefoxTrack.activeCues[0]) {
1443
+ this.firefoxTrack.activeCues[0].line = 12
1444
+ }
1445
+ },
1446
+ /**
1447
+ * @description show the subtitle
1448
+ */
1449
+ showSubtitles() {
1450
+ this.mediaElement.textTracks[0].mode = 'showing'
1451
+ this.setMediaSubtitles(true)
1452
+ if (this.getCurrentBrowser == 'Firefox') {
1453
+ let video = document.getElementsByTagName('video')[0]
1454
+ let tracks = video.textTracks
1455
+ this.firefoxTrack = tracks[0]
1456
+
1457
+ this.firefoxTrack.addEventListener('cuechange', this.fireFoxMoveCC)
1458
+ }
1459
+ },
1460
+
1461
+ hideSubtitles() {
1462
+ this.mediaElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
1463
+ this.setMediaSubtitles(false)
1464
+ },
1465
+ toggleViewSubtitle() {
1466
+ if (this.subtitlesEnabled) return this.hideSubtitles()
1467
+ if (!this.subtitlesEnabled) return this.showSubtitles()
1468
+ },
1469
+ /**
1470
+ * @description method to show or hide the transcipt.
1471
+ * @summary get toggle the value of transcriptEnabled and get content from the transcript file
1472
+ * and transfer it to Module for display.
1473
+ * @fires 'open-sidebar' to AppBaseModule
1474
+ * @fires 'close-sidebar' to AppBaseModule
1475
+ */
1476
+ toggleViewTranscript(event) {
1477
+ this.transcriptEnabled = !this.transcriptEnabled
1478
+ //When the function is called after a user interaction (mouse or keyboard), emit event
1479
+ if (event) {
1480
+ //emit transcript toggle event, listener in AppBasePage
1481
+ this.$bus.$emit(
1482
+ 'video-transcript-toggle',
1483
+ this.mediaToPlay,
1484
+ this.transcriptEnabled
1485
+ )
1486
+ }
1487
+
1488
+ if (this.transcriptEnabled && this.transcriptToShow) {
1489
+ //this.$bus.$emit('resize-media', 'sm')
1490
+ //Resize video container
1491
+ this.$emit('resize-video', 'sm')
1492
+ //Open sidebar with the transcript
1493
+ return this.$bus.$emit('open-sidebar', {
1494
+ ctx: 'ctxTranscript',
1495
+ e: this.transcriptToShow,
1496
+ w: this.pbContainer
1497
+ })
1498
+ }
1499
+
1500
+ // Send close signal for the side bar when transcipt state is not enabled
1501
+ if (!this.transcriptEnabled) {
1502
+ //this.$bus.$emit('resize-media', 'lg')
1503
+ //Resize video container
1504
+ this.$emit('resize-video', 'lg')
1505
+ //Open sidebar with the transcript
1506
+ return this.$bus.$emit('close-sidebar', 'ctxTranscript')
1507
+ }
1508
+ },
1509
+ /**
1510
+ * @description Fetching method for transcript
1511
+ * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
1512
+ * and return its content for display
1513
+ * @return {String} HTML string
1514
+ */
1515
+ async fetchTranscript() {
1516
+ try {
1517
+ //const tFile = 'exemple_transcript2.html'
1518
+ const { mTranscript } = this.mediaToPlay
1519
+ const tFile = mTranscript
1520
+ if (!tFile) throw new Error('Missing transcript File!')
1521
+
1522
+ // validate file types. Only .html are allowed
1523
+ if (!tFile.endsWith('.html'))
1524
+ throw new Error(
1525
+ 'Invalid valid transcript file. \n Expecting .html file'
1526
+ )
1527
+
1528
+ const fileUrl = !tFile.includes('/') ? `./${tFile}` : tFile //allow passing file in Public folder
1529
+ // const fileUrl = new URL(tFile, import.meta.url))
1530
+ const res = await axios.get(fileUrl, { responseType: 'blob' })
1531
+ const content = await res.data.text()
1532
+
1533
+ return content
1534
+ } catch (err) {
1535
+ console.warn("YOU'VE GOT AN ERROR!\n", err)
1536
+ }
1537
+ },
1538
+ /** ******************* Rendering Methods****************************** */
1539
+
1540
+ /**
1541
+ * @Description Method to set the transcription that need to be displayed
1542
+ */
1543
+ async setTranscript() {
1544
+ if (!this.hasTranscript) return (this.transcriptToShow = null)
1545
+
1546
+ this.transcriptToShow = await this.fetchTranscript()
1547
+ },
1548
+
1549
+ /**
1550
+ * @description Method to reset the transcript state
1551
+ */
1552
+ resetTranscript(content) {
1553
+ this.transcriptEnabled = false
1554
+ this.otherVideoTranscriptShown = false
1555
+ },
1556
+
1557
+ /**
1558
+ * @description Show the media controler */
1559
+ showControls() {
1560
+ this.showControlsValue = true
1561
+ if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
1562
+
1563
+ this.hideControls()
1564
+ },
1565
+ /**
1566
+ * @description Hide the media after the video start playing
1567
+ */
1568
+
1569
+ hideControls() {
1570
+ if (this.mediaElement.paused) return
1571
+
1572
+ this.hideTimer = setTimeout(() => {
1573
+ this.showControlsValue = false
1574
+ }, this.delayUntilHide)
1575
+ }
1576
+ }
1577
+ }
1578
+ </script>
1579
+ <style lang="scss" scoped>
1580
+ .pb-container.video {
1581
+ position: absolute;
1582
+ height: 100%;
1583
+ width: 100%;
1584
+ justify-content: center;
1585
+ display: flex;
1586
+ }
1587
+
1588
+ .playback-button {
1589
+ position: absolute;
1590
+ top: 0;
1591
+ left: 0;
1592
+ width: 100%;
1593
+ height: 100%;
1594
+ display: flex;
1595
+ flex-flow: column wrap;
1596
+ justify-content: center;
1597
+ align-items: center;
1598
+ background-color: transparent;
1599
+ .playback-wrapper {
1600
+ height: 64px;
1601
+ width: 64px;
1602
+ display: flex;
1603
+ justify-content: center;
1604
+ align-items: center;
1605
+ border-radius: 50%;
1606
+
1607
+ svg {
1608
+ height: 32px;
1609
+ width: 26.29px;
1610
+ &.play-icon {
1611
+ margin-left: 11%;
1612
+ }
1613
+ }
1614
+ }
1615
+ }
1616
+ //Wrapper
1617
+ .pb-wrapper {
1618
+ width: 100%;
1619
+ display: flex;
1620
+ flex-direction: column;
1621
+ position: absolute;
1622
+ bottom: 0;
1623
+ opacity: 0;
1624
+ z-index: 2;
1625
+ &.show-controls {
1626
+ opacity: 1;
1627
+ transition: opacity 0.35s ease-in-out;
1628
+ }
1629
+
1630
+ //Wrapper timeline
1631
+ .pb-timeline {
1632
+ --progress-bar-height: 6px;
1633
+ position: relative;
1634
+ width: 100%;
1635
+ height: var(--progress-bar-height);
1636
+ .progress-area {
1637
+ width: 100%;
1638
+ height: 400%; //Add contact surface of click on progressBar
1639
+ position: relative;
1640
+ top: -150%;
1641
+ display: flex;
1642
+ cursor: pointer;
1643
+ align-items: center;
1644
+ z-index: 8; //z-index on top of the video elm
1645
+ &:hover {
1646
+ .seek-tooltip {
1647
+ opacity: 1;
1648
+ }
1649
+ }
1650
+
1651
+ //Progress bar
1652
+ .pb-progress-bar {
1653
+ --progress-bar-border-height: 1px;
1654
+ cursor: pointer;
1655
+ width: 100%;
1656
+ height: var(--progress-bar-height);
1657
+ //background-color: transparent;
1658
+ overflow: visible;
1659
+ &:focus {
1660
+ .progress-thumb {
1661
+ }
1662
+ }
1663
+ }
1664
+
1665
+ .progress-indicator {
1666
+ width: 0;
1667
+ height: 100%;
1668
+ position: relative;
1669
+ user-select: none;
1670
+
1671
+ &.progress-animation {
1672
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1673
+ transition-duration: 0.01s;
1674
+ transition-property: all;
1675
+ transition: width 0.1s linear;
1676
+ }
1677
+ }
1678
+ //Custom thumb
1679
+ .progress-thumb {
1680
+ //Cliquable thumb
1681
+ --progress-thumb-size: 30px;
1682
+ width: var(--progress-thumb-size);
1683
+ height: var(--progress-thumb-size);
1684
+ position: absolute;
1685
+
1686
+ right: calc(var(--progress-thumb-size) / -2);
1687
+ top: calc(
1688
+ -1 * (
1689
+ var(--progress-thumb-size) / 2 - var(--progress-bar-height) / 2
1690
+ ) - var(--progress-bar-border-height) / 2
1691
+ );
1692
+ display: flex;
1693
+ align-items: center;
1694
+ justify-content: center;
1695
+ &::after {
1696
+ content: '';
1697
+ //Visible thumb
1698
+ height: 16px;
1699
+ width: 16px;
1700
+ //Colors and styling
1701
+ border-radius: 50%;
1702
+ //background-color: var(--primary700);
1703
+ }
1704
+ }
1705
+
1706
+ &:hover {
1707
+ .seek-tooltip {
1708
+ //tooltip visible only when hovering progress-area
1709
+ opacity: 1;
1710
+ }
1711
+ }
1712
+
1713
+ .seek-tooltip {
1714
+ position: absolute;
1715
+ top: -30px;
1716
+ opacity: 0;
1717
+ user-select: none;
1718
+ border-radius: 1px;
1719
+ padding: 2px;
1720
+ }
1721
+ }
1722
+ }
1723
+
1724
+ //Controls
1725
+ .pb-controls {
1726
+ padding: 12px 32px;
1727
+ @media screen and (max-width: 800px) {
1728
+ padding: 12px 12px;
1729
+ }
1730
+ & {
1731
+ width: 100%;
1732
+ //height: 76px;
1733
+ height: auto;
1734
+ display: flex;
1735
+ flex-flow: row wrap;
1736
+ justify-content: space-between;
1737
+ -webkit-justify-content: space-between;
1738
+ align-items: center;
1739
+ }
1740
+ .pb-areas {
1741
+ display: flex;
1742
+ flex-flow: row nowrap;
1743
+ justify-content: center;
1744
+ align-items: center;
1745
+
1746
+ &.right {
1747
+ .btn {
1748
+ margin-right: 24px;
1749
+
1750
+ &:last-child {
1751
+ margin-right: 0;
1752
+ }
1753
+ }
1754
+ }
1755
+
1756
+ .btn {
1757
+ position: relative;
1758
+
1759
+ &::before {
1760
+ content: attr(data-title);
1761
+ position: absolute;
1762
+ top: -50px;
1763
+ right: 0;
1764
+ margin-left: -35px;
1765
+ word-break: keep-all;
1766
+ white-space: pre;
1767
+ display: none;
1768
+ opacity: 0;
1769
+ width: fit-content;
1770
+ border-radius: 1px;
1771
+ padding: 2px;
1772
+ }
1773
+ //Icons btn specific
1774
+ &.playbar-btn {
1775
+ &::before {
1776
+ left: 0 !important;
1777
+ margin-left: 0 !important;
1778
+ }
1779
+ & {
1780
+ margin-right: 16px;
1781
+ border-radius: 50%;
1782
+ }
1783
+ &:hover {
1784
+ &::before {
1785
+ display: block;
1786
+ opacity: 1;
1787
+ }
1788
+ }
1789
+ @media screen and (max-width: 800px) {
1790
+ height: 32px;
1791
+ width: 32px;
1792
+ }
1793
+ svg.play-icon {
1794
+ margin-left: 11.875%; // 11.8% off center on the design
1795
+ }
1796
+ }
1797
+ &.volume-btn {
1798
+ margin: 0 20px;
1799
+ svg.volume-low-icon {
1800
+ height: 17.75px;
1801
+ }
1802
+ }
1803
+ }
1804
+ //Manage displayLabel (dynamic class)
1805
+ button.displayLabel {
1806
+ &:hover,
1807
+ &:focus {
1808
+ &::before {
1809
+ display: block;
1810
+ opacity: 1;
1811
+ }
1812
+ }
1813
+ }
1814
+
1815
+ .pb-timer {
1816
+ width: max-content;
1817
+ }
1818
+ //Volume
1819
+ .volume-controls {
1820
+ --volumecontrols-height: 22px;
1821
+ --volume-thumb-size: 16px;
1822
+ display: flex;
1823
+ flex-flow: row wrap;
1824
+ justify-content: center;
1825
+ position: relative;
1826
+ height: var(--volumecontrols-height);
1827
+ &:hover {
1828
+ //button title //DisplayLabel is a dynamic class
1829
+ .volume-slider.displayLabel + span {
1830
+ display: block;
1831
+ opacity: 1;
1832
+ }
1833
+ }
1834
+ .volume-slider {
1835
+ -webkit-appearance: none;
1836
+ width: 100px;
1837
+ //Responsive
1838
+ @media screen and (max-width: 800px) {
1839
+ width: 80px;
1840
+ }
1841
+ & {
1842
+ border-radius: 24px;
1843
+ cursor: pointer;
1844
+ position: relative;
1845
+ z-index: 2;
1846
+ }
1847
+ &::-webkit-slider-runnable-track {
1848
+ background: transparent;
1849
+ }
1850
+ //Input slider thumb
1851
+ @mixin volume-thumb {
1852
+ -webkit-appearance: none;
1853
+ width: var(--volume-thumb-size);
1854
+ height: var(--volume-thumb-size);
1855
+ margin-top: 12px;
1856
+ border-radius: 50%;
1857
+ position: relative;
1858
+ cursor: pointer;
1859
+ }
1860
+ &::-webkit-slider-thumb {
1861
+ @include volume-thumb;
1862
+ }
1863
+ //button title
1864
+ & + span {
1865
+ position: absolute;
1866
+ top: -62px;
1867
+ right: 0;
1868
+ margin-left: -35px;
1869
+ word-break: keep-all;
1870
+ white-space: pre;
1871
+ display: none;
1872
+ opacity: 0;
1873
+ user-select: none;
1874
+ //Colors
1875
+ //background-color: var(--primary100);
1876
+ //color: var(--primary700);
1877
+ border-radius: 1px;
1878
+ padding: 2px;
1879
+ }
1880
+ //DisplayLabel is a dynamic class
1881
+ &.displayLabel:focus {
1882
+ & + span {
1883
+ display: block;
1884
+ opacity: 1;
1885
+ }
1886
+ }
1887
+ }
1888
+
1889
+ .volume-progress {
1890
+ --volumeIndicator-height: 9px;
1891
+ height: var(--volumeIndicator-height);
1892
+ top: calc(
1893
+ var(--volumecontrols-height) / 2 - var(--volumeIndicator-height) / 2
1894
+ ); //centrer en hauteur
1895
+ width: calc(
1896
+ 100% - var(--volume-thumb-size) / 2
1897
+ ); //Width smaller than the range width to be sure the progress width don't exceed the range width
1898
+ position: absolute;
1899
+ border-radius: 20px;
1900
+ background-repeat: no-repeat;
1901
+ background-size: var(--background-size-vs, 0%) 100%; //Js dynamic variable
1902
+ //colors
1903
+ }
1904
+ }
1905
+ }
1906
+ }
1907
+
1908
+ svg {
1909
+ //width: 19px;
1910
+ //height: 19px;
1911
+ //colors
1912
+ //stroke: var(--primary700);
1913
+ }
1914
+
1915
+ button {
1916
+ height: 48px;
1917
+ width: 48px;
1918
+ background-color: transparent;
1919
+ padding: 0;
1920
+ display: flex;
1921
+ justify-content: center;
1922
+ align-items: center;
1923
+ svg {
1924
+ height: 24px;
1925
+ @media screen and (max-width: 800px) {
1926
+ height: 18px;
1927
+ }
1928
+ }
1929
+ }
1930
+ }
1931
+
1932
+ //CSS for audio only
1933
+ .audio.pb-wrapper {
1934
+ border-radius: 4px;
1935
+ position: initial;
1936
+ .pb-controls {
1937
+ padding: 8px 16px;
1938
+ .volume-controls {
1939
+ margin-left: 8px;
1940
+ width: 100%;
1941
+ --volume-thumb-size: 12px;
1942
+ .volume-slider {
1943
+ width: 50px;
1944
+ }
1945
+ .volume-progress {
1946
+ --volumeIndicator-height: 6px;
1947
+ }
1948
+ }
1949
+ }
1950
+ .pb-timer {
1951
+ margin-right: 20px;
1952
+ }
1953
+ .pb-timeline {
1954
+ .progress-area {
1955
+ justify-content: center;
1956
+ }
1957
+ .pb-progress-bar {
1958
+ border-radius: 4px;
1959
+ //max-width: 90%;
1960
+ }
1961
+ }
1962
+ .volume-btn {
1963
+ margin-left: 16px;
1964
+ }
1965
+ .pb-areas {
1966
+ width: 100%;
1967
+ display: grid;
1968
+ grid-template-columns:
1969
+ min-content
1970
+ min-content
1971
+ 1fr
1972
+ min-content
1973
+ min-content;
1974
+ align-items: center;
1975
+ justify-content: center;
1976
+ button {
1977
+ height: 24px;
1978
+ width: 24px;
1979
+ &.playbar-btn {
1980
+ margin-right: 8px;
1981
+ }
1982
+ svg {
1983
+ height: 12.25px;
1984
+ @media screen and (max-width: 800px) {
1985
+ height: 18px;
1986
+ }
1987
+ }
1988
+ &.volume-btn {
1989
+ svg {
1990
+ height: 16px;
1991
+ }
1992
+ svg.volume-low-icon {
1993
+ height: 14px;
1994
+ }
1995
+ }
1996
+ }
1997
+ }
1998
+ }
1999
+ //Playback button video (to finish)
2000
+ .playback-button {
2001
+ .playback-wrapper {
2002
+ opacity: 0;
2003
+ }
2004
+ &.initial {
2005
+ .playback-wrapper {
2006
+ animation-name: initial;
2007
+ opacity: 1;
2008
+ }
2009
+ }
2010
+ &.playback-animation {
2011
+ .playback-wrapper {
2012
+ opacity: 0;
2013
+ animation-name: handlePlayBack;
2014
+ animation-duration: 0.8s;
2015
+ animation-timing-function: ease-in-out;
2016
+ }
2017
+ }
2018
+ }
2019
+
2020
+ .audio-media-player {
2021
+ .pb-areas {
2022
+ .btn {
2023
+ &::before {
2024
+ top: inherit !important;
2025
+ bottom: -50px;
2026
+ }
2027
+ }
2028
+ }
2029
+ }
2030
+
2031
+ @keyframes handlePlayBack {
2032
+ 0% {
2033
+ opacity: 0;
2034
+ }
2035
+ 50% {
2036
+ opacity: 1;
2037
+ transform: scale(0.95);
2038
+ }
2039
+ 100% {
2040
+ opacity: 0;
2041
+ }
2042
+ }
2043
+
2044
+ @keyframes disappear {
2045
+ 0% {
2046
+ opacity: 1;
2047
+ }
2048
+ 100% {
2049
+ opacity: 0;
2050
+ }
2051
+ }
2052
+ </style>