[Released] Stream Deck Integration

Stream Deck Integration
Editor & Build support for Stream Deck Hardware

6387804--711921--icon.png

Asset StoreDocsPlugin SourcePublisher Website

Stream Deck Integration allows any official Stream Deck hardware running official software to interact and communicate with the Unity Editor and/or built Unity projects.

Improve your Editor workflow with the added benefit of the infinite button layout that a Stream Deck provides!
Show up your game or experience without any visual debug menus or interfering with the experience by using a Stream Deck as a build controller / debug screen!

This extension includes full support to call any menu item, expose any field, property or method, no matter if they are private or public, to be called by any supported Stream Deck hardware.
Editor, fully built project, Windows or macOS, everything works!

  • Features

  • Full Unity Editor and built project support

  • Play mode editor control with visual feedback

  • Pause mode editor control with visual feedback

  • Execute any menu item, built-in or custom

  • Invoke any method with up to one parameter

  • Set any field and/or property

  • Supports all main base types (int, float, bool, string)

  • Current Limitations

  • You must use an official Stream Deck hardware unit and the official software. This includes Stream Deck, Stream Deck Mini, Stream Deck XL, and the mobile app.

  • The hardware unit and the software must be connected to the machine running the built project or the Unity editor.

  • No Linux support. This is a Stream Deck limitation, as it has no official supported software for Linux systems.

  • The “Unity Integration” plugin must be installed and used to use any of the features.


You can use this thread for any questions or feedback you may have about the asset. For direct support please send an email to support@f10.dev.
Thanks!

3 Likes

Update v1.1.0

This version brings support for dynamically changing the title and icon/image visible on a specific action in the Stream Deck, among other small fixes.

  • Added support for setTitle, setImage endpoints.
  • Removed web-socket message limitation.
  • Improved offline documentation.
  • Fixed wrong Debug.Log usages.
1 Like

Thanks for this!

Works perfect!

1 Like

