1/* tb-waveform.js (v8) â DLE-friendly, no WebAudio, modern overlay waveform 2 Fixes: 3 - âдве Ð²Ð¾Ð»Ð½Ñ Ð´ÑÑг под дÑÑгомâ (еÑли CSS не пÑименилÑÑ) â инжекÑим базовÑй CSS 4 - overlay canvas ÑиÑÑеÑÑÑ Ð¿Ð¾ ÑиÑине бокÑа (а не по ÑиÑине overlayWrap) 5 - ÑикÑиÑÑем вÑÑоÑÑ (CFG.height) 6*/ 7(function () { 8 'use strict'; 9 10 var CFG = { 11 endpoint: '/tb_wave.php', 12 height: 54, 13 rootMargin: '600px', 14 maxConcurrency: 2, 15 localCacheTTL: 7 * 24 * 3600, // 7 дней 16 selectorPlayer: '.dleaudioplayer', 17 debug: false, 18 injectCSS: true 19 }; 20 21 function log() { 22 if (!CFG.debug) return; 23 try { console.log.apply(console, arguments); } catch (_) {} 24 } 25 26 function onReady(fn) { 27 if (document.readyState === 'loading') { 28 document.addEventListener('DOMContentLoaded', fn, { once: true }); 29 } else { 30 fn(); 31 } 32 } 33 34 function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); } 35 36 function debounce(fn, ms) { 37 var t = null; 38 return function () { 39 clearTimeout(t); 40 var args = arguments; 41 t = setTimeout(function () { fn.apply(null, args); }, ms); 42 }; 43 } 44 45 function hashDJB2(str) { 46 var h = 5381, i = str.length; 47 while (i) h = (h * 33) ^ str.charCodeAt(--i); 48 return (h >>> 0).toString(16); 49 } 50 51 function nowSec() { return Math.floor(Date.now() / 1000); } 52 53 function getLocal(key) { 54 try { 55 var raw = localStorage.getItem(key); 56 if (!raw) return null; 57 var obj = JSON.parse(raw); 58 if (!obj || !obj.t || !obj.v) return null; 59 if (nowSec() - obj.t > CFG.localCacheTTL) return null; 60 return obj.v; 61 } catch (_) { 62 return null; 63 } 64 } 65 66 function setLocal(key, value) { 67 try { 68 localStorage.setItem(key, JSON.stringify({ t: nowSec(), v: value })); 69 } catch (_) {} 70 } 71 72 // --- базовÑй CSS (ÑÑÐ¾Ð±Ñ Ð½Ðµ бÑло âдвÑÑ Ð²Ð¾Ð»Ð½â пÑи ÑлеÑевÑÐ¸Ñ ÑÑилÑÑ ) --- 73 function injectStyles() { 74 if (!CFG.injectCSS) return; 75 if (document.getElementById('tb-waveform-style')) return; 76 77 var css = 78 '.tb-wave{--tb-wave-h:' + (CFG.height|0) + 'px;--tb-wave-fill:rgba(59,130,246,.35);--tb-wave-line:rgba(59,130,246,.90);' + 79 '--tb-wave-base-fill:rgba(15,23,42,.10);--tb-wave-base-line:rgba(15,23,42,.25);--tb-wave-grid:rgba(15,23,42,.10);' + 80 'display:block;margin:10px 0 8px;}' + 81 82 '.tb-wave__box{position:relative;height:var(--tb-wave-h);border-radius:12px;overflow:hidden;' + 83 'background:rgba(15,23,42,.03);border:1px solid rgba(15,23,42,.08);}' + 84 85 '.tb-wave__canvas{display:block;position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;}' + 86 87 '.tb-wave__overlayWrap{position:absolute;left:0;top:0;height:100%;width:100%;overflow:hidden;}' + 88 89 '.tb-wave__tip{position:absolute;top:6px;transform:translateX(-50%);' + 90 'padding:2px 6px;border-radius:8px;font-size:11px;line-height:1.3;' + 91 'background:rgba(15,23,42,.85);color:#fff;white-space:nowrap;opacity:0;pointer-events:none;}' + 92 '.tb-wave__tip.is-on{opacity:1;}' + 93 94 '.tb-wave__status{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;' + 95 'font-size:12px;color:rgba(15,23,42,.55);pointer-events:none;}' + 96 97 '.tb-wave.is-loading .tb-wave__box:before{content:"";position:absolute;inset:0;' + 98 'background:linear-gradient(90deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.35) 50%,rgba(255,255,255,0) 100%);' + 99 'transform:translateX(-100%);animation:tbwave_shimmer 1.2s infinite;}' + 100 '@keyframes tbwave_shimmer{to{transform:translateX(100%);}}' + 101 102 '.tb-wave.is-error .tb-wave__box{border-color:rgba(239,68,68,.35);background:rgba(239,68,68,.06);}' + 103 '.tb-wave__box{cursor:pointer;}' + 104 '.tb-wave__box:hover{border-color:rgba(59,130,246,.30);box-shadow:0 6px 18px rgba(2,6,23,.08);}'; 105 106 var style = document.createElement('style'); 107 style.id = 'tb-waveform-style'; 108 style.type = 'text/css'; 109 style.appendChild(document.createTextNode(css)); 110 document.head.appendChild(style); 111 } 112 113 // --- request queue (ÑÑÐ¾Ð±Ñ Ð½Ðµ ÑловиÑÑ 429 на паÑке волн) --- 114 var q = []; 115 var active = 0; 116 117 function enqueue(taskFn) { 118 q.push(taskFn); 119 pump(); 120 } 121 122 function pump() { 123 while (active < CFG.maxConcurrency && q.length) { 124 active++; 125 var fn = q.shift(); 126 Promise.resolve() 127 .then(fn) 128 .catch(function () {}) 129 .finally(function () { 130 active--; 131 pump(); 132 }); 133 } 134 } 135 136 // --- UI builders --- 137 function createWaveShell() { 138 var wrap = document.createElement('div'); 139 wrap.className = 'tb-wave is-loading'; 140 wrap.style.setProperty('--tb-wave-h', (CFG.height|0) + 'px'); 141 wrap.innerHTML = 142 '<div class="tb-wave__box">' + 143 '<canvas class="tb-wave__canvas tb-wave__base"></canvas>' + 144 '<div class="tb-wave__overlayWrap">' + 145 '<canvas class="tb-wave__canvas tb-wave__overlay"></canvas>' + 146 '</div>' + 147 '<div class="tb-wave__tip" aria-hidden="true"></div>' + 148 '<div class="tb-wave__status" aria-hidden="true"></div>' + 149 '</div>'; 150 // на вÑÑкий â еÑли CSS не ÑÑпел пÑимениÑÑÑÑ 151 var box = wrap.querySelector('.tb-wave__box'); 152 if (box) box.style.height = (CFG.height|0) + 'px'; 153 return wrap; 154 } 155 156 function setStatus(waveEl, text) { 157 var s = waveEl.querySelector('.tb-wave__status'); 158 if (!s) return; 159 s.textContent = text || ''; 160 } 161 162 function setError(waveEl, text) { 163 waveEl.classList.remove('is-loading'); 164 waveEl.classList.add('is-error'); 165 setStatus(waveEl, text || 'оÑибка'); 166 } 167 168 function setReady(waveEl) { 169 waveEl.classList.remove('is-loading'); 170 waveEl.classList.remove('is-error'); 171 setStatus(waveEl, ''); 172 } 173 174 // --- data --- 175 function normalizeSrc(src) { 176 try { 177 var u = new URL(src, location.href); 178 if (u.origin === location.origin) return u.pathname + (u.search || ''); 179 return u.href; 180 } catch (_) { 181 return src; 182 } 183 } 184 185 function buildWaveUrl(src) { 186 var s = normalizeSrc(src); 187 return CFG.endpoint + '?src=' + encodeURIComponent(s); 188 } 189 190 function parseWaveJson(obj) { 191 if (!obj || !Array.isArray(obj.peaks) || !obj.peaks.length) return null; 192 193 var maxV = 0; 194 for (var i = 0; i < obj.peaks.length; i++) { 195 var v = obj.peaks[i] | 0; 196 if (v > maxV) maxV = v; 197 } 198 if (maxV <= 0) maxV = 100; 199 200 return { 201 peaks: obj.peaks, 202 duration: typeof obj.d === 'number' ? obj.d : null, 203 maxV: maxV 204 }; 205 } 206 207 function fetchWave(src) { 208 var cacheKey = 'tbwave:' + hashDJB2(src); 209 var cached = getLocal(cacheKey); 210 if (cached) { 211 var parsed = parseWaveJson(cached); 212 if (parsed) return Promise.resolve(parsed); 213 } 214 215 var url = buildWaveUrl(src); 216 217 return fetch(url, { credentials: 'same-origin' }) 218 .then(function (r) { 219 if (!r.ok) throw new Error('HTTP ' + r.status); 220 return r.json(); 221 }) 222 .then(function (json) { 223 setLocal(cacheKey, json); 224 var parsed = parseWaveJson(json); 225 if (!parsed) throw new Error('bad json'); 226 return parsed; 227 }); 228 } 229 230 // --- drawing --- 231 function fitCanvas(canvas, cssW, cssH) { 232 var dpr = Math.max(1, Math.min(3, window.devicePixelRatio || 1)); 233 canvas.style.width = cssW + 'px'; 234 canvas.style.height = cssH + 'px'; 235 canvas.width = Math.floor(cssW * dpr); 236 canvas.height = Math.floor(cssH * dpr); 237 var ctx = canvas.getContext('2d');
238 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 239 return ctx; 240 } 241 242 function getCssVar(el, name, fallback) { 243 try { 244 var v = getComputedStyle(el).getPropertyValue(name); 245 v = (v || '').trim(); 246 return v || fallback; 247 } catch (_) { 248 return fallback; 249 } 250 } 251 252 function smoothPeaks(peaks, windowSize) { 253 windowSize = windowSize || 3; 254 if (windowSize <= 1) return peaks; 255 var out = new Array(peaks.length); 256 var half = Math.floor(windowSize / 2); 257 for (var i = 0; i < peaks.length; i++) { 258 var sum = 0, cnt = 0; 259 for (var j = i - half; j <= i + half; j++) { 260 if (j < 0 || j >= peaks.length) continue; 261 sum += (peaks[j] | 0); 262 cnt++; 263 } 264 out[i] = cnt ? Math.round(sum / cnt) : (peaks[i] | 0); 265 } 266 return out; 267 } 268 269 function getBoxSize(waveEl) { 270 var box = waveEl.querySelector('.tb-wave__box'); 271 if (!box) return null; 272 var w = box.clientWidth || 0; 273 var h = box.clientHeight || 0; 274 if (!h) { 275 h = (CFG.height|0) || 54; 276 box.style.height = h + 'px'; 277 } 278 return { box: box, w: w, h: h }; 279 } 280 281 function drawWave(canvas, peaks, maxV, opts, waveEl) { 282 opts = opts || {}; 283 284 var s = waveEl ? getBoxSize(waveEl) : null; 285 var cssW = s ? s.w : (canvas.parentElement ? canvas.parentElement.clientWidth : canvas.clientWidth); 286 var cssH = s ? s.h : (canvas.parentElement ? canvas.parentElement.clientHeight : canvas.clientHeight); 287 288 if (!cssW || !cssH) return; 289 290 var ctx = fitCanvas(canvas, cssW, cssH); 291 ctx.clearRect(0, 0, cssW, cssH); 292 293 var mid = Math.floor(cssH / 2); 294 var pad = 8; 295 var ampH = mid - pad; 296 297 var p = smoothPeaks(peaks, 3); 298 var n = p.length; 299 var w = cssW; 300 301 var styleRoot = waveEl ? waveEl : (canvas.parentElement || document.body); 302 303 var cFill = opts.fill || getCssVar(styleRoot, '--tb-wave-fill', 'rgba(59,130,246,.35)'); 304 var cLine = opts.line || getCssVar(styleRoot, '--tb-wave-line', 'rgba(59,130,246,.85)'); 305 var cGrid = opts.grid || getCssVar(styleRoot, '--tb-wave-grid', 'rgba(15,23,42,.10)'); 306 307 // ÑенÑÑалÑÐ½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ 308 if (cGrid && cGrid !== 'rgba(0,0,0,0)') { 309 ctx.lineWidth = 1; 310 ctx.strokeStyle = cGrid; 311 ctx.beginPath(); 312 ctx.moveTo(0, mid + 0.5); 313 ctx.lineTo(w, mid + 0.5); 314 ctx.stroke(); 315 } 316 317 ctx.lineJoin = 'round'; 318 ctx.lineCap = 'round'; 319 320 ctx.beginPath(); 321 ctx.moveTo(0, mid); 322 323 for (var x = 0; x <= w; x++) { 324 var idx = Math.floor((x / w) * (n - 1)); 325 var a = (p[idx] | 0) / maxV; 326 a = clamp(a, 0, 1); 327 var y = mid - (a * ampH); 328 ctx.lineTo(x, y); 329 } 330 331 for (var x2 = w; x2 >= 0; x2--) { 332 var idx2 = Math.floor((x2 / w) * (n - 1)); 333 var a2 = (p[idx2] | 0) / maxV; 334 a2 = clamp(a2, 0, 1); 335 var y2 = mid + (a2 * ampH); 336 ctx.lineTo(x2, y2); 337 } 338 339 ctx.closePath(); 340 ctx.fillStyle = cFill; 341 ctx.fill(); 342 343 // веÑÑ Ð½ÑÑ Ð¾Ð±Ð²Ð¾Ð´ÐºÐ° 344 ctx.beginPath(); 345 for (var x3 = 0; x3 <= w; x3++) { 346 var idx3 = Math.floor((x3 / w) * (n - 1)); 347 var a3 = (p[idx3] | 0) / maxV; 348 a3 = clamp(a3, 0, 1); 349 var y3 = mid - (a3 * ampH); 350 if (x3 === 0) ctx.moveTo(x3, y3); 351 else ctx.lineTo(x3, y3); 352 } 353 ctx.strokeStyle = cLine; 354 ctx.lineWidth = 1.35; 355 ctx.stroke(); 356 } 357 358 function setProgress(waveEl, ratio) { 359 ratio = clamp(ratio, 0, 1); 360 var o = waveEl.querySelector('.tb-wave__overlayWrap'); 361 if (!o) return; 362 o.style.width = (ratio * 100).toFixed(4) + '%'; 363 } 364 365 // --- seek / binding --- 366 function formatTime(sec) { 367 if (!isFinite(sec) || sec < 0) sec = 0; 368 var m = Math.floor(sec / 60); 369 var s = Math.floor(sec % 60); 370 return m + ':' + (s < 10 ? '0' + s : s); 371 } 372 373 function findAudioNear(playerEl, src) { 374 var a = playerEl.querySelector('audio'); 375 if (a) return a; 376 377 var root = playerEl.closest('#dle-content') || document; 378 var audios = root.querySelectorAll('audio'); 379 if (!audios.length) return null; 380 381 var needle = ''; 382 try { 383 var u = new URL(src, location.href); 384 needle = (u.pathname || '').split('/').pop() || ''; 385 } catch (_) { 386 needle = (src || '').split('/').pop() || ''; 387 } 388 389 for (var i = 0; i < audios.length; i++) { 390 var cur = audios[i].currentSrc || audios[i].src || ''; 391 if (needle && cur.indexOf(needle) !== -1) return audios[i]; 392 } 393 394 if (audios.length === 1) return audios[0]; 395 return null; 396 } 397 398 function waitForAudio(playerEl, src, timeoutMs) { 399 timeoutMs = timeoutMs || 2500; 400 return new Promise(function (resolve) { 401 var found = findAudioNear(playerEl, src); 402 if (found) return resolve(found); 403 404 var done = false;
405 var t = setTimeout(function () { 406 done = true; 407 try { obs.disconnect(); } catch (_) {} 408 resolve(null); 409 }, timeoutMs); 410 411 var obs = new MutationObserver(function () { 412 if (done) return; 413 var a = findAudioNear(playerEl, src); 414 if (a) { 415 done = true; 416 clearTimeout(t); 417 try { obs.disconnect(); } catch (_) {} 418 resolve(a); 419 } 420 }); 421 422 obs.observe(playerEl.parentNode || document.body, { childList: true, subtree: true }); 423 }); 424 } 425 426 function bindSeek(waveEl, playerEl, src, durationHint) { 427 if (waveEl.dataset.tbSeekBound === '1') return; 428 waveEl.dataset.tbSeekBound = '1'; 429 430 var box = waveEl.querySelector('.tb-wave__box') || waveEl; 431 var tip = waveEl.querySelector('.tb-wave__tip'); 432 var isDown = false; 433 var lastRatio = 0; 434 435 function rectOf(el) { return el.getBoundingClientRect(); } 436 437 function ratioFromEvent(e) { 438 var r = rectOf(box); 439 var x = (e.clientX - r.left) / r.width; 440 return clamp(x, 0, 1); 441 } 442 443 function showTip(e, dur) { 444 if (!tip) return; 445 var r = rectOf(box); 446 var x = clamp(e.clientX - r.left, 0, r.width); 447 var ratio = x / r.width; 448 var sec = dur ? (ratio * dur) : 0; 449 tip.style.left = x + 'px'; 450 tip.textContent = dur ? formatTime(sec) : ''; 451 tip.classList.add('is-on'); 452 } 453 454 function hideTip() { 455 if (!tip) return; 456 tip.classList.remove('is-on'); 457 } 458 459 function getDuration(audio) { 460 var d = audio && isFinite(audio.duration) && audio.duration > 0 ? audio.duration : null; 461 if (!d && durationHint && durationHint > 0) d = durationHint; 462 return d; 463 } 464 465 function applySeek(ratio) { 466 lastRatio = ratio; 467 468 var audio = findAudioNear(playerEl, src); 469 if (!audio) { 470 waveEl.dataset.tbPendingSeek = String(ratio); 471 setProgress(waveEl, ratio); 472 return; 473 } 474 475 var dur = getDuration(audio); 476 if (!dur) { 477 waveEl.dataset.tbPendingSeek = String(ratio); 478 setProgress(waveEl, ratio); 479 return; 480 } 481 482 try { 483 audio.currentTime = ratio * dur; 484 setProgress(waveEl, ratio); 485 } catch (_) {} 486 } 487 488 box.addEventListener('pointerdown', function (e) { 489 isDown = true; 490 try { box.setPointerCapture(e.pointerId); } catch (_) {} 491 applySeek(ratioFromEvent(e)); 492 e.preventDefault(); 493 }); 494 495 box.addEventListener('pointermove', function (e) { 496 var audio = findAudioNear(playerEl, src); 497 var dur = getDuration(audio); 498 showTip(e, dur); 499 500 if (!isDown) return; 501 applySeek(ratioFromEvent(e)); 502 e.preventDefault(); 503 }); 504 505 box.addEventListener('pointerup', function (e) { 506 isDown = false; 507 hideTip(); 508 e.preventDefault(); 509 }); 510 511 box.addEventListener('pointercancel', function () { 512 isDown = false; 513 hideTip(); 514 }); 515 516 box.addEventListener('mouseleave', function () { 517 if (!isDown) hideTip(); 518 }); 519 520 waitForAudio(playerEl, src, 3500).then(function (audio) { 521 if (!audio) return; 522 523 function syncProgress() { 524 var dur = getDuration(audio); 525 if (!dur) return; 526 setProgress(waveEl, audio.currentTime / dur); 527 } 528 529 audio.addEventListener('timeupdate', syncProgress); 530 audio.addEventListener('seeked', syncProgress); 531 audio.addEventListener('loadedmetadata', function () { 532 var p = waveEl.dataset.tbPendingSeek; 533 if (p) { 534 delete waveEl.dataset.tbPendingSeek; 535 applySeek(parseFloat(p) || lastRatio); 536 } else { 537 syncProgress(); 538 } 539 }); 540 541 syncProgress(); 542 }); 543 } 544 545 // --- attach workflow --- 546 var io = null; 547 548 function ensureIO() { 549 if (io) return; 550 if (!('IntersectionObserver' in window)) return; 551 552 io = new IntersectionObserver(function (entries) { 553 entries.forEach(function (ent) { 554 if (!ent.isIntersecting) return; 555 var waveEl = ent.target; 556 io.unobserve(waveEl); 557 558 var src = waveEl.dataset.tbSrc; 559 var playerId = waveEl.dataset.tbPlayerId; 560 var playerEl = playerId ? document.querySelector('[data-tb-player-id="' + playerId + '"]') : null; 561 if (!src || !playerEl) return; 562 563 enqueue(function () { 564 setStatus(waveEl, 'loading...'); 565 return fetchWave(src) 566 .then(function (data) { 567 waveEl.dataset.tbDuration = data.duration ? String(data.duration) : ''; 568
569 var base = waveEl.querySelector('canvas.tb-wave__base'); 570 var over = waveEl.querySelector('canvas.tb-wave__overlay'); 571 if (!base || !over) throw new Error('no canvas'); 572 573 drawWave(base, data.peaks, data.maxV, { 574 fill: getCssVar(waveEl, '--tb-wave-base-fill', 'rgba(15,23,42,.10)'), 575 line: getCssVar(waveEl, '--tb-wave-base-line', 'rgba(15,23,42,.25)'), 576 grid: getCssVar(waveEl, '--tb-wave-grid', 'rgba(15,23,42,.10)') 577 }, waveEl); 578 579 drawWave(over, data.peaks, data.maxV, { 580 fill: getCssVar(waveEl, '--tb-wave-fill', 'rgba(59,130,246,.35)'), 581 line: getCssVar(waveEl, '--tb-wave-line', 'rgba(59,130,246,.90)'), 582 grid: 'rgba(0,0,0,0)' 583 }, waveEl); 584 585 setReady(waveEl); 586 587 var dHint = data.duration || parseFloat(waveEl.dataset.tbDuration || '0') || 0; 588 bindSeek(waveEl, playerEl, src, dHint); 589 590 setProgress(waveEl, 0); 591 }) 592 .catch(function (err) { 593 log('wave error', err); 594 setError(waveEl, (err && err.message) ? err.message : 'оÑибка'); 595 }); 596 }); 597 }); 598 }, { root: null, rootMargin: CFG.rootMargin, threshold: 0.02 }); 599 } 600 601 function attachToPlayer(playerEl) { 602 if (!playerEl || playerEl.dataset.tbWaveAttached === '1') return; 603 604 var li = playerEl.querySelector('li[data-url]'); 605 if (!li) return; 606 607 var src = li.getAttribute('data-url'); 608 if (!src) return; 609 610 var prev = playerEl.previousElementSibling; 611 if (prev && prev.classList && prev.classList.contains('tb-wave')) { 612 playerEl.dataset.tbWaveAttached = '1'; 613 return; 614 } 615 616 var pid = playerEl.dataset.tbPlayerId; 617 if (!pid) { 618 pid = 'p' + Math.random().toString(16).slice(2); 619 playerEl.dataset.tbPlayerId = pid; 620 } 621 622 var waveEl = createWaveShell(); 623 waveEl.dataset.tbSrc = src; 624 waveEl.dataset.tbPlayerId = pid; 625 626 playerEl.parentNode.insertBefore(waveEl, playerEl); 627 playerEl.dataset.tbWaveAttached = '1'; 628 629 ensureIO(); 630 if (io) { 631 io.observe(waveEl); 632 } else { 633 enqueue(function () { 634 return fetchWave(src) 635 .then(function (data) { 636 var base = waveEl.querySelector('canvas.tb-wave__base'); 637 var over = waveEl.querySelector('canvas.tb-wave__overlay'); 638 639 drawWave(base, data.peaks, data.maxV, { 640 fill: getCssVar(waveEl, '--tb-wave-base-fill', 'rgba(15,23,42,.10)'), 641 line: getCssVar(waveEl, '--tb-wave-base-line', 'rgba(15,23,42,.25)'), 642 grid: getCssVar(waveEl, '--tb-wave-grid', 'rgba(15,23,42,.10)') 643 }, waveEl); 644 645 drawWave(over, data.peaks, data.maxV, { 646 fill: getCssVar(waveEl, '--tb-wave-fill', 'rgba(59,130,246,.35)'), 647 line: getCssVar(waveEl, '--tb-wave-line', 'rgba(59,130,246,.90)'), 648 grid: 'rgba(0,0,0,0)' 649 }, waveEl); 650 651 setReady(waveEl); 652 653 var dHint = data.duration || 0; 654 bindSeek(waveEl, playerEl, src, dHint); 655 setProgress(waveEl, 0); 656 }) 657 .catch(function (err) { 658 setError(waveEl, (err && err.message) ? err.message : 'оÑибка'); 659 }); 660 }); 661 } 662 } 663 664 function scan() { 665 var players = document.querySelectorAll(CFG.selectorPlayer); 666 for (var i = 0; i < players.length; i++) { 667 attachToPlayer(players[i]); 668 } 669 } 670 671 function resizeAll() { 672 var waves = document.querySelectorAll('.tb-wave'); 673 for (var i = 0; i < waves.length; i++) { 674 var waveEl = waves[i]; 675 var src = waveEl.dataset.tbSrc; 676 if (!src) continue; 677 678 var cacheKey = 'tbwave:' + hashDJB2(src); 679 var cached = getLocal(cacheKey); 680 var parsed = parseWaveJson(cached); 681 if (!parsed) continue; 682 683 var base = waveEl.querySelector('canvas.tb-wave__base'); 684 var over = waveEl.querySelector('canvas.tb-wave__overlay'); 685 if (!base || !over) continue; 686 687 drawWave(base, parsed.peaks, parsed.maxV, { 688 fill: getCssVar(waveEl, '--tb-wave-base-fill', 'rgba(15,23,42,.10)'), 689 line: getCssVar(waveEl, '--tb-wave-base-line', 'rgba(15,23,42,.25)'), 690 grid: getCssVar(waveEl, '--tb-wave-grid', 'rgba(15,23,42,.10)') 691 }, waveEl); 692 693 drawWave(over, parsed.peaks, parsed.maxV, { 694 fill: getCssVar(waveEl, '--tb-wave-fill', 'rgba(59,130,246,.35)'), 695 line: getCssVar(waveEl, '--tb-wave-line', 'rgba(59,130,246,.90)'), 696 grid: 'rgba(0,0,0,0)' 697 }, waveEl); 698 } 699 } 700 701 function watchDom() { 702 var root = document.querySelector('#dle-content') || document.body; 703 var mo = new MutationObserver(debounce(function () { 704 scan(); 705 }, 120)); 706 mo.observe(root, { childList: true, subtree: true }); 707 } 708 709 function boot() { 710 injectStyles(); 711 scan(); 712 watchDom(); 713 714 window.addEventListener('pageshow', function () { 715 setTimeout(scan, 0); 716 setTimeout(scan, 700); 717 }); 718 719 window.addEventListener('resize', debounce(resizeAll, 140)); 720 721 document.addEventListener('visibilitychange', function () { 722 if (!document.hidden) { 723 setTimeout(scan, 0); 724 setTimeout(scan, 600); 725 } 726 }); 727 } 728 729 onReady(boot); 730})();
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.