吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 444|回复: 10
上一主题 下一主题
收起左侧

[其他原创] 使用nodejs 实现利用手机播放电脑声音 实时

[复制链接]
跳转到指定楼层
楼主
N79823 发表于 2024-11-14 16:45 回帖奖励
打开手机点个外卖,找个电影,发现台式机没有外放。。。。 所以开发了这款程序,可以利用手机播放电脑声音。打开原理 使用 FFmpeg 解码出来的音频数据 PCM 格式,使用H5的 Web Audio Api 来播放听起来简单,实现起来发现没办法获取window播放设备,只能读取到输入设备~尝试多种方案,发现可以使用 directshow 源捕获设备 实现虚拟设备


1.安装环境

安装nodejs 环境 :https://nodejs.org/ 安装ffmpeg并设为 目录下 bin 为全局变量安装 screen-capture-recorder-to-video-windows-free

安装后自动虚拟出 screen-capture-recorder 和 virtual-audio-capturer 设备 可通过 ffmpeg -list_devices true -f dshow -i dummy 指令查看screen-capture-recorder 获取的是电脑视频流virtual-audio-capturer 获取的是播放扬声器音频流
2.编写服务端代码 server.js

[JavaScript] 纯文本查看 复制代码
const express = require('express');
const WebSocket = require('ws');
const { spawn } = require('child_process');



const path = require('path');
const os = require('os');



// 获取当前 IP 地址
function getIpAddress() {
  const networkInterfaces = os.networkInterfaces();
  let ipAddress = '';
  
  for (let interface in networkInterfaces) {
    const interfaces = networkInterfaces[interface];
    for (let i = 0; i < interfaces.length; i++) {
      const address = interfaces[i].address;
      if (interfaces[i].family === 'IPv4' && !interfaces[i].internal) {
        ipAddress = address;
        break;
      }
    }
  }
  
  return ipAddress;
}


const app = express();

const host = '0.0.0.0';  // 指定绑定的 IP 地址

const PORT = 3000;
const cors = require('cors');
app.use(cors({
  origin: "*", // 允许所有来源,或设置为特定的IP或域名
}));



app.use(express.static('public'));

// 获取音频设备列表的 API
app.get('/audio-devices', (req, res) => {
  const ffmpeg = spawn('ffmpeg', ['-list_devices', 'true', '-f', 'dshow', '-i', 'dummy']);

  let output = '';
  ffmpeg.stderr.on('data', (data) => {
    output += data.toString();
  });

  ffmpeg.on('close', () => {
    // 解析音频设备列表
    const deviceList = [];
    const regex = /"([^"]+)"/g;
    let match;
    while ((match = regex.exec(output)) !== null) {
      deviceList.push(match[1]);
    }
    res.json(deviceList);
  });
});

// 返回当前 IP 地址
app.get('/get-ip', (req, res) => {
  const ipAddress = getIpAddress();
  res.json({ ip: ipAddress });
});


// 路由提供音频文件
app.get('/output', (req, res) => {
  res.sendFile(path.join(__dirname, 'output.wav'));
});

// WebSocket 服务器,用于实时传输音频
const wss = new WebSocket.Server({ noServer: true });

wss.on('connection', (ws, req) => {
  const selectedDevice = req.url.replace('/?device=', '');
  
  // 使用用户选择的设备启动 FFmpeg 子进程捕获音频
  const ffmpeg = spawn('ffmpeg', [
    '-f', 'dshow',
    '-i', `audio=${decodeURIComponent(selectedDevice)}`, // 使用选定的设备
    '-f', 'wav',
    'pipe:1'
  ]);

  ffmpeg.stdout.on('data', (data) => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(data); // 将音频数据发送到 WebSocket 客户端
    }
  });

  ffmpeg.stderr.on('data', (data) => {
    console.error(`FFmpeg error: ${data}`);
  });

  ws.on('close', () => {
    ffmpeg.kill(); // 关闭 FFmpeg 进程
  });
});

// 启动 HTTP 服务器并升级到 WebSocket
const server = app.listen(PORT,host, () => {
  console.log(`Server is running on http://${getIpAddress()}:${PORT}`);
});

server.on('upgrade', (request, socket, head) => {
  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request);
  });
});

大致代码解析


/audio-devices  接口 获取本地设备列表。
/get-ip接口 获取本机局域网IP
/output接口 输出默认音频,在ios设备需要通过用户点击 可以通过它播放,


编写websocket 接口




读取设备时发现ffmpeg 无法获取播放设备列表,于是找了各种方式最合适的方式就是使用ffmpeg  官方提供的解决方案 :directshow 桌面/屏幕源捕获过滤器


3.编写前端代码

