Websocket 推送音频文件流,前端播放及下载

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Websocket 推送音频文件流,前端播放及下载</title>
  <script src="PCMPlayer.js"></script>
  <script src="base64.min.js"></script>
  <script src="pcmtoWav.js"></script>
</head>
<body>
<button onclick="sendMessage()">开始</button>
<button onclick="down()">下载</button>
<script>
var ws= null
var files = []

//创建实例
var player = new PCMPlayer({
	encoding: "16bitInt",
    channels: 1,
    sampleRate: 8000,
    flushingTime: 2000
});
window.onload = function() {
	openWebSocket()
}
function openWebSocket() {
	ws = new WebSocket("ws://*.*.*.*:*/**") // 创建WebSocket对象
    ws.binaryType = 'arraybuffer';

    ws.onopen = connectionOpen;
    ws.onmessage = messageReceived;
    ws.onerror = errorOccurred;
    ws.onclose = connectionClosed;
}
// 链接建立的时候触发
function connectionOpen (e) {
	console.log("Websocket 连接成功")
}
// 收到服务端发过来的消息时触发
function messageReceived (msg) {
	console.log("Websocket 返回信息")
	const data = msg.data;
	const dataAudio = new Uint8Array(data)
    player.feed(dataAudio)
    files.push(dataAudio)
}
// 异常时触发
function errorOccurred (e) {
	console.log("WebSocket 异常")
}
// 关闭客户端链接时触发
function connectionClosed (e) {
	console.log("WebSocket 连接断开")
    ws = null
}
// 发送信息
function sendMessage () {
	files = []
	// 如果是JSON,需要用JSON.stringify()转成字符串
    ws.send("开始")
}
// 下载文件
function down() {
	let length = 0;
    files.forEach(item => {
    	length += item.length;
    });
    let mergedArray = new Uint8Array(length);
    let offset = 0;
    files.forEach(item => {
    	mergedArray.set(item, offset);
        offset += item.length;
    });
	const link = document.createElement('a');
    link.href = pcmtoWav(window.btoa(String.fromCharCode(...mergedArray)), 8000, 1)
    link.download = new Date().getTime() + '.wav'
    link.click();
}
</script>
</body>
// PCMPlayer.js

function PCMPlayer(option) {
  this.init(option);
}

PCMPlayer.prototype.init = function (option) {
  var defaults = {
      encoding: '16bitInt',//编码格式
      channels: 1,//声道
      sampleRate: 8000,//采样率
      flushingTime: 1000//pcm数据刷新间隔
  };
  this.option = Object.assign({}, defaults, option);
  this.samples = new Float32Array();
  this.flush = this.flush.bind(this);
  this.interval = setInterval(this.flush, this.option.flushingTime);
  this.maxValue = this.getMaxValue();
  this.typedArray = this.getTypedArray();
  this.createContext();
};

PCMPlayer.prototype.getMaxValue = function () {
  var encodings = {
      '8bitInt': 128,
      '16bitInt': 32768,
      '32bitInt': 2147483648,
      '32bitFloat': 1
  }

  return encodings[this.option.encoding] ? encodings[this.option.encoding] : encodings['16bitInt'];
};

PCMPlayer.prototype.getTypedArray = function () {
  var typedArrays = {
      '8bitInt': Int8Array,
      '16bitInt': Int16Array,
      '32bitInt': Int32Array,
      '32bitFloat': Float32Array
  }

  return typedArrays[this.option.encoding] ? typedArrays[this.option.encoding] : typedArrays['16bitInt'];
};

PCMPlayer.prototype.createContext = function () {
  this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  this.gainNode = this.audioCtx.createGain();
  this.gainNode.gain.value = 1;
  this.gainNode.connect(this.audioCtx.destination);
  this.startTime = this.audioCtx.currentTime;
};

