1/*---------------------------------------------------- 2 * Description: Video controls for uploaded videos 3 * attach_library('wx_vendor_libraries/videoControls') 4 * For youtube video controls see library 'youtube_background/video' 5 * ----------------------------------------------------*/ 6(function(Drupal, drupalSettings) { 7 'use strict'; 8 9 function handleUploadedVideo(videoContainer, uploadedVideo) { 10 // Handle unmuting videos with not-muted class 11 if (videoContainer.classList.contains('not-muted')) { 12 const videoTags = videoContainer.querySelectorAll('video'); 13 videoTags.forEach((video) => { 14 video.muted = false; 15 video.removeAttribute('muted'); 16 }); 17 } 18 19 // Add controls to videos if class .show-native-controls is present 20 // Override other custom settings such as rendered image on pause and custom play/pause buttons 21 if (videoContainer.classList.contains('show-native-controls')) { 22 const videoTags = videoContainer.querySelectorAll('video'); 23 const shouldStartPausedWithControls = videoContainer.classList.contains('pausedAllDevices'); 24 const hasImageOverlay = uploadedVideo.classList.contains('puasedImageRendered'); 25 26 videoTags.forEach((video) => { 27 28 if (shouldStartPausedWithControls) { 29 video.pause(); 30 uploadedVideo.classList.add('paused'); 31 } 32 33 video.setAttribute('controls', 'controls'); 34 35 // If there's an image overlay and video should start paused, make controls non-focusable 36 if (shouldStartPausedWithControls && hasImageOverlay) { 37 video.setAttribute('tabindex', '-1'); 38 } 39 40 // Start video when clicking on the YTPlayer-play button on top of image 41 const playButton = uploadedVideo.querySelector('.YTPlayer-play'); 42 if (playButton) { 43 playButton.addEventListener('click', function(event) { 44 video.play(); 45 uploadedVideo.classList.remove('paused'); 46 uploadedVideo.classList.add('playing'); 47 48 // Make video controls focusable again when video starts playing 49 if (hasImageOverlay) { 50 video.removeAttribute('tabindex'); 51 } 52 }); 53 } 54 55 // Also handle when video is played programmatically or by other means 56 video.addEventListener('play', function() { 57 if (hasImageOverlay) { 58 video.removeAttribute('tabindex'); 59 } 60 }); 61 62 // Make controls non-focusable again when video is paused and image overlay should show 63 video.addEventListener('pause', function() { 64 if (hasImageOverlay && uploadedVideo.classList.contains('paused')) { 65 video.setAttribute('tabindex', '-1'); 66 } 67 }); 68 }); 69 return; 70 } 71 72 const playButton = uploadedVideo.querySelector('.YTPlayer-play'); 73 const pauseButton = uploadedVideo.querySelector('.YTPlayer-pause'); 74 const muteButton = uploadedVideo.querySelector('.YTPlayer-mute'); 75 const unmuteButton = uploadedVideo.querySelector('.YTPlayer-unmute'); 76 const videoElement = uploadedVideo.querySelector('video'); 77 78 if (!playButton || !pauseButton || !videoElement) return; 79 80 // Track if last interaction was via keyboard 81 let lastInteractionWasKeyboard = false; 82 83 const videoContentPosition = videoContainer.querySelector('.video-content-position'); 84 85 if (videoContentPosition) { 86 const videoContent = videoContentPosition.querySelector('.video-content'); 87 const interactiveElements = videoContent.querySelectorAll('a, button, input, select, textarea, [role="button"]'); 88 89 if (interactiveElements.length > 0) { 90 interactiveElements.forEach((element) => { 91 element.style.position = 'relative'; 92 element.style.zIndex = '21'; 93 }); 94 } 95 } 96 97 const shouldStartPaused = videoContainer.classList.contains('pausedAllDevices'); 98 99 if (shouldStartPaused) { 100 // Ensure video starts paused 101 videoElement.pause(); 102 playButton.style.display = ''; 103 pauseButton.style.display = 'none'; 104 uploadedVideo.classList.add('paused'); 105 106 // Show placeholder image if it exists 107 const mobilePlaceholder = uploadedVideo.querySelector('.video-mobile-img'); 108 if (mobilePlaceholder && uploadedVideo.classList.contains('puasedImageRendered')) { 109 mobilePlaceholder.style.opacity = '1'; 110 mobilePlaceholder.style.visibility = 'visible'; 111 } 112 } else { 113 // Autoplay if no pausedAllDevices class 114 // Note: This might be blocked by browsers that require user interaction 115 videoElement.play() 116 .then(() => { 117 updatePlayState(true); 118 }) 119 .catch(() => { 120 // Autoplay was prevented 121 updatePlayState(false); 122 }); 123 } 124 125 // Handle click on the video to toggle play/pause 126 uploadedVideo.addEventListener('click', function(event) { 127 // Skip if user clicked directly on a button 128 if (event.target.closest('.YTPlayer-play') || 129 event.target.closest('.YTPlayer-pause') || 130 event.target.closest('.YTPlayer-mute') || 131 event.target.closest('.YTPlayer-unmute')) { 132 return; 133 } 134 135 // Skip if user clicked in the content area (contains links) 136 if (videoContentPosition && (event.target.closest('.video-content-position') || event.target.closest('.video-content'))) { 137 return; 138 } 139 140 togglePlayState(); 141 }); 142 143 // Handle play button click 144 playButton.addEventListener('click', function(event) { 145 lastInteractionWasKeyboard = event.detail === 0; // detail is 0 for keyboard events 146 videoElement.play(); 147 updatePlayState(true); 148 }); 149 150 // Handle pause button click 151 pauseButton.addEventListener('click', function(event) { 152 lastInteractionWasKeyboard = event.detail === 0; // detail is 0 for keyboard events 153 videoElement.pause(); 154 updatePlayState(false); 155 }); 156 157 // Handle hover and focus effects for pause, play and mute button visibility 158 function showUploadedVideoControlsWhenPlaying() { 159 if (!uploadedVideo.classList.contains('playing')) return; 160 pauseButton.style.opacity = '1'; 161 if (muteButton) muteButton.style.opacity = '1'; 162 if (unmuteButton) unmuteButton.style.opacity = '1'; 163 } 164 165 uploadedVideo.addEventListener('mouseenter', showUploadedVideoControlsWhenPlaying); 166 uploadedVideo.addEventListener('focus', showUploadedVideoControlsWhenPlaying, true); 167 168 // Handle mute button click 169 if (muteButton) { 170 muteButton.addEventListener('click', function () { 171 videoElement.muted = true; 172 updateMuteState(true); 173 }); 174 } 175 176 // Handle unmute button click 177 if (unmuteButton) { 178 unmuteButton.addEventListener('click', function () { 179 videoElement.muted = false;
180 updateMuteState(false); 181 }); 182 } 183 184 // Toggle play/pause state 185 function togglePlayState() { 186 if (videoElement.paused) { 187 videoElement.play(); 188 updatePlayState(true); 189 } else { 190 videoElement.pause(); 191 updatePlayState(false); 192 } 193 } 194 195 // Update UI based on play state 196 function updatePlayState(isPlaying) { 197 if (isPlaying) { 198 uploadedVideo.classList.remove('paused'); 199 uploadedVideo.classList.add('playing'); 200 playButton.style.display = 'none'; 201 pauseButton.classList.add('visible'); 202 pauseButton.style.display = ''; 203 204 // Transfer focus to pause button if needed (only for keyboard interactions) 205 if (lastInteractionWasKeyboard) { 206 pauseButton.focus(); 207 } 208 209 if (muteButton && unmuteButton) { 210 muteButton.classList.add('visible'); 211 unmuteButton.classList.add('visible'); 212 } 213 214 // Hide video placeholder image if it exists 215 const mobilePlaceholder = uploadedVideo.querySelector('.video-mobile-img'); 216 if (mobilePlaceholder) { 217 mobilePlaceholder.style.opacity = '0'; 218 mobilePlaceholder.style.visibility = 'hidden'; 219 } 220 } else { 221 uploadedVideo.classList.remove('playing'); 222 uploadedVideo.classList.add('paused'); 223 pauseButton.classList.remove('visible'); 224 pauseButton.style.display = 'none'; 225 playButton.style.display = ''; 226 227 // Transfer focus to play button if needed (only for keyboard interactions) 228 if (lastInteractionWasKeyboard) { 229 playButton.focus(); 230 } 231 232 if (muteButton && unmuteButton) { 233 muteButton.classList.remove('visible'); 234 unmuteButton.classList.remove('visible'); 235 } 236 237 // Show video placeholder image if it exists 238 const mobilePlaceholder = uploadedVideo.querySelector('.video-mobile-img'); 239 if (mobilePlaceholder && uploadedVideo.classList.contains('puasedImageRendered')) { 240 mobilePlaceholder.style.opacity = '1'; 241 mobilePlaceholder.style.visibility = 'visible'; 242 } 243 } 244 } 245 246 // Update UI based on mute state 247 function updateMuteState(isMuted) { 248 if (muteButton && unmuteButton) { 249 if (isMuted) { 250 muteButton.style.display = 'none'; 251 unmuteButton.style.display = ''; 252 unmuteButton.focus(); 253 } else { 254 muteButton.style.display = ''; 255 unmuteButton.style.display = 'none'; 256 muteButton.focus(); 257 } 258 } 259 } 260 } 261 262 document.addEventListener('DOMContentLoaded', function () { 263 const containerSelectors = ['.node--type-video', '.paragraph--type--video', '.video-area']; 264 let videoContainers = []; 265 containerSelectors.forEach((selector) => { 266 document.querySelectorAll(selector).forEach((container) => { 267 videoContainers.push(container); 268 }); 269 }); 270 271 if (!videoContainers.length) return; 272 273 videoContainers.forEach(function(videoContainer) { 274 const uploadedVideos = videoContainer.querySelectorAll('.uploaded-video'); 275 276 // Handle videos with .uploaded-video class 277 if (uploadedVideos.length) { 278 uploadedVideos.forEach(function(uploadedVideo) { 279 handleUploadedVideo(videoContainer, uploadedVideo); 280 }); 281 } 282 283 // Handle videos without .uploaded-video class (direct video elements) 284 const directVideoElements = videoContainer.querySelectorAll('video'); 285 directVideoElements.forEach(function(videoElement) { 286 // Skip if this video is already wrapped in .uploaded-video 287 if (!videoElement.closest('.uploaded-video')) { 288 // Pass the video-area container instead of parent element 289 handleUploadedVideo(videoContainer, videoContainer); 290 } 291 }); 292 }); 293 }); 294 295})(Drupal, drupalSettings);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.