You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Node.js在WiFi音箱Sonos Beam Gen1播放MP3实现访客姓名播报?

在Sonos Beam Gen1上播报访客姓名的Node.js实现

核心思路

Sonos Beam需通过HTTP加载音频文件完成播放,因此实现流程分为两步:将Google TTS生成的MP3文件部署到局域网可访问的HTTP服务(需与Sonos设备同网段),再通过Sonos控制库找到设备并触发播放。

步骤实现

1. 安装依赖

使用第三方Sonos控制库sonos,搭配轻量HTTP服务器托管MP3文件:

npm install sonos

2. 编写核心代码

创建doorbell-announce.js文件:

const { SonosManager, SonosDevice } = require('sonos');
const http = require('http');
const fs = require('fs');
const path = require('path');
const os = require('os');

// 配置项:替换为你的实际路径和设备信息
const MP3_PATH = './visitor-announcement.mp3'; // Google TTS生成的MP3文件路径
const SONOS_DEVICE_IP = '192.168.1.100'; // Sonos Beam的局域网IP(可选,自动发现可不填)

// 启动临时HTTP服务器托管MP3
function startHttpServer() {
  return new Promise((resolve) => {
    const server = http.createServer((req, res) => {
      if (req.url === '/announce.mp3') {
        const filePath = path.resolve(MP3_PATH);
        const stat = fs.statSync(filePath);
        res.writeHead(200, {
          'Content-Type': 'audio/mpeg',
          'Content-Length': stat.size
        });
        fs.createReadStream(filePath).pipe(res);
      } else {
        res.writeHead(404).end();
      }
    });

    server.listen(8080, '0.0.0.0', () => {
      const localIp = getLocalIP();
      const audioUrl = `http://${localIp}:8080/announce.mp3`;
      console.log(`MP3托管服务启动:${audioUrl}`);
      resolve({ audioUrl, server });
    });
  });
}

// 获取本地局域网IP(避免用127.0.0.1,确保Sonos可访问)
function getLocalIP() {
  const interfaces = os.networkInterfaces();
  for (const dev in interfaces) {
    const iface = interfaces[dev];
    for (const alias of iface) {
      if (alias.family === 'IPv4' && !alias.internal && alias.address !== '127.0.0.1') {
        return alias.address;
      }
    }
  }
  return '127.0.0.1';
}

// 控制Sonos播放播报音频
async function playAnnouncement(audioUrl) {
  let sonosDevice;
  if (SONOS_DEVICE_IP) {
    sonosDevice = new SonosDevice(SONOS_DEVICE_IP);
  } else {
    // 自动发现网络中的Sonos设备
    const manager = new SonosManager();
    await manager.InitializeWithDiscovery();
    const devices = manager.Devices;
    if (devices.length === 0) throw new Error('未找到Sonos设备');
    // 可通过设备名称筛选,比如:devices.find(d => d.Name === 'Sonos Beam')
    sonosDevice = devices[0];
    console.log(`匹配到Sonos设备:${sonosDevice.Name}`);
  }

  // 保存当前播放状态(可选,播放完成后恢复)
  const [currentState, currentTrack] = await Promise.all([
    sonosDevice.GetCurrentState(),
    sonosDevice.CurrentTrack()
  ]);

  try {
    // 暂停当前播放
    await sonosDevice.Pause();
    // 播放播报音频
    await sonosDevice.Play(audioUrl);
    // 等待播放结束
    await new Promise((resolve) => {
      const checkInterval = setInterval(async () => {
        const state = await sonosDevice.GetCurrentState();
        if (state === 'stopped') {
          clearInterval(checkInterval);
          resolve();
        }
      }, 1000);
    });
  } finally {
    // 恢复原播放状态(可选)
    if (currentState === 'playing') {
      await sonosDevice.Play(currentTrack.uri);
    }
  }
}

// 主执行逻辑
async function main() {
  let server;
  try {
    const { audioUrl, server: httpServer } = await startHttpServer();
    server = httpServer;
    await playAnnouncement(audioUrl);
    console.log('播报完成');
  } catch (err) {
    console.error('播报失败:', err.message);
  } finally {
    server?.close();
  }
}

// 导出供外部调用(比如在门铃触发API中调用)
module.exports = { main };

3. 集成到自动化流程

在你的门铃触发/人脸识别API回调中,生成MP3后调用播报逻辑:

// 示例:人脸识成功后的处理函数
async function onVisitorDetected(visitorName) {
  // 调用Google TTS生成MP3到指定路径
  await generateGoogleTTSMP3(`${visitorName}来了`, MP3_PATH);
  // 执行Sonos播报
  await require('./doorbell-announce').main();
}

关键注意事项

  • 局域网互通:运行Node.js的主机与Sonos Beam必须在同一WiFi网段,确保HTTP服务地址可被Sonos访问
  • 音频格式:确保Google TTS生成的是MP3格式(Sonos原生支持MP3、AAC)
  • 设备精准匹配:如果网络中有多个Sonos设备,建议通过SONOS_DEVICE_IP或设备名称指定目标设备,避免播错
  • 状态恢复:代码中默认会保存并恢复原播放状态,可根据需求删除该逻辑

内容的提问来源于stack exchange,提问作者Praveen Gaddam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:53:26