醉美水芙蓉 发表于 2026-6-9 22:17:33

岁月如笔写春秋(合唱)

<meta charset="UTF-8">
   
    <title>海浪频谱播放器</title>
    <style>
      * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
      }

       .showcase {
            position: relative;
            width: 1350px;
            height: 720px;
            margin-left:-258px;
            margin-top: 8px;
          background:url(https://img-baofun.zhhainiao.com/pcwallpaper_ugc/preview/fdd2ca4bc8aae69db7dc6c05f888095e_preview.webp) no-repeat center/cover;
      }

      .bg-canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            z-index: 0;
            pointer-events: none;
      }
      #spectrumCanvas {
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 2;
            display: block;
            background: transparent;
            pointer-events: none;
      }
      #lyricCanvas {
            position: absolute;
            top: 330px;
            left: 50%;
            transform: translateX(-50%);
            width: 800px;
            height: 140px;
            z-index: 15;
            cursor: pointer;
            background: transparent;
            pointer-events: auto;
      }
      #player {
            position: absolute;
            z-index: 10;
            right: 28px;
            top: 28px;
            width: 120px;
            height: 120px;
            cursor: pointer;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.1);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
            transition: transform 0.3s ease, background 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
      }
      #player svg {
            width: 70%;
            height: 70%;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
      }
      #player:hover {
            transform: scale(1.1);
            background: rgba(255, 255, 255, 0.2);
      }
      @keyframes flowerSpin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
      }
      #player.playing svg {
            animation: flowerSpin 6s linear infinite;
      }
      .time-display {
            position: absolute;
            top: 140px;
            left: 50%;
            transform: translateX(-50%);
            padding: 6px 18px;
            font-size: 18px;
            font-weight: bold;
            color: #e6f7ff;
            letter-spacing: 1px;
            z-index: 16;
            pointer-events: none;
            font-family: monospace;
            white-space: nowrap;
            text-shadow: 1px 1px 0 #003366, -1px -1px 0 #003366, 1px -1px 0 #003366, -1px 1px 0 #003366;
      }
      .song-title {
            position: absolute;
            top: 80px;
            left: 445px;
            color: #006600;
            font-size: 2.0rem;
            margin-bottom: 20px;
            letter-spacing: 6px;
            z-index: 2;
            transform: perspective(1000px) rotateY(40deg);
            text-shadow: 1px 1px 0 #ffffff, 2px 2px 0 #ffffff, 3px 3px 0 #ffffff;
            background: none;
            border: none;
            box-shadow: none;
            padding: 0;
      }
      .xlaj {
            position: absolute;
            left:33px;
            width: 40px;
            top: 100px;
            height: 200px;
            text-align: center;
            font-size: 16pt;
            filter: drop-shadow(2px 2px 2px #080808);
            color: #00ffff;
            line-height: 100%;
            font-family: "华文新魏", cursive;
            z-index: 2;
            text-shadow: -1px -1px 0 #ff0000, 1px -1px 0 #ff0000, -1px 1px 0 #ff0000, 1px 1px 0 #ff0000;
            writing-mode: vertical-rl;
            writing-mode: vertical-lr;
      }
      .dancer { position:absolute; pointer-events:none; z-index:5; }
      .dancer4, .dancer5 { position:absolute; pointer-events:none; z-index:6; }
    </style>
<div>
    <div class="showcase" id="showcase">
      <div class="song-title">岁月如笔写春秋</div>
      <div class="xlaj"></div>
      <img class="dancer4" data-gif="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/d3.gif" data-png="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/d3.png" src="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/d3.gif" style="position:absolute; left:180px; bottom:490px; width:120px; height:106px; z-index:6;">
      <img class="dancer5" data-gif="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/n7.gif" data-png="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/n7.png" src="https://wj1.zp68.com:812/lxx/yunhua/2026/06/01/n7.gif" style="position:absolute; left:750px; bottom:480px; width:70px; height:74px; z-index:6;">

      <canvas id="bgCanvas" class="bg-canvas" width="1350" height="720"></canvas>
      <canvas id="spectrumCanvas" width="1350" height="720"></canvas>
      <canvas id="lyricCanvas" width="800" height="160" style="width:800px; height:160px;"></canvas>

      <div id="player">
            <svg viewBox="0 0 100 100">
                <defs>
                  <radialGradient id="petalGrad" cx="30%" cy="30%" r="70%">
                        <stop offset="0%" stop-color="#FFB6C1"/>
                        <stop offset="60%" stop-color="#FF69B4"/>
                        <stop offset="100%" stop-color="#C71585"/>
                  </radialGradient>
                  <radialGradient id="centerGrad" cx="50%" cy="50%" r="50%">
                        <stop offset="0%" stop-color="#FFD700"/>
                        <stop offset="100%" stop-color="#FF8C00"/>
                  </radialGradient>
                  <linearGradient id="budGrad" x1="0%" y1="0%" x2="100%" y2="100%">
                        <stop offset="0%" stop-color="#FFB6C1"/>
                        <stop offset="100%" stop-color="#C71585"/>
                  </linearGradient>
                </defs>
                <g id="flowerBloom" style="display:block">
                  <path d="M50 25 Q60 10 70 20 Q80 30 65 40 Q75 30 85 40 Q95 50 80 60 Q90 55 95 65 Q100 80 85 85 Q75 90 70 75 Q65 90 50 85 Q35 90 30 75 Q25 90 15 85 Q0 80 5 65 Q10 55 20 60 Q5 50 15 40 Q25 30 35 40 Q20 30 30 20 Q40 10 50 25Z" fill="url(#petalGrad)" stroke="#C71585" stroke-width="1.5"/>
                  <path d="M50 25 L50 40 M70 20 L60 35 M85 40 L70 50 M95 65 L80 65 M85 85 L70 75 M50 85 L50 70 M15 85 L30 75 M5 65 L20 65 M15 40 L30 50 M30 20 L40 35" stroke="#FFB6C1" stroke-width="1.5" fill="none"/>
                  <circle cx="50" cy="50" r="12" fill="url(#centerGrad)" stroke="#D2691E" stroke-width="1"/>
                  <circle cx="47" cy="47" r="2" fill="#FFF8DC"/>
                  <circle cx="53" cy="52" r="2" fill="#FFF8DC"/>
                  <circle cx="50" cy="50" r="1.5" fill="#FFDAB9"/>
                </g>
                <g id="flowerBud" style="display:none">
                  <path d="M50 20 Q70 30 75 50 Q80 70 60 80 Q40 85 30 70 Q20 50 30 30 Q40 15 50 20Z" fill="url(#budGrad)" stroke="#C71585" stroke-width="1.5"/>
                  <path d="M45 25 L30 15 L38 28 M55 25 L70 15 L62 28" stroke="#228B22" stroke-width="2" fill="none"/>
                  <path d="M45 28 Q50 35 55 28" stroke="#FFB6C1" stroke-width="1.5" fill="none"/>
                  <path d="M28 50 Q15 40 20 30 Q30 35 28 50Z" fill="#32CD32" opacity="0.8"/>
                  <path d="M72 50 Q85 40 80 30 Q70 35 72 50Z" fill="#32CD32" opacity="0.8"/>
                </g>
            </svg>
      </div>
      <div class="time-display" id="timeDisplay">0:00 / 0:00</div>
    </div>
</div>

<script>
    (function() {
      const CANVAS_W = 1350;
      const CANVAS_H = 720;
      const AUDIO_LOCAL = '岁月如笔写春秋.mp3';
      const AUDIO_FALLBACK = 'https://www.joy127.com/url/152736.mp3';
      const FILL_VIDEO_URL = 'https://img-baofun.zhhainiao.com/pcwallpaper_ugc/preview/7a5e493d0984712538162266660f1d2f_preview.mp4';
      const FILL_VIDEO_FALLBACK = 'https://cdn.pixabay.com/video/2021/02/25/66063-506691951_large.mp4';
      const TOTAL_FRAMES = 24;
      const FRAME_DURATION = 0.1;
      const FRAME_BASE_PATH = 'https://img-baofun.zhhainiao';
      const FRAME_EXT = '.webp';
      function getFrameUrl(frameNumber) {
            const padded = frameNumber.toString().padStart(2, '0');
            return `${FRAME_BASE_PATH}frame_${padded}_delay-0.1s${FRAME_EXT}`;
      }

      let bgFrames = [];
      let bgFramesLoaded = false;
      let bgCanvas = document.getElementById('bgCanvas');
      let bgCtx = bgCanvas.getContext('2d');
      let bgAnimationId = null;
      function loadBackgroundFrames() {
            let loadedCount = 0;
            let failedCount = 0;
            for (let i = 0; i < TOTAL_FRAMES; i++) {
                const img = new Image();
                img.onload = () => {
                  loadedCount++;
                  if (loadedCount + failedCount === TOTAL_FRAMES) {
                        bgFramesLoaded = true;
                        drawBackgroundFrame(0);
                        if (isPlaying && !bgAnimationId) {
                            startBackgroundAnimation();
                        }
                  }
                };
                img.onerror = () => {
                  failedCount++;
                  bgFrames = null;
                  if (loadedCount + failedCount === TOTAL_FRAMES) {
                        bgFramesLoaded = true;
                        drawBackgroundFrame(0);
                        if (isPlaying && !bgAnimationId) {
                            startBackgroundAnimation();
                        }
                  }
                };
                img.src = getFrameUrl(i);
                bgFrames.push(img);
            }
      }
      function drawBackgroundFrame(frameIndex) {
            if (!bgFramesLoaded) return;
            const w = bgCanvas.clientWidth;
            const h = bgCanvas.clientHeight;
            if (bgCanvas.width !== w || bgCanvas.height !== h) {
                bgCanvas.width = w;
                bgCanvas.height = h;
            }
            let img = bgFrames;
            let actualIndex = frameIndex;
            while (actualIndex >= 0 && (!img || !img.complete || img.naturalWidth === 0)) {
                actualIndex--;
                img = actualIndex >= 0 ? bgFrames : null;
            }
            if (!img || !img.complete) return;
            const imgW = img.naturalWidth;
            const imgH = img.naturalHeight;
            const scale = Math.max(w / imgW, h / imgH);
            const drawW = imgW * scale;
            const drawH = imgH * scale;
            const dx = (w - drawW) / 2;
            const dy = (h - drawH) / 2;
            bgCtx.drawImage(img, dx, dy, drawW, drawH);
      }

      function getFrameIndexFromTime(currentTime) {
            const totalCycle = TOTAL_FRAMES * FRAME_DURATION;
            const elapsed = currentTime % totalCycle;
            let idx = Math.floor(elapsed / FRAME_DURATION);
            if (idx >= TOTAL_FRAMES) idx = TOTAL_FRAMES - 1;
            return idx;
      }
      function startBackgroundAnimation() {
            if (bgAnimationId) cancelAnimationFrame(bgAnimationId);
            function animate() {
                if (!isPlaying) {
                  bgAnimationId = requestAnimationFrame(animate);
                  return;
                }
                if (!bgFramesLoaded) {
                  bgAnimationId = requestAnimationFrame(animate);
                  return;
                }
                const current = audioElement ? audioElement.currentTime : 0;
                const idx = getFrameIndexFromTime(current);
                drawBackgroundFrame(idx);
                bgAnimationId = requestAnimationFrame(animate);
            }
            animate();
      }

      function stopBackgroundAnimation() {
            if (bgAnimationId) {
                cancelAnimationFrame(bgAnimationId);
                bgAnimationId = null;
            }
            if (bgFramesLoaded && audioElement) {
                const idx = getFrameIndexFromTime(audioElement.currentTime);
                drawBackgroundFrame(idx);
            }
      }
      const spectrumCanvas = document.getElementById('spectrumCanvas');
      const ctx = spectrumCanvas.getContext('2d');
      const playerDiv = document.getElementById('player');
      const timeDisplay = document.getElementById('timeDisplay');
      const showcase = document.getElementById('showcase');
      const flowerBloom = document.getElementById('flowerBloom');
      const flowerBud = document.getElementById('flowerBud');
      function setButtonState(playing) {
            if (playing) {
                flowerBloom.style.display = 'block';
                flowerBud.style.display = 'none';
                playerDiv.classList.add('playing');
            } else {
                flowerBloom.style.display = 'none';
                flowerBud.style.display = 'block';
                playerDiv.classList.remove('playing');
            }
      }
      const BAR_COUNT = 14;
      const BAR_SPACING = 18;
      const FFT_SIZE = 256;
      let maxBarHeight = 280;
      let bottomBaseY = 0;
      let canvasWidth = CANVAS_W, canvasHeight = CANVAS_H;
      let barWidth = 0;
      let audioContext = null;
      let audioElement = null;
      let sourceNode = null;
      let analyserNode = null;
      let spectrumAnimationId = null;
      let isPlaying = false;
      let frequencyArray = new Uint8Array(FFT_SIZE / 2);
      let fillVideo = null;
      let fillVideoReady = false;
      let offscreenCanvas = null;
      let offscreenCtx = null;
      let videoDimensionsReady = false;
      let lastTextureUpdate = 0;
      const TEXTURE_UPDATE_INTERVAL = 50;
      let spectrumTime = 0;
      const SPECTRUM_SPEED = 0.020;
      let isAudioInitializing = false, audioInitialized = false;
      let pauseBarHeights = [];
      function getBarHeights() {
            spectrumTime += SPECTRUM_SPEED;
            if (spectrumTime > Math.PI*2) spectrumTime -= Math.PI*2;
            const minHeight = 8;
            const range = maxBarHeight - minHeight;
            const amp = 0.7;
            const heights = new Array(BAR_COUNT);
            for (let i = 0; i < BAR_COUNT; i++) {
                const t = i / BAR_COUNT;
                const val = (Math.sin(spectrumTime * 1.6 + t * Math.PI * 2.7) + 1) / 2;
                const val2 = (Math.sin(spectrumTime * 1.2 + t * Math.PI * 3.4) + 1) / 2;
                const mix = (val * 0.7 + val2 * 0.3) * amp;
                const h = minHeight + mix * range;
                heights = Math.min(maxBarHeight, Math.max(minHeight, h));
            }
            return heights;
      }
      function initFillVideo() {
            if (fillVideo) return;
            const video = document.createElement('video');
            video.loop = true; video.muted = true; video.autoplay = true; video.playsInline = true;
            video.src = FILL_VIDEO_URL;
            video.addEventListener('loadedmetadata', () => {
                if (video.videoWidth > 0) {
                  offscreenCanvas = document.createElement('canvas');
                  offscreenCanvas.width = video.videoWidth;
                  offscreenCanvas.height = video.videoHeight;
                  offscreenCtx = offscreenCanvas.getContext('2d');
                  videoDimensionsReady = true;
                }
            });
            video.addEventListener('canplaythrough', () => { fillVideoReady = true; video.play().catch(()=>{}); });
            video.addEventListener('error', () => { video.src = FILL_VIDEO_FALLBACK; video.load(); });
            fillVideo = video;
            video.load();
      }
      function updateTextureIfNeeded(now) {
            if (!isPlaying) return false;
            if (!fillVideo || fillVideo.readyState < 2) return false;
            if (!offscreenCtx || !videoDimensionsReady) return false;
            if (now - lastTextureUpdate >= TEXTURE_UPDATE_INTERVAL) {
                try {
                  offscreenCtx.drawImage(fillVideo, 0, 0);
                  lastTextureUpdate = now;
                  return true;
                } catch(e) {}
            }
            return false;
      }

      function drawBarsWithVideo(heights, startX, nowTime) {
            if (!offscreenCtx || !videoDimensionsReady || !fillVideoReady) {
                drawSolidFallback(heights, startX);
                return;
            }
            updateTextureIfNeeded(nowTime);
            const vw = offscreenCanvas.width, vh = offscreenCanvas.height;
            const sliceW = vw / BAR_COUNT;
            for (let i = 0; i < BAR_COUNT; i++) {
                const x = startX + i * (barWidth + BAR_SPACING);
                const h = heights;
                const y = bottomBaseY - h;
                if (h <= 4) continue;
                const srcX = i * sliceW;
                ctx.save();
                ctx.beginPath();
                ctx.rect(x, y, barWidth, h);
                ctx.clip();
                try { ctx.drawImage(offscreenCanvas, srcX, 0, sliceW, vh, x, y, barWidth, h); } catch(e) {}
                ctx.restore();
                ctx.strokeStyle = '#b0f0ff';
                ctx.lineWidth = 2;
                ctx.strokeRect(x, y, barWidth, h);
                ctx.beginPath();
                ctx.moveTo(x+3, y);
                ctx.lineTo(x+barWidth-3, y);
                ctx.lineTo(x+barWidth-6, y+5);
                ctx.lineTo(x+6, y+5);
                ctx.fillStyle = '#fff6cf';
                ctx.fill();
            }
      }
      function drawSolidFallback(heights, startX) {
            for (let i = 0; i < BAR_COUNT; i++) {
                const x = startX + i * (barWidth + BAR_SPACING);
                const h = heights;
                const y = bottomBaseY - h;
                if (h <= 4) continue;
                const grad = ctx.createLinearGradient(x, y, x+barWidth, y+h);
                grad.addColorStop(0, '#1287ae');
                grad.addColorStop(1, '#064e66');
                ctx.fillStyle = grad;
                ctx.fillRect(x, y, barWidth, h);
                ctx.strokeStyle = '#8fd6ff';
                ctx.strokeRect(x, y, barWidth, h);
                ctx.beginPath();
                ctx.moveTo(x+3, y);
                ctx.lineTo(x+barWidth-3, y);
                ctx.lineTo(x+barWidth-6, y+5);
                ctx.lineTo(x+6, y+5);
                ctx.fillStyle = '#fff6cf';
                ctx.fill();
            }
      }

      function renderSpectrum() {
            const now = performance.now();
            let heights = isPlaying ? getBarHeights() : [...pauseBarHeights];
            if (!isPlaying && pauseBarHeights.length === 0) {
                heights = getBarHeights();
                pauseBarHeights = [...heights];
            }
            if(isPlaying) pauseBarHeights = [...heights];

            const totalW = BAR_COUNT * barWidth + BAR_SPACING * (BAR_COUNT - 1);
            let startX = (canvasWidth - totalW) / 2;
            if (startX < 12) startX = 12;
            const clearTop = bottomBaseY - maxBarHeight - 10;
            const clearHeight = maxBarHeight + 20;
            if (clearTop >= 0 && clearHeight > 0) {
                ctx.clearRect(0, clearTop, canvasWidth, clearHeight);
            }
            drawBarsWithVideo(heights, startX, now);
            ctx.fillStyle = 'rgba(70, 180, 210, 0.2)';
            for (let i = 0; i < BAR_COUNT; i++) {
                let x = startX + i * (barWidth + BAR_SPACING);
                ctx.fillRect(x, bottomBaseY - 2, barWidth, 1);
            }
            spectrumAnimationId = requestAnimationFrame(renderSpectrum);
      }

      function startSpectrumLoop() {
            if (spectrumAnimationId) cancelAnimationFrame(spectrumAnimationId);
            renderSpectrum();
      }
      function updateDimensions() {
            canvasWidth = spectrumCanvas.width;
            canvasHeight = spectrumCanvas.height;
            bottomBaseY = canvasHeight - 28;
            const totalSpacing = BAR_SPACING * (BAR_COUNT - 1);
            const avail = canvasWidth - totalSpacing - 40;
            barWidth = Math.max(44, Math.min(82, avail / BAR_COUNT));
      }
      window.addEventListener('resize', () => {
            updateDimensions();
            if (!isPlaying && bgFramesLoaded && audioElement) {
                const idx = getFrameIndexFromTime(audioElement.currentTime);
                drawBackgroundFrame(idx);
            }
      });
      const lyricCanvas = document.getElementById('lyricCanvas');
      let gl = null;
      let webglProgram = null;
      let webglTexture = null;
      let webglUniforms = {};
      let currentLyricIndex = -1;
      let webglTimeAcc = 0;
      let lyricRenderId = null;

      const lrcText = `
岁月如笔写春秋(合唱)
作词:日月同辉
作曲:欧阳尚尚
演唱:郭少杰、陈靓靓
编曲:张明文
吉他:张明文
后期:鲁晓锋
LRC编辑:醉美水芙蓉
岁月如笔写春秋
一生知己最难求
相逢本是百年修
缘聚今生不可丢
时光似酒酿温柔
心事诉与明月收
若问情根深几许
朝朝暮暮共白头
我珍惜一路 真心的相逢
感恩命运的温柔
愿余生常相伴不负这份宿命
我眷恋时光 沉淀的安宁
憧憬偕老的约定
若有来世重逢 再续未了情
岁月如笔写春秋
一生知己最难求
相逢本是百年修
缘聚今生不可丢
时光似酒酿温柔
心事诉与明月收
若问情根深几许
朝朝暮暮共白头
我珍惜一路 真心的相逢
感恩命运的温柔
愿余生常相伴不负这份宿命
我眷恋时光 沉淀的安宁
憧憬偕老的约定
若有来世重逢 再续未了情
我珍惜一路 真心的相逢
感恩命运的温柔
愿余生常相伴不负这份宿命
我眷恋时光 沉淀的安宁
憧憬偕老的约定
若有来世重逢 再续未了情
谢谢欣赏!
`;
      const lyricsData = [];
      lrcText.trim().split('\n').forEach(line => {
            const match = line.match(/^\[(\d+):(\d+\.\d+)\](.*)$/);
            if (match) {
                const m = parseFloat(match);
                const s = parseFloat(match);
                const time = m * 60 + s;
                const text = match.trim();
                lyricsData.push({ time, text });
            }
      });

      function initWebGLLyric() {
            if (!lyricCanvas) return;
            gl = lyricCanvas.getContext('webgl') || lyricCanvas.getContext('experimental-webgl');
            if (!gl) return;
            const vsSource = `
                attribute vec2 a_pos;
                attribute vec2 a_uv;
                varying vec2 v_uv;
                void main() {
                  v_uv = a_uv;
                  gl_Position = vec4(a_pos, 0.0, 1.0);
                }
            `;
            const fsSource = `
                precision highp float;
                varying vec2 v_uv;
                uniform sampler2D u_text;
                uniform float u_time;
                uniform vec2 u_mouse;
                uniform float u_hover;
                void main() {
                  vec2 uv = vec2(v_uv.x, 1.0 - v_uv.y);
                  float waveX = sin(uv.y * 10.0 + u_time * 1.2) * 0.008;
                  float waveY = cos(uv.x * 8.0 - u_time * 1.0) * 0.006;
                  vec2 distorted = uv + vec2(waveX, waveY);
                  float dist = distance(distorted, u_mouse);
                  if (u_hover > 0.1 && dist < 0.25) {
                        float off = sin(dist * 15.0 - u_time * 6.0) * 0.008 * u_hover;
                        distorted += normalize(distorted - u_mouse) * off;
                  }
                  vec4 col = texture2D(u_text, distorted);
                  if (col.a < 0.05) discard;
                  float glow = smoothstep(0.3, 0.9, col.a);
                  col.rgb = mix(col.rgb, vec3(1.0, 0.96, 0.88), glow * 0.6);
                  if (u_hover > 0.1 && dist < 0.2) {
                        float halo = (1.0 - smoothstep(0.0,0.2,dist)) * u_hover * 0.5;
                        col.rgb += vec3(0.5, 0.3, 0.1) * halo;
                  }
                  gl_FragColor = col;
                }
            `;
            function compileShader(src, type) {
                const shader = gl.createShader(type);
                gl.shaderSource(shader, src);
                gl.compileShader(shader);
                if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return null;
                return shader;
            }
            const vs = compileShader(vsSource, gl.VERTEX_SHADER);
            const fs = compileShader(fsSource, gl.FRAGMENT_SHADER);
            const prog = gl.createProgram();
            gl.attachShader(prog, vs);
            gl.attachShader(prog, fs);
            gl.linkProgram(prog);
            webglProgram = prog;
            const vertices = new Float32Array([
                -1, -1, 0, 0,
               1, -1, 1, 0,
                -1,1, 0, 1,
               1,1, 1, 1
            ]);
            const vbo = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
            const a_pos = gl.getAttribLocation(prog, 'a_pos');
            const a_uv = gl.getAttribLocation(prog, 'a_uv');
            gl.enableVertexAttribArray(a_pos);
            gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 16, 0);
            gl.enableVertexAttribArray(a_uv);
            gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 16, 8);
            webglUniforms = {
                u_time: gl.getUniformLocation(prog, 'u_time'),
                u_text: gl.getUniformLocation(prog, 'u_text'),
                u_mouse: gl.getUniformLocation(prog, 'u_mouse'),
                u_hover: gl.getUniformLocation(prog, 'u_hover')
            };
            webglTexture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, webglTexture);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            const textCanvas = document.createElement('canvas');
            textCanvas.width = lyricCanvas.width;
            textCanvas.height = lyricCanvas.height;
            const tctx = textCanvas.getContext('2d');
            function updateTextTexture(text) {
                tctx.clearRect(0, 0, textCanvas.width, textCanvas.height);
                tctx.font = 'bold 44px "Segoe UI", "Poppins", system-ui';
                tctx.textAlign = 'center';
                tctx.textBaseline = 'middle';
                tctx.lineWidth = 4.5;
                tctx.strokeStyle = '#0000ff';
                tctx.strokeText(text, textCanvas.width/2, textCanvas.height/2);
                tctx.fillStyle = '#00ffff';
                tctx.fillText(text, textCanvas.width/2, textCanvas.height/2);
                gl.bindTexture(gl.TEXTURE_2D, webglTexture);
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textCanvas);
            }
            let mouseX = 0.5, mouseY = 0.5, hoverValue = 0;
            lyricCanvas.addEventListener('mousemove', (e) => {
                const rect = lyricCanvas.getBoundingClientRect();
                mouseX = (e.clientX - rect.left) / rect.width;
                mouseY = 1.0 - (e.clientY - rect.top) / rect.height;
                hoverValue = Math.min(1.0, hoverValue + 0.06);
            });
            lyricCanvas.addEventListener('mouseleave', () => { hoverValue = 0; });
            let startTime = performance.now() / 1000;
            function renderLoop() {
                if (!gl || !webglProgram) return;
                if (isPlaying) {
                  const now = performance.now() / 1000;
                  const delta = now - startTime;
                  webglTimeAcc += delta;
                  startTime = now;
                } else {
                  startTime = performance.now() / 1000;
                }
                const nowTime = webglTimeAcc;
                gl.viewport(0, 0, lyricCanvas.width, lyricCanvas.height);
                gl.clearColor(0, 0, 0, 0);
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.useProgram(webglProgram);
                gl.uniform1f(webglUniforms.u_time, nowTime);
                gl.uniform2f(webglUniforms.u_mouse, mouseX, mouseY);
                gl.uniform1f(webglUniforms.u_hover, hoverValue);
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, webglTexture);
                gl.uniform1i(webglUniforms.u_text, 0);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
                lyricRenderId = requestAnimationFrame(renderLoop);
            }
            renderLoop();
            return { updateTextTexture };
      }

      let webglUpdater = null;
      function syncLyricByTime(currentTime) {
            let idx = 0;
            for (let i = lyricsData.length-1; i>=0; i--) {
                if (currentTime >= lyricsData.time) { idx = i; break; }
            }
            if (currentTime >= lyricsData.time) idx = lyricsData.length-1;
            if (idx !== currentLyricIndex) {
                currentLyricIndex = idx;
                if (webglUpdater) webglUpdater.updateTextTexture(lyricsData.text);
            }
      }

      function formatTime(sec) {
            if (isNaN(sec)) sec = 0;
            const mins = Math.floor(sec / 60);
            const seconds = Math.floor(sec % 60);
            return `${mins}:${seconds < 10 ? '0' : ''}${seconds}`;
      }

      function updateTimeDisplay() {
            if (!audioElement) return;
            const current = audioElement.currentTime;
            const duration = audioElement.duration || 0;
            timeDisplay.textContent = `${formatTime(current)} / ${formatTime(duration)}`;
            syncLyricByTime(current);
      }

      // 控制动态人物启停
      function syncDancers(paused) {
            const list = document.querySelectorAll('.dancer4,.dancer5');
            list.forEach(el => {
                if(el.dataset.gif && el.dataset.png){
                  el.src = paused ? el.dataset.png : el.dataset.gif;
                }
            });
      }

      async function initAudio() {
            if (audioInitialized || isAudioInitializing) return;
            isAudioInitializing = true;
            try {
                audioElement = new Audio();
                audioElement.src = AUDIO_LOCAL;
                audioElement.preload = 'auto';
                audioElement.loop = true;
                audioElement.addEventListener('ended', () => {
                  isPlaying = false;
                  setButtonState(false);
                  syncDancers(true);
                  audioElement.currentTime = 0;
                  currentLyricIndex = -1;
                  if (webglUpdater) webglUpdater.updateTextTexture(lyricsData.text);
                  updateTimeDisplay();
                  stopBackgroundAnimation();
                });
                audioElement.addEventListener('error', () => {
                  if (audioElement.src !== AUDIO_FALLBACK) {
                        audioElement.src = AUDIO_FALLBACK;
                        audioElement.load();
                  }
                });
                await new Promise((resolve, reject) => {
                  audioElement.addEventListener('canplaythrough', resolve, { once: true });
                  audioElement.addEventListener('error', reject, { once: true });
                  audioElement.load();
                });
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                await audioContext.suspend();
                analyserNode = audioContext.createAnalyser();
                analyserNode.fftSize = FFT_SIZE;
                analyserNode.smoothingTimeConstant = 0.68;
                frequencyArray = new Uint8Array(analyserNode.frequencyBinCount);
                sourceNode = audioContext.createMediaElementSource(audioElement);
                sourceNode.connect(analyserNode);
                analyserNode.connect(audioContext.destination);
                audioInitialized = true;
                updateTimeDisplay();
            } catch (err) {
                console.error("音频初始化失败", err);
            } finally {
                isAudioInitializing = false;
            }
      }
      async function startPlayback() {
            if (!audioInitialized) await initAudio().then(() => _start());
            else _start();
            async function _start() {
                if (!audioElement) return;
                if (isPlaying && !audioElement.paused) return;
                try {

                  if (audioContext && audioContext.state === 'suspended') {
                        await audioContext.resume();
                  }
                  await audioElement.play();
                  isPlaying = true;
                  setButtonState(true);
                  syncDancers(false);
                  if (!bgAnimationId && bgFramesLoaded) startBackgroundAnimation();
                  if (window.timeInterval) clearInterval(window.timeInterval);
                  window.timeInterval = setInterval(() => {
                        if (isPlaying && audioElement) updateTimeDisplay();
                  }, 200);
                } catch (err) {
                  console.warn("播放失败", err);
                  isPlaying = false;
                  setButtonState(false);
                  syncDancers(true);
                }
            }
      }

      function pausePlayback() {
            if (audioElement && !audioElement.paused) {
                audioElement.pause();
                isPlaying = false;
                setButtonState(false);
                syncDancers(true);
                if (window.timeInterval) clearInterval(window.timeInterval);
                stopBackgroundAnimation();
            }
      }

      function togglePlayback() {
            if (isPlaying && audioElement && !audioElement.paused) {
                pausePlayback();
            } else {
                startPlayback();
            }
      }

      async function attemptAutoPlay() {
            await initAudio();
            startSpectrumLoop();
            try {
                if (audioContext && audioContext.state === 'suspended') await audioContext.resume();
                await audioElement.play();
                isPlaying = true;
                setButtonState(true);
                syncDancers(false);
                if (!bgAnimationId && bgFramesLoaded) startBackgroundAnimation();
                if (window.timeInterval) clearInterval(window.timeInterval);
                window.timeInterval = setInterval(() => {
                  if (isPlaying && audioElement) updateTimeDisplay();
                }, 200);
            } catch (e) {
                setButtonState(false);
                isPlaying = false;
                syncDancers(true);
            }
      }

      async function init() {
            spectrumCanvas.width = CANVAS_W;
            spectrumCanvas.height = CANVAS_H;
            bgCanvas.width = CANVAS_W;
            bgCanvas.height = CANVAS_H;
            updateDimensions();
            initFillVideo();
            loadBackgroundFrames();
            await initAudio();
            const webglObj = initWebGLLyric();
            if (webglObj) webglUpdater = webglObj;
            if (webglUpdater) webglUpdater.updateTextTexture(lyricsData.text);

            playerDiv.addEventListener('click', togglePlayback);

            setButtonState(false);
            isPlaying = false;
            syncDancers(true);
            attemptAutoPlay();
      }

      init();
    })();
</script>

klxf 发表于 2026-6-10 16:57:15

风格迥异,别有新意,谢谢友友精彩分享

醉美水芙蓉 发表于 2026-6-10 19:35:24

klxf 发表于 2026-6-10 16:57
风格迥异,别有新意,谢谢友友精彩分享

感谢友友的支持鼓励!
页: [1]
查看完整版本: 岁月如笔写春秋(合唱)