m3u转换器

配合使用:Chrome扩展(https://www.hlsplayer.org/) 或者egdge扩展 M3u8 Player(https://microsoftedge.microsoft.com/addons/detail/hls-m3u8-player-live-/bmmmdhlnijgodpfbhpgjfkpjiigbpcbk?hl=zh-CN)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        m3u转换器
// @namespace   Violentmonkey Scripts
// @match       *://*/*.txt*
// @match       *://*/*.m3u*
// @match       http://127.0.0.1*/*playerlist*
// @grant       none
// @version     1.1.1
// @author      percygyq
// @description 配合使用:Chrome扩展(https://www.hlsplayer.org/) 或者egdge扩展 M3u8 Player(https://microsoftedge.microsoft.com/addons/detail/hls-m3u8-player-live-/bmmmdhlnijgodpfbhpgjfkpjiigbpcbk?hl=zh-CN)


let Store = {
    get: function (key, default_vaule) {
        let value = localStorage.getItem(key);
        if (value) {
            try {
                return JSON.parse(value);
            } catch (ignore) {
                if (default_vaule !== undefined) {
                    return default_vaule;
                }
                return null;
            }
        }
        if (default_vaule !== undefined) {
            return default_vaule;
        }
        return null;
    },
    contains: function (key) {
        return !is_empty(localStorage.getItem(key));
    },
    set: function (key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    },
    remove: function (key) {
        localStorage.removeItem(key);
    },
};
//region 数组功能扩展
//数组迭代函数
Array.prototype.each = function (fn) {
    fn = fn || Function.K;
    let a = [];
    let args = Array.prototype.slice.call(arguments, 1);
    for (let i = 0; i < this.length; i++) {
        let res = fn.apply(this, [this[i], i].concat(args));
        if (res != null) a.push(res);
    }
    return a;
};
//数组是否包含指定元素
Array.prototype.contains = function (suArr) {
    for (let i = 0; i < this.length; i++) {
        if (this[i] === suArr) {
            return true;
        }
    }
    return false;
};
//不重复元素构成的数组
Array.prototype.uniquelize = function () {
    // let ra = [];
    // for (let i = 0; i < this.length; i++) {
    //     if (!ra.contains(this[i])) {
    //         ra.push(this[i]);
    //     }
    // }
    // return ra;
    return this;
};
//删除某一元素
Array.prototype.remove = function (val) {
    let index = this.indexOf(val);
    if (index > -1) {
        this.splice(index, 1);
        return true;
    }
    return false;
};
Array.prototype.isEmpty = function () {
    return this.length === 0;
};
//两个数组的交集
Array.intersect = function (a, b) {
    return a.uniquelize().each(function (o) {
        return b.contains(o) ? o : null
    });
};
//两个数组的差集
Array.minus = function (a, b) {
    return a.uniquelize().each(function (o) {
        return b.contains(o) ? null : o
    });
};
//两个数组的补集
Array.complement = function (a, b) {
    return Array.minus(Array.union(a, b), Array.intersect(a, b));
};
//两个数组并集
Array.union = function (a, b) {
    return a.concat(b).uniquelize();
};

window.requestIdleCallback = window.requestIdleCallback ||
    function (cb) {
        let start = Date.now();
        return setTimeout(function () {
            cb({
                didTimeout: false,
                timeRemaining: function () {
                    return Math.max(0, 50 - (Date.now() - start));
                }
            });
        }, 1);
    };

window.cancelIdleCallback = window.cancelIdleCallback ||
    function (id) {
        clearTimeout(id);
    };

//endregion

// ==/UserScript==
(function () {
    'use strict';
    // @match       *://*/*

    //region 定义变量
    let summary_page_url = window.location.origin + '/res/playerlist'
    let pageSize = 50;//每页显示的记录条数
    let curPage = 0;//当前页
    let direct = 0;//方向
    let len;//总行数
    let page;//总页数
    let title_dic;
    let match_indexes;
    let show_indexs;
    let has_oper_td = false;

    const repeat_str = window.location.host.indexOf("a.bc") || window.location.host.indexOf("193.112.18.11") ? 'res/playerlist' : null;
    let url = window.location.href;
    url = url.split('?')[0];
    url = url.replace('#', '');
    let fav = 'fav';
    let his = 'his';
    let splitr = '~';
    let fav_prefix = fav + splitr;
    let his_prefix = his + splitr;
    let B;  // 文件夹名(C所在的文件夹)
    let C; // 文件名(m3u文件或者txt文件的文件名)
    let imgElements; // {}
    /**
     * 是否收藏状态
     */
    let fav_status = false;
    let fav_indexes;
    let his_status = false;
    let his_indexes;
    let summary;
    let categories;
    /**
     * select选择框 筛选出的 index
     */
    let select_indexes = null;
    let progress = 0;
    let start;
    let period_start;
    //endregion

    //region 逻辑代码
    if (url.startsWith('file:')) {
        //    local file
        console.log('local file------')
    }

    if (getQueryString("mode") === 'summary') {
        // 汇集模式 (显示所有的收藏和历史记录)
        summary = true;
        let datas = load_localStorage();
        printProgress('summary load_localStorage');
        install_table(datas);
        printProgress('summary install_table localStorage');
        return;
    }

    let is_match = url.endsWith(".txt") || url.endsWith(".m3u");
    if (!is_match) {
        window.addEventListener('click', throttle(listen_click, 500, 1000));
        return;
    }

    printProgress('start init html');
    let body_pre = document.querySelector('body > pre');
    if (is_empty(body_pre)) {
        return;
    }
    let text = body_pre.innerText;
    const datas = extract_datas(text);

    printProgress('extract_data_from_html');
    // write_doc(datas);
    install_table(datas);
    printProgress('install_table from html');
    //endregion

    //-----------------------  以下是函数 --------------------
    //region 以下是函数
    function init_some_name(url) {
        let split = url.split('/');
        split = split.each(x => x.length > 0 ? x : null);
        let len = split.length;
        C = decodeURI(split[len - 1]);
        if (len <= 3) {
            B = 'default';
        } else {
            B = decodeURI(split.slice(2, len - 1).join('/'));
        }
        if (!is_empty(repeat_str)) {
            B = B.replace(repeat_str + '/', '');
            B = B.replace(repeat_str, '');
        }
        if (is_empty(B)) {
            B = 'default';
        }
        console.log('B:' + B + ',C:' + C);
    }

    function listen_click(e) {
        let mhref = e && e.target && e.target.href;
        if (is_empty(mhref)) {
            return;
        }
        console.log('mhref', mhref);
        let b = mhref.split('?')[0].endsWith("m3u");
        let c = mhref.split('?')[0].endsWith("txt");
        if (mhref && (b || c)) {
            if (c) {
                // let new_window = window.open(mhref, '_blank');
                // new_window.focus();
                return;
            }
            printProgress('start click url');
            let text = readContent(mhref);
            printProgress('read data from url');
            const datas = extract_datas(text);
            if (datas.length === 0) {
                return;
            }
            url = mhref;
            e.preventDefault();
            e.stopPropagation();
            console.log("e", mhref);
            printProgress('extract data from url');
            install_table(datas);
            printProgress('install_table from url');
            return;
        }
    }

    function lazyload() { //监听页面滚动事件
        let seeHeight = document.documentElement.clientHeight; //可见区域高度
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
        for (let i of show_indexs) {
            let img = imgElements[i];
            if (img && img.offsetTop < seeHeight + scrollTop) {
                if (is_empty(img.getAttribute("src"))) {
                    // console.log("lazyload:", i);
                    img.src = img.getAttribute("data-src");
                }
            }
        }
    }


    function listen_key_down(event) {
        // console.log(event.code);
        switch (event.keyCode) {
            case 37:
                frontPage();
                break;
            case 39:
                nextPage();
                break;
            case 38:
                if (event.altKey) {
                    window.scrollTo(0, 0);
                }
                break;
            case 40:
                if (event.altKey) {
                    window.scrollTo(0, document.body.scrollHeight);
                }
                break
        }
    }


    function favorite_update(d, i) {
        let fav_page_key = fav_prefix + C;
        let fav_item_key = fav_page_key + splitr + d.title;
        let innerText = this.innerText;
        // let page_favs2 = Store.get(page_key) || [];
        let title_favs2 = Store.get(fav_page_key, []);
        if (innerText.includes('💗')) {
            // 取消收藏
            fav_indexes.remove(i);
            this.innerText = this.innerText.replace('💗', '🤍');
            Store.remove(fav_item_key);
            if (title_favs2.remove(d.title)) {
                if (title_favs2.isEmpty()) {
                    Store.remove(fav_page_key);
                    let dir_key = fav_prefix + B;
                    let dir_favs = Store.get(dir_key, []);
                    if (dir_favs.remove(C)) {
                        if (dir_favs.isEmpty()) {
                            Store.remove(dir_key);
                            let top_list = Store.get(fav, []);
                            if (top_list.remove(B)) {
                                Store.set(fav, top_list);
                            }
                        } else {
                            Store.set(dir_key, dir_favs);
                        }
                    }
                } else {
                    Store.set(fav_page_key, title_favs2);
                }
            }
        } else {
            // 收藏
            if (!fav_indexes.includes(i)) {
                fav_indexes.push(i);
            }
            this.innerText = this.innerText.replace('🤍', '💗');
            d.time = now();
            Store.set(fav_item_key, d);
            if (!title_favs2.includes(d.title)) {
                title_favs2.push(d.title);
                Store.set(fav_page_key, title_favs2);
                let dir_key = fav_prefix + B;
                let dir_favs = Store.get(dir_key, []);
                if (!dir_favs.includes(C)) {
                    dir_favs.push(C);
                    Store.set(dir_key, dir_favs);
                    let top_list = Store.get(fav, []);
                    if (!top_list.includes(B)) {
                        top_list.push(B);
                        Store.set(fav, top_list);
                    }
                }
            }
        }
    }

    function history_update(d) {
        let his_page_key = his_prefix + C;
        let title_hiss = Store.get(his_page_key, []);
        d.time = now();
        Store.set(his_page_key + splitr + d.title, d);
        if (!title_hiss.includes(d.title)) {
            title_hiss.push(d.title);
            Store.set(his_page_key, title_hiss);
            let dir_key = his_prefix + B;
            let dir_hiss = Store.get(dir_key, []);
            if (!dir_hiss.includes(C)) {
                dir_hiss.push(C);
                Store.set(dir_key, dir_hiss);
                let top_list = Store.get(his, []);
                if (!top_list.includes(B)) {
                    top_list.push(B);
                    Store.set(his, top_list);
                }
            }
        }
    }

    function fill_table(tbody, start, end, datas) {
        let fragment = document.createDocumentFragment();
        for (let i = start; i < end; i++) {
            let d = datas[i];
            let tr = document.createElement('tr');
            td_attach_event(tr, i % 2 === 0);
            fragment.appendChild(tr);
            let title = d.title;
            let logo = d.logo;
            let group = d.group;
            let url = d.url;
            if (d.index === undefined) {
                d.index = i;
            }
            title_dic[i] = title;
            let td_size = has_oper_td ? 3 : 2;
            for (let j = 0; j < td_size; j++) {
                let td = document.createElement('td');
                tr.appendChild(td);
                switch (j) {
                    case 0:
                        if (fav_indexes.includes(i)) {
                            td.innerText = i + 1 + '💗';
                        } else {
                            td.innerText = i + 1 + '🤍';
                        }
                        td.onclick = function () {
                            setBC(fav, i);
                            favorite_update.call(this, d, i);
                        };
                        break;
                    case 1:
                        let e_link = document.createElement("a");
                        e_link.setAttribute("href", url);
                        e_link.setAttribute("target", '_blank');
                        if (is_empty(group)) {
                            e_link.setAttribute('data-group', group);
                        }
                        e_link.onclick = function () {
                            // 播放记录
                            console.log(`click url ${title} -> ${url}`);
                            if (!his_indexes.includes(i)) {
                                his_indexes.push(i);
                            }
                            setBC(his, i);
                            history_update(d);
                        };
                        if (is_empty(logo)) {
                            e_link.innerText = title;
                            e_link.setAttribute('title', getTip(i, d));
                            td.appendChild(e_link);
                        } else {
                            pageSize = 10;
                            let e_img = document.createElement('img');
                            e_img.setAttribute('alt', '😎');
                            e_img.setAttribute('data-src', logo);
                            e_img.setAttribute('title', getTip(i, d));
                            e_img.onerror = function () {
                                this.src = "https://tva4.sinaimg.cn/small/87c01ec7gy1fsnqqz0c17j21kw0w07lt.jpg";
                                this.onerror = null;
                            };
                            e_link.appendChild(e_img);
                            let e_p2 = document.createElement('p');
                            e_p2.setAttribute('class', 'title');
                            e_p2.innerText = title;
                            td.appendChild(e_link);
                            td.appendChild(e_p2);
                            td.onmouseover = function (ev) {
                                if (is_empty(e_img.getAttribute("src"))) {
                                    e_img.src = e_img.getAttribute("data-src");
                                }
                            };
                            imgElements[i] = e_img;
                        }
                        break;
                    case 2:
                        td.innerHTML = "<a href='javascript:void()' style='color:#01AAED'>编辑</a>";
                        break;
                }
            }
        }
        tbody.appendChild(fragment);
    }

    function install_table(datas) {
        if (datas.length === 0) {
            alert("没有读取到任何数据!")
            return;
        }

        if (!summary) {
            init_some_name(url);
            init_history();
            init_favorite();
            document.title = C;
            printProgress('init history and favorite')
        } else {
            document.title = '汇集';
        }

        imgElements = {};
        title_dic = {};
        document.body.innerHTML = '<!DOCTYPE html><html lang="zh-CN"><body></body></html>';
        let html_str = '<a id="btn0"></a><input id="pageSize" type="text" size="1" maxlength="3" /><a> 条 </a>' +
            '<a href="#" id="pageSizeSet">设置</a> ' +
            '&nbsp;&nbsp;<input type="file" id="file_upload" style="background-color: #E1FFFF">' +
            '<p id="sjzl"></p> ' +
            '<a href="#" id="btn1">首页</a> ' +
            '<a href="#" id="btn2">上一页</a> ' +
            '<a href="#" id="btn3">下一页</a> ' +
            '<a href="#" id="btn4">尾页</a> ' +
            '<a>转到 </a> ' +
            '<input id="changePage" type="text" size="1" maxlength="4"/> ' +
            '<a>页 </a> ' +
            '<a href="#" id="btn5">跳转</a>' +
            '<br/>' +
            '<input type="text" name="name" id="keyword" style="width:150px;height:30px; ali;text-align:center;"/> ' +
            '<input  id="search_button" type="button" value="search" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="fav_button" type="button" value="收藏" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="his_button" type="button" value="记录" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="summary_button" type="button" value="汇集" style="width:50px;height:30px; ali;text-align:center;">'
        ;
        document.body.innerHTML += html_str;
        if (summary) {
            add_select();
            printProgress('summary add_select')
        }

        let my_div = document.createElement('div');
        my_div.setAttribute('id', 'center');
        document.body.appendChild(my_div);

        printProgress('summary add_select');

        createTab();

        printProgress('create top_tab');
        let tableStyle = document.createElement('style');
        tableStyle.innerHTML = 'td{' +
            'border-left: #d2d2d2 1px solid;' +
            'border-top: #d2d2d2 1px solid;' +
            'width:50px;' +
            'height:50px;' +
            'text-align:center;' +
            'border-collapse:collapse;' +
            'white-space: nowrap;' +
            'background:snow' +
            '}';
        document.head.appendChild(tableStyle);
        let tab = document.createElement("table");
        tab.setAttribute("id", "mytable");
        let tbody = document.createElement('tbody');
        tab.appendChild(tbody);

        let first_load = 1000;
        if (first_load >= datas.length) {
            first_load = datas.length;
        } else {
            requestIdleCallback(function () {
                let tab = document.querySelector("#mytable > tbody");
                printProgress("lazy load start ");
                fill_table(tab, first_load, datas.length, datas);
                datas = [];
                display();
                printProgress("lazy load end ");
            }, {timeout: 50});
        }
        fill_table(tbody, 0, first_load, datas);
        my_div.appendChild(tab);
        printProgress('create data tab');
        attach_event();
        display();
        printProgress('display');
    }

    function add_select() {
        let style = document.createElement('style');
        style.innerHTML = '.select_style {\n' +
            '    width: 200px;\n' +
            '    height: 50px;\n' +
            '    overflow: hidden;\n' +
            '    background: no-repeat 215px;\n' +
            '    border: 1px solid #ccc;\n' +
            '    -moz-border-radius: 5px; /* Gecko browsers */\n' +
            '    -webkit-border-radius: 5px; /* Webkit browsers */\n' +
            '    border-radius: 5px;\n' +
            '    margin: 10px;\n' +
            '    display: inline;\n' +
            '}\n' +
            '.select_style select {\n' +
            '    padding: 5px;\n' +
            '    background: transparent;\n' +
            '    width: 150px;\n' +
            '    font-size: 16px;\n' +
            '    border: none;\n' +
            '    height: 30px;\n' +
            '    -webkit-appearance: none;\n' +
            '}';
        document.head.appendChild(style);

        let dic = {fav: '收藏', his: '播放记录'};

        document.body.innerHTML += '<br/><br/><br/>'
        let div = document.createElement('div');
        div.setAttribute('id', 'search_options');


        div.appendChild(create_select_div('stype', '类别', Object.keys(categories), 'dir', onStypeChange));
        div.appendChild(create_select_div('dir', '文件夹', [], 'm3u', onDirChange));
        div.appendChild(create_select_div('m3u', '列表', [], '', onM3uChange));
        document.body.appendChild(div);


        function create_select_div(id, tip, values, next_id, func) {
            let div = document.createElement('div');
            div.setAttribute('class', 'select_style');
            let select = document.createElement('select');
            select.setAttribute('id', id);
            select.options.add(new Option(`--${tip}--`, ''))
            for (let value of values) {
                let option = new Option(dic[value] || value, value);
                select.options.add(option);
            }
            div.appendChild(select);
            select.onchange = function () {
                let nextOptionVaules = func(this.value);
                // console.log(this.value);
                if (!is_empty(next_id)) {
                    let e = document.getElementById(next_id);
                    e.options.length = 1;
                    let keys = nextOptionVaules || [];
                    for (let key of keys) {
                        let option = new Option(key, key);
                        e.options.add(option);
                    }
                }
            }
            return div;
        }

    }

    function createTab() {
        let top_tab = document.createElement("table");
        let my_div = document.getElementById('center');
        const tab_array = ["序号", "名称", "选择", "导出"];

        let len = tab_array.length;
        let title = document.createElement("h1");
        title.innerHTML = "节目表";

        let br = document.createElement("br");

        for (let i = 0; i < 1; i++) {
            let tr = top_tab.insertRow(i);
            for (let j = 0; j < len; j++) {
                let td = top_tab.rows[i].insertCell(j);
                td.style.cssText = "BORDER:#d2d2d2 1px solid ;width:50px;height:50px; ali;text-align:center;";
                td.innerHTML = tab_array[j];
                // td.style.background = '#f2f2f2';
                switch (j) {
                    case 2:
                        td.onclick = do_select;
                        break;
                    case 3:
                        td.onclick = do_export;
                        break;
                }
            }
        }
        my_div.appendChild(title);
        my_div.appendChild(br);
        my_div.appendChild(top_tab);
    }

    function getTip(i, d) {
        let time = d.time === undefined ? '' : ' ' + d.time;
        if (summary) {
            let tip = '';
            let stype;
            if (fav_indexes.includes(i)) {
                stype = fav;
            } else if (his_indexes.includes(i)) {
                stype = his;
            }
            if (stype) {
                let m = categories[stype].movies_dic[i];
                if (!is_empty(m)) {
                    tip += m + ' ';
                }
                tip += time;
            }
            return tip;
        } else {
            return d.title + time;
        }
    }

    function attach_event() {
        document.getElementById("btn1").onclick = function firstPage() {    // 首页
            curPage = 1;
            direct = 0;
            displayPage();
        };
        document.getElementById("btn4").onclick = function lastPage() {    // 尾页
            curPage = page;
            direct = 0;
            displayPage();
        };
        document.getElementById("btn5").onclick = function changePage() {    // 转页
            curPage = document.getElementById("changePage").value;
            if (!/^[1-9]\d*$/.test(curPage)) {
                alert("请输入正整数");
                return;
            }
            if (curPage > page) {
                alert("超出数据页面");
                return;
            }
            direct = 0;
            displayPage();
        };
        document.getElementById("btn2").onclick = frontPage;
        document.getElementById("btn3").onclick = nextPage;
        document.getElementById("pageSizeSet").onclick = setPageSize;
        document.getElementById("pageSize").onblur = setPageSize;

        document.getElementById("search_button").onclick = do_search_title;
        document.getElementById("keyword").onblur = do_search_title;
        document.getElementById("fav_button").onclick = show_favorite;
        document.getElementById("his_button").onclick = show_history;
        document.getElementById("file_upload").onchange = reload_by_file;
        document.getElementById("summary_button").onclick = function () {
            let new_window = window.open(summary_page_url + '?mode=summary', '_blank');
            new_window.focus();
        };
        // 图片懒加载
        if (imgElements.length > 0) {
            lazyload();
            window.addEventListener('scroll', throttle(lazyload, 500, 1000));
        }
        window.onkeydown = listen_key_down;
        printProgress('attach event');
    }

    function display() {
        search_title();
        printProgress('search_title');
        curPage = 1;    // 设置当前为第一页
        displayPage();//显示第一页
        printProgress('displayPage');
    }

    function frontPage() {    // 上一页
        direct = -1;
        displayPage();
    }

    function nextPage() {    // 下一页
        direct = 1;
        displayPage();
    }

    function setPageSize() {    // 设置每页显示多少条记录
        pageSize = document.getElementById("pageSize").value;    //每页显示的记录条数
        if (!/^[1-9]\d*$/.test(pageSize)) {
            alert("请输入正整数");
            return;
        }
        pageSize = parseInt(pageSize);
        curPage = 1;
        direct = 0;
        displayPage();
    }

    function do_search_title() {
        curPage = 1;
        direct = 0;
        search_title();
        displayPage();
    }

    function show_favorite() {
        curPage = 1;
        direct = 0;
        fav_status = !fav_status;
        document.getElementById("fav_button").style.backgroundColor = fav_status ? '#FFB6C1' : '#F8F8FF';
        displayPage();
    }

    function show_history() {
        curPage = 1;
        direct = 0;
        his_status = !his_status;
        document.getElementById("his_button").style.backgroundColor = his_status ? '#95c1fc' : '#F8F8FF';
        displayPage();
    }

    function reload_by_file() {
        if (this.files.length === 0) {
            console.log('请选择文件!');
            return;
        }
        let file = this.files[0];
        const reader = new FileReader();
        reader.onload = function fileReadCompleted() {
            // 当读取完成时,内容只在`reader.result`中
            // console.log(reader.result);
            let datas = extract_datas(reader.result);
            if (datas.isEmpty()) {
                console.error(`文件 [${name}] 没有读取到数据!`)
            } else {
                url = `${window.location.origin}/local/${file.name}`;
                summary = false;
                printProgress('read from localFile');
                install_table(datas);
                printProgress('install_table from localFile');
            }
        };
        reader.readAsText(file);
    }

    function search_title() {
        let keyword = document.getElementById('keyword').value;
        match_indexes = [];
        let b1 = is_empty(keyword);
        for (let i in title_dic) {
            let t = title_dic[i];
            if (is_empty(t)) {
                continue
            }
            i = parseInt(i);
            if (b1) {
                match_indexes.push(i);
            } else if (t.toLowerCase().includes(keyword.toLowerCase())) {
                match_indexes.push(i);
            }
        }
    }

    function displayPage() {
        if (curPage <= 1 && direct === -1) {
            direct = 0;
            alert("已经是第一页了");
            return;
        } else if (curPage >= page && direct === 1) {
            direct = 0;
            alert("已经是最后一页了");
            return;
        }
        let eligible_indexes = match_indexes;
        if (fav_status) {
            eligible_indexes = Array.intersect(eligible_indexes, fav_indexes);
        }
        if (his_status) {
            eligible_indexes = Array.intersect(eligible_indexes, his_indexes);
        }
        if (summary && select_indexes !== null) {
            eligible_indexes = Array.intersect(eligible_indexes, select_indexes);
        }
        len = eligible_indexes.length;
        page = len % pageSize === 0 ? len / pageSize : Math.floor(len / pageSize) + 1;

        // 修复当len=1时,curPage计算得0的bug
        if (len > pageSize) {
            curPage = ((curPage + direct + len) % len);
        } else {
            curPage = 1;
        }

        printProgress('calc page');

        document.getElementById("btn0").innerHTML = '当前 ' + curPage + '/' + page + ' 页    每页 ';
        document.getElementById("sjzl").innerHTML = '数据总量: ' + len + '';
        document.getElementById("pageSize").value = pageSize;

        show_indexs = eligible_indexes.slice((curPage - 1) * pageSize, curPage * pageSize);

        let all = document.querySelectorAll('#mytable tr');
        all.forEach(function (e, i) {
            if (show_indexs.includes(i)) {
                e.style.setProperty('display', 'block');
            } else {
                e.style.setProperty('display', 'none');
            }
        });
        printProgress('show page');

        if (direct >= 0 && pageSize <= 30) {
            // 预加载下一页的图片
            let load_indexes = eligible_indexes.slice((curPage - 1) * pageSize, (curPage + 1) * pageSize);
            for (let i of load_indexes) {
                let img = imgElements[i];
                if (img && is_empty(img.getAttribute("src"))) {
                    img.src = img.getAttribute("data-src");
                }
            }
        }
        printProgress('preload next page')
    }

    function do_select() {
        if (this.x !== 1) {
            // 点击
            this.x = 1;
        } else {
            // 再次点击
            this.x = 0;
        }
        let f = this.x;
        const t = document.querySelectorAll("#mytable tr");
        for (let i = 0; i < t.length; i++) {
            if (!show_indexs.includes(i)) {
                continue;
            }
            t[i]["x"] = f;
            if (f) {
                t[i].style.backgroundColor = "#bce774";
            } else {
                // 取消选中
                t[i].style.backgroundColor = (t[i].sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
            }
        }
    }

    function do_export() {
        const t = document.querySelectorAll("#mytable tr");
        let datas = [];
        for (let i = 0; i < t.length; i++) {
            let f = t[i]["x"];
            if (f) {
                t[i].style.backgroundColor = "#e3cf4a";
                let e = t[i].querySelector('td:nth-child(2)');
                let a = e.querySelector('td>a');

                let title = e.innerText;
                let url = a.href;
                let group = a.dataset.group || '';

                let imageElement = a.querySelector('img');
                let logo = '';
                if (imageElement) {
                    logo = imageElement.dataset.src || '';
                }
                let d = {
                    'url': url,
                    'title': title,
                    'tvg-logo': logo,
                    'group-title': group
                };
                datas.push(d);
                console.log('export:', t[i].x, i, title);
            }
        }
        let s = datas_to_m3u(datas);
        if (is_empty(s)) {
            alert("没有选中数据!");
            return;
        }
        let file_name;
        if (summary) {
            file_name = 'summary-export.m3u';
        } else {
            file_name = C.split(".")[0] + '-export.m3u';
        }
        exportRaw(file_name, s);
    }

    /**
     * 从本地存储初始化播放记录
     */
    function init_history() {
        his_indexes = [];
        let page_key = his_prefix + C;
        let title_hiss = Store.get(page_key, []);
        for (let title of title_hiss) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index !== undefined) {
                    his_indexes.push(index);
                }
            }
        }

    }

    function init_favorite() {
        fav_indexes = [];
        let page_key = fav_prefix + C;
        let title_favs = Store.get(page_key, []);
        for (let title of title_favs) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index !== undefined) {
                    fav_indexes.push(index);
                }
            }
        }
    }

    function load_favorite() {
        let page_key = fav_prefix + C;
        let page_favs1 = Store.get(page_key, []);
        for (let title of page_favs1) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index) {
                    fav_indexes.push(index);
                }
            }
        }
    }

    function load_localStorage() {
        let datas = [];
        fav_indexes = [];
        his_indexes = [];
        let c = -1;
        let duplicate_check = [];
        categories = {};
        categories.fav = load_data(fav);
        categories.his = load_data(his);

        function load_data(stype) {
            console.log('----type----', stype);
            let dirs = Store.get(stype, []);
            let prefix = stype + splitr;
            let dir_dic = {};
            let m3u_dic = {};
            let movies_dic = {};
            for (let dir of dirs) {
                let m3us = Store.get(prefix + dir, []);
                if (!m3us.isEmpty()) {
                    dir_dic[dir] = m3us;
                }
                for (let m3u of m3us) {
                    let movies = Store.get(prefix + m3u, []);
                    let index_arr = [];
                    let k = dir + splitr + m3u;
                    for (let m of movies) {
                        let item = Store.get(prefix + m3u + splitr + m);
                        if (item) {
                            let j = k + splitr + m;
                            if (duplicate_check.includes(j)) {
                                continue;
                            }
                            duplicate_check.push(j);
                            c++;
                            movies_dic[c] = k;
                            datas.push(item);
                            index_arr.push(c);
                            // console.log('m:', item);
                            switch (stype) {
                                case fav:
                                    fav_indexes.push(c);
                                    break;
                                case his:
                                    his_indexes.push(c);
                                    break;
                            }
                        }
                    }
                    if (!index_arr.isEmpty()) {
                        m3u_dic[k] = index_arr;
                    }
                }
            }
            return {
                'dir_dic': dir_dic,
                'm3u_dic': m3u_dic,
                'movies_dic': movies_dic,
            };
        }

        return datas;
    }

    function setBC(stype, i) {
        if (summary) {
            let m = categories[stype].movies_dic[i];
            if (is_empty(m)) {
                return;
            }
            let split = m.split(splitr);
            B = split[0];
            C = split[1];
        }
    }

    function onStypeChange(x) {
        document.getElementById('m3u').options.length = 1;
        select_indexes = null;
        switch (x) {
            case fav:
                his_status = false;
                fav_status = false;
                document.getElementById("his_button").style.backgroundColor = '#F8F8FF';
                show_favorite();
                break;
            case his:
                his_status = false;
                fav_status = false;
                document.getElementById("fav_button").style.backgroundColor = '#F8F8FF';
                show_history();
                break;
            default:
                his_status = false;
                fav_status = false;
                document.getElementById("fav_button").style.backgroundColor = '#F8F8FF';
                document.getElementById("his_button").style.backgroundColor = '#F8F8FF';
                curPage = 1;
                direct = 0;
                displayPage();
                return [];
        }
        return Object.keys(categories[x].dir_dic);
    }

    function onDirChange(x) {
        let pre = document.getElementById('stype');
        let stype = pre.options[pre.selectedIndex].value;
        if (is_empty(stype)) {
            return null;
        }
        if (is_empty(x)) {
            onStypeChange(stype);
            return null;
        }
        select_indexes = [];
        let m3us = categories[stype].dir_dic[x] || [];
        for (let m3u of m3us) {
            let k = x + splitr + m3u;
            let arr = categories[stype].m3u_dic[k] || [];
            for (let i of arr) {
                select_indexes.push(i);
            }
        }
        curPage = 1;
        direct = 0;
        displayPage();
        return categories[stype].dir_dic[x];
    }

    function onM3uChange(x) {
        let pre = document.getElementById('stype');
        let stype = pre.options[pre.selectedIndex].value;
        if (is_empty(stype)) {
            return;
        }
        pre = document.getElementById('dir');
        let dir = pre.options[pre.selectedIndex].value;
        if (is_empty(dir)) {
            return;
        }
        if (is_empty(x)) {
            onDirChange(dir);
            return;
        }
        select_indexes = [];
        let k = dir + splitr + x;
        let arr = categories[stype].m3u_dic[k] || [];
        for (let i of arr) {
            select_indexes.push(i);
        }
        curPage = 1;
        direct = 0;
        displayPage();
        return;
    }

    function printProgress(name) {
        let now = Date.now();
        if (isNaN(start)) {
            start = now;
            period_start = now;
        }
        let cost = now - start;
        let period_cost = now - period_start;
        period_start = now;
        console.log(`${++progress}\t\t${cost}ms\t${name} -> ${period_cost} ms`)
    }

    //endregion
})();

