Greasy Fork is available in English.

Skip Hulu Ads and Outros

Skip ads on Hulu and skip outros

// ==UserScript==
// @name         Skip Hulu Ads and Outros
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Skip ads on Hulu and skip outros
// @author       reagent
// @match        https://www.hulu.com/*
// @noframes
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    class DomWatch{
        constructor(){
            this.awaiting = [];
            this.watching = false;
            this.obs = new MutationObserver(this.onMutations.bind(this));
        }
        watch(){
            this.watching = true;
            this.obs.observe(document, {subtree:true, childList: true});
        }
        on(selector, ...fns){
            const found = document.querySelector(selector);
            if(found){
                fns.forEach(fn => fn(found));
                return this;
            }

            const i = this.awaiting.findIndex(item => item.selector === selector);
            if(i === -1){
                this.awaiting.push({selector, fns});
            }else{
                this.awaiting[i].fns = this.awaiting[i].fns.concat(fns);
            }
            if(!this.watching){
                this.watch();
            }

            return this;

        }
        off(){
            this.watching = false;
            this.obs.disconnect();
        }
        onMutations(muts) {
            for(const mut of muts){
                const item = this.awaiting.findIndex(({selector}) => mut.target.matches(selector));
                if(item !== -1){
                    this.awaiting[item].fns.forEach(fn => fn(mut.target));
                    this.awaiting.splice(item, 1);
                }else{
                    let queried = null;
                    const item = this.awaiting.findIndex(({selector}) => queried = mut.target.querySelector(selector));

                    if(item !== -1){
                        this.awaiting[item].fns.forEach(fn => fn(queried));
                        this.awaiting.splice(item, 1);
                    }
                }
                if(this.watching && !this.awaiting.length){
                    this.off();
                }
            }
        }
    }
    const watch = new DomWatch();
    watch.on("div.player-mask", mask => {
        const hideStyle = document.createElement("style");
        hideStyle.appendChild(document.createTextNode(".force-hide {display: none !important}"));
        document.head.appendChild(hideStyle);
        mask.classList.add("force-hide");
    });
    watch.on("div.end-card-container", skipContainer => {
        let skipBtn = null;
        let wasShowing = false;

        const obs = new MutationObserver(muts => muts.forEach(mut => {
            const showing = skipContainer.classList.contains("end-card-container--show")
            if(!wasShowing && showing){
                wasShowing = true;
                skipBtn = skipBtn || skipContainer.querySelector(".end-card__metadata-area-play-button");
                skipBtn.click();
            }else if(wasShowing && !showing){
                wasShowing = false;
            }
        }));
        obs.observe(skipContainer, {attributes: true, attributeFilter: ["class"]});
    });
    watch.on("video.ad-video-player", adPlayer => {
        let skip = false;
        adPlayer.addEventListener("durationchange", () => {
            if(skip) adPlayer.currentTime = adPlayer.duration - .2;
            skip = false;
        });
        const obs = new MutationObserver(muts => muts.forEach(mut => {
            if(mut.attributeName === "src" && adPlayer.src){
                skip = true;
            }
        }));
        obs.observe(adPlayer, {attributes: true});
    });
    watch.on("div.vpaid-ad-slot", adSlot => {
        console.log("got adslot", adSlot);
        const obs = new MutationObserver(muts => muts.forEach(mut => {
            console.log("ad-slot", mut);
        }));
        obs.observe(adSlot, {childList: true, subtree: true});
    });
})();