xtrars / burning series enhancer

// ==UserScript==
// @icon           https://bs.to/favicon.ico
// @name           burning series enhancer
// @name:en        burning series enhancer
// @author         xtrars
// @description    Wechselt automatisch zum Vivo-Tab auf burning series enhancer und öffnet Vivo oder Vupload. Das Tool startet das nächste Video und falls nötig die nächste Staffel, wenn eine Episode beendet wurde. Burning series enhancer bietet eine Downloadfunktion.
// @description:en Automatically switches to the Vivo tab on burning series enhancer and opens Vivo or Vupload. The tool starts the next video and if necessary the next season when an episode is finished. Burning series enhancer provides a download function.
// @version        5.5
// @run-at         document-body
// @license        GPL-3.0-or-later
// @namespace      https://greasyfork.org/users/140785
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_addValueChangeListener
// @grant          GM_removeValueChangeListener
// @grant          GM_download
// @grant          GM_info
// @grant          window.close
// @grant          window.focus

// @include        https://bs.to/*
// @include        https://burningseries.co/*
// @include        https://burningseries.sx/*
// @include        https://burningseries.vc/*
// @include        https://burningseries.ac/*
// @include        https://burningseries.cx/*
// @include        https://burningseries.nz/*
// @include        https://burningseries.se/*
// @include        https://vivo.sx/*
// @include        https://vivo.st/*
// @include        https://vupload.com/*

// @include        https://*.vivo.sx/*
// @include        https://*.vivo.st/*
// @include        https://*.megaupload.to/*
// ==/UserScript==

class BaseHandler {
    // thanks to xZaheer (https://greasyfork.org/de/scripts/400669-burningseries-autoplay/code)
    waitForElement(sSelector) {
        return new Promise(async resolve => {
            if (document.querySelector(sSelector)) {
                return resolve(document.querySelector(sSelector));

            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(sSelector)) {
                    resolve(document.querySelector(sSelector));
                    observer.disconnect();
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true,
            });
            clearInterval(waitForElementInterval);
        });
    }

    hasUrl(aSelector) {
        let isAvailable = true;
        for (let selector of aSelector) {
            isAvailable = document['location']['href'].search(selector) !== -1;
            if (!isAvailable) {
                return false;
            }
        }
        return true;
    }

    reload(iDelay = 300) {
        setTimeout(() => {
            window.location.reload();
        }, iDelay);
    }
}

class BurningSeriesHandler extends BaseHandler {
    initGMVariables() {
        if (typeof GM_getValue('bActivateEnhancer') === "undefined") {
            GM_setValue('bActivateEnhancer', false);
        }
        if (typeof GM_getValue('bAutoplayNextEpisode') === "undefined") {
            GM_setValue('bAutoplayNextEpisode', true);
        }
        if (typeof GM_getValue('bAutoplayVideo') === "undefined") {
            GM_setValue('bAutoplayVideo', true);
        }
        if (typeof GM_getValue('bAutoplayNextSeason') === "undefined") {
            GM_setValue('bAutoplayNextSeason', true);
        }
        if (typeof GM_getValue('bAutoplayRandomEpisode') === "undefined") {
            GM_setValue('bAutoplayRandomEpisode', false);
        }
        if (typeof GM_getValue('bDownloadVideo') === "undefined") {
            GM_setValue('bDownloadVideo', false);
        }
        if (typeof GM_getValue('bExtraSettings') === "undefined") {
            GM_setValue('bExtraSettings', false);
        }
        if (typeof GM_getValue('bDownloadType') === "undefined") {
            GM_setValue('bDownloadType', true);
        }
    }

    hasAnotherHoster() {
        return this.hasUrl([/^https:\/\/(bs.to|burningseries.[a-z]{2,3})\/.*[0-9]{1,2}\/[0-9]{1,2}\-.*\/[a-z]*\/(?!Vivo|Vupload).*/g]);
    }

    isEpisode() {
        return this.hasUrl([/^https:\/\/(bs.to|burningseries.[a-z]{2,3})/g, /[0-9]{1,2}\/[0-9]{1,2}\-/g]);
    }

