</
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://*.*.*.*:*/**"
)
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
=
[
]
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
>
function PCMPlayer(option) {
this.init(option);
PCMPlayer.prototype.init = function (option) {
var defaults = {
encoding: '16bitInt',
channels: 1,
sampleRate: 8000,
flushingTime: 1000
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);
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];
if (i < 50) {
audioData[i] = (audioData[i] * i) / 50;
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();
function pcmtoWav(pcmsrt, sampleRate, numChannels, bitsPerSample) {
let header = {
chunkId: [0x52, 0x49, 0x46, 0x46],
chunkSize: 0,
format: [0x57, 0x41, 0x56, 0x45],
subChunk1Id: [0x66, 0x6d, 0x74, 0x20],
subChunk1Size: 16,
audioFormat: 1,
numChannels: numChannels || 1,
sampleRate: sampleRate || 16000,
byteRate: 0,
blockAlign: 0,
bitsPerSample: bitsPerSample || 16,
subChunk2Id: [0x64, 0x61, 0x74, 0x61],
subChunk2Size: 0,
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
* 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所需的功能。