音声の出力スピーカーを切り替える

Recipe ID: hw-013

OS のデフォルトではない特定のオーディオデバイス(スピーカーやヘッドホン)を選択して音声を再生する方法です。Rust 側の cpalrodio を使用します。

1. 依存関係の追加

Rust プロジェクトのディレクトリ(src-tauri)で以下のコマンドを実行します。

cargo add rodio

rodio は内部で cpal を re-export しているため、別途 cpal を追加する必要はありません。別途追加すると名前の競合エラーが発生する可能性があります。

2. デバイス一覧の取得

rodio 0.21.x は内部で cpal を re-export しているため、別途 cpal をインポートする必要はありません。

use rodio::cpal::traits::HostTrait;
use rodio::DeviceTrait;

#[tauri::command]
fn list_output_devices() -> Result<Vec<String>, String> {
    let host = rodio::cpal::default_host();
    let devices = host.output_devices().map_err(|e| e.to_string())?;
    
    let names: Vec<String> = devices.into_iter()
        .filter_map(|d| d.name().ok())
        .collect();
        
    Ok(names)
}

3. 特定デバイスでの再生

rodio クレートは cpal の上に構築されており、高レベルな再生 API を提供します。rodio 0.21.x では OutputStreamBuilder パターンを使用します。

use std::fs::File;
use std::io::BufReader;
use rodio::cpal::traits::HostTrait;
use rodio::{Decoder, OutputStreamBuilder, Sink, DeviceTrait};

#[tauri::command]
fn play_sound_on_device(device_name: String, file_path: String) -> Result<(), String> {
    // デバイスを探す
    let host = rodio::cpal::default_host();
    let device = host.output_devices().map_err(|e| e.to_string())?
        .find(|d| d.name().map(|n| n == device_name).unwrap_or(false))
        .ok_or("Device not found")?;

    // ストリームの作成(OutputStreamBuilder パターン)
    let stream = OutputStreamBuilder::from_device(device)
        .map_err(|e| e.to_string())?
        .open_stream()
        .map_err(|e| e.to_string())?;
    
    // Sink を Mixer に接続
    let sink = Sink::connect_new(stream.mixer());

    let file = File::open(file_path).map_err(|e| e.to_string())?;
    let source = Decoder::new(BufReader::new(file)).map_err(|e| e.to_string())?;
    
    sink.append(source);
    
    // 再生完了までブロック(非同期にする場合は State で Sink を保持するなど)
    sink.sleep_until_end();
    
    Ok(())
}

※ フロントエンドの <audio> タグの出力先を変更するには setSinkId API がありますが、ブラウザ実装や権限に依存するため、Rust 側で制御するほうが確実な場合があります。