Open9

StreamDeckアプリ

プレミ社員プレミ社員

これを追加するにはmanifest.jsonでActionsを追加する

{
  "Name": "volumio-controller",
  "Version": "0.1.0.0",
  "Author": "ebina4yaka",
  "Actions": [
    {
      "Name": "Playback",
      "UUID": "com.ebina4yaka.volumio-controller.playback",
      "Icon": "imgs/actions/player/play_pause",
      "Tooltip": "Displays a count, which increments by one on press.",
      "PropertyInspectorPath": "ui/increment-counter.html",
      "Controllers": ["Keypad"],
      "States": [
        {
          "Image": "imgs/actions/counter/key",
          "TitleAlignment": "middle"
        }
      ]
    },
    {
      "Name": "Preview",
      "UUID": "com.ebina4yaka.volumio-controller.previous",
      "Icon": "imgs/actions/player/skip_previous",
      "Tooltip": "Displays a count, which increments by one on press.",
      "PropertyInspectorPath": "ui/increment-counter.html",
      "Controllers": ["Keypad"],
      "States": [
        {
          "Image": "imgs/actions/counter/key",
          "TitleAlignment": "middle"
        }
      ]
    },
    {
      "Name": "Next",
      "UUID": "com.ebina4yaka.volumio-controller.next",
      "Icon": "imgs/actions/player/skip_next",
      "Tooltip": "Displays a count, which increments by one on press.",
      "PropertyInspectorPath": "ui/increment-counter.html",
      "Controllers": ["Keypad"],
      "States": [
        {
          "Image": "imgs/actions/counter/key",
          "TitleAlignment": "middle"
        }
      ]
    }
  ],
  "Category": "volumio-controller",
  "CategoryIcon": "imgs/plugin/category-icon",
  "CodePath": "bin/plugin.js",
  "Description": "its volumio contrall app",
  "Icon": "imgs/plugin/marketplace",
  "SDKVersion": 2,
  "Software": {
    "MinimumVersion": "6.4"
  },
  "OS": [
    {
      "Platform": "mac",
      "MinimumVersion": "10.15"
    },
    {
      "Platform": "windows",
      "MinimumVersion": "10"
    }
  ],
  "Nodejs": {
    "Version": "20",
    "Debug": "enabled"
  },
  "UUID": "com.ebina4yaka.volumio-controller"
}

TooltipとPropertyInspectorPathはサンプルのまま(リリースするならちゃんと書いたほうが良さそう)
PropertyInspectorPathはhtmlファイルで定義
以下の設定画面のUI(サンプルのカウンターアプリのもの)

設定がいらないなら未指定でも動く

<!DOCTYPE html>
<html>

<head lang="en">
    <title>Increment Counter Settings</title>
    <meta charset="utf-8" />
    <script src="https://sdpi-components.dev/releases/v3/sdpi-components.js"></script>
</head>

<body>
    <!--
        Learn more about property inspector components at https://sdpi-components.dev/docs/components
    -->
    <sdpi-item label="Increment By">
        <sdpi-range setting="incrementBy" min="1" max="5" step="1" default="1" showlabels></sdpi-range>
    </sdpi-item>
</body>

</html>
プレミ社員プレミ社員

実装はsrc/actions/**.tsに書く

import {action, KeyDownEvent, SingletonAction, WillAppearEvent} from "@elgato/streamdeck";

@action({ UUID: "com.ebina4yaka.volumio-controller.playback" })
export class PlaybackToggle extends SingletonAction<CounterSettings> {
  /**
   * The {@link SingletonAction.onWillAppear} event is useful for setting the visual representation of an action when it becomes visible. This could be due to the Stream Deck first
   * starting up, or the user navigating between pages / folders etc.. There is also an inverse of this event in the form of {@link streamDeck.client.onWillDisappear}. In this example,
   * we're setting the title to the "count" that is incremented in {@link IncrementCounter.onKeyDown}.
   */
  override onWillAppear(ev: WillAppearEvent<CounterSettings>): void | Promise<void> {
    return ev.action.setTitle(`${ev.payload.settings.count ?? 0}`);
  }

  /**
   * Listens for the {@link SingletonAction.onKeyDown} event which is emitted by Stream Deck when an action is pressed. Stream Deck provides various events for tracking interaction
   * with devices including key down/up, dial rotations, and device connectivity, etc. When triggered, {@link ev} object contains information about the event including any payloads
   * and action information where applicable. In this example, our action will display a counter that increments by one each press. We track the current count on the action's persisted
   * settings using `setSettings` and `getSettings`.
   */
  override async onKeyDown(ev: KeyDownEvent<CounterSettings>): Promise<void> {
    const { settings } = ev.payload;
    settings.incrementBy ??= 1;
    settings.count = (settings.count ?? 0) + settings.incrementBy;

    await ev.action.setSettings(settings);
    await ev.action.setTitle(`${settings.count}`);
  }
}

/**
 * Settings for {@link PlaybackToggle}.
 */
type CounterSettings = {
  count?: number;
  incrementBy?: number;
};
プレミ社員プレミ社員

voluimoのRESTAPIを叩く

https://developers.volumio.com/api/rest-api

import {
  type KeyDownEvent,
  SingletonAction,
  type WillAppearEvent,
  action,
} from "@elgato/streamdeck";