    async clickPlay() {
        let playerElem = await this.waitForElement('section.serie .hoster-player').catch(() => {});
        let iNumberOfClicks = 0;
        let clickInterval = setInterval(() => {
            if(playerElem) {
                if (document.querySelector('section.serie .hoster-player > a') ||
                    document.querySelector('iframe[title="recaptcha challenge"]') ||
                    iNumberOfClicks > 120) {
                    clearInterval(clickInterval);
                }
                iNumberOfClicks++;
                let clickEvent = new Event('click');
                clickEvent.which = 1;
                clickEvent.pageX = 6;
                clickEvent.pageY = 1;
                playerElem.dispatchEvent(clickEvent);
            }

        }, 500);
    }

    playNextEpisodeIfVideoEnded(bSetEvent = true) {
        if(!bSetEvent) {
            GM_removeValueChangeListener('isLocalVideoEnded');
            GM_setValue('isLocalVideoEnded', false);
            return;
        }
        GM_addValueChangeListener('isLocalVideoEnded', () => {
            if (GM_getValue('isLocalVideoEnded')) {
                GM_setValue('isLocalVideoEnded', false);
                window.focus();
                if (GM_getValue('bAutoplayRandomEpisode')) {
                    let oRandomEpisode = document.querySelector('#sp_right > a');
                    document['location'].replace(oRandomEpisode.href);
                } else {
                    let oNextEpisode = document.querySelector('.serie .frame ul li[class^="e"].active ~ li:not(.disabled) a');
                    if (oNextEpisode) {
                        document['location'].replace(oNextEpisode.href);
                    } else if (GM_getValue('bAutoplayNextSeason')) {
                        let oNextSeason = document.querySelector('.serie .frame ul li[class^="s"].active ~ li:not(.disabled) a');
                        if (oNextSeason) {
                            GM_setValue('clickFirstSeason', true);
                            document['location'].replace(oNextSeason);
                        }
                    }
                }
            }
        });
    }

