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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/{.eslintrc.js → .eslintrc.cjs} +81 -86
  4. package/CHANGELOG +364 -364
  5. package/README.md +71 -71
  6. package/bk.scss +117 -0
  7. package/package.json +61 -63
  8. package/src/$locales/en.json +143 -179
  9. package/src/$locales/fr.json +105 -181
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1054 -614
  12. package/src/components/AppBaseButton.vue +87 -63
  13. package/src/components/AppBaseErrorDisplay.vue +438 -420
  14. package/src/components/AppBaseFlipCard.vue +84 -83
  15. package/src/components/AppBaseModule.vue +1673 -1842
  16. package/src/components/AppBasePage.vue +779 -312
  17. package/src/components/AppBasePopover.vue +41 -0
  18. package/src/components/AppCompAudio.vue +234 -0
  19. package/src/components/AppCompBranchButtons.vue +552 -582
  20. package/src/components/AppCompButtonProgress.vue +126 -147
  21. package/src/components/AppCompCarousel.vue +298 -192
  22. package/src/components/AppCompInputCheckBoxNext.vue +195 -0
  23. package/src/components/AppCompInputDropdownNext.vue +159 -0
  24. package/src/components/AppCompInputRadioNext.vue +152 -0
  25. package/src/components/{AppCompInputTextBox.vue → AppCompInputTextNext.vue} +106 -91
  26. package/src/components/AppCompInputTextTableNext.vue +141 -0
  27. package/src/components/AppCompInputTextToFillDropdownNext.vue +230 -0
  28. package/src/components/{AppCompInputTextToFillText.vue → AppCompInputTextToFillNext.vue} +171 -164
  29. package/src/components/AppCompJauge.vue +74 -55
  30. package/src/components/AppCompMenu.vue +413 -209
  31. package/src/components/AppCompMenuItem.vue +228 -174
  32. package/src/components/AppCompNavigation.vue +960 -949
  33. package/src/components/AppCompNoteCall.vue +133 -126
  34. package/src/components/AppCompNoteCredit.vue +292 -164
  35. package/src/components/AppCompPlayBar.vue +1218 -1319
  36. package/src/components/AppCompPlayBarNext.vue +2052 -0
  37. package/src/components/AppCompPlayBarProgress.vue +82 -0
  38. package/src/components/AppCompPopUpNext.vue +503 -0
  39. package/src/components/{AppCompQuiz.vue → AppCompQuizNext.vue} +2904 -2989
  40. package/src/components/AppCompQuizRecall.vue +276 -250
  41. package/src/components/AppCompSVGNext.vue +347 -0
  42. package/src/components/AppCompSettingsMenu.vue +172 -171
  43. package/src/components/AppCompTableOfContent.vue +387 -264
  44. package/src/components/AppCompTranscript.vue +24 -19
  45. package/src/components/AppCompVideoPlayer.vue +368 -336
  46. package/src/components/AppCompViewDisplay.vue +6 -6
  47. package/src/components/BaseModule.vue +72 -67
  48. package/src/composables/useQuiz.js +206 -0
  49. package/src/externalComps/ModuleView.vue +22 -0
  50. package/src/externalComps/SummaryView.vue +91 -0
  51. package/src/main.js +272 -227
  52. package/src/mixins/$mediaMixins.js +819 -0
  53. package/src/mixins/timerMixin.js +155 -156
  54. package/src/module/stores/appStore.js +893 -0
  55. package/src/module/xapi/ADL.js +376 -339
  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 -319
  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 -1890
  81. package/src/module/xapi/xapiStatement.js +444 -444
  82. package/src/plugins/bus.js +8 -3
  83. package/src/plugins/gsap.js +14 -17
  84. package/src/plugins/helper.js +308 -295
  85. package/src/plugins/i18n.js +44 -31
  86. package/src/plugins/idb.js +219 -212
  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 -21
  91. package/src/router/index.js +43 -41
  92. package/src/router/routes.js +312 -337
  93. package/src/shared/generalfuncs.js +210 -188
  94. package/src/shared/validators.js +1069 -249
  95. package/vite.config.js +27 -0
  96. package/.prettierrc.js +0 -5
  97. package/babel.config.js +0 -3
  98. package/src/components/AppBaseDragChoice.vue +0 -91
  99. package/src/components/AppBaseDropZone.vue +0 -112
  100. package/src/components/AppCompBif.vue +0 -120
  101. package/src/components/AppCompDragAndDrop.vue +0 -339
  102. package/src/components/AppCompInputAssociation.vue +0 -332
  103. package/src/components/AppCompInputCheckBox.vue +0 -227
  104. package/src/components/AppCompInputDropdown.vue +0 -184
  105. package/src/components/AppCompInputRadio.vue +0 -169
  106. package/src/components/AppCompInputTextTable.vue +0 -155
  107. package/src/components/AppCompInputTextToFillDropdown.vue +0 -255
  108. package/src/components/AppCompMediaPlayer.vue +0 -397
  109. package/src/components/AppCompPopUp.vue +0 -522
  110. package/src/components/AppCompPopover.vue +0 -27
  111. package/src/components/AppCompSVG.vue +0 -309
  112. package/src/mixins/$pageMixins.js +0 -459
  113. package/src/mixins/$quizMixins.js +0 -456
  114. package/src/module/store.js +0 -895
  115. package/src/plugins/timeManager.js +0 -77
  116. package/src/routes_bckp.js +0 -313
  117. package/src/routes_static.js +0 -344
  118. package/vue.config.js +0 -83