@action({ UUID: "com.ebina4yaka.volumio-controller.playback" })
export class PlaybackToggle extends SingletonAction {
  override onWillAppear(ev: WillAppearEvent): void | Promise<void> {
    return ev.action.setTitle("Toggle");
  }

  override async onKeyDown(ev: KeyDownEvent): Promise<void> {
    const res = await fetch(
      "http://firefly.local/api/v1/commands/?cmd=toggle",
      {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      },
    );

    await ev.action.setTitle(res.ok ? "Success" : "Failed");
  }
}

プレミ社員プレミ社員

フォルダ構成

├── com.ebina4yaka.volumio-controller.sdPlugin
│   ├── imgs
│   │   ├── actions
│   │   │   └── player
│   │   │       ├── play_pause.png
│   │   │       ├── skip_next.png
│   │   │       └── skip_previous.png
│   │   └── plugin
│   │       ├── category-icon.png
│   │       ├── category-icon@2x.png
│   │       ├── marketplace.png
│   │       └── marketplace@2x.png
│   ├── manifest.json
│   └── ui
│       └── increment-counter.html
├── package-lock.json
├── package.json
├── rollup.config.mjs
├── src
│   ├── actions
│   │   ├── playback-next.ts
│   │   ├── playback-previous.ts
│   │   └── playback-toggle.ts
│   └── plugin.ts
└── tsconfig.json

プレミ社員プレミ社員

plugin.tsで使うアクションを読み込む

import streamDeck, { LogLevel } from "@elgato/streamdeck";

import { PlaybackNext } from "./actions/playback-next";
import { PlaybackPrevious } from "./actions/playback-previous";
import { PlaybackToggle } from "./actions/playback-toggle";

// We can enable "trace" logging so that all messages between the Stream Deck, and the plugin are recorded. When storing sensitive information
streamDeck.logger.setLevel(LogLevel.TRACE);

// Register the increment action.
streamDeck.actions.registerAction(new PlaybackToggle());
streamDeck.actions.registerAction(new PlaybackPrevious());
streamDeck.actions.registerAction(new PlaybackNext());

// Finally, connect to the Stream Deck.
streamDeck.connect();

プレミ社員プレミ社員

ボタンアイコンに任意の画像を設定することもできるので再生中の曲のジャケット表示もできそう
https://docs.elgato.com/streamdeck/sdk/guides/keys/#from-data-url

import { action, KeyDownEvent, SingletonAction } from "@elgato/streamdeck";
/**
 * Example action that updates the key action image from a data URL on key press.
 */
@action({ UUID: "com.elgato.hello-world.increment" })
export class IncrementCounter extends SingletonAction {
	/**
	 * Occurs when the user presses the key action.
	 */
	override onKeyDown(ev: KeyDownEvent) {
		ev.action.setImage(
			// base64 data URL
			"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAFF2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDgtMTNUMTU6MDA6MTUtMDQwMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDgtMTNUMTU6MDE6NTMtMDQ6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDgtMTNUMTU6MDE6NTMtMDQ6MDAiCiAgIHBob3Rvc2hvcDpEYXRlQ3JlYXRlZD0iMjAyNC0wOC0xM1QxNTowMDoxNS0wNDAwIgogICBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI3MiIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjcyIgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSIzMDAvMSIKICAgdGlmZjpZUmVzb2x1dGlvbj0iMzAwLzEiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgRGVzaWduZXIgMiAyLjUuMyIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNC0wOC0xM1QxNTowMTo1My0wNDowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+xLBe4AAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHLS0JBFIc/tUhKK6pFixYS1sqiB0htgpSwQELMIKuN3nwEPi73KiFtg7ZCQdSm16L+gtoGrYOgKIJo7bqoTcXt3AyMyBnOOd/8Zs5h5gxYIxklqzcMQTZX0MIBn2shuuhqqmDHSYdYa0zR1clQKEjd8XaHxYw3A2at+uf+HS0rCV0Bi114QlG1gvC0cHCtoJq8LdylpGMrwqfCHk0uKHxr6vEqV0xOVfnDZC0S9oO1XdiV+sXxX6yktaywvBx3NlNUfu5jvsSRyM3PSewV60EnTAAfLmaYwo+XYcbFexlghEFZUSd/6Dt/lrzkKuJVSmiskiJNAY+oRamekJgUPSEzQ8ns/9++6snRkWp1hw8anwzjpQ+atuCzbBjvh4bxeQS2R7jI1fLzBzD2Knq5prn3oW0Dzi5rWnwHzjeh+0GNabFvySZmTSbh+QScUei8hualas9+9jm+h8i6fNUV7O5Bv5xvW/4CDa5nvRjbKwoAAAAJcEhZcwAALiMAAC4jAXilP3YAAABvSURBVGiB7c8BDcAgAMAwwBzK0M1VPM+eVsE279njj9bXAW8xVmOsxliNsRpjNcZqjNUYqzFWY6zGWI2xGmM1xmqM1RirMVZjrMZYjbEaYzXGaozVGKsxVmOsxliNsRpjNcZqjNUYqzFWY6zGWM0D2SQCW/zbGkwAAAAASUVORK5CYII=",
		);
	}
}