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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/.eslintrc.cjs +81 -81
  4. package/CHANGELOG +13 -0
  5. package/README.md +71 -71
  6. package/bk.scss +117 -117
  7. package/package.json +8 -8
  8. package/src/$locales/en.json +145 -143
  9. package/src/$locales/fr.json +107 -105
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1150 -1054
  12. package/src/components/AppBaseButton.test.js +22 -0
  13. package/src/components/AppBaseButton.vue +93 -87
  14. package/src/components/AppBaseErrorDisplay.vue +438 -438
  15. package/src/components/AppBaseFlipCard.vue +84 -84
  16. package/src/components/AppBaseModule.vue +1657 -1673
  17. package/src/components/AppBasePage.vue +742 -779
  18. package/src/components/AppBasePopover.vue +41 -41
  19. package/src/components/AppCompAudio.vue +265 -234
  20. package/src/components/AppCompBranchButtons.vue +556 -552
  21. package/src/components/AppCompButtonProgress.vue +121 -126
  22. package/src/components/AppCompCarousel.vue +328 -298
  23. package/src/components/AppCompInputCheckBoxNext.vue +200 -195
  24. package/src/components/AppCompInputDropdownNext.vue +201 -159
  25. package/src/components/AppCompInputRadioNext.vue +152 -152
  26. package/src/components/AppCompInputTextNext.vue +125 -106
  27. package/src/components/AppCompInputTextTableNext.vue +142 -141
  28. package/src/components/AppCompInputTextToFillDropdownNext.vue +238 -230
  29. package/src/components/AppCompInputTextToFillNext.vue +171 -171
  30. package/src/components/AppCompJauge.vue +74 -74
  31. package/src/components/AppCompMenu.vue +25 -10
  32. package/src/components/AppCompMenuItem.vue +228 -228
  33. package/src/components/AppCompNavigation.vue +972 -960
  34. package/src/components/AppCompNoteCall.vue +159 -133
  35. package/src/components/AppCompNoteCredit.vue +490 -292
  36. package/src/components/AppCompPlayBar.vue +1217 -1218
  37. package/src/components/AppCompPlayBarNext.vue +2060 -2052
  38. package/src/components/AppCompPlayBarProgress.vue +82 -82
  39. package/src/components/AppCompPopUpNext.vue +500 -503
  40. package/src/components/AppCompQuizNext.vue +2908 -2904
  41. package/src/components/AppCompQuizRecall.vue +298 -276
  42. package/src/components/AppCompSVGNext.vue +347 -347
  43. package/src/components/AppCompSettingsMenu.vue +172 -172
  44. package/src/components/AppCompTableOfContent.vue +386 -387
  45. package/src/components/AppCompTranscript.vue +24 -24
  46. package/src/components/AppCompVideoPlayer.vue +368 -368
  47. package/src/components/BaseModule.vue +55 -72
  48. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  49. package/src/composables/useQuiz.js +206 -206
  50. package/src/externalComps/ModuleView.vue +22 -22
  51. package/src/externalComps/SummaryView.vue +91 -91
  52. package/src/main.js +272 -272
  53. package/src/mixins/$mediaMixins.js +819 -819
  54. package/src/mixins/timerMixin.js +155 -155
  55. package/src/module/stores/appStore.js +954 -893
  56. package/src/module/xapi/ADL.js +380 -376
  57. package/src/module/xapi/Crypto/Hasher.js +241 -241
  58. package/src/module/xapi/Crypto/WordArray.js +278 -278
  59. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  60. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  61. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  62. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  63. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  64. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  65. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  66. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  67. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  68. package/src/module/xapi/Statement/agent.js +55 -55
  69. package/src/module/xapi/Statement/index.js +259 -259
  70. package/src/module/xapi/Statement/statement.js +253 -253
  71. package/src/module/xapi/launch.js +157 -157
  72. package/src/module/xapi/utils.js +167 -167
  73. package/src/module/xapi/verbs.js +294 -294
  74. package/src/module/xapi/wrapper.js +1963 -1963
  75. package/src/module/xapi/xapiStatement.js +444 -444
  76. package/src/plugins/bus.js +8 -8
  77. package/src/plugins/gsap.js +14 -14
  78. package/src/plugins/helper.js +355 -308
  79. package/src/plugins/i18n.js +44 -44
  80. package/src/plugins/idb.js +227 -219
  81. package/src/plugins/save.js +37 -37
  82. package/src/plugins/scorm.js +287 -287
  83. package/src/plugins/xapi.js +11 -11
  84. package/src/public/index.html +33 -33
  85. package/src/router/index.js +48 -43
  86. package/src/router/routes.js +312 -312
  87. package/src/shared/generalfuncs.js +210 -210
  88. package/src/shared/validators.js +926 -1069
  89. package/vitest.config.js +19 -0
  90. package/vite.config.js +0 -27