//-----------------------  以下是global函数 --------------------
//region 以下是global函数
function td_attach_event(tr, odd) {
    if (odd) {
        tr.style.backgroundColor = '#f8fbfc';
    } else {
        tr.style.backgroundColor = '#e5f1f4';
    }
    tr.onclick = function () {
        if (this.x !== 1) {
            // 选中
            this.x = 1;
            this.style.backgroundColor = "#bce774";
        } else {
            // 取消选中
            this.x = 0;
            this.style.backgroundColor = (this.sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
        }
    };
    tr.onmouseover = function () {
        if (this.x !== 1) this.style.backgroundColor = "#ecfbd4";
    };
    tr.onmouseout = function () {
        if (this.x !== 1) this.style.backgroundColor = (this.sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
    };
}

/**
 * 节流函数
 */
function throttle(fun, delay, time) {
    let timeout, startTime = new Date();
    return function () {
        let context = this, args = arguments, curTime = new Date();
        clearTimeout(timeout);
        // 如果达到了规定的触发时间间隔,触发 handler
        if (curTime - startTime >= time) {
            fun.apply(context, args);
            startTime = curTime;
            // 没达到触发间隔,重新设定定时器
        } else {
            timeout = setTimeout(fun, delay);
        }
    };
}

function replace_all(str, old_value, new_value) {//吧f替换成e
    let reg = new RegExp(old_value, "g"); //创建正则RegExp对象
    return str.replace(reg, new_value);
}

function is_empty(str) {
    return str == null || str === 'undefined' || !str || !/[^\s]/.test(str);
}

function readContent(url) {
    let xhr = new XMLHttpRequest(), okStatus = document.location.protocol === "file:" ? 0 : 200;
    xhr.open('GET', url, false);
    xhr.overrideMimeType("text/html;charset=utf-8");//默认为utf-8
    xhr.send(null);
    return xhr.status === okStatus ? xhr.responseText : null;
}

function extract_datas(text) {
    const dic_list = [];

    let split = text.split("\n");
    if (split.length === 1) {
        split = text.split("\r\n");
    }
    if (!split[0].includes('#EXTM3U')) {
        return dic_list;
    }
    let dic = {};
    let f = false;
    for (let s of split) {
        if (is_empty(s)) {
            continue
        }
        if (s.includes('#EXTM3U')) {
            continue
        }
        if (s.includes('#EXTINF')) {
            f = true;
            s = s.replace('#EXTINF:-1', '');
            let title = /((\s[\w,-]+)="([^"]+)")*\s*,\s*(.+)/.exec(s)[4].trim();
            let tmp_dic = {};
            let r = /(\s[\w,-]+)="([^"]+)"/g;
            while (r.test(s)) {
                tmp_dic[RegExp.$1.trim()] = RegExp.$2.trim();
            }
            dic = {};
            dic.title = title;
            dic.logo = tmp_dic['tvg-logo'] || '';
            dic.group = tmp_dic['group-title'] || '';
            dic.tvg_name = tmp_dic['tvg-name'] || '';
            continue
        }
        if (f) {
            if (is_empty(s)) {
                continue
            }
            s = s.trim();
            if (s.startsWith("#") || s.includes("#")) {
                continue;
            }
            dic.url = s;
            dic_list.push(dic)
        }
        // console.log(s)
    }
    console.log("-----------------------");
    return dic_list;
}

