1/** 2 * Check if slide has video content 3 * 4 * @param {Slide|Content} content Slide or Content object 5 * @returns Boolean 6 */ 7function isVideoContent(content) { 8 return (content && content.data && content.data.type === 'video'); 9} 10 11class VideoContentSetup { 12 constructor(lightbox, options) { 13 this.options = options; 14 15 this.initLightboxEvents(lightbox); 16 lightbox.on('init', () => { 17 this.initPswpEvents(lightbox.pswp); 18 }); 19 } 20 21 initLightboxEvents(lightbox) { 22 lightbox.on('contentLoad', this.onContentLoad.bind(this)); 23 lightbox.on('contentDestroy', this.onContentDestroy.bind(this)); 24 lightbox.on('contentActivate', this.onContentActivate.bind(this)); 25 lightbox.on('contentDeactivate', this.onContentDeactivate.bind(this)); 26 lightbox.on('contentAppend', this.onContentAppend.bind(this)); 27 lightbox.on('contentResize', this.onContentResize.bind(this)); 28 29 lightbox.addFilter('isKeepingPlaceholder', this.isKeepingPlaceholder.bind(this)); 30 lightbox.addFilter('isContentZoomable', this.isContentZoomable.bind(this)); 31 lightbox.addFilter('useContentPlaceholder', this.useContentPlaceholder.bind(this)); 32 33 lightbox.addFilter('domItemData', (itemData, element, linkEl) => { 34 if (linkEl?.dataset?.pswpType === 'video') { 35 itemData.type = 'video'; 36 37 // Quelle setzen 38 if (linkEl.dataset.pswpVideoSources) { 39 itemData.videoSources = JSON.parse(linkEl.dataset.pswpVideoSources); 40 } else if (linkEl.dataset.pswpVideoSrc) { 41 itemData.videoSrc = linkEl.dataset.pswpVideoSrc; 42 } else { 43 itemData.videoSrc = linkEl.href; 44 } 45 46 // Poster übernehmen (bereits vorhanden) 47 itemData.element = linkEl; 48 49 // Video-Attribute aus data-pswp-* lesen 50 const videoAttributes = {}; 51 const booleanAttrs = ['muted', 'loop', 'autoplay', 'playsinline', 'controls']; 52 53 for (const attr of booleanAttrs) { 54 const key = `pswp${attr[0].toUpperCase()}${attr.slice(1)}`; // z.â¯B. pswpMuted 55 if (key in linkEl.dataset) { 56 const rawValue = linkEl.dataset[key]; 57 // Alles auÃer exakt "false" wird als true interpretiert 58 videoAttributes[attr] = rawValue !== 'false'; 59 } 60 } 61 62 // preload ggf. setzen 63 if (linkEl.dataset.pswpPreload) { 64 videoAttributes.preload = linkEl.dataset.pswpPreload; 65 } 66 67 itemData.videoAttributes = videoAttributes; 68 } 69 70 return itemData; 71 }); 72 } 73 74 initPswpEvents(pswp) { 75 // Prevent draggin when pointer is in bottom part of the video 76 // todo: add option for this 77 pswp.on('pointerDown', (e) => { 78 const slide = pswp.currSlide; 79 if (isVideoContent(slide) && this.options.preventDragOffset) { 80 const origEvent = e.originalEvent; 81 if (origEvent.type === 'pointerdown') { 82 const videoHeight = Math.ceil(slide.height * slide.currZoomLevel); 83 const verticalEnding = videoHeight + slide.bounds.center.y; 84 const pointerYPos = origEvent.pageY - pswp.offset.y; 85 if (pointerYPos > verticalEnding - this.options.preventDragOffset 86 && pointerYPos < verticalEnding) { 87 e.preventDefault(); 88 } 89 } 90 } 91 }); 92 93 // do not append video on nearby slides 94 pswp.on('appendHeavy', (e) => { 95 if (isVideoContent(e.slide) && !e.slide.isActive) { 96 e.preventDefault(); 97 } 98 }); 99 100 pswp.on('close', () => { 101 if (isVideoContent(pswp.currSlide.content)) { 102 // Switch from zoom to fade closing transition, 103 // as zoom transition is choppy for videos 104 if (!pswp.options.showHideAnimationType 105 || pswp.options.showHideAnimationType === 'zoom') { 106 pswp.options.showHideAnimationType = 'fade'; 107 } 108 109 // pause video when closing 110 this.pauseVideo(pswp.currSlide.content); 111 } 112 }); 113 } 114 115 onContentDestroy({ content }) { 116 if (isVideoContent(content)) { 117 if (content._videoPosterImg) { 118 content._videoPosterImg.onload = content._videoPosterImg.onerror = null; 119 content._videoPosterImg = null; 120 } 121 } 122 } 123 124 onContentResize(e) { 125 if (isVideoContent(e.content)) { 126 e.preventDefault(); 127 128 const width = e.width; 129 const height = e.height; 130 const content = e.content; 131 132 if (content.element) { 133 content.element.style.width = width + 'px'; 134 content.element.style.height = height + 'px'; 135 } 136 137 if (content.slide && content.slide.placeholder) { 138 // override placeholder size, so it more accurately matches the video 139 const placeholderElStyle = content.slide.placeholder.element.style; 140 placeholderElStyle.transform = 'none'; 141 placeholderElStyle.width = width + 'px'; 142 placeholderElStyle.height = height + 'px'; 143 } 144 } 145 } 146 147 148 isKeepingPlaceholder(isZoomable, content) { 149 if (isVideoContent(content)) { 150 return false;
151 } 152 return isZoomable; 153 } 154 155 isContentZoomable(isZoomable, content) { 156 if (isVideoContent(content)) { 157 return false; 158 } 159 return isZoomable; 160 } 161 162 onContentActivate({ content }) { 163 if (isVideoContent(content) && this.options.autoplay) { 164 this.playVideo(content); 165 } 166 } 167 168 onContentDeactivate({ content }) { 169 if (isVideoContent(content)) { 170 this.pauseVideo(content); 171 } 172 } 173 174 onContentAppend(e) { 175 if (isVideoContent(e.content)) { 176 e.preventDefault(); 177 e.content.isAttached = true; 178 e.content.appendImage(); 179 } 180 } 181 182 onContentLoad(e) { 183 const content = e.content; // todo: videocontent 184 185 if (!isVideoContent(e.content)) { 186 return; 187 } 188 189 // stop default content load 190 e.preventDefault(); 191 192 if (content.element) { 193 return; 194 } 195 196 content.state = 'loading'; 197 content.type = 'video'; // TODO: move this to pswp core? 198 199 content.element = document.createElement('video'); 200 201// Zusammenführen: globale Plugin-Optionen + lokale data-pswp-* Optionen 202const mergedVideoAttributes = { 203 ...(this.options.videoAttributes || {}), 204 ...(content.data.videoAttributes || {}) 205}; 206 207// Attribute ans <video> setzen 208for (let key in mergedVideoAttributes) { 209 const value = mergedVideoAttributes[key]; 210 if (value === false || value === null || value === undefined) { 211 continue; // nicht setzen 212 } 213 214 if (value === true || value === '') { 215 // Boolean attribute, z.â¯B. muted, playsinline 216 content.element.setAttribute(key, ''); 217 } else { 218 // Attribut mit explizitem Wert (z.â¯B. preload="auto") 219 content.element.setAttribute(key, value); 220 } 221} 222 223// JS-Property explizit setzen für muted (wichtig für mobile Browser) 224if (mergedVideoAttributes.muted === true) { 225 content.element.muted = true; 226} 227 228// content.element.setAttribute('poster', content.data.msrc); 229// 230// this.preloadVideoPoster(content, content.data.msrc); 231 232const videoEl = content.element; 233 234// Ursprüngliches <a> Element abrufen 235const triggerEl = content.data.element; // das ist das <a>, das PhotoSwipe geöffnet hat 236 237// Poster zuerst aus data-poster holen, dann fallback auf msrc 238const customPoster = triggerEl?.getAttribute('data-poster-img'); 239const posterUrl = customPoster || content.data.msrc; 240 241if (posterUrl) { 242 videoEl.setAttribute('poster', posterUrl); 243 this.preloadVideoPoster(content, posterUrl); 244} 245 246 content.element.style.position = 'absolute'; 247 content.element.style.left = 0; 248 content.element.style.top = 0; 249 250 if (content.data.videoSources) { 251 content.data.videoSources.forEach((source) => { 252 let sourceEl = document.createElement('source'); 253 sourceEl.src = source.src; 254 sourceEl.type = source.type; 255 content.element.appendChild(sourceEl); 256 }); 257 } else if (content.data.videoSrc) { 258 // Force video preload 259 // https://muffinman.io/blog/hack-for-ios-safari-to-display-html-video-thumbnail/ 260 // this.element.src = this.data.videoSrc + '#t=0.001'; 261 content.element.src = content.data.videoSrc; 262 } 263 } 264 265 preloadVideoPoster(content, src) { 266 if (!content._videoPosterImg && src) { 267 content._videoPosterImg = new Image(); 268 content._videoPosterImg.src = src; 269 if (content._videoPosterImg.complete) { 270 content.onLoaded(); 271 } else { 272 content._videoPosterImg.onload = content._videoPosterImg.onerror = () => { 273 content.onLoaded(); 274 }; 275 } 276 } 277 } 278 279 280 playVideo(content) { 281 if (content.element) { 282 content.element.play(); 283 } 284 } 285 286 pauseVideo(content) { 287 if (content.element) { 288 content.element.pause(); 289 } 290 } 291 292 useContentPlaceholder(usePlaceholder, content) { 293 if (isVideoContent(content)) { 294 return true; 295 } 296 return usePlaceholder; 297 } 298 299} 300 301export default VideoContentSetup;
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.