@@ -1,1319 +1,1218 @@
1
- <!-- Add Bref Description of what component does and need
2
- -->
3
- <template>
4
- <div
5
- v-if="mediaToPlay"
6
- class="v-media __pb-wrapper show-controls"
7
- tabindex="0"
8
- >
9
- <!--------------------------------- PLAY-BAR Progress -------------------------------------->
10
- <div ref="$playbar-timeline" class="v-media __pb-timeline" tabindex="0">
11
- <div class="__progress-area">
12
- <div
13
- ref="$progress-bar"
14
- role="progressbar"
15
- class=" __progress-bar"
16
- aria-valuemin="0"
17
- :aria-label="mediaA11Y.label"
18
- :aria-valuemax="mediaA11Y.valMax"
19
- :aria-valuetext="mediaA11Y.valueText"
20
- :max="mediaDuration"
21
- ></div>
22
- <input
23
- ref="$progress-seek"
24
- class="progress-seek"
25
- type="range"
26
- value="0"
27
- min="0"
28
- step="1"
29
- tabindex="-1"
30
- @change="updateProgessBar"
31
- @click="skipInTime"
32
- />
33
- <div id="seek-tooltip" ref="$seek-tooltip" class="seek-tooltip">
34
- {{ tooTipTimeCode }}
35
- </div>
36
- </div>
37
- </div>
38
- <div class="__pb-controls">
39
- <div class="__pb-areas left">
40
- <!--------------------------------- PLAY-BAR Play ------------------------------------>
41
- <app-base-button
42
- id="playbar-play"
43
- ref="$btn-play"
44
- class="v-media playbar-btn"
45
- :aria-label="playLabel"
46
- :data-title="`${playLabel} (k)`"
47
- @click="handlePlay"
48
- >
49
- <svg class="app-icons-svg">
50
- <use v-show="!canReplay && !isPlaying" href="#play-icon" />
51
- <use v-show="isPlaying && !canReplay" href="#pause-icon" />
52
- <use v-show="!isPlaying && canReplay" href="#replay-icon" />
53
- </svg>
54
- </app-base-button>
55
- <!--------------------------------- PLAY-BAR Volume -------------------------------------->
56
- <div
57
- ref="zone-volume"
58
- class="v-media volume-controls"
59
- role="button"
60
- tabindex="0"
61
- :aria-label="volumeLabel"
62
- >
63
- <app-base-button
64
- id="playbar-volume"
65
- ref="$btn-volume"
66
- class="volume-btn"
67
- tabindex="-1"
68
- :aria-label="volumeLabel"
69
- :data-title="`${volumeLabel} (m)`"
70
- @click="toggleMute"
71
- >
72
- <svg class="app-icons-svg">
73
- <use v-show="volumeState == 'muted'" href="#volume-mute-icon" />
74
- <use v-show="volumeState == 'low'" href="#volume-low-icon" />
75
- <use v-show="volumeState == 'high'" href="#volume-high-icon" />
76
- </svg>
77
- </app-base-button>
78
- <input
79
- id="volume-input"
80
- ref="$volume-slider"
81
- value="1"
82
- data-mute="0.5"
83
- type="range"
84
- max="1"
85
- min="0"
86
- step="0.01"
87
- tabindex="-1"
88
- @click="updateVolumeLevel"
89
- />
90
- </div>
91
- <!--------------------------------- PLAY-BAR Time Code ----------------------------------->
92
- <div class="__pb-timer">
93
- <span aria-hidden="true">{{ timecode }} / {{ duration }}</span>
94
- <span class="sr-only">
95
- {{ mediaA11Y.timeCode }} of {{ mediaA11Y.duration }}
96
- </span>
97
- </div>
98
- </div>
99
- <div class="__pb-areas center">
100
- <!--------------------------------- PLAY-BAR CC ------------------------------------------>
101
- <app-base-button
102
- id="btn-subtitles"
103
- ref="$btn-subtitle"
104
- class="btn subtitleBtns"
105
- :aria-label="ccLabel"
106
- :data-title="`${ccLabel} (c)`"
107
- :disabled="!hasSubtitle"
108
- :class="{ md_disabled: !hasSubtitle }"
109
- @click="toggleViewSubtitle"
110
- >
111
- <svg class="app-icons-svg">
112
- <use v-show="hasSubtitle" href="#subtitle-on-icon" />
113
- <use v-show="!hasSubtitle" href="#subtitle-off-icon" />
114
- </svg>
115
- </app-base-button>
116
- <!--------------------------------- PLAY-BAR Transcript ----------------------------------->
117
- <app-base-button
118
- id="btn-transcript"
119
- ref="$btn-transcript"
120
- class="v-media subtitleBtns"
121
- :aria-label="transcriptLabel"
122
- :data-title="`${transcriptLabel} (t)`"
123
- :disabled="!hasTranscript || fullscreenOn"
124
- :class="{ md_disabled: !hasTranscript || fullscreenOn }"
125
- @click="toggleViewTranscript"
126
- >
127
- <svg class="app-icons-svg">
128
- <use v-show="hasTranscript" href="#transcript-on-icon" />
129
- </svg>
130
- </app-base-button>
131
- </div>
132
- <div class="__pb-areas right">
133
- <!--------------------------------- PLAY-BAR FullScreen ----------------------------------->
134
- <app-base-button
135
- id="btn-fullscreen"
136
- ref="$btn-fullscreen"
137
- class="v-media fullscreenBtns"
138
- :aria-label="fullscreenLabel"
139
- :disabled="transcriptEnabled"
140
- :class="{ md_disabled: transcriptEnabled }"
141
- :data-title="`${fullscreenLabel} (f)`"
142
- @click="toggleFullScreen"
143
- >
144
- <b-icon
145
- v-show="!fullscreenOn"
146
- ref="$ic-fullscreen-on"
147
- icon="fullscreen"
148
- aria-hidden="true"
149
- style="color: #ffffff;"
150
- ></b-icon>
151
- <b-icon
152
- v-show="fullscreenOn"
153
- ref="$ic-fullscreen-off"
154
- icon="fullscreen-exit"
155
- aria-hidden="true"
156
- style="color: #ffffff;"
157
- ></b-icon>
158
- </app-base-button>
159
- </div>
160
- </div>
161
- </div>
162
- </template>
163
-
164
- <script>
165
- import { mapGetters } from 'vuex'
166
-
167
- export default {
168
- props: {
169
- mediaToPlay: { type: [Object, Boolean], default: false }
170
- },
171
-
172
- data() {
173
- return {
174
- volumeBtn: null,
175
- volumeSlider: null,
176
- progressBar: null,
177
- progressSeek: null,
178
- playBarTimeline: null,
179
- seekTooltip: null,
180
- tooTipTimeCode: '00:00',
181
- mediaDuration: '',
182
- isPlaying: false,
183
- mediaHandlersSet: false,
184
- duration: '',
185
- timecode: '',
186
- subtitlesEnabled: false,
187
- volDefault: 0.5,
188
- muted: false,
189
- isAnimation: false,
190
- mediaContainer: null,
191
- canReplay: false,
192
- focusTimeout: null,
193
- delayUntilHide: 5000,
194
- hideTimer: null,
195
- transcriptEnabled: false,
196
- transcriptToShow: null,
197
- mediaA11Y: {
198
- label: 'progress',
199
- valMax: null,
200
- valNow: null,
201
- valueText: null,
202
- timeCode: 0,
203
- duration: 0
204
- },
205
- fullscreenOn: false
206
- }
207
- },
208
- computed: {
209
- ...mapGetters([
210
- 'getCurrentPage',
211
- 'getModuleChildren',
212
- 'hasMediaElOrTimeline',
213
- 'getCurrentMediaDuration',
214
- 'getCurrentBrowser',
215
- 'getAutoplayEnabled',
216
- 'getMediaVolume',
217
- 'getModuleInfo',
218
- 'getMediaSubtitles',
219
- 'getUserInteraction'
220
- ]),
221
-
222
- mediaElement() {
223
- if (!this.mediaToPlay) return null
224
-
225
- const { mType, mElement, mTimeline } = this.mediaToPlay
226
- let thisElement = null
227
-
228
- switch (true) {
229
- case mType == 'audio' || mType == 'video':
230
- if (!mElement) return null
231
- thisElement = mElement
232
- break
233
- case mType == 'animation':
234
- if (!mTimeline) return null
235
- thisElement = mTimeline
236
- break
237
- case mType == 'interactiveMedia':
238
- if (!mTimeline || !mElement) return null
239
- thisElement = { mElement, mTimeline } // mediaElement return both the HTMLMediaElement and the animation Timeline
240
- break
241
- }
242
-
243
- return thisElement
244
- },
245
-
246
- hasTranscript() {
247
- if (!this.mediaToPlay) return
248
- return this.mediaToPlay.mTranscript || false
249
- },
250
-
251
- hasSubtitle() {
252
- if (!this.mediaToPlay) return
253
- return this.mediaToPlay.mSubtitles || false
254
- },
255
-
256
- isViewed() {
257
- let userData = this.getUserInteraction
258
- let activityId = this.getCurrentPage.activityRef
259
- let pageId = this.getCurrentPage.id
260
- let viewed = false
261
- if (
262
- userData &&
263
- userData[activityId] &&
264
- userData[activityId][pageId] &&
265
- userData[activityId][pageId].userInteraction &&
266
- userData[activityId][pageId].userInteraction.mediaIsViewed
267
- ) {
268
- viewed = true
269
- }
270
-
271
- return viewed
272
- },
273
-
274
- playLabel() {
275
- let label = null
276
- switch (true) {
277
- case this.isPlaying:
278
- label = this.$t('button.pause')
279
- break
280
-
281
- case !this.isPlaying && this.canReplay:
282
- label = this.$t('button.replay')
283
- break
284
-
285
- default:
286
- label = this.$t('button.play')
287
- }
288
- return label
289
- },
290
-
291
- volumeLabel() {
292
- let label = null
293
- if (this.muted) {
294
- label = `${this.$t('button.unmute')}`
295
- } else {
296
- label = `${this.$t('button.mute')}`
297
- }
298
- return label
299
- },
300
-
301
- fullscreenLabel() {
302
- let label = null
303
- if (this.fullscreenOn) label = `${this.$t('button.full_screen_off')}`
304
- else label = `${this.$t('button.full_screen_on')}`
305
- return label
306
- },
307
-
308
- transcriptLabel() {
309
- let label = null
310
- if (this.transcriptEnabled) label = `${this.$t('button.transcript_off')}`
311
- else label = `${this.$t('button.transcript_on')}`
312
- return label
313
- },
314
-
315
- ccLabel() {
316
- let label = null
317
- if (this.subtitlesEnabled) label = `${this.$t('button.subtitle_off')}`
318
- else label = `${this.$t('button.subtitle_on')}`
319
- return label
320
- },
321
-
322
- volumeState() {
323
- let state = null
324
- switch (true) {
325
- case this.muted || this.volDefault <= 0:
326
- state = 'muted'
327
- break
328
-
329
- case !this.muted && this.volDefault <= 0.5:
330
- state = 'low'
331
- break
332
- case !this.muted && this.volDefault > 0.5:
333
- state = 'high'
334
- }
335
-
336
- return state
337
- },
338
-
339
- displayVol() {
340
- let test = Math.floor(this.volDefault * 100)
341
- return test
342
- }
343
- },
344
-
345
- beforeDestroy() {
346
- this.saveToLrs()
347
- if (this.isPlaying) {
348
- this.togglePlay()
349
- }
350
-
351
- if (this.mediaHandlersSet) {
352
- this.removeMediaHandlers()
353
- }
354
- this.$bus.$off('play-media', this.handleMediaControls)
355
- this.$bus.$off('transcript-hidden', this.resetTranscript)
356
- },
357
-
358
- mounted() {
359
- this.initialize()
360
- },
361
-
362
- methods: {
363
- initialize() {
364
- this.volumeBtn = this.$refs['$btn-volume']
365
- this.volumeSlider = this.$refs['$volume-slider']
366
- this.progressBar = this.$refs['$progress-bar']
367
- this.progressSeek = this.$refs['$progress-seek']
368
- this.playBarTimeline = this.$refs['$playbar-timeline']
369
- this.seekTooltip = document.querySelector('#seek-tooltip')
370
-
371
- this.mediaContainer = document.querySelector('.__media-container')
372
-
373
- this.$bus.$on('transcript-hidden', this.resetTranscript)
374
- this.$bus.$on('play-media', this.handleMediaControls)
375
-
376
- this.setTranscript()
377
- this.setMediaHandlers()
378
- this.updateTimeDisplay()
379
- this.updateVolumeLevel()
380
- },
381
-
382
- /**
383
- * @description Method to handle the keyboard input to controle the media player
384
- *@summary Keyboard actions:
385
- * *** Left-Arrow/ Right-Arrow : move foward/Backward media by 5s
386
- * *** Up-Arrow/ Down-Arrow: Increase/Decrease media volume by 5%
387
- * *** c : show/ hide subtitle
388
- * *** f : enter/ exit fullscreen
389
- * *** k : play/ pause media
390
- * *** m : Mute/ Unmute volume
391
- * *** t : show transcript
392
- */
393
-
394
- handleMediaControls(e) {
395
- this.showControls()
396
- switch (e) {
397
- case 'ArrowLeft':
398
- //Go back 5s in media timeline
399
- this.cursorLeft()
400
- break
401
-
402
- case 'ArrowRight':
403
- //Move forward 5s in media timeline
404
- this.cursorRight()
405
- break
406
-
407
- case 'ArrowUp':
408
- this.updateVolumeLevel('volUP')
409
- break
410
-
411
- case 'ArrowDown':
412
- this.updateVolumeLevel('volDOWN')
413
- break
414
-
415
- case 'm':
416
- this.toggleMute()
417
- break
418
-
419
- case 'f':
420
- //Toggle fullScreen
421
- this.toggleFullScreen()
422
- break
423
-
424
- case 't':
425
- //Toggle transcript view
426
- this.toggleViewTranscript()
427
- break
428
-
429
- case 'c':
430
- //Toggle subtitle view
431
- this.toggleViewSubtitle()
432
- break
433
-
434
- case 'k':
435
- //Play Pause on k bar pressed
436
- this.handlePlay()
437
- break
438
- }
439
- },
440
- setIsViewed() {
441
- let userData = this.getUserInteraction
442
- let activityId = this.getCurrentPage.activityRef
443
- let pageId = this.getCurrentPage.id
444
-
445
- this.$set(
446
- userData[activityId][pageId].userInteraction,
447
- 'mediaIsViewed',
448
- true
449
- )
450
- },
451
- saveToLrs() {
452
- let text
453
- //Defining the text to display for stmt description and definition
454
- switch (this.$i18n.locale) {
455
- case 'fr':
456
- if (this.getModuleInfo.courseID)
457
- text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
458
- else text = `Le ${this.getModuleInfo.id}`
459
- break
460
- case 'en':
461
- if (this.getModuleInfo.courseID)
462
- text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
463
- else text = `The ${this.getModuleInfo.id}`
464
- break
465
- }
466
- //Creating custom statement
467
- const stmt = {
468
- id: (() => {
469
- if (this.getModuleInfo.courseID) return this.getModuleInfo.courseID
470
- else return null
471
- })(),
472
- verb: 'played',
473
- definition: text,
474
- description: text,
475
- extension: [
476
- {
477
- id: 'playbar-values',
478
- content: {
479
- volume: this.volDefault,
480
- subtitlesEnabled: this.subtitlesEnabled
481
- }
482
- }
483
- ]
484
- }
485
- setTimeout(() => {
486
- this.$bus.$emit('send-xapi-statement', stmt) //send xapi statement
487
- }, 1000)
488
- },
489
-
490
- getCurrentTime() {
491
- return this.isAnimation
492
- ? this.mediaToPlay.timeline.time()
493
- : this.mediaElement.currentTime
494
- },
495
- /**
496
- * @description - controls to move the media timeline backward
497
- */
498
- cursorLeft() {
499
- this.setMediaTime(this.getCurrentTime() - 5)
500
- },
501
- /**
502
- * @description - control to move the media timeline forwrad
503
- */
504
- cursorRight() {
505
- this.setMediaTime(this.getCurrentTime() + 5)
506
- },
507
-
508
- updateDuration() {
509
- if (this.isAnimation) {
510
- this.duration = this.$helper.formatTime(
511
- this.mediaToPlayToPlay.timeline.totalDuration()
512
- )
513
- } else {
514
- this.duration = Number.isNaN(this.mediaElement.duration)
515
- ? '--:--'
516
- : this.$helper.formatTime(this.mediaElement.duration)
517
- }
518
- },
519
-
520
- updateTimecode() {
521
- this.timecode = this.isAnimation
522
- ? this.$helper.formatTime(this.mediaToPlay.timeline.time())
523
- : this.$helper.formatTime(this.mediaElement.currentTime)
524
- },
525
-
526
- updateTimeDisplay() {
527
- this.updateTimecode()
528
- this.updateDuration()
529
- },
530
-
531
- /** "@description -update the information of the seek tooltip*/
532
- updateSeekTooltip(evt) {
533
- let skipTo = Math.round(
534
- (evt.offsetX / evt.target.clientWidth) * this.mediaElement.duration
535
- )
536
- if (skipTo == '-0') skipTo = '0'
537
- this.tooTipTimeCode = this.$helper.formatTime(skipTo) //Update the Tooltip timecode
538
- this.playBarTimeline.setAttribute('data-seek', skipTo)
539
-
540
- let offsetX = null
541
-
542
- //Define the value of the skip tooltip position
543
- switch (true) {
544
- case evt.offsetX < 15:
545
- offsetX = 20
546
- break
547
- case evt.offsetX > evt.target.clientWidth - 15:
548
- offsetX = evt.target.clientWidth - 20
549
- break
550
- default:
551
- offsetX = evt.offsetX
552
- }
553
-
554
- this.seekTooltip.style.left = `${offsetX}px` // update visual position of tooltip
555
- },
556
- /**
557
- * @description - handle all the listeners
558
- */
559
- setMediaHandlers() {
560
- if (this.mediaElement) {
561
- this.mediaDuration = Math.round(this.mediaElement.duration)
562
- this.$bus.$on('update-media-duration', this.updateTimeDisplay)
563
-
564
- this.progressSeek.setAttribute('max', this.mediaDuration)
565
- this.progressSeek.addEventListener('input', this.skipInTime)
566
- this.progressBar.setAttribute('max', this.mediaDuration)
567
- this.volumeSlider.addEventListener('input', this.updateVolumeLevel)
568
- this.volumeSlider.setAttribute('value', this.volDefault)
569
-
570
- this.mediaElement.addEventListener('timeupdate', this.updateMediaTime)
571
- this.mediaElement.addEventListener('ended', this.mediaEnded)
572
- this.mediaElement.addEventListener(
573
- 'volumechange',
574
- this.updateVolumeSlider
575
- )
576
-
577
- this.playBarTimeline.addEventListener(
578
- 'mousemove',
579
- this.updateSeekTooltip
580
- )
581
-
582
- this.mediaContainer.addEventListener(
583
- 'fullscreenchange',
584
- this.updateFullScreenState
585
- )
586
-
587
- this.mediaContainer.addEventListener('mousemove', this.showControls)
588
- }
589
- this.mediaHandlersSet = true
590
- this.mediaElement.focus()
591
- },
592
- /**
593
- * @description - Unset all the listeners
594
- */
595
- removeMediaHandlers() {
596
- if (this.mediaElement) {
597
- this.$bus.$off('update-media-duration', this.updateTimeDisplay)
598
- this.progressSeek.removeEventListener('input', this.skipInTime)
599
- this.volumeSlider.removeEventListener('input', this.updateVolumeLevel)
600
- this.mediaElement.removeEventListener(
601
- 'timeupdate',
602
- this.updateMediaTime
603
- )
604
- this.mediaElement.removeEventListener(
605
- 'volumechange',
606
- this.updateVolumeSlider
607
- )
608
- this.mediaElement.removeEventListener('ended', this.mediaEnded)
609
- this.playBarTimeline.removeEventListener(
610
- 'mousemove',
611
- this.updateSeekTooltip
612
- )
613
- this.mediaContainer.removeEventListener(
614
- 'fullscreenchange',
615
- this.updateFullScreenState
616
- )
617
- this.mediaContainer.removeEventListener('mousemove', this.showControls)
618
- }
619
- this.mediaHandlersSet = false
620
- },
621
-
622
- /**
623
- * @description - play or pause the media
624
- */
625
- togglePlay() {
626
- const { mType } = this.mediaToPlay
627
- if (!mType) return
628
-
629
- if (this.canReplay) {
630
- const startTime = this.playBarTimeline.dataset.seek
631
- ? this.playBarTimeline.dataset.seek
632
- : 0
633
- this.setMediaTime(startTime)
634
- }
635
-
636
- switch (mType) {
637
- case 'video' || 'audio':
638
- this.playVideo()
639
-
640
- break
641
-
642
- case 'animation':
643
- this.playAnimation()
644
- break
645
-
646
- case 'InteractiveMidia':
647
- break
648
- }
649
- this.isPlaying = !this.isPlaying
650
- this.canReplay = false
651
- },
652
-
653
- playVideo() {
654
- if (this.isPlaying) {
655
- this.mediaElement.pause()
656
- this.showControls()
657
- } else {
658
- this.mediaElement.play()
659
- this.hideControls()
660
- }
661
- },
662
-
663
- /**
664
- * @description - handle the play /pause of the media and the playback animation
665
- */
666
- handlePlay() {
667
- this.togglePlay()
668
- this.$bus.$emit('hide-playback')
669
- },
670
-
671
- playAnimation() {
672
- if (this.isPlaying) {
673
- this.mediaToPlay.timeline.pause()
674
- this.mediaToPlay.timeline.paused(true)
675
- } else {
676
- this.mediaToPlay.timeline.paused(false)
677
- this.mediaToPlay.timeline.play()
678
- }
679
- },
680
-
681
- setMediaTime(time) {
682
- if (this.isAnimation) {
683
- this.mediaToPlay.timeline.time(time)
684
- } else {
685
- this.mediaElement.currentTime = time
686
- if (this.mediaToPlay.timeline) this.mediaToPlay.timeline.time(time)
687
- }
688
- },
689
-
690
- /** @description -update the position in the video of the seek*/
691
- skipInTime(evt) {
692
- const skipTo = this.playBarTimeline.dataset.seek
693
- ? this.playBarTimeline.dataset.seek
694
- : this.playBarTimeline.value
695
-
696
- this.mediaElement.currentTime = skipTo
697
- let progress = Math.round((skipTo / this.mediaDuration) * 100)
698
- this.progressBar.style.width = `${progress}%`
699
- },
700
-
701
- updateMediaTime() {
702
- this.updateProgessBar()
703
- this.updateTimecode()
704
- this.setMediaA11Y()
705
- },
706
-
707
- /** @description- update the value of the progress to reflect the time in the media */
708
- updateProgessBar() {
709
- const currentTime = Math.round(this.mediaElement.currentTime)
710
- let progress = Math.round((currentTime / this.mediaDuration) * 100)
711
- this.progressBar.style.width = `${progress}%` // set progress bar visual in %
712
- this.progressSeek.value = this.progressBar.value = currentTime
713
- },
714
- /**
715
- * @description - Toggle the state fo the sound
716
- */
717
- toggleMute() {
718
- //Check first the volume of the media
719
- if (this.mediaElement.volume == 0) this.muted = true
720
-
721
- this.muted = !this.muted
722
- this.mediaElement.muted = this.muted // set the muted state of the media
723
-
724
- if (this.muted) {
725
- this.mediaElement.volume = 0
726
- this.volumeSlider.setAttribute('data-volume', this.volumeSlider.value)
727
- this.volumeSlider.value = 0
728
- }
729
- if (!this.muted) {
730
- this.volumeSlider.value = this.volumeSlider.dataset.volume
731
- this.volDefault = this.mediaElement.volume = this.volumeSlider.value
732
- }
733
- },
734
-
735
- /**
736
- * @description control the level of the volume
737
- */
738
- updateVolumeSlider() {
739
- let volume
740
- if (this.mediaElement.muted) {
741
- volume = 0
742
- }
743
- if (!this.mediaElement.muted) {
744
- volume = this.mediaElement.volume
745
- this.muted = false
746
- }
747
-
748
- this.volumeSlider.setAttribute('value', volume)
749
- },
750
-
751
- /**
752
- * @description - Toggle the state of the screen
753
- * Set or unset the media in full screen
754
- */
755
- toggleFullScreen() {
756
- const fullscreenElement = this.mediaContainer
757
- if (document.fullscreenElement) {
758
- // exitFullscreen is only available on the Document object.
759
- document.exitFullscreen()
760
- } else {
761
- //fullscreen is available on Element.
762
- fullscreenElement.requestFullscreen()
763
- }
764
- },
765
- /**
766
- * @description update the value of fulls creen state
767
- */
768
- updateFullScreenState() {
769
- this.fullscreenOn = !this.fullscreenOn
770
- },
771
-
772
- /**
773
- * @description controls the level Of the media volume
774
- * @param d
775
- */
776
- updateVolumeLevel(d = null) {
777
- if (!this.mediaElement) return
778
-
779
- if (this.mediaElement.muted) this.mediaElement.muted = false
780
-
781
- switch (true) {
782
- case d == 'volUP':
783
- //Set volume Up
784
- if (this.mediaElement.volume >= 1) return
785
- this.mediaElement.volume = (this.mediaElement.volume + 0.05).toFixed(
786
- 2
787
- )
788
- this.volumeSlider.value = this.volDefault = this.mediaElement.volume
789
-
790
- break
791
-
792
- case d == 'volDOWN':
793
- //Set volume Down
794
- if (this.mediaElement.volume <= 0) return
795
- this.mediaElement.volume = (this.mediaElement.volume - 0.05).toFixed(
796
- 2
797
- )
798
-
799
- this.volumeSlider.value = this.volDefault = this.mediaElement.volume
800
- break
801
-
802
- default: {
803
- //set volume
804
- this.volDefault = this.mediaElement.volume = this.volumeSlider.value
805
- }
806
- }
807
- },
808
- /**
809
- * @fires mediaPlaybackEnded to AppCompNavigationFull.vue
810
- */
811
- mediaEnded() {
812
- if ((this.mediaToPlay.timeline || this.isAnimation) && this.isPlaying) {
813
- this.mediaToPlay.timeline.pause()
814
- this.mediaToPlay.timeline.paused(true)
815
- }
816
-
817
- this.isPlaying = false
818
- this.canReplay = true
819
- this.playBarTimeline.setAttribute('data-seek', 0)
820
- this.$bus.$emit('mediaPlaybackEnded')
821
- //save the viewed status in userInteraction
822
- if (this.isViewed === false) {
823
- this.setIsViewed()
824
- }
825
- },
826
-
827
- /** @description Controle the Volume of the Media */
828
- setVolume() {
829
- if (this.mediaElement && this.volumeBtn) {
830
- this.mediaElement.volume = this.volDefault = this.volumeBtn.value
831
- this.$store.dispatch('setMediaVolume', this.volumeBtn.value)
832
- }
833
- },
834
-
835
- /**
836
- * @description show the subtitle
837
- */
838
- showSubtitles() {
839
- this.mediaElement.textTracks[0].mode = 'showing'
840
- this.subtitlesEnabled = true
841
- this.$store.dispatch('setMediaSubtitles', true)
842
- if (this.getCurrentBrowser == 'Firefox') {
843
- let video = document.getElementsByTagName('video')[0]
844
- let tracks = video.textTracks
845
- let track = tracks[0]
846
-
847
- track.addEventListener('cuechange', function fireFoxMoveCC() {
848
- if (track.activeCues[0]) {
849
- track.activeCues[0].line = 12
850
- }
851
- })
852
- }
853
- },
854
-
855
- hideSubtitles() {
856
- this.mediaElement.textTracks[0].mode = 'hidden' // value can be 'disabled' also.
857
- this.subtitlesEnabled = false
858
- this.$store.dispatch('setMediaSubtitles', false)
859
- },
860
-
861
- /**
862
- * @description Toggle visibility of the subtitle.
863
- *
864
- */
865
-
866
- toggleViewSubtitle() {
867
- if (this.subtitlesEnabled) return this.hideSubtitles()
868
- if (!this.subtitlesEnabled) return this.showSubtitles()
869
- },
870
- /**
871
- * @description method to show or hide the transcipt.
872
- * @summary get toggle the value of transcriptEnabled and get content from the transcript file
873
- * and transfer it to Module for display.
874
- * @fires 'open-sidebar' to AppBaseModule
875
- * @fires 'close-sidebar' to AppBaseModule
876
- */
877
- toggleViewTranscript() {
878
- this.transcriptEnabled = !this.transcriptEnabled
879
-
880
- if (this.transcriptEnabled && this.transcriptToShow) {
881
- this.$bus.$emit('resize-media', 'sm')
882
- return this.$bus.$emit('open-sidebar', {
883
- ctx: 'ctxTranscript',
884
- e: this.transcriptToShow
885
- })
886
- }
887
-
888
- // Send close signal for the side bar when transcipt state is not enabled
889
- if (!this.transcriptEnabled) {
890
- this.$bus.$emit('resize-media', 'lg')
891
- return this.$bus.$emit('close-sidebar', 'ctxTranscript')
892
- }
893
- },
894
-
895
- /**
896
- * @description Fetching method for transcript
897
- * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
898
- * and return its content for display
899
- * @return {String} HTML string
900
- */
901
- async fetchTranscript() {
902
- try {
903
- // const tFile = 'exemple_transcript2.html'
904
- const { mTranscript } = this.mediaToPlay
905
- const tFile = mTranscript
906
-
907
- if (!tFile) throw new Error('Missing transcript File!')
908
-
909
- // validate file types. Only .html are allowed
910
- if (!tFile.endsWith('.html'))
911
- throw new Error(
912
- 'Invalid valid transcript file. \n Expecting .html file'
913
- )
914
-
915
- const fileUrl = `./${tFile}`
916
- const res = await this.axios.get(fileUrl, { responseType: 'blob' })
917
- const content = await res.data.text()
918
-
919
- return content
920
- } catch (err) {
921
- console.warn("YOU'VE GOT AN ERROR!\n", err)
922
- }
923
- },
924
- /** ******************* Rendering Methods****************************** */
925
-
926
- /** @description - methode to set A11Y support for screen reader on media element (video/audio/animation) */
927
- setMediaA11Y() {
928
- if (!this.hasMedia) return
929
-
930
- const w = this.$i18n.locale === 'en' ? 'at' : 'à'
931
- const dm =
932
- this.$i18n.locale === 'en'
933
- ? `${this.mediaToPlay.mType} duration`
934
- : `durée ${this.mediaToPlay.mType}`
935
-
936
- this.mediaA11Y.valMax = this.isAnimation
937
- ? Math.round(this.mediaToPlay.timeline.totalDuration())
938
- : Math.round(this.mediaElement.duration) // media duration in floating-point
939
-
940
- //get media Current time
941
- this.mediaA11Y.valNow = this.isAnimation
942
- ? Math.round(this.mediaToPlay.timeline.time())
943
- : Math.round(this.mediaElement.currentTime)
944
-
945
- //format text output for screen reader
946
- let currentTime = this.timecode.split(':')
947
- let fullTime = this.duration.split(':')
948
- let hrsTxt, mnTxt, ssTxt, HRSTxt, MNTxt, SSTxt, fTimeTxt, cTimeTxt
949
-
950
- const mediaTitle = this.mediaToPlay.mTitle || this.mediaToPlay.mType
951
-
952
- this.mediaA11Y.label = this.$t('a11y_sr.seek_slider')
953
-
954
- //format string to remove leading 0 digit for time
955
- const formatNum = (n) => {
956
- return n[0] == '0' ? n.substring(1).trim() : n.trim()
957
- }
958
-
959
- const formatMsg = (x, y) => {
960
- let formatStr = this.$t('a11y_sr.range_expression')
961
- .replace('{x}', x)
962
- .replace('{y}', y)
963
-
964
- return formatStr
965
- }
966
-
967
- switch (fullTime.length) {
968
- case 3:
969
- if (!currentTime.length) return
970
-
971
- //format current hours text text with plurialization apply
972
- hrsTxt =
973
- currentTime.length == 3 && parseInt(currentTime[0]) > 0
974
- ? `${formatNum(currentTime[0])} ${this.$tc(
975
- 'a11y_sr.time.hours',
976
- formatNum(currentTime[0])
977
- )}`
978
- : ''
979
-
980
- //format minutes text text with plurialization apply
981
- mnTxt =
982
- parseInt(currentTime[1]) > 0
983
- ? `${formatNum(currentTime[1])} ${this.$tc(
984
- 'a11y_sr.time.minute',
985
- formatNum(currentTime[1])
986
- )}`
987
- : ''
988
-
989
- //format seconds text with plurialization apply
990
- ssTxt =
991
- parseInt(currentTime[2]) > 0
992
- ? `${formatNum(currentTime[2])} ${this.$tc(
993
- 'a11y_sr.time.second',
994
- formatNum(currentTime[2])
995
- )}`
996
- : `0 ${this.$tc('a11y_sr.time.second')}`
997
-
998
- HRSTxt =
999
- parseInt(fullTime[0]) > 0
1000
- ? `${formatNum(fullTime[0])} ${this.$tc(
1001
- 'a11y_sr.time.hours',
1002
- formatNum(fullTime[0])
1003
- )}`
1004
- : ''
1005
-
1006
- MNTxt =
1007
- parseInt(fullTime[1]) > 0
1008
- ? `${formatNum(fullTime[1])} ${this.$tc(
1009
- 'a11y_sr.time.minute',
1010
- formatNum(fullTime[1])
1011
- )}`
1012
- : ''
1013
-
1014
- SSTxt =
1015
- parseInt(fullTime[2]) > 0
1016
- ? `${formatNum(fullTime[2])} ${this.$tc(
1017
- 'a11y_sr.time.second',
1018
- formatNum(fullTime[2])
1019
- )}`
1020
- : ''
1021
-
1022
- cTimeTxt = `${hrsTxt} ${mnTxt} ${ssTxt}`
1023
- fTimeTxt = `${HRSTxt} ${MNTxt} ${SSTxt}`
1024
-
1025
- //Format to show 0 when no value is present
1026
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1027
-
1028
- // Set sr text for timeCode && duration
1029
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1030
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1031
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(
1032
- cTimeTxt,
1033
- fTimeTxt
1034
- )}`
1035
-
1036
- break
1037
- case 2:
1038
- if (!currentTime.length) return
1039
-
1040
- //format current time text
1041
- mnTxt =
1042
- parseInt(currentTime[0]) > 0
1043
- ? `${formatNum(currentTime[0])} ${this.$tc(
1044
- 'a11y_sr.time.minute',
1045
- formatNum(currentTime[0])
1046
- )}`
1047
- : ''
1048
- ssTxt =
1049
- parseInt(currentTime[1]) > 0
1050
- ? `${formatNum(currentTime[1])} ${this.$tc(
1051
- 'a11y_sr.time.second',
1052
- formatNum(currentTime[1])
1053
- )}`
1054
- : `0 ${this.$tc('a11y_sr.time.second')}`
1055
-
1056
- // format full time text
1057
- MNTxt =
1058
- parseInt(fullTime[0]) > 0
1059
- ? `${formatNum(fullTime[0])} ${this.$tc(
1060
- 'a11y_sr.time.minute',
1061
- formatNum(fullTime[0])
1062
- )}`
1063
- : ''
1064
- SSTxt =
1065
- parseInt(fullTime[1]) > 0
1066
- ? `${formatNum(fullTime[1])} ${this.$tc(
1067
- 'a11y_sr.time.second',
1068
- formatNum(fullTime[1])
1069
- )}`
1070
- : ''
1071
-
1072
- cTimeTxt = `${mnTxt} ${ssTxt}`
1073
- fTimeTxt = `${MNTxt} ${SSTxt}`
1074
-
1075
- //Format to show 0 when no value is present
1076
- cTimeTxt = cTimeTxt.trim().length ? cTimeTxt.trim() : 0
1077
-
1078
- // Set sr text for timeCode && duration
1079
- this.mediaA11Y.timeCode = `${this.mediaToPlay.mType} ${w} ${cTimeTxt}`
1080
- this.mediaA11Y.duration = `${dm} ${fTimeTxt}`
1081
-
1082
- this.mediaA11Y.valueText = `${mediaTitle} ${formatMsg(
1083
- cTimeTxt,
1084
- fTimeTxt
1085
- )}`
1086
-
1087
- break
1088
- }
1089
- },
1090
-
1091
- /**
1092
- * @Description Method to set the transcription that need to be displayed
1093
- */
1094
- async setTranscript() {
1095
- if (!this.hasTranscript) return (this.transcriptToShow = null)
1096
-
1097
- this.transcriptToShow = await this.fetchTranscript()
1098
- },
1099
-
1100
- /**
1101
- * @description Method to reset the transcript state
1102
- */
1103
- resetTranscript() {
1104
- this.transcriptEnabled = false
1105
- },
1106
-
1107
- /**
1108
- * @description Show the media controler */
1109
- showControls() {
1110
- const wrapper = document.querySelector('.__pb-wrapper')
1111
- wrapper.classList.add('show-controls')
1112
-
1113
- if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
1114
-
1115
- this.hideControls()
1116
- },
1117
- /**
1118
- * @description Hide the media after the video start playing
1119
- */
1120
-
1121
- hideControls() {
1122
- if (this.mediaElement.paused) return
1123
-
1124
- this.hideTimer = setTimeout(() => {
1125
- const wrapper = document.querySelector('.__pb-wrapper')
1126
- wrapper.classList.remove('show-controls')
1127
- }, this.delayUntilHide)
1128
- }
1129
- }
1130
- }
1131
- </script>
1132
- <style lang="scss" scoped>
1133
- $media-progress-color: #007bff;
1134
-
1135
- * {
1136
- margin: 0;
1137
- padding: 0;
1138
- box-sizing: border-box;
1139
- font-family: 'Poppins', sans-serif;
1140
- }
1141
-
1142
- .__pb-controls,
1143
- .__pb-timer,
1144
- .__pb-areas {
1145
- display: flex;
1146
- align-items: center;
1147
- justify-content: center;
1148
- }
1149
- .__pb-wrapper {
1150
- position: absolute;
1151
- left: 0;
1152
- right: 0;
1153
- z-index: 1;
1154
- opacity: 0;
1155
- bottom: -15px;
1156
- transition: all 0.08s ease;
1157
-
1158
- &::before {
1159
- content: '';
1160
- bottom: 0;
1161
- width: 100%;
1162
- z-index: -1;
1163
- position: absolute;
1164
- height: calc(100% + 35px);
1165
- pointer-events: none;
1166
- background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
1167
- }
1168
-
1169
- .__pb-controls {
1170
- padding: 5px 20px 10px;
1171
-
1172
- .__pb-areas {
1173
- width: 100%;
1174
- &:first-child {
1175
- justify-content: flex-start;
1176
- }
1177
-
1178
- &:last-child {
1179
- justify-content: flex-end;
1180
- }
1181
-
1182
- .volume-controls {
1183
- display: flex;
1184
- align-items: center;
1185
- justify-content: space-between;
1186
- }
1187
-
1188
- .__pb-timer {
1189
- color: #efefef;
1190
- margin-left: 15px;
1191
- font-size: 14px;
1192
- }
1193
- }
1194
-
1195
- button {
1196
- cursor: pointer;
1197
- position: relative;
1198
- margin-right: 27px;
1199
- }
1200
-
1201
- button * {
1202
- pointer-events: none;
1203
- }
1204
-
1205
- button::before {
1206
- content: attr(data-title);
1207
- position: absolute;
1208
- display: none;
1209
- right: 0;
1210
- top: -50px;
1211
- background-color: rgba(28, 28, 28, 0.5);
1212
- color: #fff;
1213
- font-size: 12px;
1214
- font-weight: bold;
1215
- padding: 4px 6px;
1216
- margin-right: -35px;
1217
- word-break: keep-all;
1218
- white-space: pre;
1219
- }
1220
-
1221
- button:hover::before {
1222
- display: inline-block;
1223
- }
1224
- }
1225
- }
1226
-
1227
- .__pb-timeline {
1228
- &:hover {
1229
- .__progress-area {
1230
- #seek-tooltip {
1231
- display: block;
1232
- }
1233
-
1234
- input[type='range']::-webkit-slider-thumb {
1235
- display: block;
1236
- }
1237
- }
1238
- }
1239
- }
1240
- .__pb-timeline .__progress-area {
1241
- height: 5px;
1242
- position: relative;
1243
- background: rgba(255, 255, 255, 0.6);
1244
- }
1245
- .__progress-area #seek-tooltip {
1246
- position: absolute;
1247
- display: none;
1248
- left: 50%;
1249
- top: -25px;
1250
- font-size: 13px;
1251
- color: #fff;
1252
- padding: 0 5px;
1253
- pointer-events: none;
1254
- background-color: rgba(28, 28, 28, 0.5);
1255
- transform: translateX(-50%);
1256
- }
1257
-
1258
- //=======================PRGRESS BAR===================//
1259
- .__progress-area {
1260
- width: 100%;
1261
- height: 5px;
1262
-
1263
- position: absolute;
1264
- top: 0;
1265
-
1266
- .__progress-bar {
1267
- width: 0%;
1268
- height: 100%;
1269
- position: relative;
1270
- white-space: nowrap;
1271
- background-color: $media-progress-color;
1272
- pointer-events: none;
1273
-
1274
- &::before {
1275
- display: none;
1276
- content: '';
1277
- }
1278
- }
1279
-
1280
- .progress-seek {
1281
- width: 100%;
1282
- position: absolute;
1283
- top: 0;
1284
-
1285
- cursor: pointer;
1286
- margin: 0;
1287
- }
1288
-
1289
- input[type='range'] {
1290
- -webkit-appearance: none;
1291
- -moz-appearance: none;
1292
- height: 5px;
1293
- background: transparent;
1294
- cursor: pointer;
1295
- }
1296
-
1297
- input[type='range']::-webkit-slider-thumb {
1298
- -webkit-appearance: none;
1299
- display: none;
1300
- height: 13px;
1301
- width: 13px;
1302
- border-radius: 13px;
1303
- background: $media-progress-color;
1304
- }
1305
- }
1306
- .__pb-areas input {
1307
- height: 4px;
1308
- margin-left: 3px;
1309
- max-width: 75px;
1310
- accent-color: $media-progress-color;
1311
- }
1312
- .__media-container {
1313
- .show-controls {
1314
- opacity: 1;
1315
- bottom: 0;
1316
- transition: all 0.13s ease;
1317
- }
1318
- }
1319
- </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="!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>