(function($) { "use strict"; window.NextGEN_Video = { /** * Detect video platform from URL * @param {string} url * @returns {string|null} Platform name or null if not a video URL */ detect_platform: function(url) { if (!url) return null; url = url.trim().toLowerCase(); // YouTube patterns if (url.match(/youtube\.com|youtu\.be|youtube-nocookie\.com/)) { return 'youtube'; } // Vimeo patterns if (url.match(/vimeo\.com/)) { return 'vimeo'; } // Dailymotion patterns if (url.match(/dailymotion\.com|dai\.ly/)) { return 'dailymotion'; } // Twitch patterns if (url.match(/twitch\.tv/)) { return 'twitch'; } // VideoPress patterns if (url.match(/videopress\.com|video\.wordpress\.com/)) { return 'videopress'; } // Wistia patterns if (url.match(/wistia\.com|wistia\.net/)) { return 'wistia'; } // Local video patterns (direct video file URLs) if (url.match(/\.(mp4|webm|ogg|ogv|mov|avi|wmv|flv|mkv)(\?|$)/i)) { return 'local'; } return null; }, /** * Extract video ID from YouTube URL * @param {string} url * @returns {string|null} */ extract_youtube_id: function(url) { if (!url) return null; var patterns = [ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube-nocookie\.com\/embed\/)([^&\n?#]+)/, /youtube\.com\/.*[?&]v=([^&\n?#]+)/, ]; for (var i = 0; i < patterns.length; i++) { var match = url.match(patterns[i]); if (match && match[1]) { return match[1]; } } return null; }, /** * Extract video ID from Vimeo URL * @param {string} url * @returns {string|null} */ extract_vimeo_id: function(url) { if (!url) return null; var patterns = [ /vimeo\.com\/(\d+)/, /vimeo\.com\/.*\/(\d+)/, /player\.vimeo\.com\/video\/(\d+)/, ]; for (var i = 0; i < patterns.length; i++) { var match = url.match(patterns[i]); if (match && match[1]) { return match[1]; } } return null; }, /** * Extract video ID from Dailymotion URL * @param {string} url * @returns {string|null} */ extract_dailymotion_id: function(url) { if (!url) return null; var patterns = [ /dailymotion\.com\/video\/([^/?]+)/, /dai\.ly\/([^/?]+)/, /dailymotion\.com\/embed\/video\/([^/?]+)/, ]; for (var i = 0; i < patterns.length; i++) { var match = url.match(patterns[i]); if (match && match[1]) { return match[1]; } } return null; }, /** * Extract video ID from Twitch URL * @param {string} url * @returns {Object|null} Object with videoId and type (video/clip) */ extract_twitch_id: function(url) { if (!url) return null; // Twitch video pattern: twitch.tv/videos/VIDEO_ID var videoMatch = url.match(/twitch\.tv\/videos\/(\d+)/); if (videoMatch && videoMatch[1]) { return { videoId: videoMatch[1], type: 'video' }; } // Twitch clip pattern: twitch.tv/CLIP_ID or clips.twitch.tv/CLIP_ID var clipMatch = url.match(/(?:twitch\.tv\/|clips\.twitch\.tv\/)([^/?]+)/); if (clipMatch && clipMatch[1]) { return { videoId: clipMatch[1], type: 'clip' }; } return null; }, /** * Extract video ID from VideoPress URL * @param {string} url * @returns {string|null} */ extract_videopress_id: function(url) { if (!url) return null; var patterns = [ /videopress\.com\/v\/([^/?]+)/, /video\.wordpress\.com\/v\/([^/?]+)/, ]; for (var i = 0; i < patterns.length; i++) { var match = url.match(patterns[i]); if (match && match[1]) { return match[1]; } } return null; }, /** * Extract video ID from Wistia URL * @param {string} url * @returns {string|null} */ extract_wistia_id: function(url) { if (!url) return null; var patterns = [ /wistia\.(?:com|net)\/medias\/([^/?]+)/, /wistia\.(?:com|net)\/embed\/([^/?]+)/, ]; for (var i = 0; i < patterns.length; i++) { var match = url.match(patterns[i]); if (match && match[1]) { return match[1]; } } return null; }, /** * Get video embed URL for a platform * @param {string} platform * @param {string|Object} videoId * @param {Object} settings Video settings (showVideoControls, autoplayVideos) * @returns {string|null} */ get_embed_url: function(platform, videoId, settings) { if (!platform || !videoId) return null; settings = settings || {}; var autoplay = settings.autoplay_videos ? 1 : 0; var controls = settings.show_video_controls !== false ? 1 : 0; switch (platform) { case 'youtube': var youtubeId = typeof videoId === 'string' ? videoId : videoId.videoId; return 'https://www.youtube.com/embed/' + youtubeId + '?autoplay=' + autoplay + '&controls=' + controls + '&rel=0&modestbranding=1'; case 'vimeo': var vimeoId = typeof videoId === 'string' ? videoId : videoId.videoId; return 'https://player.vimeo.com/video/' + vimeoId + '?autoplay=' + autoplay + '&controls=' + controls; case 'dailymotion': var dmId = typeof videoId === 'string' ? videoId : videoId.videoId; return 'https://www.dailymotion.com/embed/video/' + dmId + '?autoplay=' + autoplay + '&controls=' + controls; case 'twitch': var twitchData = typeof videoId === 'object' ? videoId : { videoId: videoId, type: 'video' }; if (twitchData.type === 'clip') { return 'https://clips.twitch.tv/embed?clip=' + twitchData.videoId + '&autoplay=' + autoplay + '&parent=' + window.location.hostname; } else { return 'https://player.twitch.tv/?video=v' + twitchData.videoId + '&autoplay=' + autoplay + '&parent=' + window.location.hostname; } case 'videopress': var vpId = typeof videoId === 'string' ? videoId : videoId.videoId; return 'https://videopress.com/embed/' + vpId + '?autoplay=' + autoplay + '&controls=' + controls; case 'wistia': var wistiaId = typeof videoId === 'string' ? videoId : videoId.videoId; return 'https://fast.wistia.net/embed/iframe/' + wistiaId + '?autoplay=' + autoplay + '&controlsVisibleOnLoad=' + controls; case 'local': return typeof videoId === 'string' ? videoId : null; default: return null; } }, /** * Create HTML5 video player for local videos * @param {string} videoUrl * @param {Object} settings Video settings * @param {string} containerClass * @param {string} videoClass * @returns {HTMLElement} */ create_local_player: function(videoUrl, settings, containerClass, videoClass) { var container = document.createElement("div"); container.className = containerClass || "ngg-video-container"; var video = document.createElement("video"); video.className = videoClass || "ngg-video-player"; video.controls = settings.show_video_controls !== false; video.autoplay = settings.autoplay_videos === true; video.playsInline = true; video.preload = "auto"; video.setAttribute("playsinline", ""); video.setAttribute("webkit-playsinline", ""); video.src = videoUrl; // Global error handler for video loading failures (CORS, network, unsupported formats) video.addEventListener("error", function(e) { console.error("Video player error:", { error: e, videoUrl: videoUrl, errorCode: video.error ? video.error.code : "unknown", errorMessage: video.error ? video.error.message : "Unknown error" }); }); // Size video to match lightbox image sizing behavior video.addEventListener("loadedmetadata", function () { var naturalWidth = video.videoWidth; var naturalHeight = video.videoHeight; if (naturalWidth && naturalHeight) { var container = video.closest('.ngg-video-container'); var displayWidth = naturalWidth; var displayHeight = naturalHeight; // Detect lightbox type and apply appropriate sizing logic if (container) { var fancyboxContent = container.closest('#fancybox-content'); var tbWindow = container.closest('#TB_window'); var slImage = container.closest('.sl-image'); var shWrap = container.closest('#shWrap'); if (shWrap) { // Shutter/Shutter Reloaded var wiH = window.innerHeight || 0; var dbH = document.body.clientHeight || 0; var deH = document.documentElement ? document.documentElement.clientHeight : 0; var wHeight; if (wiH > 0) { wHeight = ((wiH - dbH) > 1 && (wiH - dbH) < 30) ? dbH : wiH; wHeight = ((wHeight - deH) > 1 && (wHeight - deH) < 30) ? deH : wHeight; } else { wHeight = (deH > 0) ? deH : dbH; } if (document.getElementsByTagName("body")[0].className.match(/admin-bar/) && document.getElementById('wpadminbar') !== null) { wHeight = wHeight - document.getElementById('wpadminbar').offsetHeight; } var shHeight = wHeight - 50; var deW = document.documentElement ? document.documentElement.clientWidth : 0; var dbW = window.innerWidth || document.body.clientWidth; var wWidth = (deW > 1) ? deW : dbW; if (displayHeight > shHeight) { displayWidth = displayWidth * (shHeight / displayHeight); displayHeight = shHeight; } if (displayWidth > (wWidth - 16)) { displayHeight = displayHeight * ((wWidth - 16) / displayWidth); displayWidth = wWidth - 16; } video.style.width = displayWidth + "px"; video.style.height = displayHeight + "px"; video.style.maxWidth = "none"; video.style.maxHeight = "none"; video.setAttribute("width", displayWidth); video.setAttribute("height", displayHeight); } else if (fancyboxContent) { // Fancybox setTimeout(function() { var contentRect = fancyboxContent.getBoundingClientRect(); if (contentRect.width > 10 && contentRect.height > 10) { var maxW = contentRect.width; var maxH = contentRect.height; if (displayWidth > maxW || displayHeight > maxH) { var ratio = displayWidth / displayHeight > maxW / maxH ? displayWidth / maxW : displayHeight / maxH; displayWidth = displayWidth / ratio; displayHeight = displayHeight / ratio; } video.style.width = displayWidth + "px"; video.style.height = displayHeight + "px"; video.setAttribute("width", displayWidth); video.setAttribute("height", displayHeight); } }, 50); return; } else if (tbWindow) { // Thickbox var pageWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var pageHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; var x = pageWidth - 150; var y = pageHeight - 150; if (displayWidth > x) { displayHeight = displayHeight * (x / displayWidth); displayWidth = x; if (displayHeight > y) { displayWidth = displayWidth * (y / displayHeight); displayHeight = y; } } else if (displayHeight > y) { displayWidth = displayWidth * (y / displayHeight); displayHeight = y; if (displayWidth > x) { displayHeight = displayHeight * (x / displayWidth); displayWidth = x; } } video.style.width = displayWidth + "px"; video.style.height = displayHeight + "px"; video.setAttribute("width", displayWidth); video.setAttribute("height", displayHeight); } else if (slImage) { // SimpleLightbox var widthRatio = 0.8; var heightRatio = 0.9; var windowWidth = window.innerWidth; var windowHeight = window.innerHeight; var maxWidth = windowWidth * widthRatio; var maxHeight = windowHeight * heightRatio; if (displayWidth > maxWidth || displayHeight > maxHeight) { var ratio = displayWidth / displayHeight > maxWidth / maxHeight ? displayWidth / maxWidth : displayHeight / maxHeight; displayWidth /= ratio; displayHeight /= ratio; } video.style.width = displayWidth + "px"; video.style.height = displayHeight + "px"; video.style.maxWidth = maxWidth + "px"; video.style.maxHeight = maxHeight + "px"; } } } }); // Attempt autoplay when ready if (settings.autoplay_videos) { video.addEventListener("canplay", function () { video.play().catch(function (error) { console.error("Video autoplay failed:", error); }); }); } container.appendChild(video); return container; }, /** * Create iframe embed for platform videos * @param {string} embedUrl * @param {Object} settings Video settings * @param {string} containerClass * @returns {HTMLElement} */ create_embed_player: function(embedUrl, settings, containerClass) { var container = document.createElement("div"); container.className = containerClass || "ngg-video-container"; var iframe = document.createElement("iframe"); iframe.src = embedUrl; iframe.frameBorder = "0"; iframe.allowFullscreen = true; iframe.setAttribute("allow", "autoplay; encrypted-media"); iframe.style.width = "100%"; iframe.style.height = "100%"; iframe.style.border = "none"; // Global error handler for iframe loading failures (CORS, network, etc.) iframe.addEventListener("error", function(e) { console.error("Video iframe error:", { error: e, embedUrl: embedUrl }); }); // Handle iframe load errors (some browsers don't fire error event on iframe) iframe.addEventListener("load", function() { // Check if iframe loaded successfully by trying to access content try { // This will throw if cross-origin restrictions prevent access var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; } catch (e) { // Cross-origin restriction is normal, not an error // Only log actual errors if (e.name !== "SecurityError") { console.error("Video iframe load error:", e); } } }); // Size iframe to match lightbox image sizing behavior // Use responsive aspect ratio (16:9 default for most platforms) var aspectRatio = 16 / 9; var maxWidth = window.innerWidth * 0.9; var maxHeight = window.innerHeight * 0.9; var width = Math.min(maxWidth, 1080); var height = width / aspectRatio; if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; } container.style.width = width + "px"; container.style.height = height + "px"; container.style.maxWidth = "100%"; container.style.maxHeight = "90vh"; container.appendChild(iframe); return container; }, /** * Generic content handler to inject video into a container * @param {Object} options * @param {string} options.videoUrl - Video URL * @param {HTMLElement|jQuery} options.container - Container to append content to * @param {Object} options.settings - Video settings (showVideoControls, showPlayPauseControls, autoplayVideos) * @param {string} [options.containerClass] - CSS class for player container * @param {string} [options.videoClass] - CSS class for video element * @param {Function} [options.onBeforeAppend] - Callback before appending * @returns {HTMLElement|null} */ handle_content: function(options) { var self = this; var videoUrl = options.videoUrl; var $targetContainer = $(options.container); var settings = options.settings || {}; if (!videoUrl) { console.error("Video URL is required"); return null; } try { var platform = self.detect_platform(videoUrl); if (!platform) { // Not a recognized video URL console.warn("Unrecognized video URL:", videoUrl); return null; } } catch (error) { console.error("Error detecting video platform:", error); return null; } var videoContent = null; var videoId = null; // Extract video ID based on platform switch (platform) { case 'youtube': videoId = self.extract_youtube_id(videoUrl); break; case 'vimeo': videoId = self.extract_vimeo_id(videoUrl); break; case 'dailymotion': videoId = self.extract_dailymotion_id(videoUrl); break; case 'twitch': videoId = self.extract_twitch_id(videoUrl); break; case 'videopress': videoId = self.extract_videopress_id(videoUrl); break; case 'wistia': videoId = self.extract_wistia_id(videoUrl); break; case 'local': videoId = videoUrl; // For local videos, use the URL directly break; } if (!videoId) { var errorMsg = self.create_error("Could not extract video ID from URL", options.errorClass); if (typeof options.onBeforeAppend === "function") options.onBeforeAppend(errorMsg); $targetContainer.append(errorMsg); return errorMsg; } // Create player based on platform try { if (platform === 'local') { videoContent = self.create_local_player(videoId, settings, options.containerClass, options.videoClass); } else { var embedUrl = self.get_embed_url(platform, videoId, settings); if (embedUrl) { videoContent = self.create_embed_player(embedUrl, settings, options.containerClass); } else { var errorMsg = self.create_error("Could not generate embed URL", options.errorClass); if (typeof options.onBeforeAppend === "function") options.onBeforeAppend(errorMsg); $targetContainer.append(errorMsg); return errorMsg; } } if (videoContent) { // Handle errors for local videos if (platform === 'local') { var video = videoContent.querySelector("video"); if (video) { video.onerror = function () { $(videoContent).remove(); var errorMsg = self.create_error("Video failed to load", options.errorClass); if (typeof options.onBeforeAppend === "function") options.onBeforeAppend(errorMsg); $targetContainer.append(errorMsg); }; } } if (typeof options.onBeforeAppend === "function") options.onBeforeAppend(videoContent); $targetContainer.append(videoContent); } } catch (error) { console.error("Error creating video player:", error); var errorMsg = self.create_error("Video player creation failed", options.errorClass); if (typeof options.onBeforeAppend === "function") options.onBeforeAppend(errorMsg); $targetContainer.append(errorMsg); return errorMsg; } return videoContent; }, /** * Create error message element * @param {string} message * @param {string} containerClass * @returns {HTMLElement} */ create_error: function(message, containerClass) { var container = document.createElement("div"); container.className = containerClass || "ngg-video-error"; container.innerHTML = '