YouTube 多重播放器

以新分頁或新視窗,同時播放複數Youtube影片。

目前為 2025-07-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube 多重播放器
// @namespace    http://tampermonkey.net/
// @version      4.1
// @match        https://www.youtube.com/
// @match        https://www.youtube.com/feed/subscriptions
// @match        https://www.youtube.com/feed/history
// @match        https://www.youtube.com/playlist?list=WL
// @match        https://www.youtube.com/playlist?list=LL
// @grant        none
// @license      MIT
// @description  以新分頁或新視窗,同時播放複數Youtube影片。
// ==/UserScript==
(function(){
    const STORAGE_POS='ytMulti_btnPos';
    const STORAGE_LIST1='ytMulti_videoList1';
    const STORAGE_LIST2='ytMulti_videoList2';
    const STORAGE_MODE='ytMulti_openMode';
    const STORAGE_CURRENT='ytMulti_currentList';
    let currentList=localStorage.getItem(STORAGE_CURRENT)||'list1';

    const panel=document.createElement('div');
    panel.id='ytMulti_panel';
    panel.style.cssText="position:fixed;background:rgba(0,0,0,0.6);color:#fff;padding:5px;border-radius:4px;z-index:9999;display:flex;align-items:center;cursor:move;gap:5px;";
    document.body.appendChild(panel);
    const savedPos=JSON.parse(localStorage.getItem(STORAGE_POS)||'null');
    if(savedPos){panel.style.top=savedPos.top;panel.style.left=savedPos.left;panel.style.right='auto';}

    panel.addEventListener('mousedown',e=>{
        e.preventDefault();
        let startX=e.clientX,startY=e.clientY;
        const rect=panel.getBoundingClientRect();
        function onMove(ev){panel.style.top=rect.top+ev.clientY-startY+'px';panel.style.left=rect.left+ev.clientX-startX+'px';}
        function onUp(){
            localStorage.setItem(STORAGE_POS,JSON.stringify({top:panel.style.top,left:panel.style.left}));
            window.removeEventListener('mousemove',onMove);
            window.removeEventListener('mouseup',onUp);
        }
        window.addEventListener('mousemove',onMove);
        window.addEventListener('mouseup',onUp);
    });

    const playBtn=document.createElement('button');
    playBtn.textContent='▶ 多重播放';
    panel.appendChild(playBtn);

    const modeBtn=document.createElement('button');
    modeBtn.textContent=localStorage.getItem(STORAGE_MODE)==='tab'?'分頁':'視窗';
    panel.appendChild(modeBtn);

    const listBtn=document.createElement('button');
    listBtn.textContent='切換清單';
    panel.appendChild(listBtn);

    panel.addEventListener('dragover',e=>e.preventDefault());
    panel.addEventListener('drop',e=>{
        e.preventDefault();
        const data=e.dataTransfer.getData('text/uri-list')||e.dataTransfer.getData('text/plain');
        const vid=parseYouTubeID(data);
        if(!vid) return;

        const storageKey=currentList==='list1'?STORAGE_LIST1:STORAGE_LIST2;
        const ids=JSON.parse(localStorage.getItem(storageKey)||'[]');
        if(!ids.includes(vid)){
            ids.push(vid);
            localStorage.setItem(storageKey,JSON.stringify(ids));
        }
    });

    modeBtn.addEventListener('click',()=>{
        const mode=localStorage.getItem(STORAGE_MODE)==='tab'?'window':'tab';
        localStorage.setItem(STORAGE_MODE,mode);
        modeBtn.textContent=mode==='tab'?'分頁':'視窗';
    });

    listBtn.addEventListener('click',()=>{
        currentList=currentList==='list1'?'list2':'list1';
        localStorage.setItem(STORAGE_CURRENT,currentList);
    });

    playBtn.addEventListener('click',()=>{
        const storageKey=currentList==='list1'?STORAGE_LIST1:STORAGE_LIST2;
        const ids=JSON.parse(localStorage.getItem(storageKey)||'[]');
        if(!ids.length) return alert('尚無影片清單');

        const html=makeBlobPage(ids,currentList);
        const blobUrl=URL.createObjectURL(new Blob([html],{type:'text/html'}));
        const mode=localStorage.getItem(STORAGE_MODE);
        mode==='tab'?window.open(blobUrl,'_blank'):window.open(blobUrl,'_blank','width=800,height=600');
    });

    function parseYouTubeID(url){
        const m=url.match(/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/);
        return m?m[1]:null;
    }

    function makeBlobPage(ids,listKey){
        const listJson=JSON.stringify(ids);
        return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>多重播放</title><style>
            body{margin:0;padding:0;background:#000;overflow:hidden;}
            .container{position:absolute;top:0;left:0;width:100vw;height:100vh;display:flex;flex-wrap:wrap;align-content:flex-start;}
            .video-wrapper{position:relative;overflow:hidden;will-change:transform;}
            .video-wrapper iframe{width:100%;height:100%;border:none;transform:scale(0.999);}
            .remove-btn{
                position:absolute;top:6px;right:6px;
                width:20px;height:20px;
                background:#ff4444;
                border-radius:3px;
                display:none;
                cursor:pointer;
                z-index:9999;
                box-shadow:0 0 3px rgba(0,0,0,0.3);
            }
            .remove-btn::after{
                content:'×';
                color:white;
                font-size:16px;
                position:absolute;
                top:50%;left:50%;
                transform:translate(-50%,-50%);
            }
            .video-wrapper:hover .remove-btn{display:block;}
        </style></head><body><div class="container"></div><script>
            const ASPECT_RATIO=16/9;
            const ids=${listJson};
            const listKey=${JSON.stringify(listKey)};
            const container=document.querySelector('.container');

            function calculateLayout(){
                const W=container.offsetWidth;
                const H=container.offsetHeight;
                const n=ids.length;
                if(n===0)return{cols:0,rows:0,itemWidth:0,itemHeight:0};

                let bestCols=1;
                let bestRows=1;
                let bestItemWidth=0;
                let bestItemHeight=0;
                let bestScore=0;

                for(let cols=1;cols<=Math.min(n,12);cols++){
                    const rows=Math.ceil(n/cols);
                    let itemWidth=W/cols;
                    let itemHeight=itemWidth/ASPECT_RATIO;

                    if(rows*itemHeight>H){
                        itemHeight=H/rows;
                        itemWidth=itemHeight*ASPECT_RATIO;
                    }

                    const usedWidth=cols*itemWidth;
                    const usedHeight=rows*itemHeight;
                    const areaScore=usedWidth*usedHeight;
                    const penalty=(W-usedWidth)*0.1+(H-usedHeight)*0.2;
                    const totalScore=areaScore-penalty;

                    if(totalScore>bestScore){
                        bestScore=totalScore;
                        bestCols=cols;
                        bestRows=rows;
                        bestItemWidth=itemWidth;
                        bestItemHeight=itemHeight;
                    }
                }

                return{
                    cols:bestCols,
                    rows:bestRows,
                    itemWidth:bestItemWidth,
                    itemHeight:bestItemHeight
                };
            }

            function updateLayout(){
                const{cols,rows,itemWidth,itemHeight}=calculateLayout();
                container.style.fontSize='0';
                Array.from(container.children).forEach((wrap,index)=>{
                    const col=index%cols;
                    const row=Math.floor(index/cols);
                    wrap.style.width=itemWidth+'px';
                    wrap.style.height=itemHeight+'px';
                    wrap.style.transform=\`translate(\${col*itemWidth}px,\${row*itemHeight}px)\`;
                });
            }

            function createVideo(id,idx){
                const wrap=document.createElement('div');
                wrap.className='video-wrapper';
                wrap.style.display='inline-block';
                wrap.style.position='absolute';

                const ifr=document.createElement('iframe');
                ifr.src='https://www.youtube.com/embed/'+id+'?autoplay=1&playsinline=1';
                ifr.allow='autoplay; encrypted-media; fullscreen';

                const delBtn=document.createElement('div');
                delBtn.className='remove-btn';
                delBtn.onclick=()=>{
                    const storageKey=listKey==='list1'?'ytMulti_videoList1':'ytMulti_videoList2';
                    const stored=JSON.parse(localStorage.getItem(storageKey)||'[]');
                    stored.splice(stored.indexOf(id),1);
                    localStorage.setItem(storageKey,JSON.stringify(stored));
                    wrap.remove();
                    updateLayout();
                };

                wrap.append(ifr,delBtn);
                return wrap;
            }

            ids.forEach((id,idx)=>container.appendChild(createVideo(id,idx)));
            updateLayout();
            window.addEventListener('resize',updateLayout);
            setInterval(updateLayout,500);
        <\/script></body></html>`;
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址