プロジェクトのニーズにより、Web 側に記録機能を実装する必要があります。当初、解決策は 2 つ見つかりました。1 つは iframe を使用する方法、もう 1 つは html5 の getUserMedia API を使用する方法でした。録画機能は IE ブラウザと互換性がある必要がないため、迷わず html5 が提供する getUserMedia を選択して実装しました。基本的なアイデアは、公式 API ドキュメントとオンラインで見つかるいくつかのソリューションを組み合わせて、プロジェクトのニーズに合ったソリューションを作成することです。ただし、パッドと PC の両方で同時に録音機能をオンにできるようにする必要があるため、いくつかの落とし穴もあります。以下はプロセスの復元です。
ステップ1新しい API は navigator.mediaDevices.getUserMedia を渡して Promise を返すためです。
旧APIはnavigator.getUserMediaなので互換性を持たせました。コードは次のとおりです。
// 古いブラウザは mediaDevices をまったく実装していない可能性があるため、最初に空のオブジェクトを設定できます if (navigator.mediaDevices === unknown) { navigator.mediaDevices = {};}// 一部のブラウザは mediaDevices を部分的にサポートしています。既存のプロパティを上書きする可能性があるため、オブジェクトに getUserMedia// を直接設定することはできません。ここでは、getUserMedia 属性が存在しない場合にのみ追加します。 if (navigator.mediaDevices.getUserMedia === 未定義) { let getUserMedia = navigator.webkitGetUserMedia || navigator.msGetUserMedia = function(constraints) { //まず、getUserMedia がある場合はそれを取得します。 // 一部のブラウザーはそれをまったく実装していません。その後、統一インターフェイスを維持するために Promise の拒否にエラーを返します。 if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia isこのブラウザには実装されていません')); } // それ以外の場合は、古い navigator.getUserMedia メソッドの Promise をラップ return new Promise(function(resolve,拒否) { getUserMedia.call(ナビゲーター、制約、解決、拒否) };ステップ2
これはインターネット上に存在する、HZRecorder をカプセル化するメソッドです。基本的にはこの方法を参考にしています。記録インターフェイスは、HZRecorder.get を呼び出すことで呼び出すことができます。このメソッドは、新しい HZRecorder の後にコールバック関数を実行し、実体化された HZRecorder オブジェクトを渡します。録音の開始、一時停止、停止、再生などの機能は、このオブジェクトのメソッドを通じて実装できます。
var HZRecorder = function (stream, config) { config = config || config.sampleBits = config.sampleBits 8; // サンプリングビット 8、16 config.sampleRate || (44100 / 6) ; //サンプリングレート (1/6 44100) //オーディオ環境オブジェクトを作成します audioContext = window.AudioContext || window.webkitAudioContext; var context = new audioContext(); // このオブジェクトにサウンドを入力します var audioInput = context.createMediaStreamSource(stream); .connect(volume); //サウンドをキャッシュするためのキャッシュを作成します varbufferSize = 4096; //サウンド キャッシュ ノードを作成します、createScriptProcessor メソッド // 2 番目と 3 番目のパラメータは、入力と出力の両方がバイノーラルであることを示します。 var records = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 //録音ファイルの長さ、バッファー: [] //録音キャッシュ、inputSampleRate: context.sampleRate //入力サンプリング レート、inputSampleBits: 16 //入力サンプリング桁 8、16、outputSampleRate: config.sampleRate //出力サンプリング レート、oututSampleBits: config.sampleBits //出力サンプルビット 8、16、入力: function (data) { this.buffer.push(new Float32Array(data)) }、compress: function () { / /マージ圧縮//マージ var data = new Float32Array(this.size); var offset = 0; for (var i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset); offset += this.buffer[i].length; } //圧縮 var COMPLEX(this.inputSampleRate / this.outputSampleRate); / 圧縮; var result = new Float32Array(length); var インデックス = 0, j = 0; while (index < length) { result[index] = data[j];インデックス++; } 結果を返します; } 、encodeWAV: function () { var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); .compress(); var dataLength = bytes.length * (サンプルビット / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); //Mono var offset = 0; var writeString = function (str) { for (var i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)) } }; // リソース交換ファイル識別子 writeString('RIFF'); offset += 4; //次のアドレスからファイルの末尾までの合計バイト数、つまりファイルサイズ -8 data.setUint32(offset, 36 + dataLength, true); /WAV ファイルフラグ writeString(' WAVE'); // 波形フォーマットフラグ writeString('fmt '); // フィルタバイト、通常は 0x10 = 16; data.setUint32(offset, 16, true); offset += 4; // フォーマットカテゴリ(PCM フォーマットのサンプリングデータ) data.setUint16(offset, 1, true); // チャンネル数 data.setUint16 ( offset, channelCount, true); // サンプリング レート、1 秒あたりのサンプル数は、各チャンネルの再生速度を表します。 data.setUint32(offset, sampleRate, true); offset += 4; //波形データ転送速度 (1 秒あたりの平均バイト数) モノラル × データ ビット/秒 × サンプルあたりのデータ ビット/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true ); offset += 4; // 高速データ調整数サンプリングが一度に占有するバイト数は、mono × サンプルあたりのデータ ビット数/8 data.setUint16(offset, channelCount *) (sampleBits / 8), true); // サンプルごとのデータ ビット数 data.setUint16(offset, sampleBits, true) // データ識別子 writeString('data'); += 4; //サンプルデータの総数、つまり総データサイズ -44 data.setUint32(offset, dataLength, true) //サンプルデータを書き込む if (sampleBits) === 8) { for (var i = 0; i < bytes.length; i++, offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); s < 0 ? s * 0x8000 : s * 0x7FFF; val = parseInt(255 / (65535 / (val + 32768))); data.setInt8(offset, val, true); } } else { for (var i = 0; i < bytes.length; i++, offset += 2) { var s = Math.max(-1) , Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } } return new Blob([data], { type: 'audio/wav' }) } }; // 録音を開始します this.start = function () { audioInput.connect(recorder); connect(context.destination) }; // this.stop = function () { register.disconnect() }; // this.end = function() を終了します。 }; //継続 this.again = function() { Recorder.connect(context.destination) }; // 音声ファイルを取得します this.getBlob = function () { this.stop(); ; }; //再生 this.play = function (audio) { audio.src = window.URL.createObjectURL(this.getBlob()) }; // アップロード this.upload = function (url, callback) { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); if (コールバック) { xhr.upload.addEventListener('progress', function (e ) { callback('アップロード', e) }, xhr.addEventListener('ロード', function (e) { callback('ok', e); }, false); xhr.addEventListener('error', function (e) { callback('error', e); }, false); (e) { callback('キャンセル', e); } xhr.open('POST', url); //オーディオコレクションrecorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); // 例外をスローします。 .throwError = function (メッセージ) { throw new function () { this.toString = function () { return メッセージ };}; //録音がサポートされているかどうか HZRecorder.canRecording = (navigator.getUserMedia != null); //レコーダーを取得 HZRecorder.get = function (callback, config) { if (callback) { navigator.mediaDevices .getUserMedia({ audio: true }) .then(function(stream) { let rec = new HZRecorder(stream, config); callback(rec); }) .catch(function(error) { HZRecorder.throwError('録画できません。デバイスのステータスを確認してください'); }); window.HZRecorder = HZRecorder;
上記でほとんどのニーズはすでに満たされています。ただし、パッド側との互換性が必要です。パッドには解決しなければならない問題がいくつかあります。
以下に両方の問題に対する解決策を示します。
ステップ3以下は、録音形式を mp3 として実装し、blob データを渡してパッド側でエラーを報告する window.URL.createObjectURL を実装するための解決策です。
1. HZRecorder の audioData オブジェクト コードを変更します。そしてインターネット上の偉い人からjsファイルlamejs.jsを紹介します
const lame = new lamejs();let audioData = {samplesMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], size : 0, // 録音ファイル長バッファ: [], //録音バッファ inputSampleRate: context.sampleRate, // 入力サンプリング レート inputSampleBits: 16, // 入力サンプル桁 8, 16 OutputSampleRate: config.sampleRate, // 出力サンプル レート OutputSampleBits: config.sampleBits, // 出力サンプル桁 8, 16 ConvertBuffer: function(arrayBuffer) { let data = new Float32Array(arrayBuffer); let out = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(data, out); return out; }, floatTo16BitPCM: for (let i = 0; i < input.長さ; i++) { let s = Math.max(-1, Math.min(1, input[i])); = s < 0 ? s * 0x8000 : s * 0x7fff; } }、appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf)) }、encode: function(arrayBuffer) { this.samplesMono = this .convertBuffer(arrayBuffer); 残り = this.samplesMono.length; (let i = 0; 残り >= 0; i += this.maxSamples) { let left = this.samplesMono.subarray(i, i + this.maxSamples); let mp3buf = this.mp3Encoder.encodeBuffer(left); .appendToBuffer(mp3buf); 残り -= this.maxSamples } }、終了: function() { this.appendToBuffer(this.mp3Encoder.flush()); return new Blob(this.dataBuffer, { type: 'audio/mp3' }) }; input: function(data) { this.buffer.push(new Float32Array() data)); this.size += data.length; }, compress: function() { // 圧縮をマージします // let data = new をマージしますFloat32Array(this.size); let offset = 0; for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset += this.buffer); [i].length; } // 圧縮 let COMPLEX = parseInt(this.inputSampleRate / this.outputSampleRate, 10); Float32Array(長さ); let j = 0; (インデックス < 長さ) { 結果 [インデックス] = j += 圧縮 }、 encodeWAV: { let サンプルレート = Math.min(this.inputSampleRate, this.outputSampleRate); let サンプルビット = Math.min(this.inputSampleBits, this.oututSampleBits); let bytes = this.compress(); let dataLength = bytes.length * (sampleBits / 8); let data = new DataView(buffer); 1; //mono let offset = 0; let writeString = function(str) { for (let i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)) } }; // リソース交換ファイル識別子 writeString('RIFF'); // 次のアドレスから末尾までの合計バイト数ファイル番号、つまりファイルサイズ - 8 data.setUint32(offset, 36 + dataLength, true); // WAV ファイルフラグ writeString('WAVE'); // 波形フォーマット フラグ writeString('fmt '); // フィルタ バイト、通常 0x10 = 16 data.setUint32(offset, 16, true); // フォーマット カテゴリ (PCM 形式のサンプリング) data) data.setUint16(offset, 1, true); offset += 2; // チャネル番号 data.setUint16(offset, channelCount, true); += 2; // サンプリング レート、1 秒あたりのサンプル数、各チャンネルの再生速度を示す data.setUint32(offset, sampleRate, true) // 波形データ送信レート (平均バイト数); 1 秒あたり) モノラル × 1 秒あたりのデータ ビット数 × サンプルあたりのデータ ビット数 / 8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), offset += 4; 1 つのサンプル モノラル チャネルが占めるバイト数の高速データ調整 Number data.setUint16(offset, sampleBits, true) // データ識別子 writeString('data'); // の合計数サンプリングされたデータ、つまり合計データ サイズ - 44 data.setUint32(offset, dataLength, true); offset += 4; // サンプルデータを書き込む if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, bytes[i])); val = s < 0 ? s * 0x8000 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true) } } else { for (let i = 0; i < bytes.length; i++ 、オフセット += 2) { const s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); } } return new Blob([data], { type: 'audio/wav' });
2. HZRecordのオーディオコレクションの呼び出し方法を変更します。
// オーディオコレクション Recorder.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. HZRecord の getBlob メソッド。
this.getBlob = function() { this.stop(); return audioData.finish();};
4. HZRecordの再生方法。 BLOB を Base64URL に変換します。
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { let fileReader = new FileReader() = function(e) { callback(e) .target.result); ファイルリーダー.readAsDataURL(データ);}
これまでのところ、上記 2 つの問題は解決されています。
ステップ4ここでは主に、録音時にダイナミックな効果を与える方法を紹介します。アニメーション要件の 1 つは次のとおりです。
流入するボリュームに応じて、円弧が動的に拡張されます。
//アナライザー ノードを作成し、オーディオの時間と周波数のデータを取得します const Analyser = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const WrapEle = $this.refs['wrap'] ; ctx = WrapEle.getContext('2d');const幅=wrapEle.width;const高さ= WrapEle.height;const center = { x: 幅 / 2, y: 高さ / 2}; 関数drawArc(ctx, color, x, y, radius, beginAngle, endAngle) { ctx.beginPath(); ; ctx.ストロークスタイル = カラー; ctx.arc(x, y, 半径, (Math.PI * beginAngle) / 180, (Math.PI * endAngle) / 180); ctx.ストローク();}(functiondrawSpectrum() {analyser.getByteFrequencyData(inputAnalyser); // 周波数領域データを取得します ctx.clearRect(0, 0, width, height); // (let i = 0; i < 1; i++) { let value = inputAnalyser[i] // 3; の線を描画します。 <===データを取得 let Colors = []; if (value <= 16) { Colors = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } else if (値 <= 32) { 色 = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4']; } else { 色 = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 '、'#f5A631'、 '#f5A631']; }drawArc(ctx, color[0], center.x, center.y, 52 + 16, -30, 30); 、52 + 16、150、210);drawArc(ctx、colors[2]、center.x、center.y、52 + 32、 -22.5, 22.5);drawArc(ctx, color[3], center.x, center.y, 52 + 32, 157.5, 202.5); + 48、-13、13);drawArc(ctx、colors[5]、center.x、center.y、52 + 48, 167, 193); } // 次のフレームをリクエストします requestAnimationFrame(drawSpectrum);})();運命の終わり
この時点で、完全な html5 録画機能ソリューションが完成しました。何か追加が必要な場合は、無理がある場合はメッセージを残してください。
ps:lamejsはこのgithubを参照できます
以上がこの記事の全内容です。皆様の学習のお役に立てれば幸いです。また、VeVb Wulin Network をご支援いただければ幸いです。