Google Meet mute indicator

userscript console-script

Originally posted as GitHub Gist on 15 Oct 2020 , updated at 11 Nov 2020 (details)


Source: https://gist.github.com/paulera/af3d1a56a3612472a11dd0bee6c3812d

Description: Google Meet Mute Indicator: Updates the favicon and shows a mute indicator on screen (Tampermonkey / Greasemonkey userscript, can also be executed directly in the browser console)


google-meet-mute-indicator.js

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// ==UserScript==
// @name         Google Meet mute indicator
// @namespace    unforgivenexception.com
// @version      1.0
// @description  Updates the favicon and shows a mute indicator on screen
// @author       Paulo Amaral
// @match        https://meet.google.com/*
// @grant        none
// ==/UserScript==

(function() {

    // The emojis that will indicate the state. They will replace the favicon of
    // the window. The mute emoji will be displayed large on top of everything
    // when on mute.
    window.emojiMute = "🙊";
    window.emojiSpeaking = "🔊";

    // Interval (milliseconds) in which the function to update the UI will be
    // repeatedly called
    window.intervalUpdateUI = 500;

    // Returns a Boolean indicating whether the mute state is activated
    function isMute() {
        // TODO: optimize me - instead of searching the DOM everytime, find
        // element and check the property data-is-muted directly.
        return document.querySelectorAll("div[data-is-muted]")[1].getAttribute("data-is-muted") == "true"
    }

    // Update the window favicon according to the mute state, replacing it with
    // the emojis defined on the begining of this code.
    function updateFavicon(muteState) {

        // Uses a <link rel="icon"> element to set the favicon
        // with a inline svg
        var id = "faviconLinkElement";
        var linkElement = document.getElementById(id);
        if (linkElement == null) {
            // <link> element not available yet, so creates one.
            var headElement = document.getElementsByTagName('head')[0];
            linkElement = document.createElement('link');
            linkElement.id = (id);
            linkElement.rel = "icon";
            headElement.appendChild(linkElement);
        }

        // change the <link> element href to manipulate the favicon
        var emoji = (muteState)?window.emojiMute:window.emojiSpeaking;
        linkElement.href = "data:image/svg+xml,<svg xmlns=%22http://www.w3.org"+
            "/2000/svg%22 viewBox=%220 0 100 100%22><text y=%221em%22 "+
            "font-size=%2280%22>"+emoji+"</text></svg>";
    }

    // Show/hide the floating indicator for mute status (true = show)
    function updateFloatingIndicator(muteState) {
        var id = "muteIndicator";
        var divElement = document.getElementById(id);
        if (divElement == null) {
            var bodyElement = document.getElementsByTagName('body')[0];
            divElement = document.createElement("div");
            divElement.id = id;
            divElement.style.zIndex = 9999;
            divElement.style.display = "none";
            divElement.style.position = "absolute";
            divElement.style.border = "4px solid black";
            divElement.style.borderRadius = "50%";
            divElement.style.width = "30vh";
            divElement.style.height = "30vh";
            divElement.style.textAlign = "center";
            divElement.style.backgroundColor = "#ff0000";
            divElement.style.opacity = "0.7";
            divElement.style.marginLeft = "1vh"
            divElement.style.marginTop = "1vh"
            
            var spanElement = document.createElement("span");
            spanElement.innerHTML = window.emojiMute;
            spanElement.style.fontSize = "20vh"; // 20% of viewport height
            divElement.appendChild(spanElement);
            bodyElement.appendChild(divElement);
        }
        divElement.style.display = (muteState)?"block":"none";
    }

    // This is the main function, which will check the mute state and
    // call UI updates accordingly. This function is idempotent.
    function updateUI() {

        // console.log ("mute indicator updateUI() - ping 💧");

        // window.interfaceMuted is the variable used to manage idempotency.
        // When it is "undefined", it means the code is running for the first
        // time and a UI update is required regardless the current state.

        if (isMute()) {
            if (!window.interfaceMuted ||
                typeof window.interfaceMuted === "undefined") {
                // console.log ("YOU ARE MUTED " + window.emojiMute);
                updateFavicon(true);
                updateFloatingIndicator(true);
                window.interfaceMuted = true;
            }
        } else {
            if (window.interfaceMuted ||
                typeof window.interfaceMuted === "undefined") {
                // console.log ("YOU ARE SPEAKING " + window.emojiSpeaking);
                updateFavicon(false);
                updateFloatingIndicator(false);
                window.interfaceMuted = false;
            }
        }

    }

    // Auxiliar function to shutdown UI updates. Must be called manually. States
    // and updates won't be reverted, they will just stop happening.
    function abortUpdateUI() {
        if (window.timerCheckMuteState) {
            clearInterval(window.timerCheckMuteState)
        }
    }

    // Run the abort function before kicking off UI updates for idempotency
    // purposes. Just in case this code was already running and it is being
    // executed again.
    abortUpdateUI();


    // Runs UI updates repeatedly. Call the function abortUpdateUI()
    // in the console to abort.
    // TODO: optimize me - call UI updates a few milisseconds after mute buton
    // is clicked intead of in a loop.
    window.timerCheckMuteState = setInterval(updateUI, window.intervalUpdateUI);

})();