b站播放列表

将b站视频播放页面分集视频(目前只支持分P视频)添加到播放列表,实现列表顺序播放、随机播放、单集循环播放(需点击播放列表里面的视频进行播放才会生效),播放列表中支持移除视频,支持拖动排序,多选拖动排序,支持全选、反选,移除选中

目前為 2024-08-24 提交的版本,檢視 最新版本

// ==UserScript==
// @name         b站播放列表
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  将b站视频播放页面分集视频(目前只支持分P视频)添加到播放列表,实现列表顺序播放、随机播放、单集循环播放(需点击播放列表里面的视频进行播放才会生效),播放列表中支持移除视频,支持拖动排序,多选拖动排序,支持全选、反选,移除选中
// @author       繁星1678
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let isListVisible = false;
    let listContainer;
    let isDragging = false;
    let offsetX, offsetY;
    let videoList = [];//存储videoDict
    let allVideoList = []; //存储videoDict的全部item
    let videoDict = {}; // 存储视频id和视频元素(element)
    let allVideoDict = {}; // 存储全部视频id和视频元素(element) 
    let playMode = 0; // 0:顺序播放 1:随机播放 2:单集循环
    // let myList = []; // 存储用户添加的视频
    let i =1 //点击图标添加的索引
    let j =1//全部添加索引
    let playId
    let currentPlayVideoTitle
    let playItemIndex = -1;
    let previousPlayItemIndex = -1;
    let isDarkMode = true;
    let videoEl
    // let myList
    // let listItems

    class vedioInfo {
        constructor(index,title,videoElement) {
            this.index = index;// 视频序号,在播放列表中的位置
            this.title = title;//名称如:P4 你好吗
            this.videoElement = videoElement;//视频播放元素,点击后可以播放
        }
    }


    // let videoItem = document.querySelector('div.clickitem')||document.querySelector('divvideo-episode-card__info-title');

    let targetNode = document.body; // 可以选择更具体的父节点

    const observer = new MutationObserver((mutationsList, observer) => {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                // 观察到视频条目插入+添加按钮
                let videoItem = document.querySelector('div.clickitem')||document.querySelector('divvideo-episode-card__info-title');
                // 找到视频页面头部就插入播放列表
                let listHead = document.querySelector("#multi_page > div.head-con") ||
                document.querySelector(".base-video-sections-v1 .video-sections-head") ||
                document.querySelector(".action-list-container .action-list-header");

                // 找到视频播放器就设置播放监听器
                videoEl = document.querySelector("div.bpx-player-video-wrap > video");
                if (videoEl&&listHead&&videoItem) {
                    // console.log(`找到 video 元素: ${videoEl}`);

                    // 添加各种事件监听器
                    videoEl.addEventListener('play', () => {
                        
                        // console.log('视频开始播放');
                    });

                    videoEl.addEventListener('pause', () => {
                        // console.log('视频暂停,播放索引:'+playItemIndex);
                    });

                    videoEl.addEventListener('ended', (event) => { 
                        // console.log('视频播放结束,播放索引:'+playItemIndex);
                        event.stopImmediatePropagation();
                        
                        let videoListLength = videoList.length;
                        if(videoListLength===0){
                            return
                        }
                        // console.log('视频播放结束');
                        let randomIndex = Math.floor(Math.random() * videoListLength);
                        restorePreviousPlayItemBackgroundPlayingGifVisible()
                        switch (playMode) {
                            case 0: // 顺序播放
                                // console.log(`顺序播放`);
                                if(videoListLength===1){
                                    allVideoList[playItemIndex+1].videoElement.click();
                                    videoList[playItemIndex].videoElement.click();
                                    
                                }else{
                                     // 更新播放索引
                                    let NextPlayItemIndex = (playItemIndex + 1) % videoListLength;
                                    
                                    if(videoList[NextPlayItemIndex].title===videoList[playItemIndex].title){
                                        if (playItemIndex + 1 < allVideoList.length) {
                                            allVideoList[playItemIndex + 1].videoElement.click();
                                            playItemIndex = NextPlayItemIndex;
                                            videoList[playItemIndex].videoElement.click();
                                        } else if (playItemIndex - 1 >= 0) {
                                            allVideoList[playItemIndex - 1].videoElement.click();
                                            playItemIndex = NextPlayItemIndex;
                                            videoList[playItemIndex].videoElement.click();
                                        }  
                                    }else{
                                        playItemIndex = NextPlayItemIndex;
                                        videoList[playItemIndex].videoElement.click(); 
                                    }
                                    
                                    // 更新列表项的背景颜色
                                    // listItems[playItemIndex].style.backgroundColor = 'skyblue';

                                    videoList[playItemIndex].videoElement.click();
                                }
                                // setCurrentItemBackgroundAndPlayingGifVisible()
                                scrollPlayItemIntoView()
                               
                                break;
                            case 1: // 随机播放
                
                                // console.log(`随机播放`);
                                // randomIndex = Math.floor(Math.random() * videoListLength);
                                // randomIndex与playItemIndex相等代表播放同一曲,若播放同一曲,播放完后再点击同一曲videoElement网页并不会重新播放,所以先点击其他videoElement的,再点击回来播放同一曲
                                if(randomIndex===playItemIndex){
                                    // console.log('if(randomIndex===playItemIndex)随机播放,随机索引是:'+randomIndex);
                                    // console.log('if(randomIndex===playItemIndex)随机播放,播放索引是:'+playItemIndex);
                                    // playItemIndex = randomIndex;
                                    

                                    if (allVideoList[0].videoElement != videoList[playItemIndex].videoElement) {
                                        allVideoList[0].videoElement.click();
                                        videoList[playItemIndex].videoElement.click();
                                    } else{
                                        allVideoList[1].videoElement.click();
                                        videoList[playItemIndex].videoElement.click();

                                    }
                                    playItemIndex = randomIndex;

                                    // videoList[playItemIndex].videoElement.click();
                                }else{
                                    // console.log('else随机播放,随机索引是:'+randomIndex);
                                    // console.log('else随机播放,播放索引是:'+playItemIndex);
                                    // playItemIndex = randomIndex;
                                    videoList[randomIndex].videoElement.click(); 
                                    playItemIndex = randomIndex;

                                }
                                // setCurrentItemBackgroundAndPlayingGifVisible()
                                scrollPlayItemIntoView()
                                break;
                            case 2: // 单集循环
                                // console.log(`单集循环`);
                                // 若播放同一曲,播放完后再点击同一曲videoElement网页并不会重新播放,所以先点击其他videoElement的,再点击回来播放同一曲
                                if (playItemIndex + 1 < allVideoList.length) {
                                    allVideoList[playItemIndex + 1].videoElement.click();
                                } else if (playItemIndex - 1 >= 0) {
                                    allVideoList[playItemIndex - 1].videoElement.click();
                                }
                                videoList[playItemIndex].videoElement.click();
                                
                                break;
                        }
                        setCurrentItemBackgroundAndPlayingGifVisible()
                    });
                    createVideoListButton()
                    createPLaylistPanel()
                    createAddBtnForVideoItem()
                     
                    
                    // 停止观察,因为我们已经找到了 video 元素
                     observer.disconnect();
                     break;
                    
                }
            }
        }
    });

    // 配置观察选项
    let config = { childList: true, subtree: true };

    // 开始观察
    observer.observe(targetNode, config);

    function getCrruentPlayVideoTitle(){
        if(playItemIndex>-1){
            currentPlayVideoTitle = videoList[playItemIndex].title;
            if(currentPlayVideoTitle){
                return currentPlayVideoTitle
            }else{return null}
            
        }else{
            return null
        }
    }
  
    // 创建播放列表按钮,点击弹出播放列表面板,再点击关闭播放列表面板
    function createVideoListButton() {
        
        // 创建播放列表按钮
        const videoListButton = document.createElement('button');
        videoListButton.textContent = '播放列表';
        videoListButton.className = 'video-list-btn';

        // 添加按钮样式
        const style = document.createElement('style');
        style.textContent = `
            .video-list-btn {
                background-color: #3e4149;
                color: #defcf9;
                border: none;
                border-radius: 4px;
                text-align: center;
                font-size: 15px;
                cursor: pointer;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
                transition: background-color 0.3s;
            }

            .video-list-btn:hover {
                background-color: #521262;
            }

            .video-list-btn:active {
                background-color: #6639a6;
            }

            .draggable {
                cursor: move;
            }
        `;
        document.head.appendChild(style);
        getListHead()
        
        // 添加按钮点击事件
        videoListButton.addEventListener('click', () => {
            isListVisible = !isListVisible;
            listContainer.style.display = isListVisible ? 'block' : 'none';
            videoListButton.textContent = isListVisible ? '关闭列表' : '播放列表';
            
        });

        function getListHead() {
            let listHead = document.querySelector("#multi_page > div.head-con") ||
            document.querySelector(".base-video-sections-v1 .video-sections-head") ||
            document.querySelector(".action-list-container .action-list-header");
            if (listHead) {
                listHead.style.position = 'relative';
                listHead.insertBefore(videoListButton, listHead.firstChild);
                
            }
        }
       
    }

    

    // 停止播放
    function stopVideo(){
        let videoEl = document.querySelector("div.bpx-player-video-wrap > video") // 获取视频元素
        videoEl.pause();
        videoEl.currentTime = 0;
    }


   

    // addItemToPLaylistPanel

    
  
    

    // 如果列表项有选中状态的项就返回true,否则返回false
    function hasCheckedCheckbox() {
        const checkboxes = document.querySelectorAll('li.list-item input[type="checkbox"]');
        for (let checkbox of checkboxes) {
            if (checkbox.checked) {
                return true;
            }
        }
        return false;
    }
    

    // 将上一个播放项的背景色还原为透明浅蓝色,并将playing-gif隐藏
    function restorePreviousPlayItemBackgroundPlayingGifVisible(){
        let myList = document.getElementById('myList');      
        let listItems = myList.getElementsByTagName('li');
        let previousPlayItem = listItems[playItemIndex]
        if(isDarkMode){
            previousPlayItem?previousPlayItem.style.color='#ba52ed':console.log("没有找到播放项");
        }else{
            previousPlayItem?previousPlayItem.style.color='#311d3f':console.log("没有找到播放项");
        }
        // previousPlayItem?previousPlayItem.style.color='#ba52ed':console.log("没有上一个播放项");
      
        // previousPlayItem?previousPlayItem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)':console.log("没有上一个播放项");
   
        previousPlayItem?previousPlayItem.querySelector('.playing-gif').style.display = 'none':console.log("没有上一个播放项");
        
        

    }

     // 设置当前播放项动图playing-gif为显示状态 设置item背景色
     function setCurrentItemBackgroundAndPlayingGifVisible() {
        // let textNode = document.querySelector('.video-name')
        let myList = document.getElementById('myList');      
        let listItems = myList.getElementsByTagName('li');
        let CurrentPlayItem = listItems[playItemIndex]
        // if(isDarkMode){
        //     CurrentPlayItem?CurrentPlayItem.style.color='#a3de83':console.log("没有找到播放项");

        // }else{
        //     CurrentPlayItem?CurrentPlayItem.style.color='#071a52':console.log("没有找到播放项");
        // }
        
        
        CurrentPlayItem?CurrentPlayItem.style.color='#ff9a00':console.log("没有找到播放项");
        // CurrentPlayItem?CurrentPlayItem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)':console.log("没有找到播放项");

        CurrentPlayItem?CurrentPlayItem.querySelector('.playing-gif').style.display = 'inline':console.log("没有找到播放项");

    }

    // 将播放项滚动到视图中
    function scrollPlayItemIntoView() {
        let myList = document.getElementById('myList');      
        let listItems = myList.getElementsByTagName('li');
        let CurrentPlayItem = listItems[playItemIndex]
        CurrentPlayItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }

