Youtube Speed By Channel

Allow to choose the default speed for specific YT channel

질문, 리뷰하거나, 이 스크립트를 신고하세요.
// ==UserScript==
// @name         Youtube Speed By Channel
// @namespace    Alpe
// @version      0.2.15
// @description  Allow to choose the default speed for specific YT channel
// @author       Alpe
// @include      https://www.youtube.com/*
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM.deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_getResourceText
// @run-at       document-start
// @resource     jquery https://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==

(async () => {
    if (window.trustedTypes && window.trustedTypes.createPolicy){
        try {
            trustedTypes?.createPolicy?.('default', {createScriptURL: s => s, createScript: s => s, createHTML: s => s});
        } catch {}
    }
    eval(GM_getResourceText('jquery'));
    const defaults = {
        DEFAULT_SPEED: 1.0,
        SHOW_RELATIVE_TIME: true,
        COLOR_SELECTED: "red",
        COLOR_NORMAL: "rgb(220,220,220)",
        BUTTON_TEMPLATES: JSON.stringify([
            ["50%", 0.5],
            ["75%", 0.75],
            ["Normal", 1],
            ["1.25x", 1.25],
            ["1.5x", 1.5],
            ["1.75x", 1.75],
            ["2x", 2],
            ["2.25x", 2.25],
            ["2.5x", 2.5],
            ["3x", 3],
            ["3.5x", 3.5]
        ]),
        AUDIO_BOOST: 1,
        SAVE_RESUME_TIME: false,
        SHOW_ON_PLAYER: false
    }

    for (let name in defaults) {
        let value = defaults[name];
        window[name] = (name === "BUTTON_TEMPLATES" ? JSON.parse(await GM.getValue("_YSC-" + name,value)) : await GM.getValue("_YSC-" + name,value));
    }

    async function toggleconfig(name,e){
        e = e||!(await GM.getValue("_YSC-" + name,defaults[name]));
        GM.setValue("_YSC-" + name,e);
        alert(name + ': ' + e);
    }

    if (typeof GM_registerMenuCommand == 'undefined') {
        this.GM_registerMenuCommand = (caption, commandFunc, accessKey) => {
            if (!document.body) {
                if (document.readyState === 'loading'
                    && document.documentElement && document.documentElement.localName === 'html') {
                    new MutationObserver((mutations, observer) => {
                        if (document.body) {
                            observer.disconnect();
                            GM_registerMenuCommand(caption, commandFunc, accessKey);
                        }
                    }).observe(document.documentElement, {childList: true});
                } else {
                    console.error('GM_registerMenuCommand got no body.');
                }
                return;
            }
            let contextMenu = document.body.getAttribute('contextmenu');
            let menu = (contextMenu ? document.querySelector('menu#' + contextMenu) : null);
            if (!menu) {
                menu = document.createElement('menu');
                menu.setAttribute('id', 'gm-registered-menu');
                menu.setAttribute('type', 'context');
                document.body.appendChild(menu);
                document.body.setAttribute('contextmenu', 'gm-registered-menu');
            }
            let menuItem = document.createElement('menuitem');
            menuItem.textContent = caption;
            menuItem.addEventListener('click', commandFunc, true);
            menu.appendChild(menuItem);
        };
    }


    $.each([
        ["List current settings", async function(){
            var set = [];
            for (let name in defaults) {
                let value = defaults[name];
                set.push(name + ' = ' + await GM.getValue('_YSC-' + name,value) + (( await GM.getValue('_YSC-' + name,value)!=defaults[name])?" [default is " + defaults[name] + "]":""));
            }
            alert(set.join('\n'));
        }],
        ["Configure default speed", async function(){
            var temp = prompt("Default: " + defaults['DEFAULT_SPEED'], await GM.getValue('_YSC-DEFAULT_SPEED',DEFAULT_SPEED));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-DEFAULT_SPEED');
                alert("default restored");
                return;
            }
            temp = parseFloat(temp);
            if (!isNaN(temp)) toggleconfig('DEFAULT_SPEED',temp);
        }],
        ["Show time relative to speed", async function(){
            var temp = prompt("Default: " + defaults['SHOW_RELATIVE_TIME'], await GM.getValue('_YSC-SHOW_RELATIVE_TIME',SHOW_RELATIVE_TIME));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-SHOW_RELATIVE_TIME');
                alert("default restored");
                return;
            }
            if (temp === "true" || temp === "false") toggleconfig('SHOW_RELATIVE_TIME', (temp === "true"));
        }],
        ["Configure Color for the selected speed", async function(){
            var temp = prompt("Default: " + defaults['COLOR_SELECTED'], await GM.getValue('_YSC-COLOR_SELECTED',COLOR_SELECTED));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-COLOR_SELECTED');
                alert("default restored");
                return;
            }
            toggleconfig('COLOR_SELECTED',temp);
        }],
        ["Configure color for unselected speed", async function(){
            var temp = prompt("Default: " + defaults['COLOR_NORMAL'], await GM.getValue('_YSC-COLOR_NORMAL',COLOR_NORMAL));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-COLOR_NORMAL');
                alert("default restored");
                return;
            }
            toggleconfig('COLOR_NORMAL',temp);
        }],
        ["Configure Buttons", async function(){
            var temp = prompt("What buttons should be displayed.\nformat: [text,speed]\neg: [half,0.5][normal,1][double,2]", '[' + JSON.parse(await GM.getValue('_YSC-BUTTON_TEMPLATES',JSON.stringify(BUTTON_TEMPLATES))).join('],[') + ']');
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-BUTTON_TEMPLATES');
                alert("default restored");
                return;
            }
            var match = temp.match(/\[[^,]+,[^\]]+\]/g);
            if (!match){
                alert("invalid option");
            } else {
                var array = [];
                for (let i=0; i < match.length; i++){
                    let match2 = match[i].match(/\[([^,]+),([^\]]+)/);
                    array.push([match2[1], parseFloat(match2[2])])
                }
                toggleconfig('BUTTON_TEMPLATES',JSON.stringify(array));
            }
        }],
        ["Configure audio boost", async function(){
            var temp = prompt("Can be any number bigger than 1.\n\n1    = function disabled\n1.5 = 50% boost\n2    = 100% boost\n\n\nDefault: " + defaults['AUDIO_BOOST'], await GM.getValue('_YSC-AUDIO_BOOST',AUDIO_BOOST));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-AUDIO_BOOST');
                alert("default restored");
                return;
            }
            temp = parseFloat(temp);
            if (!isNaN(temp) && temp >= 1){
                toggleconfig('AUDIO_BOOST',temp);
                window['AUDIO_BOOST'] = temp;
                $(temp === 1 ? 'video[vsb-audioboost]' : 'video').each(function(){
                    audioboost(this, true);
                });
            }
        }],
        ["Save resume time to url", async function(){
            var temp = prompt("Can be true or false\nIf true, it updates the url every 5 seconds with &t= parameter. So if you close the browser or tab and reopen it, the video will start playing close to where you stopped it.\n\nDefault: " + defaults['SAVE_RESUME_TIME'], await GM.getValue('_YSC-SAVE_RESUME_TIME',SAVE_RESUME_TIME));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-SAVE_RESUME_TIME');
                alert("default restored");
                return;
            }
            if (temp === "true" || temp === "false") toggleconfig('SAVE_RESUME_TIME', (temp === "true"));
        }],
        ["Show speed controls on top of the player instead of below the progress bar", async function(){
            var temp = prompt("Can be true or false\n\nDefault: " + defaults['SHOW_ON_PLAYER'], await GM.getValue('_YSC-SHOW_ON_PLAYER',SHOW_ON_PLAYER));
            if (temp === null) return;
            if (temp.length === 0){
                GM.deleteValue('_YSC-SHOW_ON_PLAYER');
                alert("default restored");
                return;
            }
            if (temp === "true" || temp === "false") toggleconfig('SHOW_ON_PLAYER', (temp === "true"));
        }],
        ["Restore default",function(){
            for (let name in defaults) {
                GM.deleteValue('_YSC-' + name);
            }
            alert("Default restored");
        }]
    ], function(a,b){ GM_registerMenuCommand(b[0],b[1]); });

    var stateKey, eventKey;
    {
        let keys = {
            hidden: "visibilitychange",
            webkitHidden: "webkitvisibilitychange",
            mozHidden: "mozvisibilitychange",
            msHidden: "msvisibilitychange"
        }
        for (stateKey in keys) {
            if (stateKey in document) {
                eventKey = keys[stateKey];
                break;
            }
        }
    }

    function vis (c) {
        if (c) document.addEventListener(eventKey, c);
        return !document[stateKey];
    }

    function getspeed(params = {}){
        let speed, reason;
        if (params.hasOwnProperty('force1x') && params.force1x){
            speed = 1;
            reason = "forcing 1x (live?)";
        } else if (params.hasOwnProperty('channelspeed') && typeof params.channelspeed === "number"){
            speed = params.channelspeed;
            reason = "channelspeed";
        } else if (params.hasOwnProperty('defspeed') && Number.isInteger(params.defspeed)){
            speed = params.defspeed;
            reason = "overwritten default (music?)";
        } else {
            speed = DEFAULT_SPEED;
            reason = "default";
        }
        if (params.channelspeed === undefined) delete params.channelspeed;
        if (params.defspeed === null) delete params.defspeed;
        if (params.force1x === false) delete params.force1x;
        params['chosenspeed'] = speed;
        params['chosenreason'] = reason;
        console.log(params);
        return speed;
    }

    function buttonclick(evt){
        let id = evt.target.parentNode.id.match(/\d+$/)[0],
            el = $(evt.target);
        el.parent().children(":not([title])").css("color", COLOR_NORMAL);
        el.css("color", COLOR_SELECTED);
        if ($('video[vsb-video="' + id + '"]').length === 0){
            youtubefix();
        }
        try {
            let video = $('video[vsb-video=' + id + ']')[0];
            video.playbackRate = parseFloat(el.attr('speed'));
            if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) changetime(video);
        } catch (err){
            console.log('error on buttonclick()', evt, err);
            setTimeout(function(){ buttonclick(evt); }, 1000);
        }
    }

    function getchannelname (id, div = null){
        try {
            if (div === null) div = $('#channel-name[vsb-channel=' + id + ']');
            let channel = div.find('#container #text:visible:first').text().trim();
            if (!channel){
                channel = div.find('.ytd-channel-name').find('a').text().trim();
            }
            if (!channel){
                let metavid = document.querySelector('#watch7-content > meta[itemprop=videoId]');
                if (metavid !== null && (new URL(div.closest('ytd-watch-flexy, ytd-browse').find('video')[0].baseURI)).searchParams.get('v') === metavid.getAttribute('content')){
                    let metaname = document.querySelector('#watch7-content > span[itemprop=author] > link[itemprop=name]');
                    if (metaname !== null) channel = metaname.getAttribute('content');
                    if (!channel) channel = '';
                }
            }
            return channel;
        } catch (e) {
            console.log("error", e);
            return '';
        }
    }

    function setchanneldefault(el){
        let id = el.target.parentNode.id.match(/\d+$/)[0];
        let channel = getchannelname(id);
        changebuttontitle(id, channel);
        let currentspeed = $('video[vsb-video=' + id + ']')[0].playbackRate;
        el = $(el.target).parent();
        el.children().css("text-decoration", "").filter('span[speed="' + currentspeed + '"]').css("text-decoration", "underline");
        GM.setValue(channel, currentspeed);
        console.log('changing default for (' + channel + ') to (' + currentspeed + ')');
    }

    function createcontainer(curspeed, id){
        let div = document.createElement("div");
        let prev_node = null;

        div.id = "vsb-container" + id;
        div.style.marginBottom = "0px";
        div.style.paddingBottom = "0px";
        div.style.float = "left";
        div.style.fontWeight = "bold";
        div.style.fontSize = "80%";

        div.innerHTML += '<span style="margin-right: 10px; color: white; cursor: pointer;" title="Set current speed as default for this channel">setdefault</span>';
        BUTTON_TEMPLATES.forEach(function(button){
            div.innerHTML += '<span style="margin-right: 10px; color: ' + (curspeed === button[1] ? COLOR_SELECTED : COLOR_NORMAL) + '; cursor: pointer;" speed="' + button[1] + '">' + button[0] + '</span>';
        });

        $('span:not([title])', div).on( "click", buttonclick);
        $('span[title]', div).on( "click", setchanneldefault);

        return div;
    }

    window.vsbid = 0;

    function getid(){
        let id = window.vsbid;
        window.vsbid++;
        return id;
    }

    function changebuttontitle(id, channelname = ''){
        let container = $('#vsb-container' + id + ' > span[title]');
        if (container.length > 0){
            container[0].title = container[0].title.split(' [')[0] + (channelname !== '' ? ' [' + channelname + ']' : '');
        }
    }

    function ob_youtube_movieplayer (mutationsList, observer){
        for(let mutation of mutationsList) {
            if (mutation.attributeName === 'video-id'){
                let el = $('[id^=vsb-container]', mutation.target);
                if (el.length === 0){
                    alert('fixing');
                    console.log('fixing this');
                    youtube();
                    el = $('[id^=vsb-container]', mutation.target);
                }
                let id = el[0].id.match(/\d+$/)[0];
                let channeldiv = $('#channel-name[vsb-channel="' + id + '"]');
                if($('span[speed="1"]', el).click().length === 0) $('video[vsb-video="' + id + '"]')[0].playbackRate = 1;
                $('span', el).css("text-decoration", "");
                changebuttontitle(id);
                setTimeout(async function(){
                    let channelspeed, channelname = getchannelname(id, channeldiv);
                    let tries = 1;
                    while(channelname === '' && tries <= 8){
                        if (tries === 1){
                            alert('sleeping');
                            console.log("id", id);
                            console.log("channeldiv", channeldiv);
                            console.log("channelname", channelname);
                        }
                        console.log('sleeping ' + tries, channeldiv);
                        await (new Promise(resolve => setTimeout(resolve, 200)));
                        channelname = getchannelname(id, channeldiv);
                        tries++;
                    }
                    if (channelname !== ''){
                        channelspeed = await GM.getValue(channelname);
                    } else {
                        channelspeed = undefined;
                    }
                    changebuttontitle(id, channelname);
                    $('span[speed="' + channelspeed + '"]', el).css("text-decoration", "underline");

                    let speed = getspeed({
                        channelname: channelname,
                        channelspeed: channelspeed,
                        defspeed: (channeldiv.find('.badge-style-type-verified-artist').length > 0 || channelname.match(/VEVO$/) ? 1 : null),
                        force1x: (el.closest('#movie_player').find('.ytp-live').length === 1)
                    });
                    if($('span[speed="' + speed + '"]', el).click().length === 0) $('video[vsb-video="' + id + '"]')[0].playbackRate = speed;
                },500);
            }
        }
    }

    function ob_youtube_c4player (mutationsList, observer){
        for(let mutation of mutationsList) {
            if (mutation.attributeName === 'src' && mutation.target.src !== ''){
                let id = mutation.target.getAttribute("vsb-video");
                let channeldiv = $('#channel-name[vsb-channel="' + id + '"]');
                $('video[vsb-video="' + id + '"]')[0].playbackRate = 1;
                setTimeout(async function(){
                    $('video[vsb-video="' + id + '"]')[0].playbackRate = getspeed({
                        channelname: getchannelname(id, channeldiv),
                        channelspeed: await GM.getValue(getchannelname(id, channeldiv)),
                        defspeed: (channeldiv.find('.badge-style-type-verified-artist').length === 1 ? 1 : null)
                    });
                },1000);
            }
        }
    }

    function youtubefix(){
        $('#movie_player[monitored], #c4-player[monitored]').each(
            function(){
                let video = $('video', this);
                if (video.attr('vsb-video') === undefined){
                    let el = $(this);
                    let id = el.attr('monitored');
                    console.log('fixing', video);
                    setTimeout(function(){
                        audioboost(video, true);
                        video.attr('vsb-video', id);
                        youtubefix2('#vsb-container' + id);
                        if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) ['timeupdate','seeked', 'pause'].forEach( function(evt) { video[0].addEventListener(evt, changetime,false) });
                    },750);
                } else {
                    console.log('fixing2', video);
                    youtubefix2('#vsb-container' + $(this).attr('monitored'));
                }
            }
        );
    }

    function youtubefix2(el, log = false){
        try {
            $('span:not([title]):visible', el).filter(function() {
                return ( this.style.color == COLOR_SELECTED );
            }).click();
            if (log) console.log('fixing3', video);
        } catch {
            if (log) console.log('fixing3 failed', video);
        }
    }

    function fancyTimeFormat(duration){
        var hrs = ~~(duration / 3600);
        var mins = ~~((duration % 3600) / 60);
        var secs = ~~duration % 60;

        var ret = "";

        if (hrs > 0) {
            ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
        }

        ret += "" + mins + ":" + (secs < 10 ? "0" : "");
        ret += "" + secs;
        return ret;
    }

    function changetime (event){
        let video = (typeof event.target === "object" ? event.target : event);
        if (SHOW_RELATIVE_TIME){
            let id = video.getAttribute('vsb-video');
            let timediv = $('#movie_player[monitored="' + id + '"]:visible .ytp-time-display:visible');
            if (timediv.length === 0) return;
            let reltimespan = timediv[0].getElementsByClassName('vsb-reltime');
            if (reltimespan.length === 0){
                timediv[0].insertAdjacentHTML('beforeend', '<span class="vsb-reltime"></span>');
                reltimespan = timediv[0].getElementsByClassName('vsb-reltime');
            }
            reltimespan[0].innerHTML = (video.playbackRate === 1 || isNaN(video.duration) ? '' : '<span> (</span>' + fancyTimeFormat(video.currentTime / video.playbackRate) + ' / ' + fancyTimeFormat(video.duration / video.playbackRate) + '<span>)</span>');
        }
        if (SAVE_RESUME_TIME){
            const time = Math.floor(video.currentTime),
                  url = new URL(location);
            if (url.pathname === "/watch" && time >= 10){
                if (typeof event.target !== "object" || event.type !== "timeupdate" || Number.isInteger(time/5)){
                    url.searchParams.set('t', (time - 5) + 's');
                    history.replaceState({}, document.title, url.toString())
                }
            } else if (url.searchParams.has('t')){
                url.searchParams.delete('t');
                history.replaceState({}, document.title, url.toString())
            }
        }
    }

    function audioboost(el = null, force = false){
        if (el === null || typeof el !== 'object') el = this;
        if (el.tagName !== "VIDEO") return;
        if (el.getAttribute('vsb-audioboost') === null){
            el.setAttribute('vsb-audioboost', getid());
        } else if (!force){
            return;
        }
        const audioCtx = "YTSBC_audioCtx_" + el.getAttribute('vsb-audioboost');
        try { window[audioCtx].close(); } catch {}
        if (AUDIO_BOOST === 1 && !force) return;
        window[audioCtx] = new AudioContext();
        const source = window[audioCtx].createMediaElementSource(el),
            gainNode = window[audioCtx].createGain();
        gainNode.gain.value = AUDIO_BOOST;
        source.connect(gainNode);
        if (AUDIO_BOOST > 1){
            const limiterNode = window[audioCtx].createDynamicsCompressor();
            limiterNode.threshold.value = -5.0;
            limiterNode.knee.value = 0;
            limiterNode.ratio.value = 40.0;
            limiterNode.attack.value = 0.001;
            limiterNode.release.value = 0.1;

            limiterNode.connect(window[audioCtx].destination);
            gainNode.connect(limiterNode);
        } else {
            gainNode.connect(window[audioCtx].destination);
        }
    }

    function observevideo(el){
        const observer = new MutationObserver((mutationsList, observer) => {
            for(const mutation of mutationsList) {
                if (mutation.target.src === ''){
                    youtubefix();
                } else {
                    const id = mutation.target.getAttribute('vsb-video');
                    youtubefix2('#vsb-container' + id);
                }
            }
        });
        observer.observe(el, {attributes: true, attributeFilter: ['src']});
    }

    function youtube(){
        $('#movie_player:visible:not([monitored]), #c4-player:visible:not([monitored])').each(async function( index ) {
            let el = $(this);
            let speed, channelspeed;
            if (this.id === "movie_player" && !this.classList.contains('ytp-player-minimized')){
                let channeldiv = el.closest('ytd-watch-flexy').find('#upload-info #channel-name');
                if (!channeldiv.length) channeldiv = $('ytd-watch-metadata #upload-info #channel-name');
                if (!channeldiv.length) return;
                let channelname = getchannelname(-1, channeldiv);
                if (channelname === '') return;
                let appendto = (SHOW_ON_PLAYER ? el.find("div.ytp-iv-video-content") : el.find("div.ytp-right-controls"));
                if (!appendto.length) return;
                let videodiv = el.find('video')
                if (!videodiv.length) return;

                let id = getid();
                el.attr('monitored', id);
                channeldiv.attr('vsb-channel', id);
                videodiv.attr('vsb-video', id);
                videodiv.each(audioboost);

                $('#ytp-id-20 .ytp-menuitem-label:contains(Playback speed)', el).parent().css('display', 'none');

                console.log("Adding video-id observer");
                let el2 = el.closest('ytd-watch-flexy');
                if (!el2.length) el2 = $('ytd-watch-flexy:visible');
                (new MutationObserver(ob_youtube_movieplayer)).observe(el2[0], { attributes: true });

                el2 = el.find('video');
                if (!el2.length) el2 = $('video:visible');
                observevideo(el2[0]);

                channelspeed = await GM.getValue(channelname);

                speed = getspeed({
                    channelname: channelname,
                    channelspeed: channelspeed,
                    defspeed: (channeldiv.find('.badge-style-type-verified-artist').length > 0 || channelname.match(/VEVO$/) ? 1 : null),
                    force1x: (el.find('.ytp-live').length === 1)
                });
                let div = createcontainer(speed, id);
                $('span[speed="' + channelspeed + '"]', div).css("text-decoration", "underline");
                if (SHOW_ON_PLAYER){
                    div.style.position = "absolute";
                    div.style.zIndex = 10;
                    div.style.textShadow = "-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000";
                }
                appendto.append(div);
                changebuttontitle(id, channelname);
                videodiv[0].playbackRate = speed;
                if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) ['timeupdate','seeked', 'pause'].forEach( function(evt) { videodiv[0].addEventListener(evt, changetime,false) });
            } else if (this.id === "c4-player"){
                let channeldiv = el.closest('ytd-browse').find('#header #channel-name');
                if (!channeldiv.length) return;
                let channelname = getchannelname(-1, channeldiv);
                if (channelname === '') return;
                let videodiv = el.find('video')
                if (!videodiv.length) return;

                el.attr('monitored', id);
                let id = getid();
                channeldiv.attr('vsb-channel', id);
                videodiv.attr('vsb-video', id);
                videodiv.each(audioboost);

                console.log("Adding c4 observer");
                (new MutationObserver(ob_youtube_c4player)).observe(el.find('video')[0], { attributes: true, subtree: true });

                videodiv[0].playbackRate = getspeed({
                    channelname: channelname,
                    channelspeed: await GM.getValue(channelname),
                    defspeed: (channeldiv.find('.badge-style-type-verified-artist').length === 1 ? 1 : null)
                });
            }
        });
        if (AUDIO_BOOST !== 1){
            $('video:not([vsb-audioboost])').each(audioboost);
        }
    }

    function mark_loop(){
        if (location.host.endsWith('youtube.com')){
            youtube();
            let test = document.querySelectorAll('[monitored]');
            if (test.length < 2){
                setTimeout(mark_loop, ((test.length === 0 || test[0].id !== "movie_player" ? 250 : 2000) * (vis() ? 1 : 2)));
            } else {
                console.log('stopping loop');
            }
        } else {
            setTimeout(mark_loop, 1500 * (vis() ? 1 : 4));
        }
    }
    if (AUDIO_BOOST !== 1){
        mark_loop();
    } else {
        window.addEventListener('load', mark_loop);
    }
})();