@@ -1,1218 +1,1217 @@
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="!hasTranscript || fullscreenOn"
324
- :class="{
325
- md_disabled: !hasTranscript || fullscreenOn,
326
- displayLabel: btnsLabelDisplay['btn-transcript'] === true
327
- }"
328
- @click="toggleViewTranscript()"
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="transcriptEnabled"
354
- :class="{
355
- md_disabled: transcriptEnabled,
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 $extendsMedia from '../mixins/$mediaMixins'
380
- import { mapState, mapActions } from 'pinia'
381
- import { useAppStore } from '../module/stores/appStore'
382
- import axios from 'axios'
383
-
384
- export default {
385
- mixins: [$extendsMedia],
386
- props: {
387
- mediaToPlay: { type: [Object, Boolean], default: false }
388
- },
389
- emits: ['resize-video'],
390
-
391
- data() {
392
- return {
393
- id: `plyr_${this.mediaToPlay.id}`,
394
- //Playback animation
395
- playClicked: false,
396
- playBackAnim: true,
397
- //ProgressSeek
398
- progressSeek: null,
399
- //Tooltip
400
- seekTooltip: null,
401
- tooTipTimeCode: '00:00',
402
- //Hiding playbar animation
403
- focusTimeout: null,
404
- delayUntilHide: 5000,
405
- hideTimer: null,
406
- //Transcript
407
- transcriptEnabled: false,
408
- transcriptToShow: null,
409
- //Fullscreen
410
- fullscreenOn: false
411
- }
412
- },
413
- computed: {
414
- ...mapState(useAppStore, ['getCurrentBrowser', 'getMediaSubtitles']),
415
- //Transcript
416
- hasTranscript() {
417
- if (!this.mediaToPlay) return
418
- return this.mediaToPlay.mTranscript || false
419
- },
420
- //Subtitle
421
- hasSubtitle() {
422
- if (!this.mediaToPlay) return
423
- return this.mediaToPlay.mSubtitles || false
424
- },
425
- subtitlesEnabled() {
426
- if (!this.hasSubtitle) return false
427
- else return this.getMediaSubtitles
428
- },
429
- //mediaContainer (only used for video)
430
- mediaContainer() {
431
- if (!this.mediaToPlay) return
432
- return this.mediaToPlay.mMediaContainer
433
- },
434
-
435
- //MediaDuration :Number (used for tooltip)
436
- mediaDuration() {
437
- return this.mediaToPlay.mElement.duration
438
- ? this.mediaToPlay.mElement.duration
439
- : 0
440
- },
441
- //Labels
442
- fullscreenLabel() {
443
- let label = null
444
- if (this.fullscreenOn) label = `${this.$t('button.full_screen_off')}`
445
- else label = `${this.$t('button.full_screen_on')}`
446
- return label
447
- },
448
- transcriptLabel() {
449
- let label = null
450
- if (this.transcriptEnabled) label = `${this.$t('button.transcript_off')}`
451
- else label = `${this.$t('button.transcript_on')}`
452
- return label
453
- },
454
- ccLabel() {
455
- let label = null
456
- if (this.subtitlesEnabled) label = `${this.$t('button.subtitle_off')}`
457
- else label = `${this.$t('button.subtitle_on')}`
458
- return label
459
- }
460
- },
461
- mounted() {
462
- //initialize media $refs
463
- this.initializeMediaElm()
464
- //initialize video $refs
465
- if (this.mediaToPlay.mType === 'video') this.initializeVideoElm()
466
-
467
- //Set eventlistener for media
468
- this.setMediaHandlers()
469
- //Set eventlistener for video
470
- if (this.mediaToPlay.mType === 'video') this.setVideoHandlers()
471
-
472
- //Set label for ProgressBar
473
- this.setProgressBarA11Y()
474
- //Set initial volume and duration
475
- this.updateVolumeLevel()
476
-
477
- //update the the information of the media element linked to this play bar. playbar instance will be added to the media info
478
- this.updateCurrentMediaElements({
479
- id: this.mediaToPlay.id,
480
- ...this.$parent.$refs
481
- })
482
- },
483
- beforeUnmount() {
484
- if (this.isPlaying) {
485
- this.togglePlay()
486
- }
487
- this.removeMediaHandlers()
488
-
489
- if (this.mediaToPlay.mType === 'video') this.removeVideoHandlers()
490
-
491
- if (this.firefoxTrack) {
492
- this.firefoxTrack.removeEventListener('cuechange', this.fireFoxMoveCC)
493
- }
494
- //this.$bus.$off('play-media', this.handleMediaControls)
495
- this.$bus.$off('transcript-hidden', this.resetTranscript)
496
- return this.$bus.$off('close-sidebar', 'ctxTranscript')
497
- },
498
- methods: {
499
- ...mapActions(useAppStore, [
500
- 'setMediaSubtitles',
501
- 'updateCurrentMediaElements'
502
- ]),
503
- /**
504
- * @description - Set the DOM elements specific for video
505
- */
506
- initializeVideoElm() {
507
- //SeekTooltip
508
- this.seekTooltip = this.$refs['$seek-tooltip']
509
- //Transcript
510
- this.$bus.$on('transcript-hidden', this.resetTranscript)
511
- this.setTranscript()
512
- },
513
-
514
- /** "@description -update the information of the seek tooltip*/
515
- updateSeekTooltip(evt) {
516
- //Elm progress Bar
517
- const progressBar = this.progressBar
518
- //progressBar position on the page
519
- const left = progressBar.getBoundingClientRect().left
520
- //Mouse position versus progressBar
521
- const hoverPos = evt.clientX - left
522
- //Get progressBar offsetWidth
523
- const width = progressBar.offsetWidth
524
-
525
- //Define the value of the skip tooltip position
526
- let offsetX = null
527
-
528
- switch (true) {
529
- case hoverPos < 15:
530
- offsetX = 20
531
- break
532
- case hoverPos > width - 55:
533
- offsetX = width - 55
534
- break
535
- default:
536
- offsetX = hoverPos
537
- }
538
-
539
- this.seekTooltip.style.left = `${offsetX}px` // update visual position of tooltip
540
-
541
- //Get % of the cursor hover position/progressBar width
542
- let percentage = 0
543
- if (hoverPos > width) percentage = 100
544
- if (hoverPos < 0) percentage = 0
545
- else {
546
- percentage = hoverPos / width
547
- }
548
- let skipTo = percentage * this.mediaDuration
549
- //Update the Tooltip time
550
- this.tooTipTimeCode = this.$helper.formatTime(skipTo)
551
- },
552
- /**
553
- * @description - handle all the listeners for videos
554
- */
555
- setVideoHandlers() {
556
- //Tooltip
557
- this.progressArea.addEventListener('mousemove', this.updateSeekTooltip)
558
- //Fullscreen
559
- this.mediaContainer.addEventListener(
560
- 'fullscreenchange',
561
- this.updateFullScreenState
562
- )
563
- //Show controls
564
- this.mediaContainer.addEventListener('mousemove', this.showControls)
565
- },
566
- /**
567
- * @description - Unset all the listeners (video)
568
- */
569
- removeVideoHandlers() {
570
- //Tooltip
571
- this.progressArea.removeEventListener('mousemove', this.updateSeekTooltip)
572
- //Fullscreen
573
- this.mediaContainer.removeEventListener(
574
- 'fullscreenchange',
575
- this.updateFullScreenState
576
- )
577
- //Show controls
578
- this.mediaContainer.removeEventListener('mousemove', this.showControls)
579
- },
580
- /**
581
- /* @description - handle the play /pause of the media and the playback video animation
582
- */
583
- handlePlayVideo(type = null) {
584
- //Play/pause
585
- this.togglePlay()
586
- //Playbar animation (show/hide)
587
- this.isPlaying ? this.hideControls() : this.showControls()
588
-
589
- //Playback animation (only the first play click)
590
- if (this.playClicked && !type) {
591
- this.playBackAnim = false
592
- return
593
- }
594
- //this.runPlaybackAnimation()
595
- this.playBackAnim = true
596
- this.playClicked = true
597
- // Should indicate with media is playing and set it in the store
598
- },
599
- /**
600
- * @description - Toggle the state of the screen
601
- * Set or unset the media in full screen
602
- */
603
- toggleFullScreen() {
604
- const fullscreenElement = this.mediaContainer
605
- if (document.fullscreenElement) {
606
- // exitFullscreen is only available on the Document object.
607
- document.exitFullscreen()
608
- } else {
609
- //fullscreen is available on Element.
610
- fullscreenElement.requestFullscreen()
611
- }
612
- },
613
- /**
614
- * @description update the value of fulls screen state
615
- */
616
- updateFullScreenState() {
617
- this.fullscreenOn = !this.fullscreenOn
618
- },
619
-
620
- fireFoxMoveCC() {
621
- if (this.firefoxTrack.activeCues[0]) {
622
- this.firefoxTrack.activeCues[0].line = 12
623
- }
624
- },
625
- /**
626
- * @description show the subtitle
627
- */
628
- showSubtitles() {
629
- this.mediaElement.textTracks[0].mode = 'showing'
630
- this.setMediaSubtitles(true)
631
- if (this.getCurrentBrowser == 'Firefox') {
632
- let video = document.getElementsByTagName('video')[0]
633
- let tracks = video.textTracks
634
- this.firefoxTrack = tracks[0]
635
-
636
- this.firefoxTrack.addEventListener('cuechange', this.fireFoxMoveCC)
637
- }
638
- },
639
-
640
- hideSubtitles() {
641
- this.mediaElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
642
- this.setMediaSubtitles(false)
643
- },
644
- toggleViewSubtitle() {
645
- if (this.subtitlesEnabled) return this.hideSubtitles()
646
- if (!this.subtitlesEnabled) return this.showSubtitles()
647
- },
648
- /**
649
- * @description method to show or hide the transcipt.
650
- * @summary get toggle the value of transcriptEnabled and get content from the transcript file
651
- * and transfer it to Module for display.
652
- * @fires 'open-sidebar' to AppBaseModule
653
- * @fires 'close-sidebar' to AppBaseModule
654
- */
655
- toggleViewTranscript() {
656
- this.transcriptEnabled = !this.transcriptEnabled
657
-
658
- if (this.transcriptEnabled && this.transcriptToShow) {
659
- //this.$bus.$emit('resize-media', 'sm')
660
- //Resize video container
661
- this.$emit('resize-video', 'sm')
662
- //Open sidebar with the transcript
663
- return this.$bus.$emit('open-sidebar', {
664
- ctx: 'ctxTranscript',
665
- e: this.transcriptToShow,
666
- w: this.pbContainer
667
- })
668
- }
669
-
670
- // Send close signal for the side bar when transcipt state is not enabled
671
- if (!this.transcriptEnabled) {
672
- //this.$bus.$emit('resize-media', 'lg')
673
- //Resize video container
674
- this.$emit('resize-video', 'lg')
675
- //Open sidebar with the transcript
676
- return this.$bus.$emit('close-sidebar', 'ctxTranscript')
677
- }
678
- },
679
- /**
680
- * @description Fetching method for transcript
681
- * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
682
- * and return its content for display
683
- * @return {String} HTML string
684
- */
685
- async fetchTranscript() {
686
- try {
687
- // const tFile = 'exemple_transcript2.html'
688
- const { mTranscript } = this.mediaToPlay
689
- const tFile = mTranscript
690
-
691
- if (!tFile) throw new Error('Missing transcript File!')
692
-
693
- // validate file types. Only .html are allowed
694
- if (!tFile.endsWith('.html'))
695
- throw new Error(
696
- 'Invalid valid transcript file. \n Expecting .html file'
697
- )
698
-
699
- const fileUrl = `./${tFile}`
700
- const res = await axios.get(fileUrl, { responseType: 'blob' })
701
- const content = await res.data.text()
702
-
703
- return content
704
- } catch (err) {
705
- console.warn("YOU'VE GOT AN ERROR!\n", err)
706
- }
707
- },
708
- /** ******************* Rendering Methods****************************** */
709
-
710
- /**
711
- * @Description Method to set the transcription that need to be displayed
712
- */
713
- async setTranscript() {
714
- if (!this.hasTranscript) return (this.transcriptToShow = null)
715
-
716
- this.transcriptToShow = await this.fetchTranscript()
717
- },
718
-
719
- /**
720
- * @description Method to reset the transcript state
721
- */
722
- resetTranscript(content) {
723
- this.transcriptEnabled = false
724
- },
725
-
726
- /**
727
- * @description Show the media controler */
728
- showControls() {
729
- this.showControlsValue = true
730
- if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
731
-
732
- this.hideControls()
733
- },
734
- /**
735
- * @description Hide the media after the video start playing
736
- */
737
-
738
- hideControls() {
739
- if (this.mediaElement.paused) return
740
-
741
- this.hideTimer = setTimeout(() => {
742
- this.showControlsValue = false
743
- }, this.delayUntilHide)
744
- }
745
- }
746
- }
747
- </script>
748
- <style lang="scss" scoped>
749
- .pb-container.video {
750
- position: absolute;
751
- height: 100%;
752
- width: 100%;
753
- justify-content: center;
754
- display: flex;
755
- }
756
-
757
- .playback-button {
758
- position: absolute;
759
- top: 0;
760
- left: 0;
761
- width: 100%;
762
- height: 100%;
763
- display: flex;
764
- flex-flow: column wrap;
765
- justify-content: center;
766
- align-items: center;
767
- background-color: transparent;
768
- .playback-wrapper {
769
- height: 64px;
770
- width: 64px;
771
- display: flex;
772
- justify-content: center;
773
- align-items: center;
774
- border-radius: 50%;
775
-
776
- svg {
777
- height: 32px;
778
- width: 26.29px;
779
- &.play-icon {
780
- margin-left: 11%;
781
- }
782
- }
783
- }
784
- }
785
- //Wrapper
786
- .pb-wrapper {
787
- width: 100%;
788
- display: flex;
789
- flex-direction: column;
790
- position: absolute;
791
- bottom: 0;
792
- opacity: 0;
793
- z-index: 2;
794
- &.show-controls {
795
- opacity: 1;
796
- transition: opacity 0.35s ease-in-out;
797
- }
798
-
799
- //Wrapper timeline
800
- .pb-timeline {
801
- --progress-bar-height: 6px;
802
- position: relative;
803
- width: 100%;
804
- height: var(--progress-bar-height);
805
- .progress-area {
806
- width: 100%;
807
- height: 400%; //Add contact surface of click on progressBar
808
- position: relative;
809
- top: -150%;
810
- display: flex;
811
- cursor: pointer;
812
- align-items: center;
813
- z-index: 8; //z-index on top of the video elm
814
- &:hover {
815
- .seek-tooltip {
816
- opacity: 1;
817
- }
818
- }
819
-
820
- //Progress bar
821
- .pb-progress-bar {
822
- --progress-bar-border-height: 1px;
823
- cursor: pointer;
824
- width: 100%;
825
- height: var(--progress-bar-height);
826
- //background-color: transparent;
827
- overflow: visible;
828
- &:focus {
829
- .progress-thumb {
830
- }
831
- }
832
- }
833
-
834
- .progress-indicator {
835
- width: 0;
836
- height: 100%;
837
- position: relative;
838
- user-select: none;
839
-
840
- &.progress-animation {
841
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
842
- transition-duration: 0.01s;
843
- transition-property: all;
844
- transition: width 0.1s linear;
845
- }
846
- }
847
- //Custom thumb
848
- .progress-thumb {
849
- //Cliquable thumb
850
- --progress-thumb-size: 30px;
851
- width: var(--progress-thumb-size);
852
- height: var(--progress-thumb-size);
853
- position: absolute;
854
-
855
- right: calc(var(--progress-thumb-size) / -2);
856
- top: calc(
857
- -1 * (
858
- var(--progress-thumb-size) / 2 - var(--progress-bar-height) / 2
859
- ) - var(--progress-bar-border-height) / 2
860
- );
861
- display: flex;
862
- align-items: center;
863
- justify-content: center;
864
- &::after {
865
- content: '';
866
- //Visible thumb
867
- height: 16px;
868
- width: 16px;
869
- //Colors and styling
870
- border-radius: 50%;
871
- //background-color: var(--primary700);
872
- }
873
- }
874
-
875
- &:hover {
876
- .seek-tooltip {
877
- //tooltip visible only when hovering progress-area
878
- opacity: 1;
879
- }
880
- }
881
-
882
- .seek-tooltip {
883
- position: absolute;
884
- top: -30px;
885
- opacity: 0;
886
- user-select: none;
887
- border-radius: 1px;
888
- padding: 2px;
889
- }
890
- }
891
- }
892
-
893
- //Controls
894
- .pb-controls {
895
- padding: 12px 32px;
896
- @media screen and (max-width: 800px) {
897
- padding: 12px 12px;
898
- }
899
- width: 100%;
900
- //height: 76px;
901
- height: auto;
902
- display: flex;
903
- flex-flow: row wrap;
904
- justify-content: space-between;
905
- -webkit-justify-content: space-between;
906
- align-items: center;
907
-
908
- .pb-areas {
909
- display: flex;
910
- flex-flow: row nowrap;
911
- justify-content: center;
912
- align-items: center;
913
-
914
- &.right {
915
- .btn {
916
- margin-right: 24px;
917
-
918
- &:last-child {
919
- margin-right: 0;
920
- }
921
- }
922
- }
923
-
924
- .btn {
925
- position: relative;
926
-
927
- &::before {
928
- content: attr(data-title);
929
- position: absolute;
930
- top: -50px;
931
- right: 0;
932
- margin-left: -35px;
933
- word-break: keep-all;
934
- white-space: pre;
935
- display: none;
936
- opacity: 0;
937
- width: fit-content;
938
- border-radius: 1px;
939
- padding: 2px;
940
- }
941
- //Icons btn specific
942
- &.playbar-btn {
943
- &::before {
944
- left: 0 !important;
945
- margin-left: 0 !important;
946
- }
947
- margin-right: 16px;
948
- //Style
949
- border-radius: 50%;
950
- //Style
951
- &:hover {
952
- &::before {
953
- display: block;
954
- opacity: 1;
955
- }
956
- }
957
- @media screen and (max-width: 800px) {
958
- height: 32px;
959
- width: 32px;
960
- }
961
- svg.play-icon {
962
- margin-left: 11.875%; // 11.8% off center on the design
963
- }
964
- }
965
- &.volume-btn {
966
- margin: 0 20px;
967
- svg.volume-low-icon {
968
- height: 17.75px;
969
- }
970
- }
971
- }
972
- //Manage displayLabel (dynamic class)
973
- button.displayLabel {
974
- &:hover,
975
- &:focus {
976
- &::before {
977
- display: block;
978
- opacity: 1;
979
- }
980
- }
981
- }
982
-
983
- .pb-timer {
984
- width: max-content;
985
- }
986
- //Volume
987
- .volume-controls {
988
- --volumecontrols-height: 22px;
989
- --volume-thumb-size: 16px;
990
- display: flex;
991
- flex-flow: row wrap;
992
- justify-content: center;
993
- position: relative;
994
- height: var(--volumecontrols-height);
995
- &:hover {
996
- //button title //DisplayLabel is a dynamic class
997
- .volume-slider.displayLabel + span {
998
- display: block;
999
- opacity: 1;
1000
- }
1001
- }
1002
- .volume-slider {
1003
- -webkit-appearance: none;
1004
- width: 100px;
1005
- //Responsive
1006
- @media screen and (max-width: 800px) {
1007
- width: 80px;
1008
- }
1009
- border-radius: 24px;
1010
- cursor: pointer;
1011
- position: relative;
1012
- z-index: 2;
1013
- &::-webkit-slider-runnable-track {
1014
- background: transparent;
1015
- }
1016
- //Input slider thumb
1017
- @mixin volume-thumb {
1018
- -webkit-appearance: none;
1019
- width: var(--volume-thumb-size);
1020
- height: var(--volume-thumb-size);
1021
- margin-top: 12px;
1022
- border-radius: 50%;
1023
- position: relative;
1024
- cursor: pointer;
1025
- }
1026
- &::-webkit-slider-thumb {
1027
- @include volume-thumb;
1028
- }
1029
- //button title
1030
- & + span {
1031
- position: absolute;
1032
- top: -62px;
1033
- right: 0;
1034
- margin-left: -35px;
1035
- word-break: keep-all;
1036
- white-space: pre;
1037
- display: none;
1038
- opacity: 0;
1039
- user-select: none;
1040
- //Colors
1041
- //background-color: var(--primary100);
1042
- //color: var(--primary700);
1043
- border-radius: 1px;
1044
- padding: 2px;
1045
- }
1046
- //DisplayLabel is a dynamic class
1047
- &.displayLabel:focus {
1048
- & + span {
1049
- display: block;
1050
- opacity: 1;
1051
- }
1052
- }
1053
- }
1054
-
1055
- .volume-progress {
1056
- --volumeIndicator-height: 9px;
1057
- height: var(--volumeIndicator-height);
1058
- top: calc(
1059
- var(--volumecontrols-height) / 2 - var(--volumeIndicator-height) / 2
1060
- ); //centrer en hauteur
1061
- width: calc(
1062
- 100% - var(--volume-thumb-size) / 2
1063
- ); //Width smaller than the range width to be sure the progress width don't exceed the range width
1064
- position: absolute;
1065
- border-radius: 20px;
1066
- background-repeat: no-repeat;
1067
- background-size: var(--background-size-vs, 0%) 100%; //Js dynamic variable
1068
- //colors
1069
- }
1070
- }
1071
- }
1072
- }
1073
-
1074
- svg {
1075
- //width: 19px;
1076
- //height: 19px;
1077
- //colors
1078
- //stroke: var(--primary700);
1079
- }
1080
-
1081
- button {
1082
- height: 48px;
1083
- width: 48px;
1084
- background-color: transparent;
1085
- padding: 0;
1086
- display: flex;
1087
- justify-content: center;
1088
- align-items: center;
1089
- svg {
1090
- height: 24px;
1091
- @media screen and (max-width: 800px) {
1092
- height: 18px;
1093
- }
1094
- }
1095
- }
1096
- }
1097
-
1098
- //CSS for audio only
1099
- .audio.pb-wrapper {
1100
- border-radius: 4px;
1101
- position: initial;
1102
- .pb-controls {
1103
- padding: 8px 16px;
1104
- .volume-controls {
1105
- margin-left: 8px;
1106
- width: 100%;
1107
- --volume-thumb-size: 12px;
1108
- .volume-slider {
1109
- width: 50px;
1110
- }
1111
- .volume-progress {
1112
- --volumeIndicator-height: 6px;
1113
- }
1114
- }
1115
- }
1116
- .pb-timer {
1117
- margin-right: 20px;
1118
- }
1119
- .pb-timeline {
1120
- .progress-area {
1121
- justify-content: center;
1122
- }
1123
- .pb-progress-bar {
1124
- border-radius: 4px;
1125
- //max-width: 90%;
1126
- }
1127
- }
1128
- .volume-btn {
1129
- margin-left: 16px;
1130
- }
1131
- .pb-areas {
1132
- width: 100%;
1133
- display: grid;
1134
- grid-template-columns:
1135
- min-content
1136
- min-content
1137
- 1fr
1138
- min-content
1139
- min-content;
1140
- align-items: center;
1141
- justify-content: center;
1142
- button {
1143
- height: 24px;
1144
- width: 24px;
1145
- &.playbar-btn {
1146
- margin-right: 8px;
1147
- }
1148
- svg {
1149
- height: 12.25px;
1150
- @media screen and (max-width: 800px) {
1151
- height: 18px;
1152
- }
1153
- }
1154
- &.volume-btn {
1155
- svg {
1156
- height: 16px;
1157
- }
1158
- svg.volume-low-icon {
1159
- height: 14px;
1160
- }
1161
- }
1162
- }
1163
- }
1164
- }
1165
- //Playback button video (to finish)
1166
- .playback-button {
1167
- .playback-wrapper {
1168
- opacity: 0;
1169
- }
1170
- &.initial {
1171
- .playback-wrapper {
1172
- animation-name: initial;
1173
- opacity: 1;
1174
- }
1175
- }
1176
- &.playback-animation {
1177
- .playback-wrapper {
1178
- opacity: 0;
1179
- animation-name: handlePlayBack;
1180
- animation-duration: 0.8s;
1181
- animation-timing-function: ease-in-out;
1182
- }
1183
- }
1184
- }
1185
-
1186
- .audio-media-player {
1187
- .pb-areas {
1188
- .btn {
1189
- &::before {
1190
- top: inherit !important;
1191
- bottom: -50px;
1192
- }
1193
- }
1194
- }
1195
- }
1196
-
1197
- @keyframes handlePlayBack {
1198
- 0% {
1199
- opacity: 0;
1200
- }
1201
- 50% {
1202
- opacity: 1;
1203
- transform: scale(0.95);
1204
- }
1205
- 100% {
1206
- opacity: 0;
1207
- }
1208
- }
1209
-
1210
- @keyframes disappear {
1211
- 0% {
1212
- opacity: 1;
1213
- }
1214
- 100% {
1215
- opacity: 0;
1216
- }
1217
- }
1218
- </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
+ :aria-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
+ :aria-disabled="!hasTranscript || fullscreenOn"
324
+ :class="{
325
+ md_disabled: !hasTranscript || fullscreenOn,
326
+ displayLabel: btnsLabelDisplay['btn-transcript'] === true
327
+ }"
328
+ @click="toggleViewTranscript()"
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
+ :aria-disabled="transcriptEnabled"
354
+ :class="{
355
+ md_disabled: transcriptEnabled,
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 $extendsMedia from '../mixins/$mediaMixins'
380
+ import { mapState, mapActions } from 'pinia'
381
+ import { useAppStore } from '../module/stores/appStore'
382
+ import axios from 'axios'
383
+
384
+ export default {
385
+ mixins: [$extendsMedia],
386
+ props: {
387
+ mediaToPlay: { type: [Object, Boolean], default: false }
388
+ },
389
+ emits: ['resize-video'],
390
+
391
+ data() {
392
+ return {
393
+ id: `plyr_${this.mediaToPlay.id}`,
394
+ //Playback animation
395
+ playClicked: false,
396
+ playBackAnim: true,
397
+ //ProgressSeek
398
+ progressSeek: null,
399
+ //Tooltip
400
+ seekTooltip: null,
401
+ tooTipTimeCode: '00:00',
402
+ //Hiding playbar animation
403
+ focusTimeout: null,
404
+ delayUntilHide: 5000,
405
+ hideTimer: null,
406
+ //Transcript
407
+ transcriptEnabled: false,
408
+ transcriptToShow: null,
409
+ //Fullscreen
410
+ fullscreenOn: false
411
+ }
412
+ },
413
+ computed: {
414
+ ...mapState(useAppStore, ['getCurrentBrowser', 'getMediaSubtitles']),
415
+ //Transcript
416
+ hasTranscript() {
417
+ if (!this.mediaToPlay) return
418
+ return this.mediaToPlay.mTranscript || false
419
+ },
420
+ //Subtitle
421
+ hasSubtitle() {
422
+ if (!this.mediaToPlay) return
423
+ return this.mediaToPlay.mSubtitles || false
424
+ },
425
+ subtitlesEnabled() {
426
+ if (!this.hasSubtitle) return false
427
+ else return this.getMediaSubtitles
428
+ },
429
+ //mediaContainer (only used for video)
430
+ mediaContainer() {
431
+ if (!this.mediaToPlay) return
432
+ return this.mediaToPlay.mMediaContainer
433
+ },
434
+
435
+ //MediaDuration :Number (used for tooltip)
436
+ mediaDuration() {
437
+ return this.mediaToPlay.mElement.duration
438
+ ? this.mediaToPlay.mElement.duration
439
+ : 0
440
+ },
441
+ //Labels
442
+ fullscreenLabel() {
443
+ let label = null
444
+ if (this.fullscreenOn) label = `${this.$t('button.full_screen_off')}`
445
+ else label = `${this.$t('button.full_screen_on')}`
446
+ return label
447
+ },
448
+ transcriptLabel() {
449
+ let label = null
450
+ if (this.transcriptEnabled) label = `${this.$t('button.transcript_off')}`
451
+ else label = `${this.$t('button.transcript_on')}`
452
+ return label
453
+ },
454
+ ccLabel() {
455
+ let label = null
456
+ if (this.subtitlesEnabled) label = `${this.$t('button.subtitle_off')}`
457
+ else label = `${this.$t('button.subtitle_on')}`
458
+ return label
459
+ }
460
+ },
461
+ mounted() {
462
+ //initialize media $refs
463
+ this.initializeMediaElm()
464
+ //initialize video $refs
465
+ if (this.mediaToPlay.mType === 'video') this.initializeVideoElm()
466
+
467
+ //Set eventlistener for media
468
+ this.setMediaHandlers()
469
+ //Set eventlistener for video
470
+ if (this.mediaToPlay.mType === 'video') this.setVideoHandlers()
471
+
472
+ //Set label for ProgressBar
473
+ this.setProgressBarA11Y()
474
+ //Set initial volume and duration
475
+ this.updateVolumeLevel()
476
+
477
+ //update the the information of the media element linked to this play bar. playbar instance will be added to the media info
478
+ this.updateCurrentMediaElements({
479
+ id: this.mediaToPlay.id,
480
+ ...this.$parent.$refs
481
+ })
482
+ },
483
+ beforeUnmount() {
484
+ if (this.isPlaying) {
485
+ this.togglePlay()
486
+ }
487
+ this.removeMediaHandlers()
488
+
489
+ if (this.mediaToPlay.mType === 'video') this.removeVideoHandlers()
490
+
491
+ if (this.firefoxTrack) {
492
+ this.firefoxTrack.removeEventListener('cuechange', this.fireFoxMoveCC)
493
+ }
494
+
495
+ this.$bus.$off('transcript-hidden', this.resetTranscript)
496
+ },
497
+ methods: {
498
+ ...mapActions(useAppStore, [
499
+ 'setMediaSubtitles',
500
+ 'updateCurrentMediaElements'
501
+ ]),
502
+ /**
503
+ * @description - Set the DOM elements specific for video
504
+ */
505
+ initializeVideoElm() {
506
+ //SeekTooltip
507
+ this.seekTooltip = this.$refs['$seek-tooltip']
508
+ //Transcript
509
+ this.$bus.$on('transcript-hidden', this.resetTranscript)
510
+ this.setTranscript()
511
+ },
512
+
513
+ /** "@description -update the information of the seek tooltip*/
514
+ updateSeekTooltip(evt) {
515
+ //Elm progress Bar
516
+ const progressBar = this.progressBar
517
+ //progressBar position on the page
518
+ const left = progressBar.getBoundingClientRect().left
519
+ //Mouse position versus progressBar
520
+ const hoverPos = evt.clientX - left
521
+ //Get progressBar offsetWidth
522
+ const width = progressBar.offsetWidth
523
+
524
+ //Define the value of the skip tooltip position
525
+ let offsetX = null
526
+
527
+ switch (true) {
528
+ case hoverPos < 15:
529
+ offsetX = 20
530
+ break
531
+ case hoverPos > width - 55:
532
+ offsetX = width - 55
533
+ break
534
+ default:
535
+ offsetX = hoverPos
536
+ }
537
+
538
+ this.seekTooltip.style.left = `${offsetX}px` // update visual position of tooltip
539
+
540
+ //Get % of the cursor hover position/progressBar width
541
+ let percentage = 0
542
+ if (hoverPos > width) percentage = 100
543
+ if (hoverPos < 0) percentage = 0
544
+ else {
545
+ percentage = hoverPos / width
546
+ }
547
+ let skipTo = percentage * this.mediaDuration
548
+ //Update the Tooltip time
549
+ this.tooTipTimeCode = this.$helper.formatTime(skipTo)
550
+ },
551
+ /**
552
+ * @description - handle all the listeners for videos
553
+ */
554
+ setVideoHandlers() {
555
+ //Tooltip
556
+ this.progressArea.addEventListener('mousemove', this.updateSeekTooltip)
557
+ //Fullscreen
558
+ this.mediaContainer.addEventListener(
559
+ 'fullscreenchange',
560
+ this.updateFullScreenState
561
+ )
562
+ //Show controls
563
+ this.mediaContainer.addEventListener('mousemove', this.showControls)
564
+ },
565
+ /**
566
+ * @description - Unset all the listeners (video)
567
+ */
568
+ removeVideoHandlers() {
569
+ //Tooltip
570
+ this.progressArea.removeEventListener('mousemove', this.updateSeekTooltip)
571
+ //Fullscreen
572
+ this.mediaContainer.removeEventListener(
573
+ 'fullscreenchange',
574
+ this.updateFullScreenState
575
+ )
576
+ //Show controls
577
+ this.mediaContainer.removeEventListener('mousemove', this.showControls)
578
+ },
579
+ /**
580
+ /* @description - handle the play /pause of the media and the playback video animation
581
+ */
582
+ handlePlayVideo(type = null) {
583
+ //Play/pause
584
+ this.togglePlay()
585
+ //Playbar animation (show/hide)
586
+ this.isPlaying ? this.hideControls() : this.showControls()
587
+
588
+ //Playback animation (only the first play click)
589
+ if (this.playClicked && !type) {
590
+ this.playBackAnim = false
591
+ return
592
+ }
593
+ //this.runPlaybackAnimation()
594
+ this.playBackAnim = true
595
+ this.playClicked = true
596
+ // Should indicate with media is playing and set it in the store
597
+ },
598
+ /**
599
+ * @description - Toggle the state of the screen
600
+ * Set or unset the media in full screen
601
+ */
602
+ toggleFullScreen() {
603
+ const fullscreenElement = this.mediaContainer
604
+ if (document.fullscreenElement) {
605
+ // exitFullscreen is only available on the Document object.
606
+ document.exitFullscreen()
607
+ } else {
608
+ //fullscreen is available on Element.
609
+ fullscreenElement.requestFullscreen()
610
+ }
611
+ },
612
+ /**
613
+ * @description update the value of fulls screen state
614
+ */
615
+ updateFullScreenState() {
616
+ this.fullscreenOn = !this.fullscreenOn
617
+ },
618
+
619
+ fireFoxMoveCC() {
620
+ if (this.firefoxTrack.activeCues[0]) {
621
+ this.firefoxTrack.activeCues[0].line = 12
622
+ }
623
+ },
624
+ /**
625
+ * @description show the subtitle
626
+ */
627
+ showSubtitles() {
628
+ this.mediaElement.textTracks[0].mode = 'showing'
629
+ this.setMediaSubtitles(true)
630
+ if (this.getCurrentBrowser == 'Firefox') {
631
+ let video = document.getElementsByTagName('video')[0]
632
+ let tracks = video.textTracks
633
+ this.firefoxTrack = tracks[0]
634
+
635
+ this.firefoxTrack.addEventListener('cuechange', this.fireFoxMoveCC)
636
+ }
637
+ },
638
+
639
+ hideSubtitles() {
640
+ this.mediaElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
641
+ this.setMediaSubtitles(false)
642
+ },
643
+ toggleViewSubtitle() {
644
+ if (this.subtitlesEnabled) return this.hideSubtitles()
645
+ if (!this.subtitlesEnabled) return this.showSubtitles()
646
+ },
647
+ /**
648
+ * @description method to show or hide the transcipt.
649
+ * @summary get toggle the value of transcriptEnabled and get content from the transcript file
650
+ * and transfer it to Module for display.
651
+ * @fires 'open-sidebar' to AppBaseModule
652
+ * @fires 'close-sidebar' to AppBaseModule
653
+ */
654
+ toggleViewTranscript() {
655
+ this.transcriptEnabled = !this.transcriptEnabled
656
+
657
+ if (this.transcriptEnabled && this.transcriptToShow) {
658
+ //this.$bus.$emit('resize-media', 'sm')
659
+ //Resize video container
660
+ this.$emit('resize-video', 'sm')
661
+ //Open sidebar with the transcript
662
+ return this.$bus.$emit('open-sidebar', {
663
+ ctx: 'ctxTranscript',
664
+ e: this.transcriptToShow,
665
+ w: this.pbContainer
666
+ })
667
+ }
668
+
669
+ // Send close signal for the side bar when transcipt state is not enabled
670
+ if (!this.transcriptEnabled) {
671
+ //this.$bus.$emit('resize-media', 'lg')
672
+ //Resize video container
673
+ this.$emit('resize-video', 'lg')
674
+ //Open sidebar with the transcript
675
+ return this.$bus.$emit('close-sidebar', 'ctxTranscript')
676
+ }
677
+ },
678
+ /**
679
+ * @description Fetching method for transcript
680
+ * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
681
+ * and return its content for display
682
+ * @return {String} HTML string
683
+ */
684
+ async fetchTranscript() {
685
+ try {
686
+ // const tFile = 'exemple_transcript2.html'
687
+ const { mTranscript } = this.mediaToPlay
688
+ const tFile = mTranscript
689
+
690
+ if (!tFile) throw new Error('Missing transcript File!')
691
+
692
+ // validate file types. Only .html are allowed
693
+ if (!tFile.endsWith('.html'))
694
+ throw new Error(
695
+ 'Invalid valid transcript file. \n Expecting .html file'
696
+ )
697
+
698
+ const fileUrl = `./${tFile}`
699
+ const res = await axios.get(fileUrl, { responseType: 'blob' })
700
+ const content = await res.data.text()
701
+
702
+ return content
703
+ } catch (err) {
704
+ console.warn("YOU'VE GOT AN ERROR!\n", err)
705
+ }
706
+ },
707
+ /** ******************* Rendering Methods****************************** */
708
+
709
+ /**
710
+ * @Description Method to set the transcription that need to be displayed
711
+ */
712
+ async setTranscript() {
713
+ if (!this.hasTranscript) return (this.transcriptToShow = null)
714
+
715
+ this.transcriptToShow = await this.fetchTranscript()
716
+ },
717
+
718
+ /**
719
+ * @description Method to reset the transcript state
720
+ */
721
+ resetTranscript(content) {
722
+ this.transcriptEnabled = false
723
+ },
724
+
725
+ /**
726
+ * @description Show the media controler */
727
+ showControls() {
728
+ this.showControlsValue = true
729
+ if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
730
+
731
+ this.hideControls()
732
+ },
733
+ /**
734
+ * @description Hide the media after the video start playing
735
+ */
736
+
737
+ hideControls() {
738
+ if (this.mediaElement.paused) return
739
+
740
+ this.hideTimer = setTimeout(() => {
741
+ this.showControlsValue = false
742
+ }, this.delayUntilHide)
743
+ }
744
+ }
745
+ }
746
+ </script>
747
+ <style lang="scss" scoped>
748
+ .pb-container.video {
749
+ position: absolute;
750
+ height: 100%;
751
+ width: 100%;
752
+ justify-content: center;
753
+ display: flex;
754
+ }
755
+
756
+ .playback-button {
757
+ position: absolute;
758
+ top: 0;
759
+ left: 0;
760
+ width: 100%;
761
+ height: 100%;
762
+ display: flex;
763
+ flex-flow: column wrap;
764
+ justify-content: center;
765
+ align-items: center;
766
+ background-color: transparent;
767
+ .playback-wrapper {
768
+ height: 64px;
769
+ width: 64px;
770
+ display: flex;
771
+ justify-content: center;
772
+ align-items: center;
773
+ border-radius: 50%;
774
+
775
+ svg {
776
+ height: 32px;
777
+ width: 26.29px;
778
+ &.play-icon {
779
+ margin-left: 11%;
780
+ }
781
+ }
782
+ }
783
+ }
784
+ //Wrapper
785
+ .pb-wrapper {
786
+ width: 100%;
787
+ display: flex;
788
+ flex-direction: column;
789
+ position: absolute;
790
+ bottom: 0;
791
+ opacity: 0;
792
+ z-index: 2;
793
+ &.show-controls {
794
+ opacity: 1;
795
+ transition: opacity 0.35s ease-in-out;
796
+ }
797
+
798
+ //Wrapper timeline
799
+ .pb-timeline {
800
+ --progress-bar-height: 6px;
801
+ position: relative;
802
+ width: 100%;
803
+ height: var(--progress-bar-height);
804
+ .progress-area {
805
+ width: 100%;
806
+ height: 400%; //Add contact surface of click on progressBar
807
+ position: relative;
808
+ top: -150%;
809
+ display: flex;
810
+ cursor: pointer;
811
+ align-items: center;
812
+ z-index: 8; //z-index on top of the video elm
813
+ &:hover {
814
+ .seek-tooltip {
815
+ opacity: 1;
816
+ }
817
+ }
818
+
819
+ //Progress bar
820
+ .pb-progress-bar {
821
+ --progress-bar-border-height: 1px;
822
+ cursor: pointer;
823
+ width: 100%;
824
+ height: var(--progress-bar-height);
825
+ //background-color: transparent;
826
+ overflow: visible;
827
+ &:focus {
828
+ .progress-thumb {
829
+ }
830
+ }
831
+ }
832
+
833
+ .progress-indicator {
834
+ width: 0;
835
+ height: 100%;
836
+ position: relative;
837
+ user-select: none;
838
+
839
+ &.progress-animation {
840
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
841
+ transition-duration: 0.01s;
842
+ transition-property: all;
843
+ transition: width 0.1s linear;
844
+ }
845
+ }
846
+ //Custom thumb
847
+ .progress-thumb {
848
+ //Cliquable thumb
849
+ --progress-thumb-size: 30px;
850
+ width: var(--progress-thumb-size);
851
+ height: var(--progress-thumb-size);
852
+ position: absolute;
853
+
854
+ right: calc(var(--progress-thumb-size) / -2);
855
+ top: calc(
856
+ -1 * (
857
+ var(--progress-thumb-size) / 2 - var(--progress-bar-height) / 2
858
+ ) - var(--progress-bar-border-height) / 2
859
+ );
860
+ display: flex;
861
+ align-items: center;
862
+ justify-content: center;
863
+ &::after {
864
+ content: '';
865
+ //Visible thumb
866
+ height: 16px;
867
+ width: 16px;
868
+ //Colors and styling
869
+ border-radius: 50%;
870
+ //background-color: var(--primary700);
871
+ }
872
+ }
873
+
874
+ &:hover {
875
+ .seek-tooltip {
876
+ //tooltip visible only when hovering progress-area
877
+ opacity: 1;
878
+ }
879
+ }
880
+
881
+ .seek-tooltip {
882
+ position: absolute;
883
+ top: -30px;
884
+ opacity: 0;
885
+ user-select: none;
886
+ border-radius: 1px;
887
+ padding: 2px;
888
+ }
889
+ }
890
+ }
891
+
892
+ //Controls
893
+ .pb-controls {
894
+ padding: 12px 32px;
895
+ @media screen and (max-width: 800px) {
896
+ padding: 12px 12px;
897
+ }
898
+ width: 100%;
899
+ //height: 76px;
900
+ height: auto;
901
+ display: flex;
902
+ flex-flow: row wrap;
903
+ justify-content: space-between;
904
+ -webkit-justify-content: space-between;
905
+ align-items: center;
906
+
907
+ .pb-areas {
908
+ display: flex;
909
+ flex-flow: row nowrap;
910
+ justify-content: center;
911
+ align-items: center;
912
+
913
+ &.right {
914
+ .btn {
915
+ margin-right: 24px;
916
+
917
+ &:last-child {
918
+ margin-right: 0;
919
+ }
920
+ }
921
+ }
922
+
923
+ .btn {
924
+ position: relative;
925
+
926
+ &::before {
927
+ content: attr(data-title);
928
+ position: absolute;
929
+ top: -50px;
930
+ right: 0;
931
+ margin-left: -35px;
932
+ word-break: keep-all;
933
+ white-space: pre;
934
+ display: none;
935
+ opacity: 0;
936
+ width: fit-content;
937
+ border-radius: 1px;
938
+ padding: 2px;
939
+ }
940
+ //Icons btn specific
941
+ &.playbar-btn {
942
+ &::before {
943
+ left: 0 !important;
944
+ margin-left: 0 !important;
945
+ }
946
+ margin-right: 16px;
947
+ //Style
948
+ border-radius: 50%;
949
+ //Style
950
+ &:hover {
951
+ &::before {
952
+ display: block;
953
+ opacity: 1;
954
+ }
955
+ }
956
+ @media screen and (max-width: 800px) {
957
+ height: 32px;
958
+ width: 32px;
959
+ }
960
+ svg.play-icon {
961
+ margin-left: 11.875%; // 11.8% off center on the design
962
+ }
963
+ }
964
+ &.volume-btn {
965
+ margin: 0 20px;
966
+ svg.volume-low-icon {
967
+ height: 17.75px;
968
+ }
969
+ }
970
+ }
971
+ //Manage displayLabel (dynamic class)
972
+ button.displayLabel {
973
+ &:hover,
974
+ &:focus {
975
+ &::before {
976
+ display: block;
977
+ opacity: 1;
978
+ }
979
+ }
980
+ }
981
+
982
+ .pb-timer {
983
+ width: max-content;
984
+ }
985
+ //Volume
986
+ .volume-controls {
987
+ --volumecontrols-height: 22px;
988
+ --volume-thumb-size: 16px;
989
+ display: flex;
990
+ flex-flow: row wrap;
991
+ justify-content: center;
992
+ position: relative;
993
+ height: var(--volumecontrols-height);
994
+ &:hover {
995
+ //button title //DisplayLabel is a dynamic class
996
+ .volume-slider.displayLabel + span {
997
+ display: block;
998
+ opacity: 1;
999
+ }
1000
+ }
1001
+ .volume-slider {
1002
+ -webkit-appearance: none;
1003
+ width: 100px;
1004
+ //Responsive
1005
+ @media screen and (max-width: 800px) {
1006
+ width: 80px;
1007
+ }
1008
+ border-radius: 24px;
1009
+ cursor: pointer;
1010
+ position: relative;
1011
+ z-index: 2;
1012
+ &::-webkit-slider-runnable-track {
1013
+ background: transparent;
1014
+ }
1015
+ //Input slider thumb
1016
+ @mixin volume-thumb {
1017
+ -webkit-appearance: none;
1018
+ width: var(--volume-thumb-size);
1019
+ height: var(--volume-thumb-size);
1020
+ margin-top: 12px;
1021
+ border-radius: 50%;
1022
+ position: relative;
1023
+ cursor: pointer;
1024
+ }
1025
+ &::-webkit-slider-thumb {
1026
+ @include volume-thumb;
1027
+ }
1028
+ //button title
1029
+ & + span {
1030
+ position: absolute;
1031
+ top: -62px;
1032
+ right: 0;
1033
+ margin-left: -35px;
1034
+ word-break: keep-all;
1035
+ white-space: pre;
1036
+ display: none;
1037
+ opacity: 0;
1038
+ user-select: none;
1039
+ //Colors
1040
+ //background-color: var(--primary100);
1041
+ //color: var(--primary700);
1042
+ border-radius: 1px;
1043
+ padding: 2px;
1044
+ }
1045
+ //DisplayLabel is a dynamic class
1046
+ &.displayLabel:focus {
1047
+ & + span {
1048
+ display: block;
1049
+ opacity: 1;
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ .volume-progress {
1055
+ --volumeIndicator-height: 9px;
1056
+ height: var(--volumeIndicator-height);
1057
+ top: calc(
1058
+ var(--volumecontrols-height) / 2 - var(--volumeIndicator-height) / 2
1059
+ ); //centrer en hauteur
1060
+ width: calc(
1061
+ 100% - var(--volume-thumb-size) / 2
1062
+ ); //Width smaller than the range width to be sure the progress width don't exceed the range width
1063
+ position: absolute;
1064
+ border-radius: 20px;
1065
+ background-repeat: no-repeat;
1066
+ background-size: var(--background-size-vs, 0%) 100%; //Js dynamic variable
1067
+ //colors
1068
+ }
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ svg {
1074
+ //width: 19px;
1075
+ //height: 19px;
1076
+ //colors
1077
+ //stroke: var(--primary700);
1078
+ }
1079
+
1080
+ button {
1081
+ height: 48px;
1082
+ width: 48px;
1083
+ background-color: transparent;
1084
+ padding: 0;
1085
+ display: flex;
1086
+ justify-content: center;
1087
+ align-items: center;
1088
+ svg {
1089
+ height: 24px;
1090
+ @media screen and (max-width: 800px) {
1091
+ height: 18px;
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+
1097
+ //CSS for audio only
1098
+ .audio.pb-wrapper {
1099
+ border-radius: 4px;
1100
+ position: initial;
1101
+ .pb-controls {
1102
+ padding: 8px 16px;
1103
+ .volume-controls {
1104
+ margin-left: 8px;
1105
+ width: 100%;
1106
+ --volume-thumb-size: 12px;
1107
+ .volume-slider {
1108
+ width: 50px;
1109
+ }
1110
+ .volume-progress {
1111
+ --volumeIndicator-height: 6px;
1112
+ }
1113
+ }
1114
+ }
1115
+ .pb-timer {
1116
+ margin-right: 20px;
1117
+ }
1118
+ .pb-timeline {
1119
+ .progress-area {
1120
+ justify-content: center;
1121
+ }
1122
+ .pb-progress-bar {
1123
+ border-radius: 4px;
1124
+ //max-width: 90%;
1125
+ }
1126
+ }
1127
+ .volume-btn {
1128
+ margin-left: 16px;
1129
+ }
1130
+ .pb-areas {
1131
+ width: 100%;
1132
+ display: grid;
1133
+ grid-template-columns:
1134
+ min-content
1135
+ min-content
1136
+ 1fr
1137
+ min-content
1138
+ min-content;
1139
+ align-items: center;
1140
+ justify-content: center;
1141
+ button {
1142
+ height: 24px;
1143
+ width: 24px;
1144
+ &.playbar-btn {
1145
+ margin-right: 8px;
1146
+ }
1147
+ svg {
1148
+ height: 12.25px;
1149
+ @media screen and (max-width: 800px) {
1150
+ height: 18px;
1151
+ }
1152
+ }
1153
+ &.volume-btn {
1154
+ svg {
1155
+ height: 16px;
1156
+ }
1157
+ svg.volume-low-icon {
1158
+ height: 14px;
1159
+ }
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ //Playback button video (to finish)
1165
+ .playback-button {
1166
+ .playback-wrapper {
1167
+ opacity: 0;
1168
+ }
1169
+ &.initial {
1170
+ .playback-wrapper {
1171
+ animation-name: initial;
1172
+ opacity: 1;
1173
+ }
1174
+ }
1175
+ &.playback-animation {
1176
+ .playback-wrapper {
1177
+ opacity: 0;
1178
+ animation-name: handlePlayBack;
1179
+ animation-duration: 0.8s;
1180
+ animation-timing-function: ease-in-out;
1181
+ }
1182
+ }
1183
+ }
1184
+
1185
+ .audio-media-player {
1186
+ .pb-areas {
1187
+ .btn {
1188
+ &::before {
1189
+ top: inherit !important;
1190
+ bottom: -50px;
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ @keyframes handlePlayBack {
1197
+ 0% {
1198
+ opacity: 0;
1199
+ }
1200
+ 50% {
1201
+ opacity: 1;
1202
+ transform: scale(0.95);
1203
+ }
1204
+ 100% {
1205
+ opacity: 0;
1206
+ }
1207
+ }
1208
+
1209
+ @keyframes disappear {
1210
+ 0% {
1211
+ opacity: 1;
1212
+ }
1213
+ 100% {
1214
+ opacity: 0;
1215
+ }
1216
+ }
1217
+ </style>