< meta charset = " UTF-8 " > < title > Websocket 推送音频文件流,前端播放及下载 </ title > < script src = " PCMPlayer.js " > </ script > < script src = " base64.min.js " > </ script > < script src = " pcmtoWav.js " > </ script > </ head > < 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),
  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,
      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}));
				
目录文件上传客户端上传文件:bootstrap-fileinput插件插件安装插件使用服务端接受文件:formidable模块文件列表推送客户端主动获取文件列表:fs模块服务端主动推送文件列表:socket.io模块文件下载、删除等操作 文件上传功能往往是web应用非常重要的功能之一,使用express框架可以简单调用模块实现这一点。 文件上传 客户端上传文件:bootstrap-fileinput插件 bootstrap-fileinput插件是基于jQuery和bootstrap的一款集合了文
server端与client端建立websocket连接,client将待传文件进行分块,然后将文件的相关信息(文件名、md5值、分块大小、总块数、当前块数)及文件数据一并上传到服务端,服务端在本地建立文件通过追加的方式将上传的数据写入文件中,当当前块与总块数相等且文件MD5相同时认为文件上传成功 与上传相反,将client当成服务端,client与server建立连接后,向服务端发送可接收请求,服务端收到后将文件 为什么做这个东西,是突然间听一后端同事说起Annie这个东西,发现这个东西下载视频挺方便的,会自动爬取网页中的视频,然后整理成列表。发现用命令执行之后是下面的样子: 心里琢磨了下,整一个界面玩一下吧。然后就做成下面这个样子了。 下载列表 本文地址仓库:https://github.com/Rynxiao/yh-tools,如果喜欢,欢迎star. Express 后端服务 Webpack 模块化编译工具 Nginx 主要做文件gzip压缩(发现Express添加gzip有点问题,才弃坑nginx) Ant-design 前端UI库 React +
要使用WebSocket推送视频,可以通过使用ffmpeg和jsmpeg.js进行实现。首先,使用ffmpeg将视频转码为MPEG格式。然后,使用WebSocket将转码后的视频发送到客户端。在客户端上,使用jsmpeg.js来接收和解码视频,并在网页上播放。这样就可以通过WebSocket推送视频了。 具体步骤如下: 1. 使用ffmpeg将视频转码为MPEG格式:使用ffmpeg命令行工具,将输入的视频转码为MPEG格式,例如: ffmpeg -i input_stream -c:v mpeg1video -s 640x480 -b:v 1000k -r 30 -f mpegts output.mpg 这将把输入的视频转码为MPEG格式,并输出到output.mpg文件中。 2. 在服务器端设置WebSocket服务器:使用Node.js等后端技术,在服务器端设置WebSocket服务器,接收来自ffmpeg的输出,并将数据推送给客户端。可以使用websocket库来实现WebSocket服务器的搭建。 3. 在客户端使用jsmpeg.js接收和解码视频:在客户端的网页中引入jsmpeg.js库,并创建一个WebSocket连接,连接到WebSocket服务器。然后,使用jsmpeg.js来接收和解码视频,并将解码后的视频渲染到网页上的video元素中。可以使用video.js等视频播放库来更好地控制和展示视频。 通过以上步骤,就可以使用WebSocket推送视频了。这样,客户端就可以实时接收和播放来自服务器的视频。请注意,为了兼容性和性能考虑,建议在客户端使用现代浏览器,并确保浏览器支持WebSocket和jsmpeg.js所需的功能。