Youtube Buzzer

add buzzer including autopause to youtube video sites (for quizzes)

// ==UserScript==
// @name            Youtube Buzzer
// @name:de         Youtube Buzzer
// @namespace       http://1fckeller.de/
// @version         0.3
// @description     add buzzer including autopause to youtube video sites (for quizzes)
// @description:de  Youtube-Videos (Quiz) per Gamepad Buzzer unterbrechen
// @author          DGZeule
// @license         GNU GPLv3
// @match           https://www.youtube.com/watch?v=*
// @icon            https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @require         https://code.jquery.com/jquery-3.7.0.min.js
// ==/UserScript==

(function($) {
    'use strict';
    var buzzerSettings={
        timer: 6000,
        animate: true,
        farben: ["#ff0000","#00ff00","#0000ff","#ffff00","#ff00ff","#00ffff","#ff7f00","#ff007f","#7fff00","#7f00ff","#00ff7f","#007fff"],
        teamNames: [],
        haveEvents: 'ongamepadconnected' in window,
        controllers: {},
        teams: [],
        ytbResetTimeout:0
    }
    $("body").append(`<style>
  #buzzer{
    position: fixed;
    font-weight: bold;
    margin: 0;
    width: 100%;
    height: 100%;
    font-size: 100px;
    padding-top: 300px;
    text-align: center;
    top: 0;
    left: 0;
    z-index: 9000;
    opacity: .8;
    display:none;
    background-repeat: no-repeat;
  }
  div#settingsBtn {
    position: fixed;
    top: 0;
    left: 195px;
    z-index: 3000;
    font-size:25px;
    color: #888;
    cursor:pointer;
  }
  #buzzerSettings{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100vh;
    background-color: #fff;
    opacity: .9;
    z-index: 3000;
    padding: 10px;
    font-size: 25px;
  }
  #buzzerSettings input,#buzzerSettings button{
    font-size: 25px;
  }
  .buzzerTeamName{
    display:block;
  }
  #closeSettings {
    float: right;
    font-size: 25px;
    border: 2px outset #999;
    border-radius:5px;
    padding:10px;
    line-height:20px;
    cursor:pointer;
  }</style>
<div id="settingsBtn">
  <svg fill="#888888" version="1.1" id="buzzerCogs" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="50px" height="50px" viewBox="924 796 200 200" enable-background="new 924 796 200 200" xml:space="preserve">
    <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
    <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
    <g id="SVGRepo_iconCarrier">
      <g>
        <path d="M1049.078,903.431h-3.447c-3.104,0-5.875-1.963-6.904-4.891c-0.626-1.793-1.354-3.536-2.176-5.227 c-1.361-2.806-0.799-6.167,1.404-8.369l2.381-2.382c4.029-4.028,4.029-10.556,0.002-14.583l-1.717-1.717 c-4.025-4.024-10.557-4.028-14.58,0l-2.436,2.433c-2.193,2.196-5.538,2.769-8.336,1.425c-1.696-0.811-3.442-1.532-5.236-2.155 c-2.948-1.017-4.928-3.795-4.928-6.91v-3.37c0-5.693-4.618-10.31-10.309-10.31h-2.43c-5.695,0-10.312,4.616-10.312,10.31v3.444 c0,3.107-1.962,5.877-4.892,6.906c-1.792,0.627-3.534,1.354-5.224,2.176c-2.803,1.361-6.166,0.796-8.371-1.406l-2.377-2.382 c-4.03-4.028-10.558-4.028-14.584,0l-1.719,1.717c-4.026,4.028-4.028,10.555,0,14.583l2.434,2.432 c2.193,2.197,2.765,5.54,1.421,8.341c-0.812,1.691-1.532,3.44-2.15,5.234c-1.021,2.945-3.798,4.926-6.915,4.926h-3.367 c-5.695,0-10.312,4.617-10.312,10.313v2.429c0,5.693,4.617,10.31,10.312,10.31h3.441c3.106,0,5.876,1.963,6.903,4.893 c0.63,1.791,1.358,3.537,2.18,5.227c1.361,2.804,0.795,6.164-1.408,8.367l-2.379,2.383c-4.029,4.027-4.027,10.555,0,14.582 l1.718,1.718c4.025,4.023,10.553,4.026,14.58-0.003l2.431-2.432c2.195-2.194,5.54-2.768,8.341-1.424 c1.694,0.813,3.441,1.533,5.236,2.155c2.946,1.018,4.927,3.795,4.927,6.913v3.364c-0.004,5.699,4.614,10.313,10.311,10.313h2.427 c5.696,0,10.314-4.614,10.311-10.309v-3.445c0-3.104,1.962-5.875,4.892-6.905c1.792-0.628,3.537-1.354,5.229-2.175 c2.801-1.362,6.165-0.798,8.368,1.404l2.379,2.38c4.027,4.029,10.555,4.025,14.583,0.002l1.717-1.718 c4.027-4.026,4.03-10.557,0-14.581l-2.432-2.433c-2.197-2.193-2.768-5.54-1.426-8.337c0.814-1.696,1.533-3.445,2.154-5.24 c1.021-2.947,3.795-4.926,6.914-4.926h3.367c5.695,0.002,10.31-4.616,10.31-10.312v-2.429 C1059.385,908.049,1054.771,903.427,1049.078,903.431z M991.694,940.147c-13.852,0-25.081-11.227-25.081-25.078 c0-13.853,11.229-25.08,25.081-25.08c13.85,0,25.079,11.228,25.079,25.08C1016.772,928.921,1005.544,940.147,991.694,940.147z"></path> <path d="M1117.307,845.487h-1.727c-2.557,0-4.847-1.583-5.752-3.974c-0.229-0.609-0.479-1.212-0.746-1.804 c-1.053-2.329-0.554-5.07,1.256-6.876l1.219-1.221c2.613-2.611,2.613-6.853,0-9.466l-0.473-0.473c-2.613-2.612-6.852-2.612-9.465,0 l-1.219,1.221c-1.809,1.809-4.547,2.308-6.877,1.258c-0.593-0.268-1.192-0.516-1.805-0.747c-2.389-0.903-3.975-3.196-3.975-5.748 v-1.729c0-3.697-2.996-6.692-6.689-6.692h-0.668c-3.698,0-6.696,2.995-6.696,6.692v1.724c0,2.557-1.581,4.85-3.972,5.753 c-0.609,0.231-1.215,0.479-1.805,0.747c-2.328,1.05-5.069,0.551-6.876-1.256l-1.22-1.221c-2.611-2.614-6.854-2.613-9.467,0.001 l-0.472,0.472c-2.613,2.613-2.613,6.853,0,9.465l1.219,1.22c1.806,1.806,2.31,4.547,1.257,6.876 c-0.268,0.592-0.517,1.194-0.748,1.804c-0.903,2.391-3.193,3.977-5.748,3.977h-1.727c-3.695-0.002-6.691,2.997-6.691,6.69v0.669 c0,3.696,2.996,6.693,6.691,6.693h1.722c2.557-0.001,4.85,1.582,5.753,3.973c0.231,0.611,0.48,1.215,0.747,1.809 c1.052,2.326,0.552,5.065-1.255,6.871l-1.219,1.224c-2.613,2.609-2.613,6.851,0,9.463l0.475,0.473c2.611,2.614,6.852,2.614,9.463,0 l1.217-1.219c1.807-1.806,4.549-2.308,6.877-1.255c0.592,0.269,1.197,0.517,1.809,0.748c2.389,0.901,3.974,3.193,3.974,5.747v1.724 c-0.004,3.694,2.995,6.692,6.692,6.692h0.669c3.693,0,6.692-2.994,6.692-6.692v-1.721c0-2.556,1.582-4.849,3.971-5.752 c0.612-0.23,1.216-0.479,1.809-0.746c2.326-1.053,5.068-0.551,6.873,1.251l1.223,1.222c2.609,2.615,6.85,2.615,9.465,0l0.473-0.475 c2.611-2.611,2.611-6.851,0-9.464l-1.221-1.22c-1.805-1.806-2.307-4.547-1.256-6.875c0.268-0.59,0.518-1.194,0.749-1.805 c0.901-2.391,3.191-3.976,5.747-3.976h1.725c3.694,0.004,6.691-2.995,6.695-6.69v-0.669 C1123.996,848.483,1121,845.487,1117.307,845.487z M1080.717,867.24c-8.131,0-14.723-6.592-14.723-14.724 s6.592-14.724,14.723-14.724c8.133,0,14.725,6.592,14.725,14.724S1088.85,867.24,1080.717,867.24z"></path>
      </g>
    </g>
  </svg>
</div>
<div id="buzzerSettings">
  <div id='closeSettings'>&times;</div>
  Timer: <input type="number" name="timer" size="2"> s<br>
  Animate timeout? <input type="checkbox" name="animate"><br>
</div>
<div id="buzzer">
</div>`);

    if(window.localStorage){
        try{
            var tmp=JSON.parse(localStorage.getItem("teamNames"));
            for(var i=0;i<tmp.length;i++)buzzerSettings.teamNames[i]=tmp[i];
            tmp=JSON.parse(localStorage.getItem("buzzerAnimate"));
            buzzerSettings.animate=tmp;
            tmp=JSON.parse(localStorage.getItem("buzzerTimer"));
            buzzerSettings.timer=tmp;
        }catch(e){};
    }

    $("#buzzerSettings").hide();
    function reset(){
        if(buzzerSettings.ytbResetTimeout)clearTimeout(buzzerSettings.ytbResetTimeout);
        buzzerSettings.ytbResetTimeout=0;
        buzzerSettings.buzzed=false;
        $("#buzzer").text("").hide().css({backgroundSize: "100%", backgroundColor:"#fff"});
        $("#buzzerSettings").hide();
        $("ytd-app").css({"--app-drawer-content-container-background-color":"#fff","background-color":"#fff" });
        $(".html5-main-video")[0].play();
    }

    $(document).keyup(function(e){
        if(e.keyCode==27){ /*Esc*/
            reset();
            $("#settingsBtn").show();
        }
    });

    function connecthandler(e){
        addgamepad(e.gamepad);
    }

    function addgamepad(gamepad) {
        buzzerSettings.controllers[gamepad.index] = gamepad;
        requestAnimationFrame(updateStatus);
    }

    function disconnecthandler(e) {
        removegamepad(e.gamepad);
    }

    function removegamepad(gamepad) {
        var d = document.getElementById("controller" + gamepad.index);
        document.body.removeChild(d);
        delete buzzerSettings.controllers[gamepad.index];
    }

    function updateStatus() {
        if (!buzzerSettings.haveEvents) {
            scangamepads();
        }
        var i = 0,t=0,j,k,team;
        buzzerSettings.teams=[];
        if(!buzzerSettings.buzzed){
            for (j in buzzerSettings.controllers) {
                var controller = buzzerSettings.controllers[j];
                if(controller.id.match("Xbox")){
                    buzzerSettings.teams[t]=[getTeam(t), buzzerSettings.farben[t], j, 0];
                    t++;
                }else if(controller.id.match("Buzz")){
                    for(k=0;k<4;k++,t++){
                        buzzerSettings.teams[t]=[getTeam(t), buzzerSettings.farben[t], j, k*5];
                    }
                }
                for(i = 0; i < controller.buttons.length; i++){
                    var val = controller.buttons[i].value;
                    if(val != 1)continue;
                    for(team=0,k=0;k<buzzerSettings.teams.length;k++){
                        if(buzzerSettings.teams[k][2]==j && buzzerSettings.teams[k][3]==i){
                            team=buzzerSettings.teams[k];
                            break;
                        }
                    }
                    if(!team)continue;
                    buzzerSettings.buzzed=1;
                    var c=team[1];
                    if(buzzerSettings.animate){
                        $("#buzzer").css(
                            {backgroundColor: c+"88",backgroundImage: "linear-gradient(0, "+c+", "+c+")", backgroundSize:"100%"}
                        ).animate({backgroundSize:"0%"}, buzzerSettings.timer);
                    }else{
                        $("#buzzer").css({backgroundColor: c});
                    }
                    $("#buzzer").text(team[0]).show();
                    $("ytd-app").css({"--app-drawer-content-container-background-color":c,"background-color":c });
                    $(".html5-main-video")[0].pause();
                    buzzerSettings.ytbResetTimeout=setTimeout(reset, buzzerSettings.timer);
                }
            }
        }
        requestAnimationFrame(updateStatus);
    }

    function scangamepads() {
        var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads():[]);
        for(var i = 0; i < gamepads.length; i++){
            if(gamepads[i]) {
                if(gamepads[i].index in buzzerSettings.controllers){
                    buzzerSettings.controllers[gamepads[i].index] = gamepads[i];
                }else{
                    addgamepad(gamepads[i]);
                }
            }
        }
    }
    window.addEventListener("gamepadconnected", connecthandler);
    window.addEventListener("gamepaddisconnected", disconnecthandler);

    if(!buzzerSettings.haveEvents){
        setInterval(scangamepads, 500);
    }

    function getTeam(t){
        var tmp=$_GET("team"+t);
        return tmp?tmp:(buzzerSettings.teamNames[t]?buzzerSettings.teamNames[t]:("Team"+(t+1)));
    }

    function $_GET(p){
        var result = null;
        (location.hash||location.search).substr(1).split("&").forEach(function(item){
            var tmp = item.split("=");
            if(tmp.shift() === p)result=decodeURIComponent(tmp.join("="));
        });
        return result;
    }
    function saveSettings(){
        var h="#";
        for(var i=0;i<buzzerSettings.teamNames.length;i++){
            h+=(i?"&":'')+"team"+i+"="+buzzerSettings.teamNames[i];
        }
        location.hash=h;
        if(window.localStorage){
            localStorage.setItem("buzzerTeamNames", JSON.stringify(buzzerSettings.teamNames));
            localStorage.setItem("buzzerTimer", JSON.stringify(buzzerSettings.timer));
            localStorage.setItem("buzzerAnimate", JSON.stringify(buzzerSettings.animate));
        }
        reset();
        $("#settingsBtn").show();
    }

    $("#buzzerSettings input[name=animate]").change(function(e){
        buzzerSettings.animate=$(this).is(":checked");
        if(e.keyCode==13)saveSettings();
    }).prop("checked", buzzerSettings.animate);
    $("#buzzerSettings input[name=timer]").keyup(function(e){
        buzzerSettings.timer=parseFloat($(this).val())*1000;
        if(e.keyCode==13)saveSettings();
    }).val(buzzerSettings.timer/1000);

    $("#settingsBtn").click(function(){
        $("#buzzerSettings").show().find(".buzzerTeamName,button").remove();
        $("#settingsBtn, #buzzer").hide();
        $(buzzerSettings.teams).each(function(k,v){
            $("#buzzerSettings").append($("<input>").addClass('buzzerTeamName').data("team", k).val(v[0]).css("backgroundColor", v[1]).keyup(function(e){
                var t=$(this).data("team");
                buzzerSettings.teamNames[t]=$(this).val();
                if(e.keyCode==13)saveSettings();
            }));
        });
        $("#buzzerSettings").append(
            $("<button>").text("save").click(saveSettings)
        );
    });


    $("#closeSettings").click(function(){
        $("#buzzerSettings").hide();
        $("#settingsBtn, #buzzer").show();
    });
})(jQuery);