PageSourceSearch

https://wudang.academy/streaming-midpoint-previews-v3.js?v=3

js wudang.academy collected 2026-10-08 12:13:19 UTC 9,433 bytes, 197 lines download raw bytes

1(function () {
2  'use strict';
3
4  function posterPolicy(source, base, manifest) {
5    const src = String(source || '').trim();
6    if (!/^\/media\/[A-Za-z0-9][A-Za-z0-9._-]*\.(avif|webp|png|jpe?g)$/i.test(src) || src.includes('..')) return null;
7    const policy = {src, ink: !!base && src === '/media/' + base + '-poster.avif'};
8    const entry = policy.ink && manifest && Object.hasOwn(manifest, base) && manifest[base];
9    if (!entry || entry.src !== src || !Number.isSafeInteger(entry.width) || entry.width < 1 || entry.width > 8000 || !Array.isArray(entry.candidates)) return policy;
10    const candidates = new Map();
11    for (const item of entry.candidates.slice(0, 3)) {
12      if (!item || !Number.isSafeInteger(item.width) || item.width < 1 || item.width >= entry.width) continue;
13      const role = ['sm','md','lg'].find(role => item.src === '/media/' + base + '-poster-' + role + '.avif');
14      if (!role || item.width > {sm:400,md:800,lg:1200}[role]) continue;
15      candidates.set(item.width, item.src);
16    }
17    if (candidates.size) {
18      candidates.set(entry.width, src);
19      policy.srcset = [...candidates].sort((a,b) => a[0]-b[0]).map(([width,url]) => url + ' ' + width + 'w').join(', ');
20    }
21    return policy;
22  }
23
24  function bindImageSource(img, policy, lazy, onFailure) {
25    let fallback = !policy.srcset;
26    let disposed = false;
27    let terminal = false;
28    const failed = () => {
29      if (disposed || terminal) return;
30      if (!fallback) {
31        fallback = true;
32        img.removeAttribute('srcset');
33        img.removeAttribute('sizes');
34        img.src = policy.src;
35        return;
36      }
37      terminal = true;
38      onFailure();
39    };
40    img.addEventListener('error', failed);
41    if (policy.srcset) {
42      img.sizes = lazy ? '(max-width: 600px) 64px, 88px' : '100vw';
43      img.srcset = policy.srcset;
44    }
45    img.src = policy.src;
46    return {dispose() {
47      if (disposed) return;
48      disposed = true;
49      img.removeEventListener('error', failed);
50    }};
51  }
52
53  function bindPreviewLifecycle(media, setVisible) {
54    const hide = () => setVisible(false);
55    const show = () => setVisible(true);
56    media.addEventListener('play', hide);
57    media.addEventListener('playing', hide);
58    media.addEventListener('ended', show);
59    return {
60      seek: hide,
61      dispose() {
62        media.removeEventListener('play', hide);
63        media.removeEventListener('playing', hide);
64        media.removeEventListener('ended', show);
65      }
66    };
67  }
68
69  if (typeof module === 'object' && module.exports) {
70    module.exports = {posterPolicy, bindPreviewLifecycle, bindImageSource};
71    return;
72  }
73  if (location.hostname !== 'wudang-academy.aioengine.io') return;
74  if (document.getElementById('wudang-midpoint-preview-style')) return;
75
76  const style = document.createElement('style');
77  style.id = 'wudang-midpoint-preview-style';
78  style.textContent = `
79    .sp-ep-preview{display:block;position:relative;flex:0 0 88px;width:88px;aspect-ratio:16/9;overflow:hidden;border-radius:8px;background:#181712}
80    .sp-ep-preview img{display:block;width:100%;height:100%;object-fit:contain}
81    .sp-ep-preview[hidden]{display:none!important}
82    .sp-ep-header:has(.sp-ep-preview){gap:12px}
83    .sp-ep-header:has(.sp-ep-preview) h3{min-width:0;flex:1;overflow-wrap:anywhere}
84    .wudang-ink-preview{background:#302b20}
85    .wudang-ink-preview img{filter:sepia(.55) saturate(.68) contrast(1.06)}
86    .wudang-ink-preview:after{content:'';position:absolute;inset:0;pointer-events:none;box-shadow:inset 0 0 0 1px #b8860b66,inset 0 0 20px #2d201b22;background:linear-gradient(135deg,#dac59822,transparent 38%,transparent 70%,#3e312122)}
87    .mp-screen>.mp-midpoint-preview{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden}
88    .mp-midpoint-preview img{width:100%;height:100%;display:block;object-fit:contain}
89    .mp-midpoint-preview[hidden]{display:none!important}
90    .mp-preview-visible .mp-subtitle{visibility:hidden}
91    .sp-ep-progress[hidden]{display:none!important}
92    @media(max-width:600px){.sp-ep-preview{flex-basis:64px;width:64px;border-radius:6px}.sp-ep-header:has(.sp-ep-preview){gap:9px}.sp-ep-header:has(.sp-ep-preview) h3{font-size:15px;line-height:1.3}.sp-ep-header .sp-ep-progress{min-width:0;padding:2px 7px}.sp-ep-header .sp-ep-toggle{flex-shrink:0}}
93  `;
94  document.head.appendChild(style);
95  const bindings = new Map();
96  const headerBindings = new Map();
97  const manifest = window.WUDANG_POSTER_CANDIDATES_V1;
98
99  function previewImage(policy, className, lazy) {
100    const frame = document.createElement('span');
101    frame.className = className + (policy.ink ? ' wudang-ink-preview' : '');
102    frame.setAttribute('aria-hidden', 'true');
103    const img = document.createElement('img');
104    img.alt = '';
105    img.decoding = 'async';
106    img.loading = lazy ? 'lazy' : 'eager';
107    frame.appendChild(img);
108    return {frame, img};
109  }
110
111  function reconcile() {
112    for (const [img, binding] of headerBindings) {
113      if (!img.isConnected || !binding.owner.isConnected || binding.owner.dataset.mpPoster !== binding.source || binding.owner.dataset.mpSubtitleBase !== binding.base) {
114        binding.dispose(); binding.frame.remove(); headerBindings.delete(img);
115      }
116    }
117    for (const [video, binding] of bindings) {
118      if (!video.isConnected || !binding.active()) {binding.dispose(); bindings.delete(video);}
119    }
120    document.querySelectorAll('.sp-episode').forEach(episode => {
121      const holder = episode.querySelector('[data-mp-mode="video"]');
122      if (!holder) return;
123      const policy = posterPolicy(holder.dataset.mpPoster, holder.dataset.mpSubtitleBase, manifest);
124      if (!policy) return;
125      const header = episode.querySelector('.sp-ep-header');
126      if (header && !header.querySelector('.sp-ep-preview')) {
127        const thumbnail = previewImage(policy, 'sp-ep-preview', true);
128        const imageBinding = bindImageSource(thumbnail.img, policy, true, () => {thumbnail.frame.hidden = true;});
129        headerBindings.set(thumbnail.img, {owner:holder, source:holder.dataset.mpPoster, base:holder.dataset.mpSubtitleBase, frame:thumbnail.frame, dispose:imageBinding.dispose});
130        header.prepend(thumbnail.frame);
131      }
132      const video = holder.querySelector('video');
133      const screen = holder.querySelector('.mp-screen');
134      if (!video || !screen || bindings.has(video)) return;
135      // Player teardown clones its DOM. Only the registered live media instance
136      // may own a preview; inherited overlays are not new players.
137      const posterSource = holder.dataset.mpPoster;
138      const posterBase = holder.dataset.mpSubtitleBase;
139      const active = () => typeof getMediaPlayer === 'function' && getMediaPlayer(holder.id)?.media === video && holder.dataset.mpPoster === posterSource && holder.dataset.mpSubtitleBase === posterBase;
140      if (!active()) return;
141      screen.querySelectorAll('.mp-midpoint-preview').forEach(frame => frame.remove());
142      const preview = previewImage(policy, 'mp-midpoint-preview', false);
143      preview.frame.hidden = true;
144      let ready = false;
145      let disposed = false;
146      let wanted = video.paused;
147      const render = () => {
148        if (disposed) return;
149        const visible = ready && wanted;
150        preview.frame.hidden = !visible;
151        holder.classList.toggle('mp-preview-visible', visible);
152      };
153      const binding = bindPreviewLifecycle(video, visible => {wanted = visible; render();});
154      const loaded = () => {ready = true; render();};
155      const failed = () => {ready = false; render();};
156      preview.img.addEventListener('load', loaded, {once:true});
157      const imageBinding = bindImageSource(preview.img, policy, false, failed);
158      screen.appendChild(preview.frame);
159      if (preview.img.complete && preview.img.naturalWidth > 0) {ready = true; render();}
160      const seek = event => {
161        if (event.target.closest('.mp-wave,.mp-progress,.mp-skip')) binding.seek();
162      };
163      holder.addEventListener('pointerdown', seek);
164      holder.addEventListener('keydown', seek);
165      bindings.set(video, {active, dispose() {
166        disposed = true;
167        binding.dispose();
168        preview.img.removeEventListener('load', loaded);
169        imageBinding.dispose();
170        holder.removeEventListener('pointerdown', seek);
171        holder.removeEventListener('keydown', seek);
172        holder.classList.remove('mp-preview-visible');
173        preview.frame.remove();
174      }});
175    });
176  }
177  const root = document.getElementById('app');
178  if (!root) return;
179  let scheduled = false;
180  const observer = new MutationObserver(() => {
181    if (scheduled) return;
182    scheduled = true;
183    queueMicrotask(() => {scheduled = false; reconcile();});
184  });
185  observer.observe(root, {childList:true, subtree:true, attributes:true, attributeFilter:['data-mp-poster','data-mp-subtitle-base']});
186  reconcile();
187  window.addEventListener('pagehide', () => {
188    observer.disconnect();
189    for (const binding of bindings.values()) binding.dispose();
190    bindings.clear();
191    for (const binding of headerBindings.values()) {binding.dispose(); binding.frame.remove();}
192    headerBindings.clear();
193  });
194  window.addEventListener('pageshow', event => {
195    if (event.persisted) {observer.observe(root, {childList:true, subtree:true, attributes:true, attributeFilter:['data-mp-poster','data-mp-subtitle-base']}); reconcile();}
196  });
197})();

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.