    async buildButton() {
        const style = document.createElement('style');
        style.innerHTML = `
  :root {
  --inner-pl: 14px;
  --inner-bc-before: #2FB536;
  --inner-bc-after: #12A6F6;
  --color: white;
}

  @keyframes shake {
  10%, 90% {transform: translate3d(-.5px, 0, 0);}
  20%, 80% {transform: translate3d(1px, 0, 0);}
  30%, 50%, 70% {transform: translate3d(-2px, 0, 0);}
  40%, 60% {transform: translate3d(2px, 0, 0);}
}
.onoffswitch {
    z-index: 161;
    position: relative; width: 350px;
    -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.onoffswitch-label {
    width: 350px;
    display: block;
    overflow: hidden;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: 20px;
}
.onoffswitch-inner {
    display: block; width: 200%; margin-left: -100%;
    transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
    display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
    font-size: 10px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
    box-sizing: border-box;
}
.onoffswitch-switch {
    display: block;
    width: 23px; margin: 3.5px;
    background: #FFFFFF;
    position: absolute;
    top: 0;
    bottom: 2px;
    right: 314px;
    border: 2px solid #999999;
    border-radius: 20px;
    transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
    margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
    right: 0px;
}

#xtrars-btn {
   position: absolute;
   bottom: 70px;
   right: 18px;
   background: #12a6f6;
   border-radius: 50%;
   width: 70px;
   height: 70px;
   line-height: 81px;
   text-align: center;
   cursor: pointer;
   animation: shake 1s ease 1s 1 normal;
}
#xtrars-btn:hover {
    cursor: auto;
}
#xtrars-menu {
   right: 4;
}
#xtrars-btn-icon {
   color: var(--color);
}
.onoffswitch-inner.autoplay:before {
    content: "Video-Autoplay aktiviert";
}
.onoffswitch-inner.autoplay:after {
    content: "Video-Autoplay deaktiviert";
}
.onoffswitch-inner.auto-next-episode:before {
    content: "Nächste Episode wird automatisch abgespielt";
}
.onoffswitch-inner.auto-next-episode:after {
    content: "Nächste Episode wird manuell abgespielt";
}
.onoffswitch-inner.enable-enhancer:before {
    content: "burning series enhancer aktiviert";
}
.onoffswitch-inner.enable-enhancer:after {
    content: "burning series enhancer deaktiviert";
}
.onoffswitch-inner.auto-next-season:before {
    content: "Nächste Staffel wird automatisch abgespielt";
}
.onoffswitch-inner.auto-next-season:after {
    content: "Nächste Staffel wird manuell abgespielt";
}
.onoffswitch-inner.auto-random-episode:before {
    content: "Nächste folgende Episode wird abgespielt";
}
.onoffswitch-inner.auto-random-episode:after {
    content: "Nächste Episode wird zufällig abgespielt";
}
.onoffswitch-inner.download:before {
    content: "Downloadfunktion aktiviert";
}
.onoffswitch-inner.download:after {
    content: "Downloadfunktion deaktiviert";
}
.onoffswitch-inner.extra-settings:before {
    content: "Erweiterte Einstellungen eingeblendet";
}
.onoffswitch-inner.extra-settings:after {
    content: "Erweiterte Einstellungen ausgeblendet";
}
.onoffswitch-inner.download-type:before {
    content: "Video wird heruntergeladen & Stream angeschaut";
}
.onoffswitch-inner.download-type:after {
    content: "Video wird heruntergeladen & Stream geschlossen";
}

.switch:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
}

.switch:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
}
.disabled:after {
    background-color: darkgrey !important;
}
.disabled:before {
    background-color: darkgrey !important;
}
.hidden {
    visibility: hidden !important;
}

  `;
        document.head.appendChild(style);

        const button = document.createElement("div");
        button['innerHTML'] = '<div id="xtrars-btn">' +
            '<i id="xtrars-btn-icon" class="fas fa-cogs fa-2x"></i>' +
            '</div>';
        button['style'] = 'position: relative; height: 0;'
        await this.waitForElement('.infos').catch(() => {});
        document.getElementsByClassName('infos')[0].appendChild(button);

        const menu = document.createElement("div");
        menu['innerHTML'] = '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner enable-enhancer switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-next-episode switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-random-episode switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner extra-settings switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-next-season switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner autoplay switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner download switch"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner download-type switch"></span><span class="onoffswitch-switch"></span></label></div>';

        menu['style'] = 'display: none;';
        menu['id'] = 'xtrars-menu';
        document.getElementById('xtrars-btn').appendChild(menu);

        this.initEvents();
    }

