まだ 1 行もアプリのコードを書いていません。それなのに、名前だけは npm と crates.io と GitHub の 3 か所で押さえてしまいました。
順番がおかしい気もします。でも、個人開発でいちばん後悔するのは「いい名前が先に取られていたこと」だと、何度か痛い目を見て学びました。だから今回は、作ると決めた瞬間に名前の確保と配布方針の調査から入りました。
この記事は、その「作る前の助走」の記録です。何を作ろうとしているのか、なぜ既存ツールで満足できなかったのか、どうやって名前を決めて予約したのか、そして Windows でアプリを配るときに必ずぶつかる SmartScreen とどう付き合うのか。実装より前の、地味だけど後で効いてくる部分を、経緯から調査結果まで残しておきます。
同じように「Windows の音を遠隔で切り替えたい」と思っている人と、「個人 OSS の名前と配布ってどう決めるのが正解なんだ」と迷っている人の、両方に何か残せたらうれしいです。
この記事のたどった道すじを 1 枚にすると上のようになります。困りごとから始まって、設計判断、命名、配布方針、名前予約、先行事例調査、と進みます。実装はまだ、その手前です。
音を切り替えるだけのために、母艦へ戻る毎日
まず、何に困っていたかを具体的に書きます。
普段の作業環境はこうです。物理の Windows 11 が母艦としてあって、その上で Hyper-V の Windows 11 ゲストを動かしています。開発作業のだいたい 9 割はこのゲストの中に入りっぱなしで、ホスト側に戻るのはゲームをするときくらい。体感で 1 割あるかどうかです。
音の出し先には、はっきりした 2 つのモードがあります。
- 普段の作業中: Nest Hub Max(Bluetooth)で音楽を流しっぱなしにしたい。手元のスピーカーというより、部屋に音楽が流れている感じ
- ビデオ会議のとき: そのときだけ、有線のイヤホンかヘッドフォンに切り替えたい。相手の声をちゃんと聞きたいし、こちらの生活音や流している音楽を相手に垂れ流したくない
やることは単純です。会議が始まる直前に「Nest Hub Max から有線イヤホンへ」切り替えて、会議が終わったら「有線イヤホンから Nest Hub Max へ」戻す。ただそれだけ。でも、これを 1 日に何度もやります。
問題はここからです。この出力先はどれも「母艦の Windows」に紐づいています。でも私はゲストの中に入りっぱなし。会議のたびに、いちいちホストのデスクトップに戻って、タスクバーのスピーカーアイコンをクリックして、出力デバイスを選び直す。会議が終わったら、また戻して Nest Hub Max に付け替える。この往復が、地味に、しかし確実にストレスでした。
1 回 10 秒くらいの操作です。でも会議の前後で毎回やると、そのたびに思考が途切れます。ゲストの全画面から抜けて、ホストのどこかをクリックして、また戻る。会議直前のいちばん集中したいタイミングで「今なんの準備してたっけ」が挟まる。あの感じ、伝わるでしょうか。
図にすると上のような往復です。② リモートを最小化して、③ ホストで切り替えて、④ また戻る。音の切替そのものは ③ だけで、② と ④ は成果ゼロの行き来です。これを会議の前と後で、毎回くり返しています。
最初は「Hyper-V 上のゲストから、ホストの音を切り替えるツールがあればいいのに」と考えていました。でも、少し考えて、それは筋が悪いと気づきました。
Hyper-V 専用にした瞬間、用途が狭くなる
「Hyper-V ゲストからホストの音を操作する」と定義すると、それは Hyper-V に依存したツールになります。でも、よく考えると私が本当に欲しいのは Hyper-V との連携ではありません。
欲しいのは「母艦の音声出力を、手元にある何かから切り替えられること」です。手元にある何かは、Hyper-V ゲストのこともあれば、WSL2 のこともあれば、隣の部屋のスマホのこともある。ソファでタブレットを触っているときに、母艦の音を Nest Hub Max に切り替えたい、なんてこともあります。
ここで発想を裏返しました。
「Hyper-V ゲストからホストを操作するツール」ではなく、「母艦の Windows で動く、軽量なローカル Web サービス」にする。同じネットワークから届きさえすれば、ブラウザを持っている端末はぜんぶクライアントになれる。
Browser(何でもいい)
↓ HTTP / HTTPS
Windows 母艦で動くアプリ
↓
Windows Core Audio
↓
物理オーディオデバイス
こうすると、Hyper-V は「利用例のひとつ」に格下げされます。WSL2 でも、RDP でも、別の PC でも、スマホでもタブレットでも、全部同じ仕組みで使える。ブラウザさえあれば OS も問わない。
イメージとしては、母艦のホスト名とポートを開くと、こんな画面が出る感じです。
Audio Output
● Nest Hub Max(普段の音楽)
○ 有線イヤホン(会議用)
○ ヘッドフォン
会議が始まる前にスマホで有線イヤホンをタップ、終わったら Nest Hub Max に戻す。ラジオボタンをクリックすると、母艦の既定の音声出力先が切り替わる。それだけ。本当にそれだけのツールです。でも、その「それだけ」が毎日ほしかった。
上の図のように、サーバーは母艦にひとつ。クライアントは「ブラウザを開ける端末」ならなんでもいい。この非対称さがこのツールの肝で、サーバー側は Windows 専用、操作側はクロスプラットフォーム、という役割分担になります。
既存ツールで済むなら、作りません
ここで正直な話をします。個人開発でいちばんやってはいけないのは、既にあるものを知らずに車輪を再発明することです。だから「本当に既存ツールで解決できないのか」を、名前を取ったあとに改めて調べ直しました。順番が逆なのはご愛嬌ということで。
Windows の音声デバイスを切り替える手段は、実はたくさんあります。調べた結果、大きく 4 つのカテゴリに分かれていました。そして、どれも私の用途にはピタッとは当たりませんでした。
ローカル GUI 切り替えツール
いちばん有名なのは SoundSwitch です(https://github.com/Belphemur/SoundSwitch )。ホットキーやトレイから既定デバイスを切り替えられる、完成度の高いアプリです。マルチデバイス構成やスマートな復元にも対応しています。
似たものに davkean さんの audio-switcher(https://github.com/davkean/audio-switcher )もあります。通知領域に常駐して、既定の入出力デバイスをさっと切り替えるタイプ。
これらはよくできています。でも、あくまで「同じ PC の上で」切り替えるものです。ホットキーもトレイも、その母艦の前に座っていないと押せません。私が困っているのは、まさに「母艦の前にいない(ゲストや別端末にいる)」ときなので、ここが噛み合わない。
コマンドラインツール
NirSoft の SoundVolumeView(svcl)や nircmd は、コマンドラインから既定デバイスを変更できます(https://blog.nirsoft.net/2020/05/29/set-default-audio-device-of-specific-application-from-command-line-on-windows-10/ )。PowerShell なら AudioDeviceCmdlets というモジュールもあります。
これらは強力で、実は今回作るツールの「切り替えの核」として内部で使える候補でもあります。ただ、単体では UI も遠隔操作もありません。RDP や SSH で母艦に入ってコマンドを叩けば「遠隔」にはなりますが、それは「ブラウザで一覧を見てクリックする」体験とはぜんぜん違う。スマホから svcl を叩きたいかというと、叩きたくないですよね。
.NET ライブラリ
xenolightning さんの AudioSwitcher(https://github.com/xenolightning/AudioSwitcher )は、Windows の MMDevice API をラップした .NET ライブラリです。これは「アプリ」ではなく「部品」です。切り替えロジックを自分のアプリに組み込むための素材で、そのまま使うものではありません。
言い換えると、切り替えの難しい部分は、こうしたライブラリのおかげで既に解かれているということでもあります。ここは後で効いてきます。
音声リモート系(これがまぎらわしい)
検索でよく引っかかるのが SoundRemote(https://github.com/SoundRemote/server-windows )や virtAudio のような「音声リモート」ツールです。名前がそっくりなので最初は「あ、もうあるのか」と思いました。
でも中身は逆方向でした。これらは「PC の音声を別のデバイスへストリーミングして、そっちで聞く」ためのものです。スマホを PC のスピーカー代わりにする、みたいな用途。私がやりたいのは音を飛ばすことではなく、母艦につながっている物理デバイスのうち、どれを既定にするかを切り替えることです。音の行き先そのものを、母艦のハードウェアのレベルで付け替えたい。ストリーミングとは目的が違います。
ちなみにブラウザ側にも Audio Output Devices API という仕様があって(https://developer.mozilla.org/en-US/docs/Web/API/Audio_Output_Devices_API )、Web ページが再生先デバイスを選べる、というものがあります。でもこれは「そのタブの音をどこで鳴らすか」の話で、OS の既定出力を変える話ではありません。これもまた別レイヤーです。
調べ終えて分かったこと
4 カテゴリを見渡して、こう整理できました。
- 切り替えはできる。でもローカル操作前提(GUI 系)
- 遠隔でも叩ける。でも UI がない(CLI 系)
- 部品はある。でも完成品ではない(ライブラリ)
- Web で遠隔もある。でも用途が音のストリーミングで別物(リモート系)
「母艦の既定出力を、任意のブラウザから一覧クリックで切り替える」という、この 4 つの真ん中に落ちる完成品が、探した範囲では見つかりませんでした。隙間は本当に空いている。ニッチではあるけれど、空いている。
ニッチという言葉は、個人 OSS ではそんなに怖くないと思っています。第一のユーザーは自分で、その自分が毎日困っている。既存が無いのは「誰も欲しがらない」ではなく「同じ不便を我慢している少数がいる」の可能性のほうが高い。しかも切り替えの核は既存ライブラリで解けているから、私が新しく作るのは「その上に薄い Web の層を載せる」だけ。労力は小さく、日々のリターンは大きい。この費用対効果なら、やる価値はある、と判断しました。
サーバーは Windows 専用、クライアントは何でも
設計の骨格を決めます。ここはあまり悩みませんでした。
音声切り替えの中身は Windows Core Audio、具体的には IPolicyConfig という(ドキュメント化されていない)COM インターフェース周りに依存します。これは Windows 固有の世界なので、macOS や Linux でサーバーを動かすことはそもそもできません。無理にマルチプラットフォーム化しても、macOS や Linux では実装をほぼ別物として書き直すことになる。それは労力に見合いません。
なので、割り切ります。
- サーバー側: Windows 11 専用。母艦で動き、既定の再生デバイスを変更し、Web UI と API を出す
- 操作側: OS 不問。ブラウザさえあれば、ゲストでも WSL2 でも Linux でも Mac でもスマホでもいい
表現するなら「Windows 専用サーバー、クロスプラットフォーム対応 Web クライアント」です。
Windows 10 を対象に含めるかは少し迷いましたが、外しました。Windows 10 は通常サポートが 2025 年 10 月に終了しています。これから新しく作る個人 OSS で、サポートの切れた OS を正式対応に含めるメリットは薄い。実装と保守を絞るために、Windows 11 x64 を正式対応にして、ARM64 は需要が出てから、Windows 10 と macOS と Linux は非対応、と決めました。動くかもしれないけど保証はしない、というスタンスです。
技術的な要点だけ、決めたことを並べておきます。
- デバイスは自動列挙する。デバイス名を設定ファイルに固定しない。母艦にある再生デバイスを取得して、デバイス ID・表示名・接続状態・現在の既定を Web に返す
- 切り替えは表示名ではなくデバイス ID で行う。表示名は変わりうるので、ID で指す
- Windows の既定出力は用途別に Console・Multimedia・Communications の 3 つに分かれている。通常の切り替えボタンでは、この 3 つをまとめて同じデバイスに変更する。個別切り替えは将来の課題。ここは今回のシナリオと地味に効いてくる部分で、ビデオ会議アプリの多くは Communications の既定を使う。もし Multimedia だけ有線イヤホンにして Communications が Nest Hub Max のままだと、会議の声だけスピーカーから漏れる、みたいな中途半端が起きる。だから会議のときは 3 つまとめて有線イヤホンに倒す。これがいちばん事故らない
- 既定は安全側に倒す。
127.0.0.1(ループバック)にバインドして、トークン認証を必須にする。LAN に公開するときだけ、明示的にバインド先を変える
このループバック既定は、あとで出てくる配布の話ともつながっています。ローカルサーバーを 127.0.0.1 に閉じておくと、Windows のファイアウォールも余計なプロンプトを出しません。安全側の初期設定は、そのままユーザー体験の良さにもなる。
サーバー側の言語は Rust にしました。Core Audio を叩くのに windows クレートが使えるし、常駐サーバーとの相性もいい。単一バイナリで配りやすいのも魅力でした。ただ、この「単一バイナリで配りやすい」が、次の落とし穴につながります。
画面はブラウザだけじゃなくていい
Web UI と書いてきましたが、正直に言うと、毎日の会議の切替でブラウザのタブをわざわざ開くのも、それはそれで少し面倒です。タブを探して、スリープから起こして、クリックする。摩擦はゼロにはならない。
なので、今のところこう考えています。ブラウザの Web UI は土台として残す。API を実証する場所であり、スマホや別 PC からの入口でもあるからです。そのうえで、毎日いちばん使うゲストの中には、小さな常駐アプリを置く。トレイのアイコンをクリックすると出力デバイスの一覧がポップアップして、選ぶだけで切り替わる。会議の開始と終了は、ホットキー 1 発で Nest Hub Max と有線イヤホンをトグルする。
大事なのは、これが全部「同じ API を叩くだけのクライアント」だということです。ブラウザも、トレイのアイコンも、ホットキーも、スマホも、母艦の同じサーバーを見ている。だから土台さえ作れば、入口は後からいくらでも足せる。Web UI を作ることは無駄になりません。
上のイメージのように、トレイのアイコンから母艦の出力先をその場で選ぶ。母艦のデスクトップには戻らない。まだ絵に描いた理想ですが、作るならこの形を目指しています。
名前で 3 往復した話
ここから、作る前の助走のハイライトです。名前決め。
最初のたたき台は WinAudioRemote でした。企画メモの中で自分でそう呼んでいたので、それを正式化するのが無難ではありました。ただ、リポジトリ名として小文字ケバブにすると win-audio-remote。3 語 17 文字で、ちょっと長い。打つのもだるい。
そこで詰めていきました。この過程、地味に往復したので正直に残します。
最初に「記述的だけど長い」問題を解こうとして、短縮案を並べました。winaudio(8 文字)、audioremote、sndremote、winsound など。このとき私は winaudio を推していました。短くて、Windows の音だと分かる。
でも、ここで自分の中の優先順位に気づきました。このツールの本質は「リモート」なんです。母艦の前にいないときに切り替えられること。それがこのツールの存在理由。winaudio は短いけれど、いちばん大事な「remote」を落としてしまっている。芯を外していました。
remote を主役に残す前提で組み直すと、こうなりました。
-
audioremote(11 文字)。「音声のリモート操作」が一目で伝わる。remote が主役 -
sndremote(9 文字)。最短級だけど、snd の略が一般には少し硬い -
win-audio-remote。いちばん明快だけど、また長さの問題に戻る
audioremote にしました。「Windows」は名前から落ちるけれど、そこは README で「Windows 専用サーバー」と書けば済む。remote が主役で、11 文字なら許容範囲。表示名だけキャメルケースの AudioRemote にすると見栄えがいいので、リポ名は小文字 audioremote、UI 表示名は AudioRemote に分けました。
上のような感じで、短さと分かりやすさと「remote が主役かどうか」の間で、しばらく行ったり来たりしていました。
名前ひとつでこんなに往復するのか、と自分でも思います。でも名前は一生ものです。特に、次に書く「配布」の都合で、名前は複数のレジストリに刻まれます。取り返しがつかない場所もある。だから往復してでも納得のいくところまで詰めてよかったと思っています。
学びとして 1 つ。次に命名で相談を受けたら、いきなり候補を出す前に「名前で最も残したい 1 語はどれ?」を先に聞こうと思いました。今回でいえば、それが「remote」だと最初に固まっていれば、3 往復は 1 往復で済んでいたはずです。
単一 exe で配ると、なぜ Windows に怒られるのか
名前が決まったので、配布の話に進みます。ここが今回いちばん調べ込んだところです。
Rust で作るなら、単一 exe をポンと GitHub Releases に置くのが素直です。インストール不要で試せるし、Rust との相性もいい。実際、OSS の初期版としてはよくある配り方です。
ただ、Windows で配ると必ずこれにぶつかります。SmartScreen です。
GitHub Releases からダウンロードした、署名されていない新しい exe を実行しようとすると、「WindowsによってPCが保護されました」的な警告が出ることがあります。あの青い画面です。ユーザーからすると「これ、怪しいアプリなのでは」と身構える瞬間。せっかく作ったのに、最初のハードルがこれだと悲しい。
なぜ出るのか、仕組みを理解しておく必要がありました。私の理解を書きます。
SmartScreen は、ざっくり言うと「そのファイルのハッシュ」と「発行元の評判」を見ています。署名されていない新しいファイルは評判がゼロからのスタートで、しかも Rust の release ビルドはバージョンごとにバイナリのハッシュが変わります。つまり、バージョンを上げるたびに「初めて見るファイル」として評判を積み直すことになる。永遠に警告が出続けかねない構造です。
ここで、ひとつ大事な区別に行き当たりました。SmartScreen が引っかかるのは「ブラウザでダウンロードした exe」なんです。より正確には、ブラウザでネットからダウンロードしたファイルには Mark of the Web(MotW)という印が付きます。この印が付いた、未署名で評判の低い実行ファイルが警告の対象になる。
逆に言うと、MotW が付かない経路で配れば、この警告そのものを回避できる。
上の図が、今回の調査でいちばんスッキリした点です。分かれ道は「Python か JS か」でも「Rust かどうか」でもなくて、「単一 exe をブラウザでダウンロードさせるか、パッケージマネージャ経由で入れさせるか」でした。
それなら npm で配ればいい、という結論
MotW が付かない経路。ここで「パッケージマネージャ」が効いてきます。
npm や pnpm でインストールしたものは、ブラウザでダウンロードしたわけではないので MotW が付きません。だから SmartScreen の警告も出ない。JS/TS の CLI ツールだと、実行用のランチャーがインストール時にローカルで生成されるので、なおさら印が付きません。
「でも今回は Rust だよね?」と思いますよね。私もそう思いました。ここが今回の調査でいちばん腹落ちした部分です。
Rust や Go で書かれたツールでも、コンパイル済みのバイナリを npm パッケージに同梱して配る、という定石があります。esbuild や Biome、swc がまさにこの方式です。プラットフォームごとのバイナリを optionalDependencies で配って、npx <tool> や npm i -g <tool> で入れる。npm がパッケージを展開してバイナリを取り出すので、ここでも MotW は付きません。結果、Rust の単一 exe でも SmartScreen 警告を避けられる。
つまり、こういう配布方針に落ち着きました。
- 主流: npm レジストリ(
pnpm publish)。npx audioremote(お試し)とnpm i -g audioremote(常用・サーバー起動向き)の 2 通りを README に書く。Rust バイナリを同梱する - 副: Node を持っていない人向けに、GitHub Releases に exe と SHA256SUMS を置く。README に「未署名なので SmartScreen 警告が出る場合がある」と明記する
前提として、母艦に Node が要ることになります。でも、このツールを母艦に入れる人は Hyper-V や WSL2 を使う開発者なので、Node はほぼ確実に入っている。追加のハードルは実質ゼロです。「ユーザーが既に持っているもので配る」という原則にも合っています。
SmartScreen をどうしても完全に消したくなったら、その先の選択肢もあります。コード署名(Authenticode)を入れれば評判が積み上がるし、Azure Trusted Signing は月 10 ドル程度で使える今どきの安めの正規ルートです。Microsoft Store 経由なら Microsoft の証明書で署名されるので、ダウンロード警告を避けるいちばん確実な方法になります。ただ、これらは v0.1 でやることではありません。利用者が増えて、警告ゼロが本当に要件になってから考えれば十分です。最初は未署名 + README 明記で割り切る。OSS の初期版としては、恥ずかしくない選択だと思っています。
winget に名前を取られるのが、実は怖くなかった話
配布を調べていて、ずっと引っかかっていた不安がありました。winget です。
私はいつも、新しいツールを配るときは配布先の名前を全部先に押さえる運用をしています。過去に「いい名前が先に取られていた」で泣いたので。だから winget も「先に誰かに取られたら怖い」と身構えていました。
でも調べてみると、winget は 2 つの意味で私の思い込みと違っていました。
ひとつ。winget は SmartScreen の回避策ではありません。winget は Windows 標準のパッケージマネージャ体験を良くするものです。統一されたインストール、公式っぽい導線、更新チャネル。でも、配るのは結局 exe やインストーラーの本体です。その exe が未署名で評判が低ければ、winget 経由でも警告は出うる。winget に載せれば SmartScreen が消える、というのは誤解でした。winget が意味を持つのは、署名済みの exe とセットにして、しかも Node を持たないユーザーにも届けたい、という要件が出たときだけです。
もうひとつ。これが安心した点です。winget の識別子は Publisher.Package という形式で、発行者名で名前空間が切られています。たとえば私なら ishizakahiroshi.AudioRemote になる。だから、他人が私の識別子を勝手に取ることは基本できません。第三者にできるのは 別人.AudioRemote を出すことだけで、私の ID とは衝突しない。しかも winget は「素の名前だけ予約」ができません。manifest の PR に実際のインストーラーや exe が必要なので、exe が無い段階では取りたくても取れない。
つまり winget に関しては、「先に取られる恐怖」がそもそも当てはまらなかった。発行者名前空間で守られているし、予約は exe ができてからで間に合う。ここは杞憂でした。
じゃあ「先着で奪われる恐怖」が本当に当てはまるのはどこか。発行者で名前空間が切られない、フラットな名前空間です。npm、crates.io、PyPI。ここは早い者勝ちなので、「配るときは全部先に取る」という私の運用が、まさに正解になる場所でした。
名前を 3 か所で押さえる。crates.io で 403 を 2 回踏む
というわけで、フラットな名前空間で audioremote を押さえにいきました。押さえたのは 3 か所です。
- npm:
audioremote。主流チャネルなので最優先。README だけの最小スタブを 1 回 publish して名前を確保 - crates.io:
audioremote。Rust なので防衛的に確保。0.0.0 のスタブを publish - GitHub:
ishizakahiroshi/audioremote。リポジトリを作れば実質確保(owner 名前空間なので急がないけれど、作れば確定)
npm と GitHub はすんなり済みました。npm は空きを確認して、README と package.json だけの最小パッケージを publish。GitHub はリポジトリを作るだけ。
問題は crates.io でした。ここで 403 を 2 回踏みます。
まず前提として、crates.io は一度 publish した名前は yank しても再利用できません。永久占有です。0.0.0 のスタブでも占有される。だから予約 publish は取り返しがつかない操作で、typo の確認を 2 回してから実行しました。ここは慎重に。
crates.io で publish するには、メール認証と API トークンが要ります。メール認証は済ませました。プロフィールで「Email Verified」の緑表示を確認。ここまでは順調でした。
トークンを作って cargo login して、いざ publish。すると 403 Forbidden: authentication failed です。
トークンの形式は正しい。cio で始まっていて、長さも妥当で、余計な空白も入っていない。それなのに認証に失敗する。ここで少し時間を溶かしました。「なんでだ」と。
トークンは合っているのに弾かれる。この「正しいはずなのに」が、いちばん時間を食う種類のはまり方でした。
原因はトークンのスコープでした。トークンを作るとき、被害を限定しようと思って Crates 欄に audioremote と、対象の crate 名を指定していたんです。最小権限の発想としては良さそうに見える。でも、これが罠でした。
crate 名にスコープを絞ったトークンは、その crate の既存の所有を前提にします。ところが audioremote はまだ存在しない新規 crate です。存在しない crate の所有を確認できないので、publish が弾かれる。つまり「新規 crate を予約するときに、その crate 名でスコープを絞ってはいけない」。
対処はシンプルでした。トークンを作り直して、今度は Crates 欄を Unrestricted(空欄)にする。publish-new スコープだけ付けて、crate は絞らない。漏洩対策は Expiration を 1 日にすることで担保する。
古いトークンを revoke して、Unrestricted で作り直して、再度 login。もう一度 publish。すると、また 403。
2 回目は原因が違いました。Unrestricted のトークンは正しく作れていたのに、まだ 403。よく見ると、トークンファイルの中身が、revoke した古いトークンのままだったんです。作成フォームで Unrestricted に設定しただけで、「Generate Token」を押して表示される新しい値を、まだファイルに保存できていなかった。だから login には古い(無効化済みの)値が使われていて、認証に失敗していた。
新しいトークンの値をちゃんと保存して、3 回目。ようやく Published audioremote v0.0.0 が出ました。HTTP で叩くと 200。予約完了です。
2 回踏んだのは反省点です。次からは最初から Crates 欄を Unrestricted にして、かつ「Generate を押して表示された値そのものを保存する」ところまでを 1 セットで確認しようと思いました。この学びは、自分用のツール配布手順にも追記しておきました。同じ罠は二度踏みたくない。
図にすると単純なんですが、渦中にいると「トークンは正しいのになぜ」で頭が回ります。crates.io の 403 authentication failed を踏んだら、まずトークンのスコープ(crate 名を絞っていないか)と、保存した値が最新か、この 2 つを疑うといいです。
v0.1 でやること、やらないこと
助走が長くなりましたが、実装の前に v0.1 のスコープも固めておきます。あれもこれもと盛ると、いつまでも出せなくなるので。
v0.1 でやること。
- Windows 11 対応
- 再生デバイスの自動列挙。現在の既定出力を表示。接続中のデバイスを一覧表示
- ブラウザのクリック操作で切り替え。Console・Multimedia・Communications をまとめて変更
- Web UI 内蔵。単一の実行体で動く
- Windows ログオン時に自動起動できる(サービスの SYSTEM 権限ではなく、ログオンユーザーの権限で動かす。音声デバイスは対話ユーザーのセッションに属するため、ここは譲れない)
- HTTP でアクセス。HTTPS は任意で有効化できる設計にする
- API トークン認証。既定は
127.0.0.1バインドで安全側 - LAN 内での利用
API はこのくらいのシンプルさで考えています。
GET /api/devices
GET /api/status
POST /api/devices/{device_id}/default
返却はこんなイメージです。
{
"devices": [
{
"id": "{0.0.0.00000000}.{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",
"name": "Speakers (Realtek Audio)",
"state": "active",
"is_default_console": true,
"is_default_multimedia": true,
"is_default_communications": true
}
]
}
リアルタイム性は、最初は数秒ごとのポーリングで十分だと思っています。WebSocket まで持ち出すのは、必要になってから。UI もまずはレスポンシブなカード型で、スマホからでも押しやすい大きさにする。それくらいで v0.1 は成立します。
やらないこと。
- macOS / Linux のサーバー実装(切り替えの中身が Windows 依存なので対象外)
- Windows 10 の正式対応
- コード署名と winget 登録(Node を持つ開発者が対象なので、当面は不要。exe を本気で配る要件が出てから、署名とセットで考える)
- 用途別(Console / Multimedia / Communications)の個別切り替え(将来の課題)
- HTTPS まわりを初期版から複雑にすること(自己署名や mkcert 対応は、実用フェーズで足す)
この「やらないこと」を先に書いておくのが、個人開発では効きます。あとで「あれもやらなきゃ」に飲まれないための、自分への予防線です。
この助走で学んだこと
まだコードは書いていないのに、学びは意外とたくさんありました。箇条書きで残します。
- ツールの定義は、いちばん広い用途で切る。「Hyper-V 専用」にした瞬間に用途が狭まった。「母艦の音を任意のブラウザから切り替える」に広げたら、Hyper-V は利用例のひとつに収まった
- 車輪の再発明を避けるための調査は、名前を取る前でも後でもいいから必ずやる。今回は 4 カテゴリを見て「真ん中の隙間が空いている」と確認できた。ニッチでも、空いていて、自分が毎日困っているなら作る価値はある
- Windows の配布で SmartScreen に引っかかる本質は「単一 exe をブラウザでダウンロードさせること」。パッケージマネージャ経由なら MotW が付かず、Rust バイナリでも npm 同梱で警告を避けられる
- winget は SmartScreen の回避策ではない。かつ発行者名前空間で守られているので、名前を先に奪われる心配もない。予約の緊張感が要るのは npm / crates.io / PyPI のようなフラットな名前空間のほう
- crates.io の予約で 403 authentication failed が出たら、トークンを crate 名でスコープ制限していないか疑う。新規 crate は Unrestricted トークンでないと publish できない。そして一度 publish した名前は永久占有
- 名前は「最も残したい 1 語」を先に決めると往復が減る。今回は「remote」がそれだった
どれも、実装そのものとは関係ない周辺の話です。でも、この周辺を最初に片付けておくと、実装に入ってから「名前どうしよう」「配布どうしよう」で手が止まらない。助走は無駄じゃなかった、と思っています。
おわりに
名前を取っただけで、まだ何も動きません。audioremote という名前の、中身が Hello world のスタブが 3 か所にあるだけです。
でも、母艦の前に戻らずに音を切り替えたい、というあの小さな面倒は、まだ毎日そこにあります。だから次は、Core Audio を叩いて既定デバイスを列挙するところから、少しずつ手を動かしていきます。動くものができたら、また記録を残します。
派手なツールではありません。自分の毎日のちょっとした不便を、最小限の実装で、確実に動くものにする。いつもそれだけを考えて作っています。今回もそのつもりで、小さく始めます。
同じように「Windows の音を遠隔で切り替えたい」と思っていた人がいたら、動くものができたときにまた声をかけさせてください。こんな相談、歓迎です。
※ ヘッダー画像とインフォグラフィックは AI(画像生成)で作成しています。
※ 本文の挿絵も AI(画像生成)で作成しています。
書いた人: ishizakahiroshi
群馬の北部で、保護猫2匹と暮らす、在宅エンジニア(何でも屋)
https://ishizakahiroshi.com/
https://github.com/ishizakahiroshi
X(業務委託・各種相談はこちら):
https://x.com/ishizakahiroshi
バックエンド・インフラ・AI連携まわりで、業務委託のご相談を受け付けています。フルリモートです。スポットや週2〜3時間からでも歓迎で、いろんな案件に携われたらうれしいです。こんな相談、歓迎です。








