Managing StreamYard using the Elgato Stream Deck

Introduction

At the start of the global pandemic, it was soon apparent that we would be working and speaking from home for quite some time. As such, I decided early on to do a full upgrade of my workplace at home. One of the improvements I did was to get an Elgato Stream Deck, which allows us to have physical programmable buttons. I use these to switch lights, set scenes, enable and disable audio and video, and more.

One of the streaming platforms which has emerged over the recent months is StreamYard. This platform was a pleasant surprise to me, both as a speaker and as an organizer. It is just straightforward to use as a speaker, follow a link, give access to your microphone and camera, and done. No installation of additional software, no need for specific accounts, it just works. Moreover, as an organizer, it provides me with an effortless experience. It allows us to set custom logos and backgrounds and comes with various layouts where speakers and content can be highlighted in different ways, all for just a small fee.

StreamYard layout

However, there is one downside to StreamYard, which is the lack of support for (global) hotkeys. They are working on adding these; however, for now, the support is not yet there. As a speaker, I use my Stream Deck extensively for global hotkeys, as it allows me, for example, to mute myself quickly. Additionally, as a producer, it would be great if I can rapidly switch between different layouts. This post explains how to set this up by using Tampermonkey combined with AutoHotkey. You can find all of the used resources in the last paragraph.

StreamDeck

Creating hotkeys with Tampermonkey

The first step is to create hotkeys within StreamYard. The ones I wanted to have were to toggle my audio and video and switch between layouts. As StreamYard does not support hotkeys yet, we need to find a way to implement this ourselves. Here is where Tampermonkey comes in, as it allows us to insert user scripts into our web pages and has plugins for all major browsers. After a quick search, I found this script from Justin Garrison, which allows the audio and video toggling. As this script uses the m and v buttons to do the toggling, I started making a couple of changes. Consequently, this ensures I do not trigger the hotkeys by accident, such as when typing in the chat.

if (e.key == "b" && !e.shiftKey && e.ctrlKey && e.altKey && !e.metaKey) {
    var unmuteButton = document.querySelector('[aria-label="Unmute microphone"]');
    var muteButton = document.querySelector('[aria-label="Mute microphone"]');
    if (unmuteButton !== null) {
        unmuteButton.click();
    } else {
        muteButton.click();
    }
} else if (e.key == "v" && !e.shiftKey && e.ctrlKey && e.altKey && !e.metaKey) {
    var faceUnmuteButton = document.querySelector('[aria-label="turn on camera"]');
    var faceMuteButton = document.querySelector('[aria-label="turn off camera"]');
    if (faceUnmuteButton !== null) {
        faceUnmuteButton.click();
    } else {
        faceMuteButton.click();
    }
}

Accordingly, the hotkeys are now set to CTRL+ALT+B and CTRL+ALT+V, respectively, which is already much better. The reason CTRL+ALT+B was used for the audio hotkey is that I found that using CTRL+ALT+M gave some unexpected behavior when combined with AutoHotkey. Now that the initial setup is done, the next step was adding the various layout options’ hotkeys. To do this, I just needed to find the aria labels for the different options, which can be found using the developer tools.

Aria labels in Developer Tools

The aria labels for the different layouts are as following, which are all set up with a hotkey combination of ALT+#.

  1. Solo layout. The host camera fills all the space. If no host camera, the first guest that was added is used.
  2. Thin layout. All cameras are visible and squished to fill up all the space.
  3. Group layout. All cameras are visible and spaced out.
  4. Leader layout. All cameras are visible. One is larger than the others.
  5. Small screen layout. One camera and the shared screen are visible. If no screen, it behaves like the leader layout.
  6. Large screen layout. The shared screen is large, all cameras are visible but small. If no screen, it behaves like the group layout.
  7. Full screen layout. Only the shared screen is visible. If no screen, it behaves like the group layout.
else if (e.key == "1" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var soloLayoutButton = document.querySelector('[aria-label="Solo layout. The host camera fills all the space. If no host camera, the first guest that was added is used."]');
            if (soloLayoutButton !== null) {
                soloLayoutButton.click();
            }
        }

After introducing all the hotkeys I ended up with the script below, which could then be loaded into Tampermonkey.

