@telus-uds/components-web 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/CHANGELOG.md +41 -2
  2. package/lib/Autocomplete/Autocomplete.js +393 -0
  3. package/lib/Autocomplete/Loading.js +51 -0
  4. package/lib/Autocomplete/Suggestions.js +81 -0
  5. package/lib/Autocomplete/constants.js +19 -0
  6. package/lib/Autocomplete/dictionary.js +19 -0
  7. package/lib/Autocomplete/index.js +13 -0
  8. package/lib/Breadcrumbs/Breadcrumbs.js +8 -3
  9. package/lib/Callout/Callout.js +3 -0
  10. package/lib/Card/Card.js +180 -0
  11. package/lib/Card/CardContent.js +110 -0
  12. package/lib/Card/CardFooter.js +98 -0
  13. package/lib/Card/index.js +13 -0
  14. package/lib/Countdown/Countdown.js +189 -0
  15. package/lib/Countdown/Segment.js +111 -0
  16. package/lib/Countdown/constants.js +14 -0
  17. package/lib/Countdown/dictionary.js +29 -0
  18. package/lib/Countdown/index.js +13 -0
  19. package/lib/Countdown/types.js +39 -0
  20. package/lib/Countdown/useCountdown.js +40 -0
  21. package/lib/Footnote/Footnote.js +67 -24
  22. package/lib/Modal/ModalContent.js +11 -4
  23. package/lib/OptimizeImage/OptimizeImage.js +127 -0
  24. package/lib/OptimizeImage/index.js +13 -0
  25. package/lib/OptimizeImage/utils/getFallbackUrl.js +18 -0
  26. package/lib/OptimizeImage/utils/getOptimizedUrl.js +32 -0
  27. package/lib/OptimizeImage/utils/hasWebpSupport.js +38 -0
  28. package/lib/OptimizeImage/utils/index.js +31 -0
  29. package/lib/OptimizeImage/utils/isSvgUrl.js +10 -0
  30. package/lib/QuantitySelector/QuantitySelector.js +253 -0
  31. package/lib/QuantitySelector/dictionary.js +33 -0
  32. package/lib/QuantitySelector/index.js +13 -0
  33. package/lib/QuantitySelector/styles.js +40 -0
  34. package/lib/StoryCard/StoryCard.js +244 -0
  35. package/lib/StoryCard/index.js +13 -0
  36. package/lib/TermsAndConditions/ExpandCollapse.js +141 -0
  37. package/lib/TermsAndConditions/TermsAndConditions.js +221 -0
  38. package/lib/TermsAndConditions/dictionary.js +19 -0
  39. package/lib/TermsAndConditions/index.js +15 -0
  40. package/lib/Testimonial/Testimonial.js +226 -0
  41. package/lib/Testimonial/index.js +13 -0
  42. package/lib/Toast/Toast.js +15 -8
  43. package/lib/Video/ControlBar/ControlBar.js +315 -0
  44. package/lib/Video/ControlBar/Controls/VideoButton/VideoButton.js +91 -0
  45. package/lib/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +186 -0
  46. package/lib/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +221 -0
  47. package/lib/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +213 -0
  48. package/lib/Video/MiddleControlButton/MiddleControlButton.js +89 -0
  49. package/lib/Video/Video.js +1072 -0
  50. package/lib/Video/index.js +13 -0
  51. package/lib/Video/videoText.js +62 -0
  52. package/lib/WebVideo/WebVideo.js +170 -0
  53. package/lib/WebVideo/index.js +13 -0
  54. package/lib/baseExports.js +0 -6
  55. package/lib/index.js +91 -1
  56. package/lib/shared/VideoSplash/SplashButton/SplashButton.js +102 -0
  57. package/lib/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +234 -0
  58. package/lib/shared/VideoSplash/VideoSplash.js +86 -0
  59. package/lib/shared/VideoSplash/helpers.js +38 -0
  60. package/lib/utils/index.js +8 -0
  61. package/lib-module/Autocomplete/Autocomplete.js +369 -0
  62. package/lib-module/Autocomplete/Loading.js +38 -0
  63. package/lib-module/Autocomplete/Suggestions.js +64 -0
  64. package/lib-module/Autocomplete/constants.js +5 -0
  65. package/lib-module/Autocomplete/dictionary.js +12 -0
  66. package/lib-module/Autocomplete/index.js +2 -0
  67. package/lib-module/Breadcrumbs/Breadcrumbs.js +8 -3
  68. package/lib-module/Callout/Callout.js +3 -0
  69. package/lib-module/Card/Card.js +158 -0
  70. package/lib-module/Card/CardContent.js +92 -0
  71. package/lib-module/Card/CardFooter.js +80 -0
  72. package/lib-module/Card/index.js +2 -0
  73. package/lib-module/Countdown/Countdown.js +165 -0
  74. package/lib-module/Countdown/Segment.js +94 -0
  75. package/lib-module/Countdown/constants.js +4 -0
  76. package/lib-module/Countdown/dictionary.js +22 -0
  77. package/lib-module/Countdown/index.js +2 -0
  78. package/lib-module/Countdown/types.js +23 -0
  79. package/lib-module/Countdown/useCountdown.js +32 -0
  80. package/lib-module/Footnote/Footnote.js +65 -24
  81. package/lib-module/Modal/ModalContent.js +10 -4
  82. package/lib-module/OptimizeImage/OptimizeImage.js +106 -0
  83. package/lib-module/OptimizeImage/index.js +2 -0
  84. package/lib-module/OptimizeImage/utils/getFallbackUrl.js +8 -0
  85. package/lib-module/OptimizeImage/utils/getOptimizedUrl.js +22 -0
  86. package/lib-module/OptimizeImage/utils/hasWebpSupport.js +32 -0
  87. package/lib-module/OptimizeImage/utils/index.js +4 -0
  88. package/lib-module/OptimizeImage/utils/isSvgUrl.js +3 -0
  89. package/lib-module/QuantitySelector/QuantitySelector.js +232 -0
  90. package/lib-module/QuantitySelector/dictionary.js +26 -0
  91. package/lib-module/QuantitySelector/index.js +2 -0
  92. package/lib-module/QuantitySelector/styles.js +21 -0
  93. package/lib-module/StoryCard/StoryCard.js +220 -0
  94. package/lib-module/StoryCard/index.js +2 -0
  95. package/lib-module/TermsAndConditions/ExpandCollapse.js +120 -0
  96. package/lib-module/TermsAndConditions/TermsAndConditions.js +193 -0
  97. package/lib-module/TermsAndConditions/dictionary.js +12 -0
  98. package/lib-module/TermsAndConditions/index.js +1 -0
  99. package/lib-module/Testimonial/Testimonial.js +204 -0
  100. package/lib-module/Testimonial/index.js +2 -0
  101. package/lib-module/Toast/Toast.js +15 -8
  102. package/lib-module/Video/ControlBar/ControlBar.js +292 -0
  103. package/lib-module/Video/ControlBar/Controls/VideoButton/VideoButton.js +74 -0
  104. package/lib-module/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +167 -0
  105. package/lib-module/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +201 -0
  106. package/lib-module/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +193 -0
  107. package/lib-module/Video/MiddleControlButton/MiddleControlButton.js +72 -0
  108. package/lib-module/Video/Video.js +1042 -0
  109. package/lib-module/Video/index.js +2 -0
  110. package/lib-module/Video/videoText.js +55 -0
  111. package/lib-module/WebVideo/WebVideo.js +144 -0
  112. package/lib-module/WebVideo/index.js +2 -0
  113. package/lib-module/baseExports.js +1 -1
  114. package/lib-module/index.js +10 -0
  115. package/lib-module/shared/VideoSplash/SplashButton/SplashButton.js +85 -0
  116. package/lib-module/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +216 -0
  117. package/lib-module/shared/VideoSplash/VideoSplash.js +65 -0
  118. package/lib-module/shared/VideoSplash/helpers.js +23 -0
  119. package/lib-module/utils/index.js +2 -1
  120. package/package.json +7 -5
  121. package/src/Autocomplete/Autocomplete.jsx +354 -0
  122. package/src/Autocomplete/Loading.jsx +18 -0
  123. package/src/Autocomplete/Suggestions.jsx +52 -0
  124. package/src/Autocomplete/constants.js +6 -0
  125. package/src/Autocomplete/dictionary.js +12 -0
  126. package/src/Autocomplete/index.js +3 -0
  127. package/src/Breadcrumbs/Breadcrumbs.jsx +4 -3
  128. package/src/Callout/Callout.jsx +1 -1
  129. package/src/Card/Card.jsx +170 -0
  130. package/src/Card/CardContent.jsx +88 -0
  131. package/src/Card/CardFooter.jsx +70 -0
  132. package/src/Card/index.js +3 -0
  133. package/src/Countdown/Countdown.jsx +144 -0
  134. package/src/Countdown/Segment.jsx +69 -0
  135. package/src/Countdown/constants.js +4 -0
  136. package/src/Countdown/dictionary.js +22 -0
  137. package/src/Countdown/index.js +3 -0
  138. package/src/Countdown/types.js +23 -0
  139. package/src/Countdown/useCountdown.js +34 -0
  140. package/src/Footnote/Footnote.jsx +73 -23
  141. package/src/Modal/ModalContent.jsx +8 -4
  142. package/src/OptimizeImage/OptimizeImage.jsx +131 -0
  143. package/src/OptimizeImage/index.js +3 -0
  144. package/src/OptimizeImage/utils/getFallbackUrl.js +9 -0
  145. package/src/OptimizeImage/utils/getOptimizedUrl.js +30 -0
  146. package/src/OptimizeImage/utils/hasWebpSupport.js +33 -0
  147. package/src/OptimizeImage/utils/index.js +5 -0
  148. package/src/OptimizeImage/utils/isSvgUrl.js +3 -0
  149. package/src/QuantitySelector/QuantitySelector.jsx +245 -0
  150. package/src/QuantitySelector/dictionary.js +27 -0
  151. package/src/QuantitySelector/index.js +3 -0
  152. package/src/QuantitySelector/styles.js +83 -0
  153. package/src/StoryCard/StoryCard.jsx +198 -0
  154. package/src/StoryCard/index.js +3 -0
  155. package/src/TermsAndConditions/ExpandCollapse.jsx +106 -0
  156. package/src/TermsAndConditions/TermsAndConditions.jsx +161 -0
  157. package/src/TermsAndConditions/dictionary.js +12 -0
  158. package/src/TermsAndConditions/index.js +1 -0
  159. package/src/Testimonial/Testimonial.jsx +169 -0
  160. package/src/Testimonial/index.js +3 -0
  161. package/src/Toast/Toast.jsx +12 -5
  162. package/src/Video/ControlBar/ControlBar.jsx +261 -0
  163. package/src/Video/ControlBar/Controls/VideoButton/VideoButton.jsx +61 -0
  164. package/src/Video/ControlBar/Controls/VideoMenu/VideoMenu.jsx +159 -0
  165. package/src/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.jsx +185 -0
  166. package/src/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.jsx +184 -0
  167. package/src/Video/MiddleControlButton/MiddleControlButton.jsx +64 -0
  168. package/src/Video/Video.jsx +988 -0
  169. package/src/Video/index.js +3 -0
  170. package/src/Video/videoText.js +58 -0
  171. package/src/WebVideo/WebVideo.jsx +131 -0
  172. package/src/WebVideo/index.js +3 -0
  173. package/src/baseExports.js +0 -1
  174. package/src/index.js +10 -0
  175. package/src/shared/VideoSplash/SplashButton/SplashButton.jsx +64 -0
  176. package/src/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.jsx +128 -0
  177. package/src/shared/VideoSplash/VideoSplash.jsx +50 -0
  178. package/src/shared/VideoSplash/helpers.js +27 -0
  179. package/src/utils/index.js +10 -1
  180. package/types/Autocomplete.d.ts +32 -0
  181. package/types/Card.d.ts +45 -0
  182. package/types/ControlBar.d.ts +59 -0
  183. package/types/MiddleControlButton.d.ts +15 -0
  184. package/types/Video.d.ts +39 -0
  185. package/types/VideoButton.d.ts +14 -0
  186. package/types/VideoMenu.d.ts +16 -0
  187. package/types/VideoProgressBar.d.ts +17 -0
  188. package/types/VolumeSlider.d.ts +20 -0