    initEvents() {
        let button = document.getElementById('xtrars-btn');
        let menu = document.getElementById('xtrars-menu');
        let activateEnhancer = document.querySelectorAll('.xtrars-onoffswitch + label')[0]; //enable-enhancer
        let autoplayNextEpisode = document.querySelectorAll('.xtrars-onoffswitch + label')[1]; //auto-next-episode
        let autoplayRandomEpisode = document.querySelectorAll('.xtrars-onoffswitch + label')[2]; //auto-random-episode
        let extraSettings = document.querySelectorAll('.xtrars-onoffswitch + label')[3]; //extra-settings
        let autoplayNextSeason = document.querySelectorAll('.xtrars-onoffswitch + label')[4]; //auto-next-season
        let autoplayVideo = document.querySelectorAll('.xtrars-onoffswitch + label')[5]; //autoplay
        let downloadVideo = document.querySelectorAll('.xtrars-onoffswitch + label')[6]; //download
        let downloadType = document.querySelectorAll('.xtrars-onoffswitch + label')[7]; //download-type

        activateEnhancer.previousSibling.checked = GM_getValue('bActivateEnhancer');
        autoplayNextEpisode.previousSibling.checked = GM_getValue('bAutoplayNextEpisode');
        autoplayRandomEpisode.previousSibling.checked = !GM_getValue('bAutoplayRandomEpisode');
        extraSettings.previousSibling.checked = GM_getValue('bExtraSettings');
        autoplayNextSeason.previousSibling.checked = GM_getValue('bAutoplayNextSeason');
        autoplayVideo.previousSibling.checked = GM_getValue('bAutoplayVideo');
        downloadVideo.previousSibling.checked = GM_getValue('bDownloadVideo');
        downloadType.previousSibling.checked = GM_getValue('bDownloadType');


        button.addEventListener('mouseover', () => {
            menu.style = 'display: block; position: absolute; top: 0; line-height: normal;'
            if (extraSettings.previousSibling.checked) {
                document.getElementById('xtrars-btn').style = 'background: transparent; border-radius: 0; width: 355px; right: -130px; height: 300px; bottom: -50px';
            } else {
                document.getElementById('xtrars-btn').style = 'background: transparent; border-radius: 0; width: 355px; right: -130px; height: 150px; bottom: 100px';
            }
            document.getElementById('xtrars-btn-icon').style = 'color: transparent;';
        });

        button.addEventListener('mouseleave', () => {
            menu.style = 'display: none;';
            document.getElementById('xtrars-btn').style = '';
            document.getElementById('xtrars-btn-icon').style = '';
        });

        let oManagedButtons = {
            'activateEnhancer': activateEnhancer,
            'autoplayNextEpisode': autoplayNextEpisode,
            'autoplayRandomEpisode': autoplayRandomEpisode,
            'autoplayNextSeason': autoplayNextSeason,
            'autoplayVideo': autoplayVideo,
            'downloadVideo': downloadVideo,
            'extraSettings': extraSettings,
            'downloadType': downloadType,
        };


        this.manageButtonState(oManagedButtons);

        activateEnhancer.addEventListener('click', () => {
            activateEnhancer.previousSibling.checked = !activateEnhancer.previousSibling.checked;
            GM_setValue('bActivateEnhancer', activateEnhancer.previousSibling.checked);
            this.manageButtonState(oManagedButtons);
            this.reload();
        });


        autoplayNextEpisode.addEventListener('click', () => {
            if (!autoplayNextEpisode.childNodes[0].classList.contains('disabled')) {
                autoplayNextEpisode.previousSibling.checked = !autoplayNextEpisode.previousSibling.checked;
                GM_setValue('bAutoplayNextEpisode', autoplayNextEpisode.previousSibling.checked);
                this.manageButtonState(oManagedButtons);
            }
        });

        autoplayRandomEpisode.addEventListener('click', () => {
            if (!autoplayRandomEpisode.childNodes[0].classList.contains('disabled')) {
                autoplayRandomEpisode.previousSibling.checked = !autoplayRandomEpisode.previousSibling.checked;
                GM_setValue('bAutoplayRandomEpisode', !autoplayRandomEpisode.previousSibling.checked);
                this.manageButtonState(oManagedButtons);
            }
        });


        autoplayNextSeason.addEventListener('click', () => {
            if (!autoplayNextSeason.childNodes[0].classList.contains('disabled')) {
                autoplayNextSeason.previousSibling.checked = !autoplayNextSeason.previousSibling.checked;
                GM_setValue('bAutoplayNextSeason', autoplayNextSeason.previousSibling.checked);
            }
        });

        autoplayVideo.addEventListener('click', () => {
            if (!autoplayVideo.childNodes[0].classList.contains('disabled')) {
                autoplayVideo.previousSibling.checked = !autoplayVideo.previousSibling.checked;
                GM_setValue('bAutoplayVideo', autoplayVideo.previousSibling.checked);
            }
        });

        downloadVideo.addEventListener('click', () => {
            if (!downloadVideo.childNodes[0].classList.contains('disabled')) {
                downloadVideo.previousSibling.checked = !downloadVideo.previousSibling.checked;
                GM_setValue('bDownloadVideo', downloadVideo.previousSibling.checked);
                this.manageButtonState(oManagedButtons);

                if(GM_info['downloadMode'] !== 'browser' && downloadVideo.previousSibling.checked) {
                    // TODO Anleitung anzeigen, wie Downloadmodus aktiviert wird
                    alert('Downloadfunktion nicht im Userscript-Manager aktiviert!');
                }
            }
        });

        extraSettings.addEventListener('click', () => {
            if (!extraSettings.childNodes[0].classList.contains('disabled')) {
                extraSettings.previousSibling.checked = !extraSettings.previousSibling.checked;
                GM_setValue('bExtraSettings', extraSettings.previousSibling.checked);
                this.manageButtonState(oManagedButtons);
            }
        });
        downloadType.addEventListener('click', () => {
            if (!downloadType.childNodes[0].classList.contains('disabled')) {
                downloadType.previousSibling.checked = !downloadType.previousSibling.checked;
                GM_setValue('bDownloadType', downloadType.previousSibling.checked);
                this.manageButtonState(oManagedButtons);
            }
        });
    }

