PageSourceSearch

https://h.land/js/HZrecorder.js

js h.land collected 2026-10-01 23:50:22 UTC 7,909 bytes, 253 lines download raw bytes

1(function(window) {
2  //兼容
3  window.URL = window.URL || window.webkitURL;
4  navigator.getUserMedia =
5    navigator.getUserMedia ||
6    navigator.webkitGetUserMedia ||
7    navigator.mozGetUserMedia ||
8    navigator.msGetUserMedia;
9
10  var HZRecorder = function(stream, config) {
11    config = config || {};
12    config.sampleBits = config.sampleBits || 8; //采样数位 8, 16
13    config.sampleRate = config.sampleRate || 44100 / 6; //采样率(1/6 44100)
14
15    var context = new (window.webkitAudioContext || window.AudioContext)();
16    var audioInput = context.createMediaStreamSource(stream);
17    var createScript =
18      context.createScriptProcessor || context.createJavaScriptNode;
19    var recorder = createScript.apply(context, [4096, 1, 1]);
20
21    var audioData = {
22      size: 0, //录音文件长度
23      buffer: [], //录音缓存
24      inputSampleRate: context.sampleRate, //输入采样率
25      inputSampleBits: 16, //输入采样数位 8, 16
26      outputSampleRate: config.sampleRate, //输出采样率
27      oututSampleBits: config.sampleBits, //输出采样数位 8, 16
28      input: function(data) {
29        this.buffer.push(new Float32Array(data));
30        this.size += data.length;
31      },
32      compress: function() {
33        //合并压缩
34        //合并
35        var data = new Float32Array(this.size);
36        var offset = 0;
37        for (var i = 0; i < this.buffer.length; i++) {
38          data.set(this.buffer[i], offset);
39          offset += this.buffer[i].length;
40        }
41        //压缩
42        var compression = parseInt(
43          this.inputSampleRate / this.outputSampleRate
44        );
45        var length = data.length / compression;
46        var result = new Float32Array(length);
47        var index = 0,
48          j = 0;
49        while (index < length) {
50          result[index] = data[j];
51          j += compression;
52          index++;
53        }
54        return result;
55      },
56      encodeWAV: function() {
57        var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
58        var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
59        var bytes = this.compress();
60        var dataLength = bytes.length * (sampleBits / 8);
61        var buffer = new ArrayBuffer(44 + dataLength);
62        var data = new DataView(buffer);
63
64        var channelCount = 1; //单声道
65        var offset = 0;
66
67        var writeString = function(str) {
68          for (var i = 0; i < str.length; i++) {
69            data.setUint8(offset + i, str.charCodeAt(i));
70          }
71        };
72
73        // 资源交换文件标识符
74        writeString("RIFF");
75        offset += 4;
76        // 下个地址开始到文件尾总字节数,即文件大小-8
77        data.setUint32(offset, 36 + dataLength, true);
78        offset += 4;
79        // WAV文件标志
80        writeString("WAVE");
81        offset += 4;
82        // 波形格式标志
83        writeString("fmt ");
84        offset += 4;
85        // 过滤字节,一般为 0x10 = 16
86        data.setUint32(offset, 16, true);
87        offset += 4;
88        // 格式类别 (PCM形式采样数据)
89        data.setUint16(offset, 1, true);
90        offset += 2;
91        // 通道数
92        data.setUint16(offset, channelCount, true);
93        offset += 2;
94        // 采样率,每秒样本数,表示每个通道的播放速度
95        data.setUint32(offset, sampleRate, true);
96        offset += 4;
97        // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
98        data.setUint32(
99          offset,
100          channelCount * sampleRate * (sampleBits / 8),
101          true
102        );
103        offset += 4;
104        // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
105        data.setUint16(offset, channelCount * (sampleBits / 8), true);
106        offset += 2;
107        // 每样本数据位数
108        data.setUint16(offset, sampleBits, true);
109        offset += 2;
110        // 数据标识符
111        writeString("data");
112        offset += 4;
113        // 采样数据总数,即数据总大小-44
114        data.setUint32(offset, dataLength, true);
115        offset += 4;
116        // 写入采样数据
117        if (sampleBits === 8) {
118          for (var i = 0; i < bytes.length; i++, offset++) {
119            var s = Math.max(-1, Math.min(1, bytes[i]));
120            var val = s < 0 ? s * 0x8000 : s * 0x7fff;
121            val = parseInt(255 / (65535 / (val + 32768)));
122            data.setInt8(offset, val, true);
123          }
124        } else {
125          for (var i = 0; i < bytes.length; i++, offset += 2) {
126            var s = Math.max(-1, Math.min(1, bytes[i]));
127            data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
128          }
129        }
130
131        return new Blob([data], { type: "audio/wav" });
132      }
133    };
134
135    //开始录音
136    this.start = function() {
137      audioInput.connect(recorder);
138      recorder.connect(context.destination);
139    };
140
141    //停止
142    this.stop = function() {
143      recorder.disconnect();
144    };
145
146    //获取音频文件
147    this.getBlob = function() {
148      this.stop();
149      return audioData.encodeWAV();
150    };
151
152    //回放
153    this.play = function(audio) {
154      audio.src = window.URL.createObjectURL(this.getBlob());
155    };
156
157    //上传
158    this.upload = function(url, callback) {
159      var fd = new FormData();
160      fd.append("audioData", this.getBlob());
161      var xhr = new XMLHttpRequest();
162      if (callback) {
163        xhr.upload.addEventListener(
164          "progress",
165          function(e) {
166            callback("uploading", e);
167          },
168          false
169        );
170        xhr.addEventListener(
171          "load",
172          function(e) {
173            callback("ok", e);
174          },
175          false
176        );
177        xhr.addEventListener(
178          "error",
179          function(e) {
180            callback("error", e);
181          },
182          false
183        );
184        xhr.addEventListener(
185          "abort",
186          function(e) {
187            callback("cancel", e);
188          },
189          false
190        );
191      }
192      xhr.open("POST", url);
193      xhr.send(fd);
194    };
195
196    //音频采集
197    recorder.onaudioprocess = function(e) {
198      audioData.input(e.inputBuffer.getChannelData(0));
199      //record(e.inputBuffer.getChannelData(0));
200    };
201  };
202  //抛出异常
203  HZRecorder.throwError = function(message) {
204    alert(message);
205    throw new (function() {
206      this.toString = function() {
207        return message;
208      };
209    })();
210  };
211  //是否支持录音
212  HZRecorder.canRecording = navigator.getUserMedia != null;
213  //获取录音机
214  HZRecorder.get = function(callback, config) {
215    if (callback) {
216      if (navigator.getUserMedia) {
217        navigator.getUserMedia(
218          { audio: true }, //只启用音频
219          function(stream) {
220            var rec = new HZRecorder(stream, config);
221            callback(rec);
222          },
223          function(error) {
224            switch (error.code || error.name) {
225              case "PERMISSION_DENIED":
226              case "PermissionDeniedError":
227                HZRecorder.throwError("用户拒绝提供信息。");
228                break;
229              case "NOT_SUPPORTED_ERROR":
230              case "NotSupportedError":
231                HZRecorder.throwError("浏览器不支持硬件设备。");
232                break;
233              case "MANDATORY_UNSATISFIED_ERROR":
234              case "MandatoryUnsatisfiedError":
235                HZRecorder.throwError("无法发现指定的硬件设备。");
236                break;
237              default:
238                HZRecorder.throwError(
239                  "无法打开麦克风。异常信息:" + (error.code || error.name)
240                );
241                break;
242            }
243          }
244        );
245      } else {
246        HZRecorder.throwErr("当前浏览器不支持录音功能。");
247        return;
248      }
249    }
250  };
251
252  window.HZRecorder = HZRecorder;
253})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.