1/** 2 * Video panel launcher 3 * Vanilla JS reimplementation of the old Alpine.js `video_panel()` component. 4 * Binds directly to .video-launcher elements using the existing 5 * data-embed / data-target attributes already output by the video panel 6 * PHP template â no markup changes required. 7 * 8 * Supports: 9 * - YouTube (via the YouTube IFrame Player API) â autoplays once the 10 * click-through image is clicked, per the panel's documented behavior. 11 * - Vimeo (via the Vimeo Player API) â loads on click but does NOT 12 * autoplay; the visitor presses Vimeo's own on-screen play button. 13 * - Panopto (via a directly-inserted iframe) â same as Vimeo: loads on 14 * click, does not autoplay, and has no lightweight client-side player 15 * API, so "pause other videos when this one starts" does not apply 16 * to Panopto players; see loadPanoptoVideo below. 17 * 18 * Requires the PHP template to tag each platform with its own 19 * data-target id prefix (video- / vimeo- / panopto-) and, for Panopto, 20 * a data-host attribute giving the tenant's Panopto domain. 21 */ 22(function () { 23 'use strict'; 24 25 // Tracks every active player instance, keyed by player element id, 26 // so that starting one video can pause any others already playing. 27 var playerList = {}; 28 29 var youtubeApiReady = false; 30 var youtubeApiLoading = false; 31 var pendingYoutubeQueue = []; 32 33 var vimeoApiReady = false; 34 var vimeoApiLoading = false; 35 var pendingVimeoQueue = []; 36 37 function loadScript(src, onload) { 38 var script = document.createElement('script'); 39 script.src = src; 40 script.async = true; 41 if (onload) { 42 script.onload = onload; 43 } 44 var firstScriptTag = document.getElementsByTagName('script')[0]; 45 firstScriptTag.parentNode.insertBefore(script, firstScriptTag); 46 } 47 48 // --------------------------------------------------------------- 49 // YouTube 50 // --------------------------------------------------------------- 51 52 // Required global callback name â the YouTube IFrame API looks for 53 // this exact function on window once it finishes loading. 54 window.onYouTubeIframeAPIReady = function () { 55 youtubeApiReady = true; 56 pendingYoutubeQueue.forEach(function (fn) { fn(); }); 57 pendingYoutubeQueue = []; 58 }; 59 60 function loadYoutubeVideo(playerId, videoId) { 61 function create() { 62 var player = new YT.Player(playerId, { 63 height: '100%', 64 width: '100%', 65 videoId: videoId, 66 playerVars: { 67 modestbranding: 1, 68 showinfo: 0, 69 rel: 0, 70 enablejsapi: 1 71 }, 72 events: { 73 onReady: function (event) { 74 event.target.playVideo(); 75 }, 76 onStateChange: function (event) { 77 onYoutubeStateChange(event, playerId); 78 } 79 } 80 }); 81 playerList[playerId] = player; 82 } 83 84 if (youtubeApiReady) { 85 create(); 86 return; 87 } 88 89 pendingYoutubeQueue.push(create); 90 91 // Only ever inject the IFrame API script once. 92 if (!youtubeApiLoading) { 93 youtubeApiLoading = true; 94 loadScript('https://www.youtube.com/iframe_api'); 95 } 96 } 97 98 function onYoutubeStateChange(event, currentPlayerId) { 99 if (event.data !== YT.PlayerState.PLAYING) { 100 return; 101 } 102 pauseAllExcept(currentPlayerId); 103 } 104 105 // --------------------------------------------------------------- 106 // Vimeo 107 // --------------------------------------------------------------- 108 109 function loadVimeoApi(onload) { 110 if (vimeoApiReady) { 111 onload(); 112 return; 113 } 114 115 pendingVimeoQueue.push(onload); 116 117 if (!vimeoApiLoading) { 118 vimeoApiLoading = true; 119 loadScript('https://player.vimeo.com/api/player.js', function () { 120 vimeoApiReady = true; 121 pendingVimeoQueue.forEach(function (fn) { fn(); }); 122 pendingVimeoQueue = []; 123 }); 124 } 125 } 126 127 function loadVimeoVideo(playerId, videoId) { 128 loadVimeoApi(function () { 129 // NOTE: no player.play() here on purpose. Per the documented 130 // panel behavior, only YouTube autoplays on click â Vimeo 131 // (and Panopto) load the player but wait for the visitor to 132 // press that platform's own play button. 133 var player = new Vimeo.Player(playerId, { 134 id: videoId, 135 responsive: true 136 }); 137 playerList[playerId] = player; 138 player.on('play', function () { 139 pauseAllExcept(playerId); 140 }); 141 }); 142 } 143 144 // --------------------------------------------------------------- 145 // Panopto 146 // --------------------------------------------------------------- 147 148 // Panopto's embed viewer doesn't offer a lightweight client-side 149 // player API the way YouTube/Vimeo do, so this just builds and 150 // inserts an iframe directly â no external script to load, and no 151 // play/pause coordination with other players (a Panopto video 152 // won't auto-stop if another video starts, unlike YouTube/Vimeo). 153 function loadPanoptoVideo(playerId, videoId, host) { 154 var target = document.getElementById(playerId); 155 if (!target || !host) { 156 return; 157 } 158 159 var iframe = document.createElement('iframe'); 160 // NOTE: no autoplay param here on purpose â same reasoning as 161 // Vimeo above. Only YouTube autoplays on click.
162 iframe.src = 'https://' + host + '/Panopto/Pages/Embed.aspx?id=' + encodeURIComponent(videoId); 163 iframe.width = '100%'; 164 iframe.height = '100%'; 165 iframe.style.border = '0'; 166 iframe.allow = 'autoplay; fullscreen'; 167 iframe.allowFullscreen = true; 168 169 target.appendChild(iframe); 170 playerList[playerId] = iframe; 171 } 172 173 // --------------------------------------------------------------- 174 // Shared helpers 175 // --------------------------------------------------------------- 176 177 // Only one video should play at a time. Stop/pause every other 178 // tracked player when a new one starts. (Panopto iframes are 179 // skipped here â see the note on loadPanoptoVideo above.) 180 function pauseAllExcept(activePlayerId) { 181 Object.keys(playerList).forEach(function (key) { 182 if (key === activePlayerId) { 183 return; 184 } 185 var p = playerList[key]; 186 if (!p) { 187 return; 188 } 189 if (typeof p.stopVideo === 'function') { 190 p.stopVideo(); 191 } else if (typeof p.pause === 'function') { 192 p.pause(); 193 } 194 }); 195 } 196 197 // The PHP template gives the wrapper/target div an id prefix that 198 // identifies the platform: "vimeo-", "panopto-", or "video-" for 199 // YouTube. 200 function getPlatformFromTargetId(playerId) { 201 if (playerId.indexOf('vimeo-') === 0) { 202 return 'vimeo'; 203 } 204 if (playerId.indexOf('panopto-') === 0) { 205 return 'panopto'; 206 } 207 return 'youtube'; 208 } 209 210 function handleLauncherClick(e) { 211 var launcher = e.currentTarget; 212 var videoId = launcher.getAttribute('data-embed'); 213 var playerId = launcher.getAttribute('data-target'); 214 var host = launcher.getAttribute('data-host'); 215 216 if (!videoId || !playerId) { 217 return; 218 } 219 220 var wrapper = document.getElementById(playerId + '-wrapper'); 221 if (wrapper) { 222 wrapper.classList.add('video-wrapper'); 223 } 224 225 // Hide the click-through image/button once the player is loading. 226 launcher.style.display = 'none'; 227 228 var platform = getPlatformFromTargetId(playerId); 229 230 if (platform === 'vimeo') { 231 loadVimeoVideo(playerId, videoId); 232 } else if (platform === 'panopto') { 233 loadPanoptoVideo(playerId, videoId, host); 234 } else { 235 loadYoutubeVideo(playerId, videoId); 236 } 237 } 238 239 function init() { 240 var launchers = document.querySelectorAll('.video-launcher'); 241 launchers.forEach(function (launcher) { 242 launcher.addEventListener('click', handleLauncherClick); 243 244 // The launchers use role="button", so support keyboard 245 // activation (Enter / Space) as well as click, for parity 246 // with native button accessibility. 247 launcher.addEventListener('keydown', function (e) { 248 if (e.key === 'Enter' || e.key === ' ') { 249 e.preventDefault(); 250 handleLauncherClick(e); 251 } 252 }); 253 }); 254 } 255 256 if (document.readyState === 'loading') { 257 document.addEventListener('DOMContentLoaded', init); 258 } else { 259 init(); 260 } 261})();
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.