PageSourceSearch

https://globalassemblages.org/wp-content/themes/uok-theme-legacy-a…/assets/js/video-panel.js?ver=1.1.0

js globalassemblages.org collected 2026-10-07 23:56:26 UTC 7,738 bytes, 261 lines download raw bytes

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.