// ==UserScript==
// @name         Streamyard Keyboard Shortcuts
// @namespace    http://streamyard.com
// @version      1.0
// @description  Keyboard shortcuts for streamyard
// @author       [email protected]
// @match        https://streamyard.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==
(function () {
    'use strict';
    document.addEventListener('keydown', function (e) {
        if (e.key == "b" && !e.shiftKey && e.ctrlKey && e.altKey && !e.metaKey) {
            var unmuteButton = document.querySelector('[aria-label="Unmute microphone"]');
            var muteButton = document.querySelector('[aria-label="Mute microphone"]');
            if (unmuteButton !== null) {
                unmuteButton.click();
            } else {
                muteButton.click();
            }
        } else if (e.key == "v" && !e.shiftKey && e.ctrlKey && e.altKey && !e.metaKey) {
            var faceUnmuteButton = document.querySelector('[aria-label="turn on camera"]');
            var faceMuteButton = document.querySelector('[aria-label="turn off camera"]');
            if (faceUnmuteButton !== null) {
                faceUnmuteButton.click();
            } else {
                faceMuteButton.click();
            }
        } else if (e.key == "1" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var soloLayoutButton = document.querySelector('[aria-label="Solo layout. The host camera fills all the space. If no host camera, the first guest that was added is used."]');
            if (soloLayoutButton !== null) {
                soloLayoutButton.click();
            }
        } else if (e.key == "2" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var thinLayoutButton = document.querySelector('[aria-label="Thin layout. All cameras are visible and squished to fill up all the space."]');
            if (thinLayoutButton !== null) {
                thinLayoutButton.click();
            }
        } else if (e.key == "3" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var groupLayoutButton = document.querySelector('[aria-label="Group layout. All cameras are visible and spaced out."]');
            if (groupLayoutButton !== null) {
                groupLayoutButton.click();
            }
        } else if (e.key == "4" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var leaderLayoutButton = document.querySelector('[aria-label="Leader layout. All cameras are visible. One is larger than the others."]');
            if (leaderLayoutButton !== null) {
                leaderLayoutButton.click();
            }
        } else if (e.key == "5" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var smallScreenLayoutButton = document.querySelector('[aria-label="Small screen layout. One camera and the shared screen are visible. If no screen, it behaves like the leader layout."]');
            if (smallScreenLayoutButton !== null) {
                smallScreenLayoutButton.click();
            }
        } else if (e.key == "6" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var largeScreenLayoutButton = document.querySelector('[aria-label="Large screen layout. The shared screen is large, all cameras are visible but small. If no screen, it behaves like the group layout."]');
            if (largeScreenLayoutButton !== null) {
                largeScreenLayoutButton.click();
            }
        } else if (e.key == "7" && !e.shiftKey && !e.ctrlKey && e.altKey && !e.metaKey) {
            var fullScreenLayoutButton = document.querySelector('[aria-label="Full screen layout. Only the shared screen is visible. If no screen, it behaves like the group layout."]');
            if (fullScreenLayoutButton !== null) {
                fullScreenLayoutButton.click();
            }
        }
    }, false);
})();

Tampermonkey user scripts

Creating global hotkeys using AutoHotkey

Now that the hotkeys are configured in StreamYard, the next step is to create global hotkeys. A global hotkey allows us to execute our hotkeys, even when the StreamYard window does not have focus. To implement this, I decided to use AutoHotkey, which allows us to register global hotkeys and send commands to specific windows.

First, let us register a global hotkey of SHIFT+CTRL+ALT+B to toggle the mute button. Once the hotkey is triggered, the current window’s ID is retrieved by using the WinGet function of AHK. This ID allows us to come back to the current window after executing the hotkey. Next, use the IfWinExist function to retrieve the StreamYard window. Subsequently, if the window was found, activate the window, and execute the hotkey CTRL+ALT+B, toggling the mute button. Finally, switch back to the initial window.

; StreamYard - Toggle Mute
+^!B::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, ^!b   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return

To ensure this is always running, I created a script called global-hotkeys.ahk, and placed a shortcut to the script into my startup folder, which can be found by running shell:startup.

Run shell:startup
Windows startup folder

Whenever I use the global hotkey, AutoHotkey captures it, checks for a StreamYard window, and executes the corresponding hotkey in that window. With this in place, it was now easy to add the other global hotkeys to the script, which ended up as below.

SetTitleMatchMode, 2 ; 2 = a partial match on the title

;shift: +
;control: ^
;alt: !

; StreamYard - Toggle Mute
+^!B::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, ^!b   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Toggle Video
+^!V::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, ^!v   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 1
+^!1::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !1   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 2
+^!2::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !2   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 3
+^!3::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !3   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 4
+^!4::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !4   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 5
+^!5::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !5   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 6
+^!6::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !6   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return
 
; StreamYard - Set scene 7
+^!7::
WinGet, winid, ID, A    ; Save the current window ID
IfWinExist, StreamYard ahk_class Chrome_WidgetWin_1
{
    WinActivate
    Send, !7   
    WinActivate ahk_id %winid% ; Restore previous window focus
}
return

Stream Deck configuration

The last step was to configure my Stream Deck to execute the various global hotkeys. I used the Hotkey action for switching the profiles and configured these to send the global hotkeys we registered in AHK.

Elgato Hotkey action for StreamYard layout

For toggling the audio and video, I decided to use the Hotkey Switch action, as this allows to send different commands and have different icons when pressing the button. I used the same global hotkeys for both; however, I did use two different icons. This way, I can immediately see on my Stream Deck if I am muted and if the webcam is turned on.

Elgato Hotkey Switch action for StreamYard mute
Elgato Hotkey Switch action for StreamYard unmute

Resources

I hope this post helped you set up your StreamYard global hotkeys and maybe also gave inspiration for additional possibilities. I have made all the resources, such as the various scripts and images available on my GitHub. Feel free to use these to get started quickly.