PCMPlayer.prototype.isTypedArray = function (data) {
  return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer);
};
// 播放原始pcm裸数据
PCMPlayer.prototype.feed = function (data) {
  if (!this.isTypedArray(data)) return;
  data = this.getFormatedValue(data);
  var tmp = new Float32Array(this.samples.length + data.length);
  tmp.set(this.samples, 0);
  tmp.set(data, this.samples.length);
  this.samples = tmp;
};
// 格式化
PCMPlayer.prototype.getFormatedValue = function (data) {
  var data = new this.typedArray(data.buffer),
      float32 = new Float32Array(data.length),
      i;
  for (i = 0; i < data.length; i++) {
      float32[i] = data[i] / this.maxValue;
  }
  return float32;
};
// 控制播放器音量
PCMPlayer.prototype.volume = function (volume) {
  this.gainNode.gain.value = volume;
};
// 销毁播放器实例
PCMPlayer.prototype.destroy = function () {
  if (this.interval) {
      clearInterval(this.interval);
  }
  this.samples = null;
  this.audioCtx.close();
  this.audioCtx = null;
};

PCMPlayer.prototype.flush = function () {
  if (!this.samples.length) return;
  var bufferSource = this.audioCtx.createBufferSource(),
      length = this.samples.length / this.option.channels,
      audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate),
      audioData,
      channel,
      offset,
      i,
      decrement;

  for (channel = 0; channel < this.option.channels; channel++) {
      audioData = audioBuffer.getChannelData(channel);
      offset = channel;
      decrement = 50;
      for (i = 0; i < length; i++) {
          audioData[i] = this.samples[offset];
          /* fadein */
          if (i < 50) {
              audioData[i] = (audioData[i] * i) / 50;
          }
          /* fadeout*/
          if (i >= (length - 51)) {
              audioData[i] = (audioData[i] * decrement--) / 50;
          }
          offset += this.option.channels;
      }
  }

  if (this.startTime < this.audioCtx.currentTime) {
    this.startTime = this.audioCtx.currentTime;
  }
  bufferSource.buffer = audioBuffer;
  bufferSource.connect(this.gainNode);
  bufferSource.start(this.startTime);
  this.startTime += audioBuffer.duration;
  this.samples = new Float32Array();
};
// pcmtoWav

function pcmtoWav(pcmsrt, sampleRate, numChannels, bitsPerSample) {
  //参数->(base64编码的pcm流,采样频率,声道数,采样位数)
  let header = {
    // OFFS SIZE NOTES
    chunkId: [0x52, 0x49, 0x46, 0x46], // 0    4    "RIFF" = 0x52494646
    chunkSize: 0, // 4    4    36+SubChunk2Size = 4+(8+SubChunk1Size)+(8+SubChunk2Size)
    format: [0x57, 0x41, 0x56, 0x45], // 8    4    "WAVE" = 0x57415645
    subChunk1Id: [0x66, 0x6d, 0x74, 0x20], // 12   4    "fmt " = 0x666d7420
    subChunk1Size: 16, // 16   4    16 for PCM
    audioFormat: 1, // 20   2    PCM = 1
    numChannels: numChannels || 1, // 22   2    Mono = 1, Stereo = 2...
    sampleRate: sampleRate || 16000, // 24   4    8000, 44100...
    byteRate: 0, // 28   4    SampleRate*NumChannels*BitsPerSample/8
    blockAlign: 0, // 32   2    NumChannels*BitsPerSample/8
    bitsPerSample: bitsPerSample || 16, // 34   2    8 bits = 8, 16 bits = 16
    subChunk2Id: [0x64, 0x61, 0x74, 0x61], // 36   4    "data" = 0x64617461
    subChunk2Size: 0, // 40   4    data size = NumSamples*NumChannels*BitsPerSample/8
  }
  function u32ToArray(i) {
    return [i & 0xff, (i >> 8) & 0xff, (i >> 16) & 0xff, (i >> 24) & 0xff]
  }
  function u16ToArray(i) {
    return [i & 0xff, (i >> 8) & 0xff]
  }
  let pcm = Base64.toUint8Array(pcmsrt)
  header.blockAlign = (header.numChannels * header.bitsPerSample) >> 3
  header.byteRate = header.blockAlign * header.sampleRate
  header.subChunk2Size = pcm.length * (header.bitsPerSample >> 3)
  header.chunkSize = 36 + header.subChunk2Size

  let wavHeader = header.chunkId.concat(
    u32ToArray(header.chunkSize),
    header.format,
    header.subChunk1Id,
    u32ToArray(header.subChunk1Size),
    u16ToArray(header.audioFormat),
    u16ToArray(header.numChannels),
    u32ToArray(header.sampleRate),
    u32ToArray(header.byteRate),
    u16ToArray(header.blockAlign),
    u16ToArray(header.bitsPerSample),
    header.subChunk2Id,
    u32ToArray(header.subChunk2Size)
  )
  let wavHeaderUnit8 = new Uint8Array(wavHeader)

  let mergedArray = new Uint8Array(wavHeaderUnit8.length + pcm.length)
  mergedArray.set(wavHeaderUnit8)
  mergedArray.set(pcm, wavHeaderUnit8.length)
  let blob = new Blob([mergedArray], { type: 'audio/wav' })
  let blobUrl = window.URL.createObjectURL(blob)
  return blobUrl
}
// base64.min.js
/**
 * Minified by jsDelivr using Terser v5.15.1.
 * Original file: /npm/js-base64@3.7.5/base64.js
 *
 * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
 */
