PageSourceSearch

https://wirres.net/assets/js/pswp/photoswipe-video-plugin/video-content-setup.js

js wirres.net collected 2026-09-24 19:03:36 UTC 8,840 bytes, 301 lines download raw bytes

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.