    manageButtonState(oManagedButtons) {
        let activateEnhancer = oManagedButtons['activateEnhancer'];
        let autoplayNextEpisode = oManagedButtons['autoplayNextEpisode'];
        let autoplayRandomEpisode = oManagedButtons['autoplayRandomEpisode'];
        let autoplayNextSeason = oManagedButtons['autoplayNextSeason'];
        let autoplayVideo = oManagedButtons['autoplayVideo'];
        let downloadVideo = oManagedButtons['downloadVideo'];
        let extraSettings = oManagedButtons['extraSettings'];
        let downloadType = oManagedButtons['downloadType'];

        if (!activateEnhancer.previousSibling.checked) {
            this.disableButton(autoplayVideo);
            this.disableButton(autoplayNextEpisode);
            this.disableButton(autoplayNextSeason);
            this.disableButton(autoplayRandomEpisode);
            this.disableButton(downloadVideo);
            this.disableButton(extraSettings);
            this.disableButton(downloadType);
        } else {
            if (extraSettings.previousSibling.checked) {
                this.showButton(autoplayNextSeason);
                this.showButton(autoplayVideo);
                this.showButton(downloadVideo);
                this.showButton(downloadType);
            } else {
                this.hideButton(autoplayNextSeason);
                this.hideButton(autoplayVideo);
                this.hideButton(downloadVideo);
                this.hideButton(downloadType);
            }

            this.enableButton(autoplayNextEpisode);
            this.enableButton(downloadVideo);

            if (autoplayNextEpisode.previousSibling.checked) {
                this.enableButton(autoplayRandomEpisode);
                this.disableButton(autoplayVideo);

                if(!autoplayRandomEpisode.previousSibling.checked) {
                    this.disableButton(autoplayNextSeason);
                } else {
                    this.enableButton(autoplayNextSeason);
                }
            } else {
                this.disableButton(autoplayRandomEpisode);
                this.disableButton(autoplayNextSeason);
                this.enableButton(autoplayVideo);
            }

            if (downloadVideo.previousSibling.checked) {
                this.enableButton(downloadType);
            } else {
                this.disableButton(downloadType);
            }
        }
    }

    hideButton(oElement) {
        oElement.parentElement.style.visibility = 'hidden';
    }

    showButton(oElement) {
        oElement.parentElement.style.visibility = 'visible';
    }

    disableButton(oElement) {
        if (oElement.childNodes[0].classList.contains('auto-next-episode')) {
            this.playNextEpisodeIfVideoEnded(false);
        }
        oElement.childNodes[0].classList.add('disabled');
    }

    enableButton(oElement) {
        oElement.childNodes[0].classList.remove('disabled');
    }
}

