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