Would it be possible to expose more settings? like all font style (alignment, size color etc?

Sadly, the plugin is limited by whatever the SDK exposes, and as far as I know, the only allowed payload data is the title itself (string) for the setTitle event.

https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#settitle

well never mind :wink: i can design my own textures with the titel included

but what you allready did with the settings is really handy and awesome!

1 Like

As a quick heads-up, for anyone not being able to access the online documentation, the data was hosted using OVH servers in Europe, which got completely destroyed in today’s fire.

I will try to spin up a new instance as soon as possible and re-generate the documentation. Until then, I would suggest using the offline documentation provided with the assets.

EDIT: SG3 group (VPS where the documentation is hosted) is expected to be back up without data loss after the 19th of March.

1 Like

oh damn that sucks!

There is a bug in Build, which not occurs in the Editor.

If i press one button, any action gets invoked 8 times…
See videos.

Video 1: Button behaviour

Video 2: Button hit counter

Hey Dan, any specific flow you may be doing? I mean, are you using the “StreamDeckRuntime” type, or something custom? - If you use “StreamDeckRuntime”, keep in mind that by default it will not be destroyed on scene load, so you could have multiple runtime instances at once.

Also, what happens if you build the Demo scene included with the package?

I’ll test the runtime flow again now, just to make sure.

  1. we only have one scene.
  2. i use the runtime script from you
  3. we use the buttons to controll a show, so forward back, play stop and pause

we have 4 streamdecks (currently tested everything with 2 streamdecks, bug happens on both streamdecks)

but i will try to use your demoscene and see what happens.

1 Like

Tested it with this:

using F10.StreamDeckIntegration;
using F10.StreamDeckIntegration.Attributes;
using UnityEngine;

public class StreamDeckCustomExample : MonoBehaviour
{
    public Texture2D[] tex;
    private Texture2D GetIcon => tex[Random.Range(0, tex.Length - 1)];
    private int nextCounter = 0, lastCounter = 0;
    private void OnEnable() {
        // Registers this class as a StreamDeck enabled class
        StreamDeck.Add(this);
       
        StreamDeckSettings.SetButtonImage(GetIcon, "SIGNEXT0");
        StreamDeckSettings.SetButtonTitle(nextCounter.ToString(), "SIGNEXT0");
        StreamDeckSettings.SetButtonImage(GetIcon, "SIGLAST0");
        StreamDeckSettings.SetButtonTitle(lastCounter.ToString(), "SIGLAST0");
    }

    private void OnDisable() {
        // Removes this class as a StreamDeck enabled class
        StreamDeck.Remove(this);
    }
    [StreamDeckButton]
    public void SIGNEXT0()
    {
        nextCounter++;
        StreamDeckSettings.SetButtonImage(GetIcon, "SIGNEXT0");
        StreamDeckSettings.SetButtonTitle(nextCounter.ToString(), "SIGNEXT0");
    }
   
    [StreamDeckButton]
    public void SIGLAST0()
    {
        lastCounter++;
        StreamDeckSettings.SetButtonImage(GetIcon, "SIGLAST0");
        StreamDeckSettings.SetButtonTitle(lastCounter.ToString(), "SIGLAST0");
    }
}

and buttons got called 5x

@dan_wipf Tested locally, and I can’t manage to reproduce this behavior. What I tested:

public class StreamDeckExample : MonoBehaviour {

        [SerializeField]
        private Text _text;

        private int rep = 0;

        private void OnEnable() {
            // Registers this class as a StreamDeck enabled class
            StreamDeck.Add(this);
        }

        private void OnDisable() {
            // Removes this class as a StreamDeck enabled class
            StreamDeck.Remove(this);
        }

        [StreamDeckButton]
        private void TestRep() {
            rep++;
            _text.text = rep.ToString();
        }
    }

The scene consists of a Canvas with a text object, and a single GameObject with this “StreamDeckExample” component, and the “StreamDeckRuntime” component. All of this tested on Windows.

Seems like it could be an issue with multiple Stream Decks? I haven’t tested this situation, as I only have one unit.
Also, it seems now it repeats only x5 times? Seeing how it’s now apparently going from x8 to x5, I would first think that there is something going on with your specific project, but I’m honestly not sure what could be causing this behaviour

managed it to work with coroutines… but still not really a solid way i think…

public Texture2D[] tex;
        private Texture2D GetIcon => tex[Random.Range(0, tex.Length - 1)];
        public int nextCounter = 0, lastCounter = 0;
        public bool buttonPressed = false;
        private void OnEnable() {
            // Registers this class as a StreamDeck enabled class
            StreamDeck.Add(this);
        }

        private void Start()
        {
            StreamDeckSettings.SetButtonImage(GetIcon, "SIGNEXT0", immediate:true);
            StreamDeckSettings.SetButtonTitle(nextCounter.ToString(), "SIGNEXT0", immediate:true);
            StreamDeckSettings.SetButtonImage(GetIcon, "SIGLAST0", immediate:true);
            StreamDeckSettings.SetButtonTitle(lastCounter.ToString(), "SIGLAST0", immediate:true);
        }

        private void OnDisable() {
            // Removes this class as a StreamDeck enabled class
            StreamDeck.Remove(this);
        }

        private Coroutine nextCR, previousCR;
       
        [StreamDeckButton]
        public void SIGNEXT0()
        {
            if(nextCR == null)
                nextCR = StartCoroutine(next());
        }

        IEnumerator next()
        {
            nextCounter++;
            StreamDeckSettings.SetButtonImage(GetIcon, "SIGNEXT0", immediate:true);
            StreamDeckSettings.SetButtonTitle(nextCounter.ToString(), "SIGNEXT0", immediate:true);
            yield return new WaitForEndOfFrame();
            nextCR = null;
        }
        [StreamDeckButton]
        public void SIGLAST0()
        {
            if(previousCR == null)
                previousCR = StartCoroutine(previous());
        }

        IEnumerator previous()
        {
            lastCounter++;
            StreamDeckSettings.SetButtonImage(GetIcon, "SIGLAST0");
            StreamDeckSettings.SetButtonTitle(lastCounter.ToString(), "SIGLAST0");

            yield return new WaitForEndOfFrame();
            previousCR = null;
        }

Ok tested some more, even with one streamdeck it jumps, (now it was two times) but as mentioned above, the solution with locking a coroutine and realease it after one frame is working stable :slight_smile:

the settings working now all with just a millisecond lag, which is fine because they all apply now on the same time!

I’m glad you could make it work. Seeing the code, you are clearly mitigating whatever is causing the method to be called multiple times per frame. If you ever find out what’s causing this you can let me know, but everything I tried ends up with only one trigger for me.

I also see you found the immediate parameter :). By default messages are queued to be sent at one per frame, but in your case, you want things to update immediately, so as long as you don’t send A LOT of data on the same frame, you’ll be good.

