ComfyUI-Custom-ScriptsのPlaySound🐍ノードで「on empty queue」モードが使えたり使えなかったりするのを解消する

導入方法

  • ComfyUI\custom_nodes\ComfyUI-Custom-Scripts\web\js\playSound.js を下記のコードに変更してComfyUIを再起動
  • これで、PlaySound ノードの mode を "on empty queue" に設定すると、最後のキューのみ音が鳴る。(はず)

プランA

import { app } from "../../../scripts/app.js";

/**
 * 音を再生するヘルパー関数
 * @param {object} node - PlaySoundノードのインスタンス
 */
function playSound(node) {
    let file = node.widgets[2].value;
    if (!file) {
        file = "notify.mp3"; // デフォルトファイル
    }

    let url;
    try {
        // httpから始まる完全なURLかチェック
        url = new URL(file);
    } catch (e) {
        // URLでない場合は相対パスとして扱う
        if (!file.includes("/")) {
            // "assets/" フォルダをデフォルトの場所とする
            file = "assets/" + file;
        }
        // スクリプトの場所を基準にURLを解決
        url = new URL(file, import.meta.url);
    }

    const audio = new Audio(url);
    audio.volume = node.widgets[1].value;
    audio.play().catch(e => console.error("PlaySound: Audio playback failed.", e));
}

app.registerExtension({
    name: "pysssss.PlaySound",
    async beforeRegisterNodeDef(nodeType, nodeData, app) {
        if (nodeData.name === "PlaySound|pysssss") {
            const onExecuted = nodeType.prototype.onExecuted;

            nodeType.prototype.onExecuted = async function () {
                onExecuted?.apply(this, arguments);

                const mode = this.widgets[0].value;

                if (mode === "always") {
                    // "always"モードの場合は常に音を再生
                    playSound(this);
                } else if (mode === "on empty queue") {
                    // "on empty queue"モードの場合、APIでキューの状態を確認する

                    // ノード実行完了からAPIの状態が更新されるまで少し待つ
                    //await new Promise((r) => setTimeout(r, 100));

                    try {
                        // 現在のページのオリジンからAPIのURLを動的に構築
                        const api_url = new URL("/queue", window.location.origin);
                        const response = await fetch(api_url);

                        if (!response.ok) {
                            throw new Error(`HTTP error! status: ${response.status}`);
                        }

                        const queueData = await response.json();

                        // 待機中のキュー (queue_pending) が空の場合のみ音を再生
                        if (queueData.queue_pending.length === 0) {
                            playSound(this);
                        }
                    } catch (error) {
                        console.error("PlaySound: Could not fetch queue status.", error);
                    }
                }
            };
        }
    },
});

AIによるコードの解説

  1. playSound(node) 関数の分離: 音を鳴らすロジックを独立した関数にしました。これによりコードが読みやすくなり、always モードと on empty queue モードの両方から呼び出せるようになりました。
  2. onExecuted のオーバーライド:
  • const mode = this.widgets[0].value; で、ノードのドロップダウンメニューで選択された値("always" または "on empty queue")を取得します。
  • if (mode === "always") の場合は、従来通り無条件で playSound(this) を呼び出します。
  1. キューのチェックロジック (on empty queue モード):
  • const api_url = new URL("/queue", window.location.origin);: window.location.origin (例: http://127.0.0.1:8188) を使って、APIのURLを動的に生成します。これにより、ComfyUIを異なるアドレスやポートで起動しても正しく動作します。
  • await fetch(api_url): 生成したURLに対して fetch APIでリクエストを送信します。
  • const queueData = await response.json();: レスポンスをJSONとして解析します。
  • if (queueData.queue_pending.length === 0): 取得したデータの中から queue_pending 配列の長さをチェックし、0(空)であれば、playSound(this) を呼び出して音を鳴らします。
  • try...catch: fetch が失敗した場合(サーバーがオフラインなど)にコンソールにエラーを出力し、スクリプト全体が停止するのを防ぎます。

プランB(別方式)

import { app } from "../../../scripts/app.js";

app.registerExtension({
    name: "pysssss.PlaySound.SimpleCheck", // 拡張機能の名前を区別するために変更
    // asyncは不要になります
    beforeRegisterNodeDef(nodeType, nodeData, app) {
        if (nodeData.name === "PlaySound|pysssss") {
            const onExecuted = nodeType.prototype.onExecuted;
            // asyncは不要になります
            nodeType.prototype.onExecuted = function () {
                onExecuted?.apply(this, arguments);

                // "on empty queue"が選択されている場合のみ、以下のチェックを行う
                if (this.widgets[0].value === "on empty queue") {

                    // キューの残りが1以下でない場合(つまり2以上の場合)は、
                    // 音を鳴らさずにここで処理を終了します。
                    if (app.ui.lastQueueSize > 1) {
                        return;
                    }

                }

                // --- ここから下の音声再生処理は、条件を満たした場合にのみ実行される ---

                let file = this.widgets[2].value;
                if (!file) {
                    file = "notify.mp3";
                }
                if (!file.startsWith("http")) {
                    if (!file.includes("/")) {
                        file = "assets/" + file;
                    }
                    file = new URL(file, import.meta.url)
                }

                const url = new URL(file);
                const audio = new Audio(url);
                audio.volume = this.widgets[1].value;
                audio.play();
            };
        }
    },
});
  1. PlaySoundノードの処理が完了します。
  2. on empty queueが設定されているか確認します。
  3. 設定されている場合、その瞬間のapp.ui.lastQueueSizeの値を見ます。
  • もし値が 2 以上なら、「まだ後ろにタスクが控えている」と判断し、returnで即座に処理を中断します。音は鳴りません。
  • もし値が 1 または 0 なら、このif文の条件(> 1)には当てはまらないため、処理は中断されずにそのまま下の音声再生コードに進みます。そして音が鳴ります。
Edit

Pub: 25 Jul 2025 18:34 UTC

Edit: 26 Jul 2025 18:58 UTC

Views: 96