!function(t,n){var r,e;"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(r=t.Base64,(e=n()).noConflict=function(){return t.Base64=r,e},t.Meteor&&(Base64=e),t.Base64=e)}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:this,(function(){"use strict";var t,n="3.7.5",r="function"==typeof atob,e="function"==typeof btoa,o="function"==typeof Buffer,u="function"==typeof TextDecoder?new TextDecoder:void 0,i="function"==typeof TextEncoder?new TextEncoder:void 0,f=Array.prototype.slice.call("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="),c=(t={},f.forEach((function(n,r){return t[n]=r})),t),a=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/,d=String.fromCharCode.bind(String),s="function"==typeof Uint8Array.from?Uint8Array.from.bind(Uint8Array):function(t){return new Uint8Array(Array.prototype.slice.call(t,0))},l=function(t){return t.replace(/=/g,"").replace(/[+\/]/g,(function(t){return"+"==t?"-":"_"}))},h=function(t){return t.replace(/[^A-Za-z0-9\+\/]/g,"")},p=function(t){for(var n,r,e,o,u="",i=t.length%3,c=0;c<t.length;){if((r=t.charCodeAt(c++))>255||(e=t.charCodeAt(c++))>255||(o=t.charCodeAt(c++))>255)throw new TypeError("invalid character found");u+=f[(n=r<<16|e<<8|o)>>18&63]+f[n>>12&63]+f[n>>6&63]+f[63&n]}return i?u.slice(0,i-3)+"===".substring(i):u},y=e?function(t){return btoa(t)}:o?function(t){return Buffer.from(t,"binary").toString("base64")}:p,A=o?function(t){return Buffer.from(t).toString("base64")}:function(t){for(var n=[],r=0,e=t.length;r<e;r+=4096)n.push(d.apply(null,t.subarray(r,r+4096)));return y(n.join(""))},b=function(t,n){return void 0===n&&(n=!1),n?l(A(t)):A(t)},g=function(t){if(t.length<2)return(n=t.charCodeAt(0))<128?t:n<2048?d(192|n>>>6)+d(128|63&n):d(224|n>>>12&15)+d(128|n>>>6&63)+d(128|63&n);var n=65536+1024*(t.charCodeAt(0)-55296)+(t.charCodeAt(1)-56320);return d(240|n>>>18&7)+d(128|n>>>12&63)+d(128|n>>>6&63)+d(128|63&n)},B=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,x=function(t){return t.replace(B,g)},C=o?function(t){return Buffer.from(t,"utf8").toString("base64")}:i?function(t){return A(i.encode(t))}:function(t){return y(x(t))},m=function(t,n){return void 0===n&&(n=!1),n?l(C(t)):C(t)},v=function(t){return m(t,!0)},U=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,F=function(t){switch(t.length){case 4:var n=((7&t.charCodeAt(0))<<18|(63&t.charCodeAt(1))<<12|(63&t.charCodeAt(2))<<6|63&t.charCodeAt(3))-65536;return d(55296+(n>>>10))+d(56320+(1023&n));case 3:return d((15&t.charCodeAt(0))<<12|(63&t.charCodeAt(1))<<6|63&t.charCodeAt(2));default:return d((31&t.charCodeAt(0))<<6|63&t.charCodeAt(1))}},w=function(t){return t.replace(U,F)},S=function(t){if(t=t.replace(/\s+/g,""),!a.test(t))throw new TypeError("malformed base64.");t+="==".slice(2-(3&t.length));for(var n,r,e,o="",u=0;u<t.length;)n=c[t.charAt(u++)]<<18|c[t.charAt(u++)]<<12|(r=c[t.charAt(u++)])<<6|(e=c[t.charAt(u++)]),o+=64===r?d(n>>16&255):64===e?d(n>>16&255,n>>8&255):d(n>>16&255,n>>8&255,255&n);return o},E=r?function(t){return atob(h(t))}:o?function(t){return Buffer.from(t,"base64").toString("binary")}:S,D=o?function(t){return s(Buffer.from(t,"base64"))}:function(t){return s(E(t).split("").map((function(t){return t.charCodeAt(0)})))},R=function(t){return D(T(t))},z=o?function(t){return Buffer.from(t,"base64").toString("utf8")}:u?function(t){return u.decode(D(t))}:function(t){return w(E(t))},T=function(t){return h(t.replace(/[-_]/g,(function(t){return"-"==t?"+":"/"})))},Z=function(t){return z(T(t))},j=function(t){return{value:t,enumerable:!1,writable:!0,configurable:!0}},I=function(){var t=function(t,n){return Object.defineProperty(String.prototype,t,j(n))};t("fromBase64",(function(){return Z(this)})),t("toBase64",(function(t){return m(this,t)})),t("toBase64URI",(function(){return m(this,!0)})),t("toBase64URL",(function(){return m(this,!0)})),t("toUint8Array",(function(){return R(this)}))},O=function(){var t=function(t,n){return Object.defineProperty(Uint8Array.prototype,t,j(n))};t("toBase64",(function(t){return b(this,t)})),t("toBase64URI",(function(){return b(this,!0)})),t("toBase64URL",(function(){return b(this,!0)}))},P={version:n,VERSION:"3.7.5",atob:E,atobPolyfill:S,btoa:y,btoaPolyfill:p,fromBase64:Z,toBase64:m,encode:m,encodeURI:v,encodeURL:v,utob:x,btou:w,decode:Z,isValid:function(t){if("string"!=typeof t)return!1;var n=t.replace(/\s+/g,"").replace(/={0,2}$/,"");return!/[^\s0-9a-zA-Z\+/]/.test(n)||!/[^\s0-9a-zA-Z\-_]/.test(n)},fromUint8Array:b,toUint8Array:R,extendString:I,extendUint8Array:O,extendBuiltins:function(){I(),O()},Base64:{}};return Object.keys(P).forEach((function(t){return P.Base64[t]=P[t]})),P}));
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
WebSocket 是一种基于 TCP 的协议,用于实现客户端和服务器之间的双向通信。它允许服务器主动向客户端推送数据,包括音频文件。 要使用 WebSocket 推送音频文件,首先需要在服务器端建立一个 WebSocket 服务器,接受客户端的连接。客户端通过 WebSocket 连接到服务器后,可以发送音频文件给服务器,同时服务器也可以将音频文件推送给客户端。以下是简单的实现步骤: 1. 建立 WebSocket 服务器:使用合适的编程语言和框架,如Node.js的socket.io库,建立一个 WebSocket 服务器。 2. 客户端连接:在客户端代码中,使用 WebSocket 客户端库连接到服务器。例如,可以使用JavaScriptWebSocket API创建一个 WebSocket 对象并连接到服务器。 3. 服务器端接收音频:当客户端连接到服务器后,可以通过 WebSocket 对象发送音频文件给服务器。服务器端需要监听客户端发送的数据,并处理音频文件。 4. 服务器端推送音频:服务器可以主动向连接的客户端推送音频文件。根据具体需求,可以按照一定的规则或触发条件将音频推送给客户端。 5. 客户端接收音频:在客户端的 WebSocket 连接上监听服务器发送的数据。当服务器推送音频文件时,客户端接收并处理音频数据。 实现 WebSocket 推送音频文件的关键是服务器和客户端之间的互相通信和数据处理。通过建立 WebSocket 连接,服务器可以实时地向客户端推送音频文件,使得客户端能够即时收到并播放音频。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值