1/** 2 * Yatra Video Player Module 3 * Reusable video player for YouTube and other video sources 4 * 5 * @package Yatra 6 * @version 1.0.0 7 */ 8 9(function() { 10 'use strict'; 11 12 // Translation helper. Same shim as tour-viewer.js â resolves 13 // through wp.i18n when available, falls back to identity otherwise. 14 // Call sites must pass `'yatra'` so wp i18n make-pot --domain=yatra 15 // picks them up (see tour-viewer.js for the full rationale). 16 var __ = (window.wp && window.wp.i18n && window.wp.i18n.__) 17 ? function (text, domain) { return window.wp.i18n.__(text, domain || 'yatra'); } 18 : function (text) { return text; }; 19 20 class YatraVideoPlayer { 21 constructor() { 22 this.modal = null; 23 this.iframe = null; 24 this.currentVideo = null; 25 this.init(); 26 } 27 28 init() { 29 // Nothing is built here on purpose. The modal markup used to be 30 // injected into <body> as soon as this script loaded â on every 31 // page, including ones with no video. It is now created on the 32 // first play() call, so pages that never play a video never get 33 // the extra DOM. 34 } 35 36 /** 37 * Build the modal once, on first use. 38 */ 39 ensureModal() { 40 if (this.modal) { 41 return; 42 } 43 this.createModal(); 44 this.attachEventListeners(); 45 } 46 47 createModal() { 48 // Check if modal already exists 49 if (document.getElementById('yatra-video-player-modal')) { 50 this.modal = document.getElementById('yatra-video-player-modal'); 51 this.iframe = document.getElementById('yatra-video-player-iframe'); 52 return; 53 } 54 55 // Pre-compute the translated label OUT of the template literal 56 // so makepot extracts it reliably (see tour-viewer.js note). 57 var closeVideoLabel = __('Close Video', 'yatra'); 58 59 // Create modal HTML 60 const modalHTML = ` 61 <div id="yatra-video-player-modal" class="yatra-video-player-modal" style="display: none;"> 62 <div class="yatra-video-player-overlay"></div> 63 <div class="yatra-video-player-content"> 64 <button type="button" class="yatra-video-player-close" aria-label="${closeVideoLabel}"> 65 <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 66 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> 67 </svg> 68 </button> 69 <div class="yatra-video-player-wrapper"> 70 <iframe id="yatra-video-player-iframe" 71 width="100%" 72 height="100%" 73 frameborder="0" 74 allowfullscreen 75 allow="autoplay; encrypted-media; picture-in-picture"> 76 </iframe> 77 </div> 78 </div> 79 </div> 80 `; 81 82 // Append to body 83 document.body.insertAdjacentHTML('beforeend', modalHTML); 84 85 this.modal = document.getElementById('yatra-video-player-modal'); 86 this.iframe = document.getElementById('yatra-video-player-iframe'); 87 } 88 89 attachEventListeners() { 90 // Close button 91 const closeBtn = this.modal.querySelector('.yatra-video-player-close'); 92 if (closeBtn) { 93 closeBtn.addEventListener('click', () => this.close()); 94 } 95 96 // Overlay click to close 97 const overlay = this.modal.querySelector('.yatra-video-player-overlay'); 98 if (overlay) { 99 overlay.addEventListener('click', () => this.close()); 100 } 101 102 // ESC key to close 103 document.addEventListener('keydown', (e) => { 104 if (e.key === 'Escape' && this.modal.style.display === 'flex') { 105 this.close(); 106 } 107 }); 108 } 109 110 /** 111 * Play a video 112 * @param {Object} video - Video object with url, embed_url, video_id, title 113 */ 114 play(video) { 115 if (!video || !video.url) { 116 console.error('YatraVideoPlayer: Invalid video object'); 117 return; 118 } 119 120 121 this.currentVideo = video; 122 this.ensureModal(); 123 124 // Determine the embed URL 125 let embedUrl = ''; 126 127 if (video.embed_url) { 128 // Use provided embed URL 129 embedUrl = video.embed_url; 130 } else if (video.video_id) { 131 // YouTube video ID provided 132 embedUrl = `https://www.youtube.com/embed/${video.video_id}?autoplay=1&rel=0`; 133 } else if (video.url.includes('youtube.com') || video.url.includes('youtu.be')) { 134 // Extract YouTube video ID from URL 135 const videoId = this.extractYoutubeVideoId(video.url); 136 if (videoId) { 137 embedUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`; 138 } 139 } else if (video.url.includes('vimeo.com')) { 140 // Vimeo video 141 const vimeoId = video.url.split('/').pop(); 142 embedUrl = `https://player.vimeo.com/video/${vimeoId}?autoplay=1`; 143 } else { 144 // Direct video URL 145 embedUrl = video.url; 146 } 147 148 149 150 151 152 // Add load event handlers for debugging 153 this.iframe.onload = () => { 154 155 }; 156 157 this.iframe.onerror = (error) => { 158 console.error('YouTube iframe failed to load:', error); 159 }; 160 161 // Set iframe source 162 163 this.iframe.src = embedUrl; 164 165 // Verify iframe src was set 166 setTimeout(() => { 167 168 169 }, 100); 170 171 // Show modal 172 this.open(); 173 } 174 175 /** 176 * Extract YouTube video ID from various URL formats 177 * @param {string} url - YouTube URL 178 * @returns {string|null} Video ID or null 179 */ 180 extractYoutubeVideoId(url) { 181 const patterns = [ 182 /youtube\.com\/watch\?v=([^&]+)/, 183 /youtube\.com\/embed\/([^?]+)/, 184 /youtu\.be\/([^?]+)/, 185 /youtube\.com\/v\/([^?]+)/, 186 /youtube\.com\/shorts\/([^?&]+)/ 187 ]; 188 189 for (const pattern of patterns) { 190 const match = url.match(pattern); 191 if (match) { 192 return match[1]; 193 } 194 } 195 196 return null; 197 } 198 199 open() { 200 201 202 this.modal.style.display = 'flex'; 203 document.body.style.overflow = 'hidden'; 204 205 } 206 207 close() { 208 if (!this.modal) { 209 return; // never opened on this page 210 } 211 this.modal.style.display = 'none'; 212 document.body.style.overflow = ''; 213 214 // Stop video playback by clearing iframe src 215 this.iframe.src = ''; 216 this.currentVideo = null; 217 } 218 } 219 220 // Create global instance 221 window.YatraVideoPlayer = new YatraVideoPlayer(); 222 223 // Log successful initialization 224 225 226})();
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.