@@ -0,0 +1,988 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+ import fscreen from 'fscreen'
5
+ import { useThemeTokens, selectSystemProps } from '@telus-uds/components-base'
6
+ import Spinner from '../Spinner'
7
+
8
+ import { warn, htmlAttrs } from '../utils'
9
+ import VideoSplash from '../shared/VideoSplash/VideoSplash'
10
+ import MiddleControlButton from './MiddleControlButton/MiddleControlButton'
11
+ import ControlBar from './ControlBar/ControlBar'
12
+ import videoText from './videoText'
13
+
14
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
15
+
16
+ // TODO: replace with the actual spinner component from UDS
17
+ const VideoPlayerContainer = styled.div(({ videoBorder, borderColor }) => ({
18
+ width: '100%',
19
+ outline: 'none',
20
+ position: 'relative',
21
+ border: videoBorder ? `20px solid ${borderColor}` : undefined
22
+ }))
23
+
24
+ const VideoElementContainer = styled.div({
25
+ outline: 'none',
26
+ width: '100%',
27
+ height: '100%',
28
+ fontSize: 0
29
+ })
30
+
31
+ const VideoPlayer = styled.video({
32
+ width: '100%',
33
+ height: '100%'
34
+ })
35
+
36
+ const VideoOverlayContainer = styled.div(({ mouseInactive }) => ({
37
+ width: '100%',
38
+ height: '100%',
39
+ position: 'absolute',
40
+ cursor: mouseInactive ? 'none' : 'pointer'
41
+ }))
42
+
43
+ const VideoSplashContainer = styled.div({
44
+ position: 'absolute',
45
+ width: '100%',
46
+ height: '100%',
47
+ zIndex: 5
48
+ })
49
+
50
+ const VideoOverlayElementContainer = styled.div({
51
+ position: 'absolute',
52
+ zIndex: 4,
53
+ display: 'flex',
54
+ justifyContent: 'center',
55
+ alignItems: 'center',
56
+ width: '100%',
57
+ height: '100%'
58
+ })
59
+
60
+ const Video = (props) => {
61
+ const refVideoPlayer = useRef({})
62
+ const refVideoPlayerContainer = useRef({})
63
+ const refKeyboardInstructions = useRef({})
64
+
65
+ let inactivityTimer = null
66
+ const {
67
+ videoBorder,
68
+ simpleMode,
69
+ copy,
70
+ posterSrc,
71
+ crossOrigin,
72
+ defaultVolume,
73
+ sources,
74
+ tracks,
75
+ defaultDesktopQuality,
76
+ tokens,
77
+ variant = {},
78
+ ...rest
79
+ } = props
80
+
81
+ const { borderColor, pauseIcon, playIcon, replayIcon } = useThemeTokens('Video', tokens, variant)
82
+
83
+ const playerOptions = {
84
+ mouseTimeout: simpleMode ? 750 : 1500, // defined in ms
85
+ keyboardSeekIncrement: 5, // defined in s
86
+ keyboardVolumeIncrement: 0.1, // from 0 to 1
87
+ compactModeThreshold: 700 // in px
88
+ }
89
+
90
+ const [videoPlayerState, setVideoPlayerState] = useState({
91
+ loadedSources: null,
92
+ loadedTracks: null,
93
+ videoLength: 0,
94
+ videoCurrentTime: -1,
95
+ videoBufferEnd: 0,
96
+ videoUnplayed: true,
97
+ videoIsBuffering: false,
98
+ videoCurrentVolume: 1,
99
+ videoIsPlaying: false,
100
+ videoIsMuted: false,
101
+ videoIsFullscreen: false,
102
+ mouseInactive: false,
103
+ videoEnded: false,
104
+ videoQuality: defaultDesktopQuality,
105
+ videoQualityChanged: false,
106
+ selectedTextTrack: -1,
107
+ mouseOnControlBar: false,
108
+ qualityMenuOpen: false,
109
+ captionsMenuOpen: false,
110
+ isMobile: false,
111
+ videoPlayerWidth: 0,
112
+ percentageWatched: 'watched 0%'
113
+ })
114
+
115
+ const generateSources = (videoQuality) => {
116
+ return sources.map((source) => {
117
+ if (source.qualityRank === videoQuality) {
118
+ return React.createElement('source', {
119
+ src: source.source,
120
+ type: source.mediaType,
121
+ key: source.source
122
+ })
123
+ }
124
+
125
+ return undefined
126
+ })
127
+ }
128
+
129
+ const generateTracks = () => {
130
+ return tracks.map((track) => {
131
+ return React.createElement('track', {
132
+ label: track.label,
133
+ kind: track.kind,
134
+ srcLang: track.language,
135
+ src: track.source,
136
+ default: track.isDefault,
137
+ key: track.source
138
+ })
139
+ })
140
+ }
141
+
142
+ const refreshMedia = () => {
143
+ const { defaultMobileQuality } = props
144
+ const { videoUnplayed } = videoPlayerState
145
+
146
+ // Handle mobile check
147
+ const isMobile = navigator && navigator.userAgent.indexOf('Mobi') >= 0
148
+ if (videoUnplayed && isMobile) {
149
+ setVideoPlayerState((prevState) => ({
150
+ ...prevState,
151
+ isMobile,
152
+ videoQuality: isMobile ? defaultMobileQuality : defaultDesktopQuality
153
+ }))
154
+ }
155
+
156
+ // Load media
157
+ setVideoPlayerState((prevState) => ({
158
+ ...prevState,
159
+ loadedSources: generateSources(prevState.videoQuality),
160
+ loadedTracks: tracks && generateTracks()
161
+ }))
162
+ }
163
+
164
+ const percentageBucket = (percentValue) => {
165
+ if (percentValue < 25) {
166
+ return setVideoPlayerState((prevState) => {
167
+ if (prevState !== 'watched 0%') {
168
+ return { ...prevState, percentageWatched: 'watched 0%' }
169
+ }
170
+ return false
171
+ })
172
+ }
173
+ if (percentValue < 50) {
174
+ return setVideoPlayerState((prevState) => {
175
+ if (prevState !== 'watched 25%') {
176
+ return { ...prevState, percentageWatched: 'watched 25%' }
177
+ }
178
+ return false
179
+ })
180
+ }
181
+ if (percentValue < 75) {
182
+ return setVideoPlayerState((prevState) => {
183
+ if (prevState !== 'watched 50%') {
184
+ return { ...prevState, percentageWatched: 'watched 50%' }
185
+ }
186
+ return false
187
+ })
188
+ }
189
+ if (percentValue < 100) {
190
+ return setVideoPlayerState((prevState) => {
191
+ if (prevState !== 'watched 75%') {
192
+ return { ...prevState, percentageWatched: 'watched 75%' }
193
+ }
194
+ return false
195
+ })
196
+ }
197
+ if (percentValue === 100) {
198
+ return setVideoPlayerState((prevState) => {
199
+ if (prevState !== 'watched 100%') {
200
+ return { ...prevState, percentageWatched: 'watched 100%' }
201
+ }
202
+ return false
203
+ })
204
+ }
205
+ return false
206
+ }
207
+
208
+ // required for analytics
209
+ const calculatePercentageWatched = () => {
210
+ const { videoCurrentTime, videoLength, percentageWatched } = videoPlayerState
211
+ const { videoTitle, analyticsTracking } = props
212
+
213
+ let percentValue = (videoCurrentTime / videoLength) * 100
214
+ percentValue = Math.round(percentValue)
215
+
216
+ const previousValue = percentageWatched
217
+ percentageBucket(percentValue)
218
+
219
+ if (previousValue !== percentageWatched) {
220
+ const analyticsObject = { name: 'video tracking', details: videoTitle }
221
+ analyticsObject.action = percentageWatched
222
+ analyticsTracking(analyticsObject)
223
+ }
224
+ }
225
+
226
+ const setPlaying = async (isPlaying) => {
227
+ const videoPlayer = refVideoPlayer.current
228
+ const { analyticsTracking, videoTitle } = props
229
+
230
+ if (isPlaying) {
231
+ await videoPlayer.play()
232
+
233
+ if (analyticsTracking !== undefined && videoTitle) {
234
+ const intervalId = setInterval(calculatePercentageWatched, 300)
235
+ setVideoPlayerState((prevState) => ({ ...prevState, intervalId }))
236
+ }
237
+ } else {
238
+ const { intervalId } = videoPlayerState
239
+
240
+ videoPlayer.pause()
241
+ clearInterval(intervalId)
242
+ }
243
+ }
244
+
245
+ const updateAnalyticsData = () => {
246
+ const { videoTitle, analyticsTracking } = props
247
+ const { videoIsPlaying, percentageWatched } = videoPlayerState
248
+
249
+ const analyticsObject = { name: 'video tracking', details: videoTitle }
250
+ analyticsObject.action = videoIsPlaying ? 'play' : 'pause'
251
+ if (percentageWatched !== 'watched 100%') {
252
+ analyticsTracking(analyticsObject)
253
+ }
254
+ }
255
+
256
+ const setSeek = (seconds) => {
257
+ refVideoPlayer.current.currentTime = seconds
258
+ }
259
+
260
+ const qualitySwitchSeek = () => {
261
+ const { videoCurrentTime } = videoPlayerState
262
+
263
+ // The following setState gets the video length on the splash screen in iOS
264
+ setVideoPlayerState((prevState) => ({
265
+ ...prevState,
266
+ videoLength: refVideoPlayer.current.duration
267
+ }))
268
+
269
+ if (videoCurrentTime > -1) {
270
+ setSeek(videoCurrentTime)
271
+ }
272
+ }
273
+
274
+ const updateVideoTimestamp = () => {
275
+ setVideoPlayerState((prevState) => ({
276
+ ...prevState,
277
+ videoCurrentTime: refVideoPlayer.current.currentTime
278
+ }))
279
+ }
280
+
281
+ const initializeVideoDuration = () => {
282
+ // This will run on every load() call, meaning it will also run when video quality is changed.
283
+ setVideoPlayerState((prevState) => {
284
+ return {
285
+ ...prevState,
286
+ videoLength: refVideoPlayer.current.duration,
287
+ videoBufferEnd:
288
+ refVideoPlayer.current.buffered.length === 0
289
+ ? 0
290
+ : refVideoPlayer.current.buffered.end(refVideoPlayer.current.buffered.length - 1),
291
+ videoUnplayed: refVideoPlayer.current.played.length === 0 && !prevState.videoQualityChanged
292
+ }
293
+ })
294
+
295
+ const { videoQualityChanged, videoIsPlaying } = videoPlayerState
296
+
297
+ // Prevents an IE 11 bug where the video will not continue playing after a quality switch
298
+ if (videoQualityChanged && videoIsPlaying) {
299
+ setPlaying(true)
300
+ }
301
+ }
302
+
303
+ const clearInactivityTimer = () => {
304
+ setVideoPlayerState((prevState) => ({ ...prevState, mouseInactive: false }))
305
+ clearTimeout(inactivityTimer)
306
+ }
307
+
308
+ const setAsBuffering = () => {
309
+ // Prevent IE infinite buffer bug with readyState
310
+ if (refVideoPlayer.current.readyState < 4) {
311
+ clearInactivityTimer()
312
+ setVideoPlayerState((prevState) => ({
313
+ ...prevState,
314
+ videoIsPlaying: false,
315
+ videoIsBuffering: true,
316
+ mouseInactive: true
317
+ }))
318
+ setPlaying(false)
319
+ }
320
+ }
321
+
322
+ const playAfterBuffer = () => {
323
+ const { videoIsBuffering, videoCurrentTime, videoQualityChanged } = videoPlayerState
324
+
325
+ if (videoIsBuffering && videoCurrentTime !== -1 && !videoQualityChanged) {
326
+ setPlaying(true)
327
+ setVideoPlayerState((prevState) => ({ ...prevState, videoIsBuffering: false }))
328
+ }
329
+ }
330
+
331
+ const resetInactivityTimer = () => {
332
+ clearInactivityTimer()
333
+
334
+ const { videoQualityChanged, videoIsPlaying, mouseOnControlBar } = videoPlayerState
335
+
336
+ if (!videoQualityChanged && videoIsPlaying) {
337
+ inactivityTimer = setTimeout(() => {
338
+ if (!mouseOnControlBar) {
339
+ setVideoPlayerState((prevState) => ({ ...prevState, mouseInactive: true }))
340
+ }
341
+ }, playerOptions.mouseTimeout)
342
+ }
343
+ }
344
+
345
+ const setAsPlaying = () => {
346
+ setVideoPlayerState((prevState) => ({
347
+ ...prevState,
348
+ videoIsPlaying: true,
349
+ videoIsBuffering: false,
350
+ videoEnded: false,
351
+ videoUnplayed: false,
352
+ videoQualityChanged: false
353
+ }))
354
+
355
+ const { analyticsTracking } = props
356
+
357
+ if (analyticsTracking) {
358
+ updateAnalyticsData()
359
+ }
360
+
361
+ resetInactivityTimer()
362
+ }
363
+
364
+ const setAsPaused = () => {
365
+ clearInactivityTimer()
366
+ setVideoPlayerState((prevState) => ({ ...prevState, videoIsPlaying: false }))
367
+
368
+ const { analyticsTracking } = props
369
+
370
+ if (analyticsTracking) {
371
+ updateAnalyticsData()
372
+ }
373
+ }
374
+
375
+ const returnFromEndState = () => {
376
+ resetInactivityTimer()
377
+ setVideoPlayerState((prevState) => ({ ...prevState, videoEnded: false }))
378
+ }
379
+
380
+ const setAsEnded = () => {
381
+ setVideoPlayerState((prevState) => ({ ...prevState, videoIsPlaying: false, videoEnded: true }))
382
+ clearInactivityTimer()
383
+ }
384
+
385
+ const updateBufferProgress = () => {
386
+ const { videoCurrentTime, videoQualityChanged } = videoPlayerState
387
+ const videoPlayer = refVideoPlayer.current
388
+ if (videoPlayer && videoPlayer.readyState >= 2) {
389
+ const { buffered } = videoPlayer
390
+ setVideoPlayerState((prevState) => ({
391
+ ...prevState,
392
+ videoBufferEnd: buffered.length === 0 ? 0 : buffered.end(buffered.length - 1)
393
+ }))
394
+ } else if (videoCurrentTime !== -1 && !videoQualityChanged && !videoPlayer) {
395
+ setVideoPlayerState((prevState) => ({
396
+ ...prevState,
397
+ videoIsPlaying: false,
398
+ videoIsBuffering: true
399
+ }))
400
+ }
401
+ }
402
+
403
+ const updateVolumeState = () => {
404
+ resetInactivityTimer()
405
+
406
+ const videoPlayer = refVideoPlayer.current
407
+
408
+ setVideoPlayerState((prevState) => ({
409
+ ...prevState,
410
+ videoCurrentVolume: videoPlayer.volume,
411
+ videoIsMuted: videoPlayer.muted
412
+ }))
413
+ }
414
+
415
+ const getPlayerWidth = () => {
416
+ setVideoPlayerState((prevState) => ({
417
+ ...prevState,
418
+ videoPlayerWidth: refVideoPlayerContainer.current.offsetWidth
419
+ }))
420
+ }
421
+
422
+ const setMouseOnControlBar = (isOver) => {
423
+ setVideoPlayerState((prevState) => ({ ...prevState, mouseOnControlBar: isOver }))
424
+ }
425
+
426
+ const togglePlayPause = () => {
427
+ const { videoIsPlaying } = videoPlayerState
428
+
429
+ setPlaying(!videoIsPlaying)
430
+ }
431
+
432
+ const setTextTracks = (trackNumber) => {
433
+ const { textTracks } = refVideoPlayer.current
434
+
435
+ for (let i = 0; i < textTracks.length; i += 1) {
436
+ textTracks[i].mode = i === trackNumber ? 'showing' : 'hidden'
437
+ }
438
+ setVideoPlayerState((prevState) => ({ ...prevState, selectedTextTrack: trackNumber }))
439
+ }
440
+
441
+ const beginVideo = () => {
442
+ setTextTracks(-1)
443
+ setPlaying(true)
444
+ refVideoPlayerContainer.current.focus()
445
+ }
446
+
447
+ const closeSubMenus = () => {
448
+ setVideoPlayerState((prevState) => ({
449
+ ...prevState,
450
+ qualityMenuOpen: false,
451
+ captionsMenuOpen: false
452
+ }))
453
+ }
454
+
455
+ const incrementSeek = (seconds) => {
456
+ refVideoPlayer.current.currentTime += seconds
457
+ }
458
+
459
+ const replayVideo = async () => {
460
+ setSeek(0)
461
+ togglePlayPause()
462
+ }
463
+
464
+ const incrementVolume = (amount) => {
465
+ refVideoPlayer.current.volume += amount
466
+ }
467
+
468
+ const setVolume = (amount) => {
469
+ refVideoPlayer.current.volume = amount
470
+ }
471
+
472
+ const toggleMute = () => {
473
+ refVideoPlayer.current.muted = !refVideoPlayer.current.muted
474
+ }
475
+
476
+ const setCaptionsMenuOpen = (isOpen) => {
477
+ setVideoPlayerState((prevState) => ({ ...prevState, captionsMenuOpen: isOpen }))
478
+ }
479
+
480
+ const toggleFullscreen = () => {
481
+ if (fscreen.fullscreenEnabled) {
482
+ if (fscreen.fullscreenElement === null) {
483
+ fscreen.requestFullscreen(refVideoPlayerContainer.current)
484
+ } else {
485
+ fscreen.exitFullscreen()
486
+ }
487
+ setVideoPlayerState((prevState) => {
488
+ return { ...prevState, videoIsFullscreen: !prevState.videoIsFullscreen }
489
+ })
490
+ }
491
+ }
492
+
493
+ const setVideoQuality = async (newVideoQuality) => {
494
+ await setPlaying(false)
495
+ setVideoPlayerState((prevState) => ({
496
+ ...prevState,
497
+ videoLength: 0,
498
+ videoBufferEnd: 0,
499
+ videoQuality: newVideoQuality,
500
+ loadedSources: generateSources(newVideoQuality),
501
+ loadedTracks: tracks && generateTracks(),
502
+ videoQualityChanged: true
503
+ }))
504
+ await refVideoPlayer.current.load()
505
+ resetInactivityTimer()
506
+ setSeek(videoPlayerState.videoCurrentTime)
507
+ }
508
+
509
+ const setQualityMenuOpen = (isOpen) => {
510
+ setVideoPlayerState((prevState) => ({ ...prevState, qualityMenuOpen: isOpen }))
511
+ }
512
+
513
+ const handleClick = (event) => {
514
+ resetInactivityTimer()
515
+
516
+ // This allows playing videos within components that act like
517
+ // links, e.g. `PreviewCard`, `StoryCard`, etc.
518
+ event?.preventDefault()
519
+ event?.stopPropagation()
520
+ }
521
+
522
+ const handleKeyboard = (event) => {
523
+ const { videoLength, videoCurrentVolume } = videoPlayerState
524
+
525
+ const key = event.key || event.keyCode
526
+
527
+ // *** Begin Seek & Play Control ****
528
+ if (key === ' ' || key === 32 || key === 'k' || key === 75) {
529
+ event.preventDefault()
530
+ resetInactivityTimer()
531
+ togglePlayPause()
532
+ }
533
+
534
+ // Disables all keys except for play/pause when in simpleMode
535
+ if (!simpleMode) {
536
+ if (key === 'ArrowRight' || key === 39 || key === '.' || key === 190) {
537
+ resetInactivityTimer()
538
+
539
+ incrementSeek(playerOptions.keyboardSeekIncrement)
540
+ }
541
+
542
+ if (key === 'ArrowLeft' || key === 37 || key === ',' || key === 188) {
543
+ resetInactivityTimer()
544
+
545
+ incrementSeek(-playerOptions.keyboardSeekIncrement)
546
+ }
547
+
548
+ if (key === '0' || key === 48 || key === 'numpad 0' || key === 96) {
549
+ setSeek(0)
550
+ }
551
+
552
+ if (key === '1' || key === 49 || key === 'numpad 1' || key === 97) {
553
+ setSeek(videoLength * 0.1)
554
+ }
555
+
556
+ if (key === '2' || key === 50 || key === 'numpad 2' || key === 98) {
557
+ setSeek(videoLength * 0.2)
558
+ }
559
+
560
+ if (key === '3' || key === 51 || key === 'numpad 3' || key === 99) {
561
+ setSeek(videoLength * 0.3)
562
+ }
563
+
564
+ if (key === '4' || key === 52 || key === 'numpad 4' || key === 100) {
565
+ setSeek(videoLength * 0.4)
566
+ }
567
+
568
+ if (key === '5' || key === 53 || key === 'numpad 5' || key === 101) {
569
+ setSeek(videoLength * 0.5)
570
+ }
571
+
572
+ if (key === '6' || key === 54 || key === 'numpad 6' || key === 102) {
573
+ setSeek(videoLength * 0.6)
574
+ }
575
+
576
+ if (key === '7' || key === 55 || key === 'numpad 7' || key === 103) {
577
+ setSeek(videoLength * 0.7)
578
+ }
579
+
580
+ if (key === '8' || key === 56 || key === 'numpad 8' || key === 104) {
581
+ setSeek(videoLength * 0.8)
582
+ }
583
+
584
+ if (key === '9' || key === 57 || key === 'numpad 9' || key === 105) {
585
+ setSeek(videoLength * 0.9)
586
+ }
587
+
588
+ // **** End Seek & Play Control ****
589
+
590
+ // **** Begin Volume Control ****
591
+
592
+ const { keyboardVolumeIncrement } = playerOptions
593
+
594
+ if (
595
+ key === 'ArrowUp' ||
596
+ key === 38 ||
597
+ key === '=' ||
598
+ key === 187 ||
599
+ key === 'add' ||
600
+ key === 107
601
+ ) {
602
+ resetInactivityTimer()
603
+
604
+ if (videoCurrentVolume + keyboardVolumeIncrement < 1) {
605
+ incrementVolume(keyboardVolumeIncrement)
606
+ } else {
607
+ setVolume(1)
608
+ }
609
+ }
610
+
611
+ if (
612
+ key === 'ArrowDown' ||
613
+ key === 40 ||
614
+ key === '-' ||
615
+ key === 189 ||
616
+ key === 'subtract' ||
617
+ key === 109
618
+ ) {
619
+ resetInactivityTimer()
620
+
621
+ if (videoCurrentVolume - keyboardVolumeIncrement > 0) {
622
+ incrementVolume(-keyboardVolumeIncrement)
623
+ } else {
624
+ setVolume(0)
625
+ }
626
+ }
627
+
628
+ if (key === 'm' || key === 77) {
629
+ resetInactivityTimer()
630
+
631
+ toggleMute()
632
+ }
633
+
634
+ if (key === 'f' || key === 70) {
635
+ resetInactivityTimer()
636
+
637
+ toggleFullscreen()
638
+ }
639
+
640
+ if (key === 'Escape' || key === 27) {
641
+ // Resets focus back to container if user is focused on ControlBar button
642
+ refVideoPlayerContainer.current.focus()
643
+ closeSubMenus()
644
+ }
645
+
646
+ if (key === 'q' || key === 81) {
647
+ refKeyboardInstructions.current.focus()
648
+ }
649
+ }
650
+ }
651
+
652
+ // Prepares video and caption files
653
+ useEffect(() => {
654
+ refreshMedia()
655
+ // eslint-disable-next-line react-hooks/exhaustive-deps
656
+ }, [])
657
+
658
+ // Initial Setup after loading sources
659
+ useEffect(() => {
660
+ const { loadedSources, loadedTracks } = videoPlayerState
661
+
662
+ if (loadedSources && loadedTracks) {
663
+ const { beginMuted, analyticsTracking, videoTitle } = props
664
+
665
+ const videoPlayer = refVideoPlayer.current
666
+
667
+ // Initializes Settings
668
+ videoPlayer.volume = defaultVolume / 100
669
+ videoPlayer.muted = beginMuted || simpleMode
670
+
671
+ getPlayerWidth()
672
+
673
+ // Reports when the video has completed loading metadata (used for seeking after quality switch)
674
+ videoPlayer.onloadedmetadata = qualitySwitchSeek
675
+
676
+ // Reports the video's duration when the video player is ready to play
677
+ videoPlayer.oncanplay = initializeVideoDuration
678
+
679
+ // Reports the video's width on resize
680
+ window.addEventListener('resize', getPlayerWidth)
681
+
682
+ // Reports the current video timestamp
683
+ videoPlayer.ontimeupdate = updateVideoTimestamp
684
+
685
+ // Reports when the video has paused due to buffering
686
+ videoPlayer.onwaiting = setAsBuffering
687
+
688
+ // Reports the video's latest buffering progress if video player is properly initialized
689
+ videoPlayer.onprogress = updateBufferProgress
690
+
691
+ // Reports when the video has recovered from buffering
692
+ videoPlayer.oncanplaythrough = playAfterBuffer
693
+
694
+ // Reports when the video has ended
695
+ videoPlayer.onended = setAsEnded
696
+
697
+ // Reports when the video is playing and disables the buffer spinner (even if buffering is still needed)
698
+ videoPlayer.onplay = setAsPlaying
699
+
700
+ // Reports when the video has been paused
701
+ videoPlayer.onpause = setAsPaused
702
+
703
+ // Reports when the video has been seeked
704
+ videoPlayer.onseeked = returnFromEndState
705
+
706
+ // Reports when the video's volume has been changed, or if the video has been muted
707
+ videoPlayer.onvolumechange = updateVolumeState
708
+
709
+ // Video Analytics
710
+ if (
711
+ (analyticsTracking !== undefined && videoTitle === undefined) ||
712
+ (analyticsTracking === undefined && videoTitle !== undefined)
713
+ ) {
714
+ warn(
715
+ 'Video',
716
+ 'Both the `analyticsTracking` and `videoTitle` props must be defined in order to return a proper analytics object.'
717
+ )
718
+ }
719
+
720
+ return () => {
721
+ clearInactivityTimer()
722
+ window.removeEventListener('resize', getPlayerWidth)
723
+ clearInterval(videoPlayerState.intervalId)
724
+ }
725
+ }
726
+
727
+ return () => {} // returning a noop for sake of consistency
728
+ // eslint-disable-next-line react-hooks/exhaustive-deps
729
+ }, [videoPlayerState.loadedSources, videoPlayerState.loadedTracks])
730
+
731
+ const {
732
+ loadedSources,
733
+ loadedTracks,
734
+ isMobile,
735
+ mouseInactive,
736
+ selectedTextTrack,
737
+ qualityMenuOpen,
738
+ captionsMenuOpen,
739
+ videoUnplayed,
740
+ videoEnded,
741
+ videoIsBuffering,
742
+ videoIsPlaying,
743
+ videoIsFullscreen,
744
+ videoIsMuted,
745
+ videoQualityChanged,
746
+ videoLength,
747
+ videoBufferEnd,
748
+ videoCurrentTime,
749
+ videoCurrentVolume,
750
+ videoQuality,
751
+ videoPlayerWidth
752
+ } = videoPlayerState
753
+ const { style, className, as, ...safeRest } = rest
754
+
755
+ return (
756
+ <VideoPlayerContainer
757
+ {...safeRest}
758
+ ref={refVideoPlayerContainer}
759
+ videoBorder={videoBorder}
760
+ borderColor={borderColor}
761
+ onMouseMove={resetInactivityTimer}
762
+ onClick={handleClick}
763
+ onKeyDown={handleKeyboard}
764
+ tabIndex="0"
765
+ aria-label={simpleMode ? videoText[copy].videoSelectedSimple : videoText[copy].videoSelected}
766
+ data-testid="videoPlayer"
767
+ {...selectProps(rest)}
768
+ >
769
+ <VideoOverlayContainer
770
+ mouseInactive={mouseInactive}
771
+ onClick={() => {
772
+ closeSubMenus()
773
+ togglePlayPause()
774
+ }}
775
+ >
776
+ {/* ======== Video Splash Screen ======== */}
777
+ {videoUnplayed && !videoQualityChanged && (
778
+ <VideoSplashContainer>
779
+ <VideoSplash
780
+ poster={posterSrc}
781
+ copy={copy}
782
+ videoLength={videoLength}
783
+ simpleMode={simpleMode}
784
+ iconLeftOffsetPx={2}
785
+ onClick={beginVideo}
786
+ />
787
+ </VideoSplashContainer>
788
+ )}
789
+ {/* =================================== */}
790
+ <VideoOverlayElementContainer>
791
+ {/* ======== Replay Button ======== */}
792
+ {videoEnded && <MiddleControlButton icon={replayIcon} onClick={replayVideo} />}
793
+ {/* ================================ */}
794
+ {/* ======== Middle Play/Pause Button ======= */}
795
+ {!videoUnplayed && !videoIsBuffering && !videoEnded && !isMobile && (
796
+ <MiddleControlButton
797
+ icon={videoIsPlaying ? pauseIcon : playIcon}
798
+ isHidden={mouseInactive}
799
+ onClick={togglePlayPause}
800
+ onFocus={resetInactivityTimer}
801
+ />
802
+ )}
803
+ {/* ========================================== */}
804
+
805
+ {/* ======== Spinner Display ======= */}
806
+ {videoIsBuffering && !isMobile && <Spinner show />}
807
+ {/* ================================ */}
808
+ </VideoOverlayElementContainer>
809
+ </VideoOverlayContainer>
810
+
811
+ {/* ======== Video Element ======= */}
812
+ <VideoElementContainer videoIsFullscreen={videoIsFullscreen} mouseInactive={mouseInactive}>
813
+ <VideoPlayer
814
+ ref={refVideoPlayer}
815
+ controls={isMobile}
816
+ videoIsFullscreen={videoIsFullscreen}
817
+ crossOrigin={crossOrigin}
818
+ playsinline
819
+ >
820
+ {loadedSources}
821
+ {loadedTracks}
822
+ Your browser does not support the video tag.
823
+ </VideoPlayer>
824
+ </VideoElementContainer>
825
+ {/* ============================== */}
826
+
827
+ {!simpleMode && (
828
+ <ControlBar
829
+ videoPlayer={refVideoPlayer}
830
+ videoPlayerContainer={refVideoPlayerContainer}
831
+ sources={sources}
832
+ tracks={tracks}
833
+ videoLength={videoLength}
834
+ videoBufferEnd={videoBufferEnd}
835
+ videoCurrentTime={videoCurrentTime}
836
+ videoPlaying={refVideoPlayer.current !== null ? !refVideoPlayer.current.paused : false}
837
+ videoUnplayed={videoUnplayed}
838
+ videoCurrentVolume={videoCurrentVolume}
839
+ videoIsMuted={videoIsMuted}
840
+ setVolume={setVolume}
841
+ isHidden={(mouseInactive || videoUnplayed) && !videoEnded}
842
+ isMobile={isMobile}
843
+ tracksAvailable={tracks !== undefined}
844
+ togglePlayPause={togglePlayPause}
845
+ setSeek={setSeek}
846
+ toggleMute={toggleMute}
847
+ toggleFullscreen={toggleFullscreen}
848
+ videoIsFullscreen={videoIsFullscreen}
849
+ setTextTracks={setTextTracks}
850
+ selectedTextTrack={selectedTextTrack}
851
+ resetInactivityTimer={resetInactivityTimer}
852
+ videoQuality={videoQuality}
853
+ setVideoQuality={setVideoQuality}
854
+ qualityMenuOpen={qualityMenuOpen}
855
+ setQualityMenuOpen={setQualityMenuOpen}
856
+ captionsMenuOpen={captionsMenuOpen}
857
+ setCaptionsMenuOpen={setCaptionsMenuOpen}
858
+ clearInactivityTimer={clearInactivityTimer}
859
+ copy={copy}
860
+ compactModeThreshold={playerOptions.compactModeThreshold}
861
+ videoPlayerWidth={videoPlayerWidth}
862
+ onMouseOver={() => setMouseOnControlBar(true)}
863
+ onMouseOut={() => setMouseOnControlBar(false)}
864
+ onFocus={() => {}}
865
+ onBlur={() => {}}
866
+ />
867
+ )}
868
+
869
+ {/* ======== Screen Reader Keyboard Instructions ======= */}
870
+ <span
871
+ tabIndex="-1"
872
+ ref={refKeyboardInstructions}
873
+ aria-label={videoText[copy].videoPlayer}
874
+ role="note"
875
+ />
876
+ {/* ==================================================== */}
877
+ </VideoPlayerContainer>
878
+ )
879
+ }
880
+
881
+ Video.propTypes = {
882
+ ...selectedSystemPropTypes,
883
+ /**
884
+ * An array of objects that defines each video file. See the "Basic Usage" section for more information.
885
+ */
886
+ sources: PropTypes.arrayOf(
887
+ PropTypes.shape({
888
+ /** A path to a video file */
889
+ source: PropTypes.string.isRequired,
890
+ /** The video's MIME type (example: `video/mp4`) */
891
+ mediaType: PropTypes.string.isRequired,
892
+ /** The label to be given to this file in the quality selector (example: `1080p`) */
893
+ qualityName: PropTypes.string.isRequired,
894
+ /** A number indicating the video's quality, with 1 being the best, and the number increasing from there as quality degrades */
895
+ qualityRank: PropTypes.number.isRequired,
896
+ /** A boolean that defines its source as a fallback for another source with the same `qualityRank` */
897
+ isFallback: PropTypes.bool
898
+ })
899
+ ).isRequired,
900
+ /**
901
+ * A path of the image that will be displayed on the video's splash screen.
902
+ */
903
+ posterSrc: PropTypes.string.isRequired,
904
+ /**
905
+ * An array of objects that defines each caption file. See the "Basic Usage" section for more information.
906
+ */
907
+ tracks: PropTypes.arrayOf(
908
+ PropTypes.shape({
909
+ /** A path to the track file */
910
+ source: PropTypes.string.isRequired,
911
+ /** The name of the track file as it will appear in the "Captions" dialogue */
912
+ label: PropTypes.string.isRequired,
913
+ /** The track's file type, typically one of "subtitles", "captions", or "descriptions". See [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track#Attributes) for more information on these types */
914
+ kind: PropTypes.string.isRequired,
915
+ /** The language of the track file represented as a ISO 639-1 language code */
916
+ language: PropTypes.string.isRequired
917
+ })
918
+ ),
919
+ /**
920
+ * The video's default volume, defined from 1 to 100. Please use the `beginMuted` prop to have the video start silenced.
921
+ */
922
+ defaultVolume: PropTypes.number,
923
+ /**
924
+ * Defines if the video should start muted.
925
+ */
926
+ beginMuted: PropTypes.bool,
927
+ /**
928
+ * The default quality level if the user is on mobile. See the `sources` prop for available quality levels.
929
+ */
930
+ defaultMobileQuality: PropTypes.number,
931
+ /**
932
+ * The default quality level if the user is on desktop. See the `sources` prop for available quality levels.
933
+ */
934
+ defaultDesktopQuality: PropTypes.number,
935
+ /**
936
+ * The video player UI's language as an ISO language code. It currently supports English and French.
937
+ */
938
+ copy: PropTypes.oneOf(['en', 'fr']).isRequired,
939
+ /**
940
+ * Sets the `video` tag's `crossorigin` mode. Please note that content loaded without CORS approval may be insecure.
941
+ * @since 1.1.0
942
+ */
943
+ crossOrigin: PropTypes.oneOf(['anonymous', 'use-credentials']),
944
+ /**
945
+ * Disables the control bar during playback and reduces the amount of time until the UI hides itself.
946
+ * For special approved internal uses only.
947
+ * @since 1.2.0
948
+ */
949
+ simpleMode: PropTypes.bool,
950
+ /**
951
+ * Renders a gainsboro coloured border around the video. Used to seperate the video from the rest of the page if the video's background is the same colour as the container's.
952
+ * @since 1.2.0
953
+ */
954
+ videoBorder: PropTypes.bool,
955
+ /**
956
+ * A callback function that fires when a customer interacts with Video.
957
+ * When using `analyticsTracking`, `videoTitle` must also be defined.
958
+ *
959
+ * When invoked, your callback function returns an object containing three keys (see below).
960
+ * @since 1.3.0
961
+ *
962
+ * @param {Object} analyticsObject Contains video data based on customer interactions
963
+ * @param {string} analyticsObject.name Always 'video tracking'
964
+ * @param {string} analyticsObject.action Contains the latest customer action
965
+ * @param {string} analyticsObject.details The name of the video based on the `videoTitle` prop
966
+ */
967
+ analyticsTracking: PropTypes.func,
968
+ /**
969
+ * The title of the video being rendered. This is used for analytics purposes in conjunction with `analyticsTracking`.
970
+ * @since 1.3.0
971
+ */
972
+ videoTitle: PropTypes.string
973
+ }
974
+
975
+ Video.defaultProps = {
976
+ tracks: undefined,
977
+ defaultVolume: 100,
978
+ beginMuted: false,
979
+ defaultMobileQuality: 1,
980
+ defaultDesktopQuality: 1,
981
+ crossOrigin: undefined,
982
+ simpleMode: false,
983
+ videoBorder: false,
984
+ analyticsTracking: undefined,
985
+ videoTitle: undefined
986
+ }
987
+
988
+ export default Video