<!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}));
文章来源地址https://www.toymoban.com/news/detail-601735.html
文章来源:https://www.toymoban.com/news/detail-601735.html
到了这里,关于Websocket 推送音频文件流,前端播放及下载的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!