class StreamingHandler extends BaseHandler {
    applyShortcuts(oVideoElement) {
        document.addEventListener('keydown', function(e) {
            if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
                switch(e.keyCode){
                    case 37: // Left
                        e.preventDefault();
                        oVideoElement.currentTime -= 10;
                        break;
                    case 38: // Up
                        e.preventDefault();
                        if (oVideoElement.volume < 0.9) {
                            oVideoElement.volume += 0.1;
                        } else {
                            oVideoElement.volume = 1;
                        }
                        break;
                    case 39: // Right
                        e.preventDefault();
                        oVideoElement.currentTime += 10;
                        break;
                    case 40: // Down
                        e.preventDefault();
                        if (oVideoElement.volume > 0.1) {
                            oVideoElement.volume -= 0.1;
                        } else {
                            oVideoElement.volume = 0;
                        }
                        break;
                    case 70: // F
                        if (!document.fullscreenElement) {
                            oVideoElement.requestFullscreen().catch(() => {
                                video.style.width = "100%";
                                video.style.height = "100%";
                                document.body.style.margin = "0px";
                            });
                        } else {
                            document.exitFullscreen();
                        }
                        break;
                    case 75: // K
                        if(oVideoElement.paused) {
                            oVideoElement.play();
                        }
                        else {
                            oVideoElement.pause();
                        }
                        break;
                }
            }
        }, true);
    }

    async handleStreamBehavior(sSrc) {
        if (GM_getValue('bDownloadVideo') && GM_info['downloadMode'] === 'browser') {
            GM_download(sSrc, GM_getValue('sEpisodeName') + '.mp4');
            if (!GM_getValue('bDownloadType')) {
                setTimeout(() => {
                    GM_setValue('isLocalVideoEnded', true);
                    window.close();
                }, 3000);
            }
        } else if ((GM_getValue('bAutoplayVideo') || GM_getValue('bAutoplayNextEpisode')) && (!GM_getValue('bDownloadVideo') || GM_getValue('bDownloadType'))) {
            window['location'].replace(sSrc);
        }
    }


    async isStreamingHoster(regex, selector) {
        return this.hasUrl([regex]) && await this.waitForElement(selector).catch(() => {});
    }

    async handleStreamingHoser(aHoster) {
        for (let oHoster of aHoster) {
            if (await this.isStreamingHoster(oHoster['regex'], oHoster['selector'])) {
                let video = await this.waitForElement(oHoster['selector'] + oHoster['detailSelector']).catch(() => {});
                this.handleStreamBehavior(video['src']);
            }
        }
        this.handleLocalVideo();
    }

    async handleLocalVideo() {
        let video;
        if (video = await this.waitForElement('html > head + body > video').catch(() => {})) {
            GM_setValue('isLocalVideoEnded', false);
            video.style.width = "100%";
            video.style.height = "100%";
            document.body.style.margin = "0px";
            video.requestFullscreen().catch(() => {});
            this.applyShortcuts(video);
            video.onended = () => {
                GM_setValue('isLocalVideoEnded', true);
                window.close();
            }
        }
    }
}

(async function() {
    'use strict';

    let bsHandler = new BurningSeriesHandler();
    let streamingHandler = new StreamingHandler();

    if (GM_getValue('clickFirstSeason')) {
        GM_setValue('clickFirstSeason', false);
        let sSelector = '.serie > .episodes > tbody > tr:first-child > td:first-child > a:first-child';
        await bsHandler.waitForElement(sSelector);
        document['location'].replace(document.querySelector(sSelector));
    }

    bsHandler.initGMVariables();

    if (bsHandler.isEpisode()) {
        if (GM_getValue('bActivateEnhancer') && !bsHandler.hasAnotherHoster() && !bsHandler.hasUrl(['/Vivo']) && !bsHandler.hasUrl(['/Vupload'])) {
            document['location'].replace(document['location']['href'] + '/Vivo');
        }
        await bsHandler.buildButton();

        if (GM_getValue('bActivateEnhancer') && !bsHandler.hasAnotherHoster() && (bsHandler.hasUrl(['/Vivo']) || bsHandler.hasUrl(['/Vupload']))) {
            if (GM_getValue('bAutoplayNextEpisode')) {
                bsHandler.playNextEpisodeIfVideoEnded();
            }
            let oName = await bsHandler.waitForElement('.episode > h2');
            GM_setValue('sEpisodeName', oName['outerText']);
            bsHandler.clickPlay();
        }
    }

    if (GM_getValue('bActivateEnhancer')) {
        // add new hoster here
        let aHoster = [
            {
                selector: 'video:not(#player)',
                regex: /^https:\/\/vivo.[a-z]{2,3}\//g,
                detailSelector: ' > source'
            },
            {
                selector: '#vjsplayer_html5_api',
                regex: /^https:\/\/vupload.[a-z]{2,3}\//g,
                detailSelector: '[src]',
            },
        ];
        streamingHandler.handleStreamingHoser(aHoster);
    }
})();