// 创建播放列表,添加列表项,相关按钮,播放列表项点击事件,播放列表项拖拽排序事件


    // 获取选中的列表项,将其文本组成一个数组,将其文本组成一个数组

    function getCheckedItemsTextAndIndex() {
        // 获取所有被勾选的 checkbox
        const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');

        // 获取所有 li 元素
        const listItems = document.getElementById('myList').getElementsByTagName('li');

        // 创建两个数组来存储文本和索引
        let texts = [];
        let indices = [];

        // 遍历被勾选的 checkbox,获取对应的 li 元素的文本和索引
        checkboxes.forEach(checkbox => {
            const parentLi = checkbox.closest('li');
            if (parentLi) {
                const index = Array.from(listItems).indexOf(parentLi);
                texts.push(parentLi.textContent.trim());
                indices.push(index);
            }
        });

        // 根据索引从小到大排序
        indices.sort((a, b) => a - b);
        texts.sort((a, b) => indices[texts.indexOf(a)] - indices[texts.indexOf(b)]);

        // console.log('文本数组:', texts);
        // console.log('索引数组:', indices);
        return {texts, indices};


    }



    // 切换主题
    function toggleTheme() {
        const elements = document.querySelectorAll('.list-item');
        
        
        // 遍历所有找到的元素
        if (isDarkMode) {
            // 替换类名 "dark-mode-list-group-item" 为 "list-group-item"
            elements.forEach((element, index) => {
                element.classList.replace('dark-mode-list-group-item', 'list-group-item');
                // if (playItemIndex > -1 && playItemIndex === index) {
                //     element.style.color = '#a3de83';
                // }
            });
            event.target.textContent = "透明";

        } else {
            // 替换类名 "list-group-item" 为 "dark-mode-list-group-item"
            elements.forEach((element, index) => {
                element.classList.replace('list-group-item', 'dark-mode-list-group-item');
                // if (playItemIndex > -1 && playItemIndex === index) {
                //     element.style.color = '#005691';
                // }
            });
            event.target.textContent = "暗黑";
        }
        
    }

      // 点击+向播放列表面板添加列表项,同时提取所有li的视频名称 索引号 li元素存放在数组中
      function addItemToPLaylistPanel(itemText) {

        // <img src="//i0.hdslb.com/bfs/static/jinkela/video/asserts/playing.gif" style=""></img>

         // 创建播放动图<img> 元素并设置其属性
        const imgElement = document.createElement('img');
        imgElement.src = "//i0.hdslb.com/bfs/static/jinkela/video/asserts/playing.gif";
        imgElement.className = 'playing-gif';
        imgElement.style.cssText = "color:#ff7c38;display: none; height: 17px; width: 17px; margin-right: 7px; vertical-align: middle;";

        const newItem = document.createElement('li');
        newItem.classList.add('list-item','dark-mode-list-group-item');
        // newItem.className = 'dark-mode-list-group-item';
        newItem.draggable = true;
        // newItem.textContent = `${itemText}`;
        // newItem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'
        newItem.title = '点击播放,拖动排序,多选拖动排序';
        // newItem.style.color = '#ba52ed'
       
         // 创建一个文本节点
        const textNode = document.createTextNode(itemText);
        // textNode.className = 'video-name';
        textNode.textContent = `${itemText}`
        // 将 img 元素和文本节点添加到 newItem 中
        newItem.appendChild(imgElement);
        newItem.appendChild(textNode);

        // 创建一个包含 SVG 图标和多选框的容器
        const iconContainer = document.createElement('span');
        iconContainer.style.float = 'right'; // 将容器浮动到右边
        // iconContainer.style.display = 'inline-block';
        iconContainer.style.alignItems = 'center'; // 垂直居中对齐
    
        // 创建 SVG 图标,移除播放列表条目
        const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svgIcon.setAttribute('width', '24');
        svgIcon.setAttribute('height', '24');
        svgIcon.setAttribute('viewBox', '0 0 12 12');
        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('fill', '#ba52ed');
        path.setAttribute('d', 'M5 3h2a1 1 0 0 0-2 0M4 3a2 2 0 1 1 4 0h2.5a.5.5 0 0 1 0 1h-.441l-.443 5.17A2 2 0 0 1 7.623 11H4.377a2 2 0 0 1-1.993-1.83L1.941 4H1.5a.5.5 0 0 1 0-1zm3.5 3a.5.5 0 0 0-1 0v2a.5.5 0 0 0 1 0zM5 5.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5M3.38 9.085a1 1 0 0 0 .997.915h3.246a1 1 0 0 0 .996-.915L9.055 4h-6.11z');
        svgIcon.appendChild(path);
    
        // 设置 SVG 图标的点击事件监听器
        svgIcon.addEventListener('click', () => {
            // 获取 SVG 图标的父元素(即包含它的 <li> 元素)
            const parentLi = svgIcon.closest('li');
            if (parentLi) {
                // 获取被移除的 <li> 元素的索引
                const listItems = document.getElementById('myList').getElementsByTagName('li');
                const index = Array.from(listItems).indexOf(parentLi);

                // console.log(`被移除的 <li> 元素的索引: ${index}`);
    
                // 从 DOM 中移除 <li> 元素
                parentLi.remove();

                videoList.splice(index, 1);
                // 当删除播放列表面板列表项时需要更新playItemIndex,才能正确播放对应列表项
                if (index < playItemIndex) {
                    playItemIndex--;
                } else if (index === playItemIndex) {
                    // 如果当前播放的列表项被删除,则停止播放
                    stopVideo();
                }


            }
            let selectAllItemBtn = document.querySelector("#selectAllItem");
            hasCheckedCheckbox()?selectAllItemBtn.textContent="反选":selectAllItemBtn.textContent="全选"
            
        });
    
        // 创建多选框
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.style.height = "20px";
        checkbox.style.width = "18px";
        checkbox.style.color = '#f5c7f7';
        checkbox.style.marginLeft = '8px'; // 添加一些间距
    
        // 将 SVG 图标和多选框添加到容器中
        iconContainer.appendChild(svgIcon);
        iconContainer.appendChild(checkbox);
    

        // 添加 change 事件监听器
        checkbox.addEventListener('change', (event) => {
           
            let selectAllItemBtn = document.querySelector("#selectAllItem");
            hasCheckedCheckbox()?selectAllItemBtn.textContent="反选":selectAllItemBtn.textContent="全选"
            
            // console.log('复选框状态发生变化:', event.target.checked);
        });

        // 添加 click 事件监听器
        checkbox.addEventListener('click', (event) => {
           
            let selectAllItemBtn = document.querySelector("#selectAllItem");
            hasCheckedCheckbox()?selectAllItemBtn.textContent="反选":selectAllItemBtn.textContent="全选"
            // console.log('复选框被点击:', event.target.checked);
        });

        // 将容器添加到 <li> 元素中
        newItem.appendChild(iconContainer);
    
        document.getElementById('myList').appendChild(newItem);

        
    }
    // 创建播放列表面板
    function createPLaylistPanel() {
        // 创建一个带有样式的列表容器
        listContainer = document.createElement('div');
        // listContainer.style.width = '500px';
        // listContainer.style.minWidth = '470px';
        listContainer.style.width = 'fit-content';
        
        listContainer.style.height = '600px';
        listContainer.style.left = '700px';
        listContainer.style.top = '200px';
        listContainer.style.zIndex = '999999';
        listContainer.style.overflowY = 'scroll';
        // listContainer.style.overflowX = 'hidden';
        listContainer.style.alignItems = 'center';
        listContainer.style.backgroundColor = 'rgba(255, 255, 255, 0)';
        listContainer.style.borderRadius = '6px';
        listContainer.style.display = 'none'; // 初始隐藏
        listContainer.style.position = 'fixed'; // 自适应固定在可见位置
        // listContainer.style.flexDirection = 'column';
        listContainer.style.boxShadow = '0 8px 5px rgba(0, 0, 0, 0.5)'; // 添加边框阴影
        listContainer.className = 'container mt-5 draggable'; // 添加容器类和可拖动类
        listContainer.innerHTML = `
        <div style="position: sticky; top: 0; background-color: rgba(0, 0, 0, 0);margin-bottom: 10px;justify-content: space-between;align-items: center;">
            <button type="button" class="btn btn-primary" id="closePlaylist">关闭列表</button>
            <button type="button" class="btn btn-primary" id="playMode">顺序播放</button>
            <button type="button" class="btn btn-primary" id="removeItem">移除选中</button>
            <button type="button" class="btn btn-primary" id="selectAllItem">全选</button>
            <button type="button" class="btn btn-primary" id="addAllItem">全部添加</button>
            <button type="button" class="btn btn-primary" id="toggleStyleBtn">暗黑</button>
            <span class="text-tip">点击视频左边+号添加</span>
        </div>
        <ul class="list-group myList" id="myList">
        </ul>
        `;
    
         // 添加 CSS 样式
         const style = document.createElement('style');
         style.textContent = `
             .list-group myList{
                 margin-top: 10px;
             }
              .text-tip {
                 margin-right: 8px;
                 font-size: 16px;
                 color: skyblue; 
             }
             .btn.btn-primary{
                 cursor: pointer; /* 设置鼠标指针为小手 */
                 border-radius: 5px;
                 margin-right: 6px;
                 height: 30px;
                 text-align: center;
                 font-size: 14px;
                 color: #c5e3f6; /* 设置字体颜色 */
                 background-color: #48466d; /* 设置背景颜色 */
             }
                  /* 基本样式 */
                ul {
                    list-style-type: none;
                    padding: 0;
                }
                .list-group-item {
                    cursor: pointer; /* 设置鼠标指针为小手 */
                    margin-bottom: 6px; /* 设置li的行距 */
                    background-color: rgba(186, 82, 237, 0.26);
                    font-size: 16px; /* 设置字体大小 */
                    border-radius: 6px; /* 圆角边框 */
                    border: 1.4px solid #a82ffc;
                    color:#311d3f;
                    padding: 5px;
                    margin: 5px;
                    transition: box-shadow 0.3s ease;
                }
                .dark-mode-list-group-item {
                    background-color: rgba(0, 0, 0, 0.8);
                    cursor: pointer; /* 设置鼠标指针为小手 */
                    margin-bottom: 6px; /* 设置li的行距 */
                    font-size: 16px; /* 设置字体大小 */
                    border-radius: 6px; /* 圆角边框 */
                    border: 1.4px solid #a82ffc;
                    color: #ba52ed; /* 设置字体颜色 */
                    padding: 5px;
                    margin: 5px;
                    transition: box-shadow 0.3s ease;
                }
                /* 鼠标悬停时的阴影效果 */
                .list-item:hover {
                    box-shadow: 5px 5px 10px rgba(255, 201, 60, 0.68);
                }
         `;
         document.head.appendChild(style);



        let currentSelectedItem = null; // 用于跟踪当前被选中的 <li> 元素
        let isSelctedAll = false; // 用于跟踪是否全选
    

        // 为item创建拖动排序
        //////////////////////////////////
        // 在 listContainer 上添加拖动事件监听器
        let isDraggingListContainer = false;
        let offsetX = 0;
        let offsetY = 0;

        // 在 listContainer 上添加拖动事件监听器
        listContainer.addEventListener('dragstart', dragStart);
        listContainer.addEventListener('dragover', dragOver);
        listContainer.addEventListener('drop', drop);
        listContainer.addEventListener('dragenter', dragEnter);
        listContainer.addEventListener('dragleave', dragLeave);
        listContainer.addEventListener('dragend', dragEnd);

        let dragSrcEl;
        let LiTextsList
        let LiIndexList
        let checkedItemNum

        let dropIndex
        let dropText
        let ul
        let moveItemList
        let moveItem

        function dragStart(e) {
            if (e.target.tagName === 'LI') {
                dragSrcEl = []; // 将 dragSrcEl 改为数组
                LiTextsList = [];
                LiIndexList = [];
                
                checkedItemNum = 0;
                const result = getCheckedItemsTextAndIndex();
                LiTextsList = result.texts;
                LiIndexList = result.indices;
                checkedItemNum = LiTextsList.length;
                // console.log('被拖动的项目数量' + checkedItemNum);
                // console.log('被拖动的项目索引' + LiIndexList);
                
                if (checkedItemNum) {
                    let combinedHtml = '';
                    for (let i = 0; i < checkedItemNum; i++) {
                        let videoListIndex = LiIndexList[i];
                        let selectedLi = document.querySelector(`#myList li:nth-child(${videoListIndex})`);
                        // selectedLi.style.backgroundColor = '#87ceeb'; // 修改背景色
                        dragSrcEl.push(selectedLi); // 将选中的 li 元素添加到 dragSrcEl 数组中
                        combinedHtml += selectedLi.outerHTML; // 将每个选中的 li 元素的 HTML 内容添加到 combinedHtml 中
                    }
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/html', combinedHtml); // 将合并后的 HTML 内容设置为数据传输对象的数据
                    dragSrcEl.forEach(element => {
                        element.style.opacity = '1';
                    });
                }else{
                    dragSrcEl = e.target;
                    dragSrcEl.style.opacity = '1';
                    
                    
                    
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/html', e.target.innerHTML);
                }
                
                
                
                
                
            }
            
        }

        function dragEnter(e) {
            if (e.target.tagName === 'LI') {
                e.target.classList.add('over');
            }
        }

        function dragLeave(e) {
            if (e.target.tagName === 'LI') {
                e.target.classList.remove('over');
            }
        }

        function dragOver(e) {
            if (e.preventDefault) {
                e.preventDefault();
            }
            e.dataTransfer.dropEffect = 'move';
            return false;
        }

        
        function drop(e) {
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            // if (dragSrcEl !== e.target && e.target.tagName === 'LI') {
                // 获取 ul 元素
                
                ul = e.target.parentNode;
                // 获取所有的 li 元素
                const listItems = ul.getElementsByTagName('li');
                // 找到拖动元素和目标元素的索引
                const dragIndex = Array.from(listItems).indexOf(dragSrcEl);
                // console.log("拖动元素的索引: " + dragIndex);
                
                    // ul = e.target.parentNode;
                    // 获取所有的 li 元素
                    // const listItems = ul.getElementsByTagName('li');
                dropIndex = Array.from(listItems).indexOf(e.target);
                    // console.log("目标元素的索引: " + dropIndex);
                // console.log("目标元素的索引: " + dropIndex);
                dropText = videoList[dropIndex].title;// 获取目标元素的文本
                // console.log('目标元素文本: ' + dropText);
               
                ul.innerHTML = '';
                // // 交换 videoList 中的元素位置



                     moveItemList = []
                    let moveItem = null;
                // // ///////////////////
                if(checkedItemNum>0){
                    currentPlayVideoTitle = getCrruentPlayVideoTitle()
                    for(let i=0;i<checkedItemNum;i++){
                        let videoListIndex = LiIndexList[i]
                        // console.log('被拖动的项目索引'+videoListIndex)
                        // let moveItem = videoList.splice(videoListIndex,1)[0]
                        moveItem = videoList[videoListIndex];
                        
                        // videoList.splice(videoListIndex, 1);
                        // console.log('被拖动的项目'+moveItem)
                        // console.log('被拖动的项目标题'+moveItem.title)
                        moveItemList.push(moveItem)
                    }
                    LiIndexList.sort((a, b) => b - a);//数组元素从大到小排序
                //     /*目标项目的位置在原来数组中的索引是dropIndex,判断被拖动的项目在原数组最小索引的元素是否在目标元素之前,
                // 如果是,是从上往下拖,拖到目标项目之后,则拖动后的位置是新数组中目标元素索引+1处即insertIndex+1,
                // 如果被拖动项目在原数组中的索引大于目标项目在原数组中的索引dropIndex若不等于0,即被拖动元素在目标元素之后,是从下往上拖,
                // ,拖动目标项目之前,则拖动后的位置是目标元素在新数组中的索引-1,即insertIndex-1(dropIndex若不等于0,等于0 insertIndex为0)   
                
                // 原数组目标项目索引:dropIndex
                // 原数组拖动项目最小索引:LiIndexList[LiIndexList.length-1]
                // */
                //     // console.log('被拖动的项目索引列表中最大值: ' + LiIndexList[0])
                //     // console.log('被拖动的项目索引列表中最小值: ' + LiIndexList[LiIndexList.length-1])
                    LiIndexList.forEach((index) => {
                        videoList.splice(index, 1);
                    })


                
                //     // 根据目标元素文本求出其在被删减后的videoList中的索引,再根据索引进行插入
                //     // 从上往下拖,是插入到insertIndex+1位置,从下往上拖,是插入到insertIndex位置
                setTimeout(() => {
                    let insertIndex = videoList.findIndex(video => video.title === dropText);
                    // console.log('新数组目标项目索引是:'+insertIndex)
                    if (LiIndexList[LiIndexList.length-1] < dropIndex) {
                        // 从上往下拖动,是插入到insertIndex+1位置
                        insertIndex=insertIndex+1
                        videoList.splice(insertIndex, 0, ...moveItemList);
                       
                    }else if(LiIndexList[LiIndexList.length-1] > dropIndex){
                        // 从下往上拖动,是插入到insertIndex-1位置
                        videoList.splice(insertIndex, 0, ...moveItemList);

                        
                    }

                    videoList.forEach((item) => {
                        addItemToPLaylistPanel(item.title);
                    })
                    playItemIndex = videoList.findIndex(item => item.title === currentPlayVideoTitle);
                    if(playItemIndex>-1){
                        setCurrentItemBackgroundAndPlayingGifVisible()
                    }else{console.log('没有播放视频')}
                    // playItemIndex?setCurrentItemBackgroundAndPlayingGifVisible():console.log('没有播放视频');
                    // setCurrentItemBackgroundAndPlayingGifVisible()
                    // console.log('拖动结束后的videoList.length: ', videoList.length);
                },5)
                
        }else{
            
            currentPlayVideoTitle = getCrruentPlayVideoTitle()
            let dragItem = videoList.splice(dragIndex, 1)[0];//将拖动项从列表中删除
            if(dragIndex<dropIndex){
                let insertIndex = videoList.findIndex(video => video.title === dropText)+1;

                // let insertIndex = videoList.findIndex(video => video.title === dropText);
                videoList.splice(insertIndex, 0, dragItem);//将拖动项添加到目标位置,对应的是播放面板上面拖到的位置
                // console.log('从上往下拖动目标元素索引dropIndex是dragIndex:'+dropIndex);
                // console.log('从上往下拖动目标元素索引dropIndex是:'+dropIndex);
                

            }else if(dragIndex>dropIndex){
                // let dragItem = videoList.splice(dragIndex, 1)[0];//将拖动项从列表中删除
                console.log('从下往上拖动目标元素的索引dropIndex是:'+dropIndex);
                let insertIndex = videoList.findIndex(video => video.title === dropText);
                videoList.splice(insertIndex, 0, dragItem);//将拖动项添加到目标位置,对应的是播放面板上面拖到的位置

            }
            
             videoList.forEach((item) => {
                    addItemToPLaylistPanel(item.title);
                })
            if(currentPlayVideoTitle){
                playItemIndex = videoList.findIndex(item => item.title === currentPlayVideoTitle);
                // playItemIndex?setCurrentItemBackgroundAndPlayingGifVisible():console.log('没有播放视频');
                if(playItemIndex>-1){
                    setCurrentItemBackgroundAndPlayingGifVisible()
                }else{console.log('没有播放视频')}
            }
  
            
        }

            return false;
     }

        function dragEnd(e) {
            if (e.target.tagName === 'LI') {
                e.target.classList.remove('over');
            }
            
        }



////////////////////////////////////////////



        
        // 拖动 listContainer 的事件处理
        listContainer.addEventListener('mousedown', (event) => {
            if (!event.target.matches('li')) {
                isDraggingListContainer = true;
                offsetX = event.clientX - listContainer.offsetLeft;
                offsetY = event.clientY - listContainer.offsetTop;
            }
        });

        document.addEventListener('mousemove', (event) => {
            if (isDraggingListContainer) {
                listContainer.style.left = `${event.clientX - offsetX}px`;
                listContainer.style.top = `${event.clientY - offsetY}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDraggingListContainer = false;
        });







        //////////////////////////////////
         





        // 为列表项添加点击事件监听器,点击歌曲条目播放
        listContainer.addEventListener('click', (event) => {
            
             
            
            // 如果点击播放列表项
            if (event.target.matches('.list-item')) {
                
                restorePreviousPlayItemBackgroundPlayingGifVisible()
                currentSelectedItem = event.target; // 记录当前被选中的 <li> 元素

                // currentSelectedItem.style.backgroundColor = 'skyblue'; // 设置被选中元素的背景颜色
    
                // // 获取 ul 元素
                let myList = document.getElementById('myList');
    
                // // 获取所有的 li 元素
                let listItems = myList.getElementsByTagName('li');

                 // 获取被点击的 li 元素的索引
                let clickedIndex = Array.from(listItems).indexOf(event.target);
    
                // console.log('点击的 li 元素的索引:', clickedIndex);
    
                // 点击列表项时提取 PNumber
                let textContent = currentSelectedItem.textContent.trim();
                // 点击列表项时获取列表项的索引作为播放列表项索引
                // playItemIndex = Number(textContent.split('.')[0]) - 1;
                playItemIndex = clickedIndex;
                // console.log('点击列表项playItemIndex:' + playItemIndex);
                // let match = textContent.match(/P(\d+)/);
                videoList[playItemIndex].videoElement.click();
                setCurrentItemBackgroundAndPlayingGifVisible()
                videoEl.play()
                
                
                // 如果点击了关闭播放列表按钮.video-list-btn
            } else if (event.target.matches('#closePlaylist')) {
                let videoListButton = document.querySelector('.video-list-btn')
                videoListButton.click()
                // alert(`点击了: ${event.target.textContent}`);
                // listContainer.style.display = 'none'; // 隐藏播放列表
                // videoListButton.textContent = '播放列表';
    
                // 如果点击了播放模式按钮
            } else if (event.target.matches('#playMode')) {
                playMode = (playMode + 1) % 3;
                // console.log("播放模式按钮被点击,当前playMode是:"+playMode);
                if (playMode === 0) {
                    event.target.textContent = "顺序播放";
                    // alert("已开启顺序播放,playMode: "+playMode);
                } else if (playMode === 1) {
                    event.target.textContent = "随机播放";
                    // alert("已开启随机播放,playMode: "+playMode);
                } else if (playMode === 2) {
                    event.target.textContent = "单集循环";
                    // alert("已开启单集循环,playMode: "+playMode);
                }
                // alert(`点击了: ${event.target.textContent}`);
    

                // 如果点击了全选按钮
            } else if (event.target.matches('#selectAllItem')) {
                
                isSelctedAll = !isSelctedAll;
               
                const checkboxes = listContainer.querySelectorAll('input[type="checkbox"]');
                
                checkboxes.forEach(checkbox => {
                    checkbox.checked = !checkbox.checked; // 反转多选框的选中状态
                });
                let selectAllItemBtn = document.querySelector("#selectAllItem");
                hasCheckedCheckbox()?selectAllItemBtn.textContent="反选":selectAllItemBtn.textContent="全选"

    
                // 如果点击移除选中按钮
            } else if (event.target.matches('#removeItem')) {
                
                
                // selectAllItemBtn.textContent="全选"
                
                // 移除所有选中状态的多选框对应的 <li> 元素
                const checkboxes = listContainer.querySelectorAll('input[type="checkbox"]:checked');
                const listItems = document.getElementById('myList').getElementsByTagName('li');
                const indicesToRemove = [];
    
                checkboxes.forEach(checkbox => {
                    const parentLi = checkbox.closest('li');
                    if (parentLi) {
                        // 获取被移除的 <li> 元素的索引
                        const index = Array.from(listItems).indexOf(parentLi);
                        indicesToRemove.push(index);
                    }
                });
    
                // 按索引从大到小排序,确保移除时索引不会变化
                indicesToRemove.sort((a, b) => b - a);
    
                for (let index of indicesToRemove) {
                    videoList.splice(index, 1);
                     // 当删除播放列表面板列表项时需要更新playItemIndex,才能正确播放对应列表项
                    if (index < playItemIndex) {
                        playItemIndex--;
                    } else if (index === playItemIndex) {
                        // 如果当前播放的列表项被删除,则停止播放
                        stopVideo();
                    }
                }
    
                indicesToRemove.forEach(index => {
                    // console.log(`被移除的 <li> 元素的索引: ${index}`);
                    listItems[index].remove();
                    
                    
    
                });
               
                let selectAllItemBtn = document.querySelector("#selectAllItem");
                hasCheckedCheckbox()?selectAllItemBtn.textContent="反选":selectAllItemBtn.textContent="全选"

                // 如果点击全部添加按钮
            }else if(event.target.matches('#addAllItem')){
                let selectAllItemBtn = document.querySelector("#selectAllItem");
                selectAllItemBtn.textContent="全选"
                let videoEl = document.querySelector("div.bpx-player-video-wrap > video")
                videoEl.pause();
                videoEl.currentTime = 0;
                if(allVideoList.length>0){
                    // 获取ul元素
                    var ulElement = document.getElementById('myList');
    
                    // 移除ul中的所有li元素
                    while (ulElement.firstChild) {
                        ulElement.removeChild(ulElement.firstChild);
                    }
    
                    
    
                    for(let i=0;i<allVideoList.length;i++){
                        addItemToPLaylistPanel(allVideoList[i].title)
                    }
                    // 清空数组
                    videoList.length = 0;
                    // 将allVideoList中的元素添加到videoList中
                    videoList.push(...allVideoList);
                    
                }  

                // 如果点击切换style按钮
            }else if (event.target.matches('#toggleStyleBtn')) {
               
                toggleTheme()
                isDarkMode = !isDarkMode;
                
            }
    
        });
    
        // 将列表容器插入到 document.body 中
        document.body.appendChild(listContainer);
    
    }
    
    // 为视频前面添加+号,点击后可以添加到播放列表
    function createAddBtnForVideoItem() {
            
        const ul = document.querySelector('ul.list-box');
        if (ul) {
            // observer.disconnect();
            

            // 定义SVG图标按钮
            const svgIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="margin-right: 10px;" title="添加到播放列表"><path fill="#000000" d="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z"/></svg>`;
            // ul.list-box > li > a > div.clickitem
            // 遍历ul中的每个li元素
            ul.querySelectorAll('li').forEach(li => {
                // 获取li中的两个span元素
                const spans = li.querySelectorAll('span');
                const firstSpanText = spans[0].textContent?spans[0].textContent:'P';
                const secondSpanText = spans[1].textContent?spans[1].textContent:'未匹配标题';
                let vedioeElement = li.querySelector('a > div.clickitem');

                let videoTitle = firstSpanText + ' ' + secondSpanText;
                // let allPnumber = videoTitle.match(/P(\d+)/)[1];
                // allVideoDict[allPnumber] = vedioeElement;
                let allVideoInfo = new vedioInfo(j,videoTitle,vedioeElement);

                allVideoList.push(allVideoInfo);
                j++
                // 获取li中的img元素
                const img = li.querySelector('img');
                if (img) {
                    // 在img前面插入SVG图标按钮
                    img.insertAdjacentHTML('beforebegin', svgIcon);
                    // 获取新插入的SVG元素
                    const newSvg = img.previousElementSibling;
                    // 点击+图标按钮时,添加到播放列表
                    
                    newSvg.addEventListener('click', (event) => {
                        
                        event.preventDefault();
                        event.stopPropagation();
                        

                        ///////////
                        // 创建自定义提示框
                        const tooltip = document.createElement('div');
                        tooltip.textContent = `已添加${videoTitle}到播放列表`;
                        tooltip.style.position = 'fixed';
                        tooltip.style.top = '100px';
                        tooltip.style.left = '750px';
                        tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
                        tooltip.style.color = 'skyblue';
                        tooltip.style.padding = '10px';
                        tooltip.style.borderRadius = '5px';
                        tooltip.style.zIndex = '1000';
                        document.body.appendChild(tooltip);

                        // 设置定时器,3秒后自动消失
                        setTimeout(() => {
                            document.body.removeChild(tooltip);
                        }, 3000);
                        ///////////

                        // console.log('点击的li元素:'+li);
                        // 提取点击项的PNumber
                        // let Pnumber = videoTitle.match(/P(\d+)/)[1];
                        // if (Pnumber){
                        //     console.log(`PNumber: ${Pnumber}`);
                        // }else{
                        //     alert(`78行无法提取PNumber: ${Pnumber}`);
                        // }

                        let mvedioInfo = new vedioInfo(i, videoTitle, vedioeElement);

                        // videoDict[Pnumber]=vedioeElement
                        videoList.push(mvedioInfo);
                    
                        // console.log('videoList中元素个数:'+videoList.length);

                        // 输出字典中所有键值对
                        // let entries = Object.entries(videoDict);
                        // console.log(entries);
                        ///////////////////

                        // alert('你点击了SVG图标按钮!视频标题为:' + videoTitle);
                        addItemToPLaylistPanel(videoTitle)
                        // toggleTheme()
                        // console.log(`当前歌曲列表中元素: ${videoList.join(',')}`);
                        // return videoTitle;
                        i++

                        
                    });
                }
            });
        }
    }


    /////////////////////////////////

    // Your code here...
})();

QingJ © 2025

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