[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Select Audio Device</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <h1>电脑 PCM 播放器</h1>
  <select id="deviceSelect"></select>
  <button>读取音频</button>
  <h1>当前 IP 地址:</h1>
  <p id="ip-address">加载中...</p>

  <audio ref="audio"  src="../output" controls></audio>

  <!-- <audio id="audioPlayer" controls></audio>
   -->
  <p>
    <input type="button" id="toggle" value="防锁屏:off" />
  </p>

  <script type="text/javascript" src="js/NosSleep.js"></script>
  <script type="text/javascript" src="js/pcm-player.js"></script>


  <script>
    
    var noSleep = new NoSleep();

    var wakeLockEnabled = false;
    var toggleEl = document.querySelector("#toggle");
    toggleEl.addEventListener('click', function() {
      if (!wakeLockEnabled) {
        noSleep.enable(); // keep the screen on!
        wakeLockEnabled = true;
        toggleEl.value = "防锁屏:on";
        document.body.style.backgroundColor = "red";
      } else {
        noSleep.disable(); // let the screen turn off.
        wakeLockEnabled = false;
        toggleEl.value = "防锁屏:off";
        document.body.style.backgroundColor = "";
      }
    }, false);

    // 加载时获取 IP 地址
    async function fetchIp() {
      try {
        const response = await fetch('/get-ip');
        const data = await response.json();
        document.getElementById('ip-address').textContent = data.ip;
      } catch (error) {
        console.error('获取 IP 地址失败:', error);
        document.getElementById('ip-address').textContent = '无法获取 IP 地址';
      }
    }
    fetchIp();

    // 加载音频设备
    async function loadDevices() {
      const response = await fetch('/audio-devices');
      const devices = await response.json();
      const deviceSelect = document.getElementById('deviceSelect');
      devices.forEach(device => {
        const option = document.createElement('option');
        option.value = device;
        option.textContent = device;
        deviceSelect.appendChild(option);
      });
    }
    const videodd = document.querySelector("video");


    let ws;

    // 开始音频流
    function startStreaming() {

      const ip = document.getElementById('ip-address').textContent;
      let audioContext;
      const selectedDevice = document.getElementById('deviceSelect').value;

      ws = new WebSocket(`ws://${ip}:3000/?device=${encodeURIComponent(selectedDevice)}`);
      console.log(`ws://${ip}:3000/?device=${encodeURIComponent(selectedDevice)}`)

      ws.onopen = function () {
        console.log('WebSocket connected');
      };

      
      var player = new PCMPlayer({
              encoding: '16bitInt',
              channels: 2,
              sampleRate: 48000,
              flushingTime: 0
      });
     
      ws.binaryType = 'arraybuffer';
      


      ws.onopen = function() {
        //如果是重连则关闭轮询
        timeid && window.clearInterval(timeid);
        if(reconnect){
          alert('重连成功,部分');
        }
      };

      ws.onmessage = function(e){
        var data = new Uint8Array(e.data);

        if (player.audioCtx.state === 'suspended') {
          player.audioCtx.resume();  // 确保 AudioContext 已被激活
        }

        player.feed(data);
        
        // document.getElementById('ip-address').textContent = player

        player.volume(1); 
      }; 

      //当断开时进行判断
      ws.onclose=function(e){

        setTimeout(startStreaming, 5000);  // 重新连接
      }

      
      // ws.addEventListener('message',function(event) {
      //   const stream = new Blob([event.data], { type: 'audio/mp3' });
      //       videodd.srcObject = stream;
      //       // 创建 MediaStreamAudioSourceNode // 将 HTMLMediaElement 提供给它
      //       const audioCtx = new AudioContext();
      //       const source = audioCtx.createMediaStreamSource(stream);

      //       // 创建双二阶滤波器
      //       const biquadFilter = audioCtx.createBiquadFilter();
      //       biquadFilter.type = "lowshelf";
      //       biquadFilter.frequency.value = 1000;
      //       biquadFilter.gain.value = range.value;

      //       // 将 AudioBufferSourceNode 连接到 gainNode // 并将 gainNode 连接到目的地,这样我们就可以播放 // 音乐并使用鼠标光标调整音量
      //       source.connect(biquadFilter);
           
      // });

      // ws.onmessage = (event) => {


       
      //   // 假设事件中包含的是音频数据
      //   console.log('Received audio data');
      //   const audioBlob = new Blob([event.data], { type: 'audio/mp3' });
      //   const audioUrl = URL.createObjectURL(audioBlob);

      //   // 获取 audio 元素并设置其源
      //   const audioPlayer = document.getElementById('audioPlayer');
      //   audioPlayer.src = audioUrl;

      //   // 播放音频
      //   audioPlayer.play().catch((error) => {
      //     console.error('播放音频时出错:', error);
      //   });

      // };

      ws.onerror = (error) => {
        console.error('WebSocket error:', error);
      };

      ws.onopen = () => {
        console.log('WebSocket connection established');
      };
    }

    // 初始化设备列表
    loadDevices();

  </script>

  
</body>
</html>





手机选择 virtual-audio-capturer 设备  即可获取电脑播放的声音前端主要难点,在于web屏幕常亮和web播放二进制流wav数据完整项目地址:winodwsAudio




免费评分

参与人数 5吾爱币 +11 热心值 +3 收起 理由
Luncode + 1 用心讨论,共获提升!
zhaols24 + 1 + 1 谢谢@Thanks!
wushaominkk + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
ma4907758 + 1 谢谢@Thanks!
kittylang + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
 楼主| N79823 发表于 2024-11-14 16:46 |楼主
完整项目地址:https://gitee.com/pmhw/winodwsAudio
3#
52pojie19 发表于 2024-11-14 18:09
4#
bingchuan111 发表于 2024-11-14 18:20
5#
wuyemeigui 发表于 2024-11-14 22:13

大佬牛逼
6#
Feik1K 发表于 2024-11-15 11:05
非常实用欸
7#
Luncode 发表于 2024-11-15 11:39
最近正在想,台式机没外放,怎么转手机上来。LZ就搞出来了,强
8#
aosikaiii 发表于 2024-11-16 11:42
台式机没外设,这不无敌
9#
amnsdx 发表于 2024-11-16 12:41
大佬牛逼
10#
shi147517631 发表于 2024-11-16 12:56
厉害 厉害
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 09:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表