1/** 2 * Note: This is complex initialization, but it is necessary for Gutenberg and Elementor compatibility. There are some known issues in Gutenberg that require this complex setup. 3 */ 4var playerInit = []; 5 6 7// Event listener for when the DOM content is loaded 8document.addEventListener('DOMContentLoaded', function () { 9 10 11 const overlayMask = document.createElement('div'); 12 overlayMask.className = 'overlay-mask'; 13 14 15 // Select all embed wrappers with the class 'ep-embed-content-wraper' 16 let embedWrappers = document.querySelectorAll('.ep-embed-content-wraper'); 17 18 // Initialize the player for each embed wrapper 19 embedWrappers.forEach(wrapper => { 20 initPlayer(wrapper); 21 }); 22 23 // Mutation observer to detect any changes in the DOM 24 const observer = new MutationObserver(mutations => { 25 mutations.forEach(mutation => { 26 const addedNodes = Array.from(mutation.addedNodes); 27 addedNodes.forEach(node => { 28 traverseAndInitPlayer(node); 29 }); 30 }); 31 }); 32 33 // Start observing changes in the entire document body and its subtree 34 observer.observe(document.body, { childList: true, subtree: true }); 35 36 // Recursive function to traverse the DOM and initialize the player for each embed wrapper 37 function traverseAndInitPlayer(node) { 38 if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('ep-embed-content-wraper')) { 39 initPlayer(node); 40 } 41 42 if (node.hasChildNodes()) { 43 node.childNodes.forEach(childNode => { 44 traverseAndInitPlayer(childNode); 45 }); 46 } 47 } 48 49 50}); 51 52 53// Function to initialize the player for a given wrapper 54function initPlayer(wrapper) { 55 const playerId = wrapper.getAttribute('data-playerid'); 56 57 // Get the options for the player from the wrapper's data attribute
58 let options = document.querySelector(`[data-playerid='${playerId}']`)?.getAttribute('data-options'); 59 60 61 62 if (!options) { 63 return false; 64 } 65 66 // Parse the options string into a JSON object 67 if (typeof options === 'string') { 68 try { 69 options = JSON.parse(options); 70 } catch (e) { 71 return; 72 } 73 } else { 74 return; 75 } 76 77 78 if (!options.poster_thumbnail) { 79 wrapper.style.opacity = "1"; 80 } 81 82 // Create DOM elements from the icon strings 83 const pipPlayIconElement = document.createElement('div'); 84 pipPlayIconElement.className = 'pip-play'; 85 pipPlayIconElement.innerHTML = '<svg width="20" height="20" viewBox="-0.15 -0.112 0.9 0.9" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin" class="jam jam-play"><path fill="#fff" d="M.518.357A.037.037 0 0 0 .506.306L.134.08a.039.039 0 0 0-.02-.006.038.038 0 0 0-.038.037v.453c0 .007.002.014.006.02a.039.039 0 0 0 .052.012L.506.37A.034.034 0 0 0 .518.358zm.028.075L.174.658A.115.115 0 0 1 .017.622.109.109 0 0 1 0 .564V.111C0 .05.051 0 .114 0c.021 0 .042.006.06.017l.372.226a.11.11 0 0 1 0 .189z"/></svg>'; 86 pipPlayIconElement.style.display = 'none'; 87 88 89 const pipPauseIconElement = document.createElement('div'); 90 pipPauseIconElement.className = 'pip-pause'; 91 pipPauseIconElement.innerHTML = '<svg fill="#fff" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 2.5 2.5" xml:space="preserve"><path d="M1.013.499 1.006.5V.499H.748a.054.054 0 0 0-.054.054v1.394c0 .03.024.054.054.054h.266a.054.054 0 0 0 .054-.054V.553a.054.054 0 0 0-.054-.054zm.793 1.448V.553a.054.054 0 0 0-.054-.054L1.745.5V.499h-.258a.054.054 0 0 0-.054.054v1.394c0 .03.024.054.054.054h.265a.054.054 0 0 0 .054-.054z"/></svg>'; 92 93 const pipCloseElement = document.createElement('div'); 94 pipCloseElement.className = 'pip-close'; 95 pipCloseElement.innerHTML = '<svg width="20" height="20" viewBox="0 0 0.9 0.9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M.198.198a.037.037 0 0 1 .053 0L.45.397.648.199a.037.037 0 1 1 .053.053L.503.45l.198.198a.037.037 0 0 1-.053.053L.45.503.252.701A.037.037 0 0 1 .199.648L.397.45.198.252a.037.037 0 0 1 0-.053z" fill="#fff"/></svg>'; 96 97 // Check if the player has not been initialized for this wrapper 98 if (playerId && !wrapper.classList.contains('plyr-initialized')) { 99 100 101 let selector = `[data-playerid='${playerId}'] .ose-embedpress-responsive`; 102 103 104 if (options.self_hosted && options.hosted_format === 'video') { 105 selector = `[data-playerid='${playerId}'] .ose-embedpress-responsive video`; 106 } 107 else if (options.self_hosted && options.hosted_format === 'audio') { 108 selector = `[data-playerid='${playerId}'] .ose-embedpress-responsive audio`; 109 wrapper.style.opacity = "1"; 110 } 111 112 113 // Set the main color of the player
114 document.querySelector(`[data-playerid='${playerId}']`).style.setProperty('--plyr-color-main', options.player_color); 115 document.querySelector(`[data-playerid='${playerId}'].custom-player-preset-1, [data-playerid='${playerId}'].custom-player-preset-3, [data-playerid='${playerId}'].custom-player-preset-4`)?.style.setProperty('--plyr-range-fill-background', '#ffffff'); 116 117 // Set the poster thumbnail for the player 118 if (document.querySelector(`[data-playerid='${playerId}'] iframe`)) { 119 document.querySelector(`[data-playerid='${playerId}'] iframe`).setAttribute('data-poster', options.poster_thumbnail); 120 } 121 if (document.querySelector(`[data-playerid='${playerId}'] video`)) { 122 document.querySelector(`[data-playerid='${playerId}'] video`).setAttribute('data-poster', options.poster_thumbnail); 123 } 124 125 // Define the controls to be displayed 126 const controls = [ 127 'play-large', 128 options.restart ? 'restart' : '', 129 options.rewind ? 'rewind' : '', 130 'play', 131 options.fast_forward ? 'fast-forward' : '', 132 'progress', 133 'current-time', 134 'duration', 135 'mute', 136 'volume', 137 'captions', 138 'settings', 139 options.pip ? 'pip' : '', 140 'airplay', 141 options.download ? 'download' : '', 142 options.fullscreen ? 'fullscreen' : '', 143 144 ].filter(control => control !== ''); 145 146 // Detect if we're on iOS 147 const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 148 149 // Detect if this is a YouTube video
150 const isYouTube = document.querySelector(`[data-playerid='${playerId}'] iframe[src*="youtube"]`) !== null; 151 152 // For iOS YouTube videos, we need to use fallback fullscreen instead of native 153 // because webkitEnterFullscreen() doesn't work on iframes 154 const shouldUseFallbackFullscreen = isIOS && isYouTube; 155 156 157 158 // Create a new Plyr player instance with the specified options and controls 159 const player = new Plyr(selector, { 160 controls: controls, 161 seekTime: 10, 162 poster: options.poster_thumbnail, 163 storage: { 164 enabled: true, 165 key: 'plyr_volume' 166 }, 167 displayDuration: true, 168 tooltips: { controls: options.player_tooltip, seek: options.player_tooltip }, 169 hideControls: options.hide_controls, 170 // iOS fullscreen configuration - use fallback for YouTube on iOS 171 fullscreen: { 172 enabled: options.fullscreen !== false, 173 fallback: true, 174 iosNative: !shouldUseFallbackFullscreen // Disable iosNative for YouTube on iOS 175 }, 176 // Enable playsinline for iOS devices to allow custom controls 177 playsinline: true, 178 youtube: { 179 ...(options.autoplay && { autoplay: options.autoplay }), 180 ...(options.start && { start: options.start }), 181 ...(options.end && { end: options.end }), 182 ...(options.rel && { rel: options.rel }), 183 ...(options.fullscreen && { fs: options.fullscreen }) 184 }, 185 vimeo: { 186 byline: false, 187 portrait: false, 188 title: false, 189 speed: true, 190 transparent: false, 191 controls: false, 192 ...(options.t && { t: options.t }), 193 ...(options.vautoplay && { autoplay: options.vautoplay }), 194 ...(options.autopause && { autopause: options.autopause }), 195 ...(options.dnt && { dnt: options.dnt }), 196 } 197 }); 198 199 playerInit[playerId] = player; 200 201 202 // iOS YouTube fullscreen fix: Ensure iframe has proper attributes 203 if (shouldUseFallbackFullscreen) {
204 const iframe = document.querySelector(`[data-playerid='${playerId}'] iframe[src*="youtube"]`); 205 if (iframe) { 206 // Ensure the iframe allows fullscreen 207 iframe.setAttribute('allowfullscreen', ''); 208 iframe.setAttribute('webkitallowfullscreen', ''); 209 iframe.setAttribute('mozallowfullscreen', ''); 210 211 // Add iOS-specific class for styling 212 iframe.classList.add('ios-youtube-iframe'); 213 214 // Listen for fullscreen events to handle iOS-specific behavior 215 player.on('enterfullscreen', () => { 216 // Force viewport meta tag update for better fullscreen experience 217 const viewport = document.querySelector('meta[name="viewport"]'); 218 if (viewport) { 219 const originalContent = viewport.getAttribute('content'); 220 viewport.setAttribute('data-original-content', originalContent); 221 viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'); 222 } 223 }); 224 225 player.on('exitfullscreen', () => { 226 // Restore original viewport meta tag 227 const viewport = document.querySelector('meta[name="viewport"]'); 228 if (viewport && viewport.hasAttribute('data-original-content')) { 229 viewport.setAttribute('content', viewport.getAttribute('data-original-content')); 230 viewport.removeAttribute('data-original-content'); 231 } 232 }); 233 } 234 } 235 236 237 238 // Mark the wrapper as initialized 239 wrapper.classList.add('plyr-initialized'); 240 241 const posterElement = wrapper.querySelector('.plyr__poster'); 242 243 if (posterElement) { 244 const interval = setInterval(() => { 245 if (posterElement && posterElement.style.backgroundImage) { 246 wrapper.style.opacity = '1'; 247 clearInterval(interval); 248 } 249 }, 200); 250 251 } 252 253 } 254 255 // Check for the existence of the player's pip button at regular intervals 256 const pipInterval = setInterval(() => { 257
258 let playerPip = document.querySelector(`[data-playerid="${playerId}"] [data-plyr="pip"]`); 259 if (playerPip) { 260 clearInterval(pipInterval); 261 262 let options = document.querySelector(`[data-playerid="${playerId}"]`).getAttribute('data-options'); 263 264 options = JSON.parse(options); 265 if (!options.self_hosted) { 266 267 const iframeSelector = document.querySelector(`[data-playerid="${playerId}"] .plyr__video-wrapper`); 268 269 // Add click event listener to toggle the pip mode 270 playerPip.addEventListener('click', () => { 271 iframeSelector.classList.toggle('pip-mode'); 272 273 let parentElement = iframeSelector.parentElement; 274 while (parentElement) { 275 parentElement.style.zIndex = '9999'; 276 parentElement = parentElement.parentElement; 277 } 278 279 }); 280 281 282 283 if (options.pip) { 284 iframeSelector.appendChild(pipPlayIconElement); 285 iframeSelector.appendChild(pipPauseIconElement); 286 iframeSelector.appendChild(pipCloseElement);
287 const pipPlay = document.querySelector(`[data-playerid="${playerId}"] .plyr__video-wrapper .pip-play`); 288 const pipPause = document.querySelector(`[data-playerid="${playerId}"] .plyr__video-wrapper .pip-pause`); 289 const pipClose = document.querySelector(`[data-playerid="${playerId}"] .plyr__video-wrapper .pip-close`); 290 291 pipClose.addEventListener('click', () => { 292 iframeSelector.classList.remove('pip-mode'); 293 }); 294 295 296 iframeSelector.addEventListener('click', () => { 297 const ariaPressedValue = document.querySelector(`[data-playerid="${playerId}"] .plyr__controls [data-plyr="play"]`).getAttribute('aria-pressed'); 298 299 if (ariaPressedValue === 'true') { 300 pipPause.style.display = 'none'; 301 pipPlay.style.display = 'flex'; 302 } else { 303 pipPlay.style.display = 'none'; 304 pipPause.style.display = 'flex'; 305 } 306 }); 307 308 } 309 310 311 } 312 } 313 314 }, 200); 315 316}
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.