画中画歌词

176 阅读3分钟
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Picture-in-Picture Lyrics</title>
    <style>
        #pip-button {
            position: absolute;
            top: 10px;
            right: 10px;
            padding: 10px 20px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #pip-button:hover {
            background-color: #0056b3;
        }
        #pip-video {
          display: none;
        }
    </style>
</head>
<body>
    <button id="pip-button">画中画歌词</button>
    <video id="pip-video" width="375" height="480" muted></video>

    <script>
        (function() {
            'use strict';

            // 扩展歌词数组并延长歌词时间
            const lyrics = [
                [0.00, { lrc: "第一行歌词", tlrc: "First line" }],
                [2.00, { lrc: "第二行歌词", tlrc: "Second line" }],
                [4.00, { lrc: "第三行歌词", tlrc: "Third line" }],
                [6.00, { lrc: "第四行歌词", tlrc: "Fourth line" }],
                [8.00, { lrc: "第五行歌词", tlrc: "Fifth line" }],
                [10.00, { lrc: "第六行歌词", tlrc: "Sixth line" }],
                [12.00, { lrc: "第七行歌词", tlrc: "Seventh line" }],
                [14.00, { lrc: "第八行歌词", tlrc: "Eighth line" }],
                [16.00, { lrc: "第九行歌词", tlrc: "Ninth line" }],
                [18.00, { lrc: "第十行歌词", tlrc: "Tenth line" }],
                [20.00, { lrc: "第十一行歌词", tlrc: "Eleventh line" }], // Added more lyrics
                [22.00, { lrc: "第十二行歌词", tlrc: "Twelfth line" }],
                [24.00, { lrc: "第十三行歌词", tlrc: "Thirteenth line" }],
                [26.00, { lrc: "第十四行歌词", tlrc: "Fourteenth line" }],
                [28.00, { lrc: "第十五行歌词", tlrc: "Fifteenth line" }],
                [30.00, { lrc: "第十六行歌词", tlrc: "Sixteenth line" }],
                [32.00, { lrc: "第十七行歌词", tlrc: "Seventeenth line" }],
                [34.00, { lrc: "第十八行歌词", tlrc: "Eighteenth line" }],
                [36.00, { lrc: "第十九行歌词", tlrc: "Nineteenth line" }],
                [38.00, { lrc: "第二十行歌词", tlrc: "Twentieth line" }]
            ];

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = 375;
            canvas.height = 480;

            const video = document.getElementById('pip-video');
            const button = document.getElementById('pip-button');

            let currentLyricIndex = 0;
            let isPip = false;
            let startTime = null;
            let targetScrollPosition = 0;
            let scrollSpeed = 0.1;
            let lyricFontSize = 30;
            let lyricHeight = 35;
            let lyricColor = 'white';
            let currentLyricColor = 'yellow';
            const lyricDisplayPosition = canvas.height / 2 - lyricHeight / 2;
            let scrollPosition = 0; // 当前滚动位置

            const stream = canvas.captureStream(30);
            video.srcObject = stream;

            /**
             * 绘制歌词到画布上,并实现歌词滚动效果
             * @param {number} time - 当前播放时间,单位为秒
             */
            function drawLyrics(time) {
                // 清除画布
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                // 设置歌词默认颜色
                ctx.fillStyle = lyricColor;
                // 设置歌词字体
                ctx.font = `${lyricFontSize}px Arial`;

                let currentLyricFound = false;
                // 找到当前应该高亮的歌词索引
                for (let i = 0; i < lyrics.length; i++) {
                    if (time >= lyrics[i][0]) {
                        currentLyricIndex = i;
                        currentLyricFound = true;
                    }
                }
                // 如果没有找到当前歌词,则将索引置为0
                if (!currentLyricFound) {
                    currentLyricIndex = 0;
                }

                const maxVisibleLyrics = Math.floor(canvas.height / lyricHeight);  // 计算画布上最多能显示的歌词行数
                const startLyricIndex = Math.max(0, currentLyricIndex - Math.floor(maxVisibleLyrics / 2)); // 计算开始绘制的歌词索引
                const endLyricIndex = Math.min(lyrics.length, startLyricIndex + maxVisibleLyrics);    // 计算结束绘制的歌词索引

                // 计算目标滚动位置,使当前歌词始终显示在画布中央附近
                targetScrollPosition = (currentLyricIndex * lyricHeight) - lyricDisplayPosition;
                // 平滑滚动:scrollPosition逐渐接近targetScrollPosition
                scrollPosition += (targetScrollPosition - scrollPosition) * scrollSpeed;

                // 绘制歌词
                for (let i = startLyricIndex; i < endLyricIndex; i++) {
                    const [ , { lrc }] = lyrics[i]; // 修复:这里不需要startTime
                    const y = (i * lyricHeight) - scrollPosition; // 计算每行歌词的y坐标

                    if (y > -lyricHeight && y < canvas.height) {
                        // 当前歌词高亮显示
                        ctx.fillStyle = (i === currentLyricIndex) ? currentLyricColor : lyricColor;
                        ctx.fillText(lrc, 50, y); // 在画布上绘制歌词
                    }
                }
            }

            /**
             * 动画循环函数,不断更新歌词显示
             * @param {number} timestamp - 当前时间戳
             */
            function animate(timestamp) {
                if (!startTime) startTime = timestamp; // 记录开始时间
                const elapsed = (timestamp - startTime) / 1000; // 计算经过的时间,单位为秒

                drawLyrics(elapsed); // 绘制歌词
                requestAnimationFrame(animate); // 请求下一次动画帧
            }

            /**
             * 切换画中画模式
             */
            function togglePip() {
                if (isPip) {
                    document.exitPictureInPicture(); // 退出画中画模式
                } else {
                    video.requestPictureInPicture(); // 请求进入画中画模式
                }
            }

            // 监听video的enterpictureinpicture事件
            video.addEventListener('enterpictureinpicture', () => {
                isPip = true; // 设置画中画状态为true
            });

            // 监听video的leavepictureinpicture事件
            video.addEventListener('leavepictureinpicture', () => {
                isPip = false; // 设置画中画状态为false
            });

            // 监听video的loadedmetadata事件,在视频元数据加载完成后播放视频
            video.addEventListener('loadedmetadata', () => {
                video.play().catch(error => {
                    console.error('Error playing video:', error);
                });
            });

            // 监听button的click事件,点击按钮切换画中画模式
            button.addEventListener('click', togglePip);

            // 开始动画循环
            animate();
        })();
    </script>
</body>
</html>