Some proof of evidence =)

i send those data at once:

/// <summary>
        /// StreamDeck Button Settings (Icon & Title) coroutine
        /// </summary>
        /// <param name="isActive">is StreamDeck active?</param>
        /// <param name="ExhibitionID">Exhibition to change</param>
        /// <param name="invoke">wait for one frame?</param>
        /// <returns></returns>
        public IEnumerator SetButtons(bool isActive, int ExhibitionID = -1, bool invoke = false)
        {
            var sequencerStatus = SequencerStatus.isStopped;
           

            if (invoke)
                yield return new WaitForEndOfFrame();
           
           
           
            for (var i = 0; i < 4; i++)
            {
                if (i == ExhibitionID && isActive) continue;

                SetTitle("", SIGNEXT, i);
                SetTitle("", SIGPREVIOUS, i);
                SetTitle("", SIGCLIP, i);
                SetTitle($"{InfoTitle} {(i + 1)}", SIGINFO, i);
                SetTitle(PlayTitle, SIGPLAY, i);
                SetTitle("", SIGSTOP, i);

                SetIcon(EmptyTexture, SIGPREVIOUS, i);
                SetIcon(EmptyTexture, SIGNEXT, i);
                SetIcon(EmptyTexture, SIGCLIP, i);
                SetIcon(EmptyTexture, SIGINFO, i);
                SetIcon(PlayTexture, SIGPLAY, i);
                SetIcon(EmptyTexture, SIGSTOP, i);
            }

            if (!isActive)
            {
                yield return new WaitForEndOfFrame();
               
                lockButton = null;
                yield break;
            }
           
            controller.GetSequencerStatus(ExhibitionID);
            sequencerStatus = controller.sequencerStatus;
           
            var clipStatus = GetClipStatus(ExhibitionID);
           
            SetTitle(NextTitle, SIGNEXT, ExhibitionID);
            SetTitle(PreviousTitle, SIGPREVIOUS, ExhibitionID);
           
            SetTitle($"{ClipTitle} {clipStatus.x}/{clipStatus.y}", SIGCLIP, ExhibitionID);
            SetTitle($"{InfoTitle} {(ExhibitionID + 1)}", SIGINFO, ExhibitionID);
           
            SetTitle(StopTitle, SIGSTOP, ExhibitionID);

            SetIcon(clipStatus.x == 1 ? PreviousTextureDisabled : PreviousTexture, SIGPREVIOUS, ExhibitionID);
            SetIcon(clipStatus.x == clipStatus.y ? NextTextureDisabled : NextTexture, SIGNEXT, ExhibitionID);
           
           
            switch (sequencerStatus)
            {
                case SequencerStatus.isPlaying:
                    SetIcon(PauseTexture, SIGPLAY, ExhibitionID);
                    SetTitle(PauseTitle, SIGPLAY, ExhibitionID);
                    break;
                case SequencerStatus.isPausing:
                    SetIcon(PlayTexture, SIGPLAY, ExhibitionID);
                    SetTitle(PlayTitle, SIGPLAY, ExhibitionID);
                    break;
                case SequencerStatus.isImage:
                    SetIcon(PlayTextureDisabled, SIGPLAY, ExhibitionID);
                    SetTitle(PlayTitle, SIGPLAY, ExhibitionID);
                    break;
               
            }
           
            SetIcon(StopTexture, SIGSTOP, ExhibitionID);

            yield return new WaitForEndOfFrame();
            lockButton = null;
        }
1 Like

Will you add the switchToProfile function listed in elgato sdk documentation?

Hmm, you mean this? (https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/#switchtoprofile)

  • Note that a plugin can only switch to read-only profiles that are declared in its manifest.json file

As far as I understand from profiles, apps can only switch to read-only profiles, that already come bundled with the app (manifest.json). Currently, the “Stream Deck Integration” app has no profiles set up. So, it would be useless to support this, as there is nothing to switch to… unless I misunderstood you

Yes, I’m talking about this feature.

I would like to create multiple profiles and be able to switch from one to another at runtime.
Could I create my own manifest.json and handle it with your asset ?