function datas_to_m3u(datas) {
    if (datas.length === 0) {
        return "";
    }
    let s = "#EXTM3U\n";
    for (let d of datas) {
        s += "#EXTINF:-1 ";
        if (!is_empty(d['tvg-logo'])) {
            s += `tvg-logo="${d['tvg-logo']}" `;
        }
        if (is_empty(d['group-title'])) {
            s += `group-title="${d['group-title']}" `;
        }
        s += `, ${d.title}\n${d.url}\n`;
    }
    return s;
}

function fakeClick(obj) {
    let ev = document.createEvent("MouseEvents");
    ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    obj.dispatchEvent(ev);
}

function exportRaw(name, data) {
    let urlObject = window.URL || window.webkitURL || window;
    let export_blob = new Blob([data]);
    let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
    save_link.href = urlObject.createObjectURL(export_blob);
    save_link.download = name;
    fakeClick(save_link);
}

function getQueryString(name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    let r = decodeURI(window.location.search.substr(1)).match(reg);
    if (r != null) return unescape(r[2]);
    return null;
}

function now() {
    return dateFormat("YYYY-mm-dd HH:MM:SS", new Date());
}

function dateFormat(fmt, date) {
    let ret;
    const opt = {
        "Y+": date.getFullYear().toString(),        // 年
        "m+": (date.getMonth() + 1).toString(),     // 月
        "d+": date.getDate().toString(),            // 日
        "H+": date.getHours().toString(),           // 时
        "M+": date.getMinutes().toString(),         // 分
        "S+": date.getSeconds().toString()          // 秒
    };
    for (let k in opt) {
        ret = new RegExp("(" + k + ")").exec(fmt);
        if (ret) {
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        }
    }
    return fmt;
}


//endregion