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