نظرًا لاحتياجات المشروع، نحتاج إلى تنفيذ وظيفة التسجيل على جانب الويب. في البداية، تم العثور على حلين، أحدهما كان من خلال iframe، والآخر كان getUserMedia api الخاص بـ html5. نظرًا لأن وظيفة التسجيل لدينا لا تحتاج إلى أن تكون متوافقة مع متصفح IE، فلم نتردد في اختيار getUserMedia المقدم من html5 لتنفيذها. الفكرة الأساسية هي دمجها مع وثائق واجهة برمجة التطبيقات الرسمية وبعض الحلول الموجودة عبر الإنترنت لإيجاد حل يناسب احتياجات المشروع. ولكن نظرًا لأنه يتعين علينا التأكد من إمكانية تشغيل وظيفة التسجيل على كل من اللوحة والكمبيوتر الشخصي في نفس الوقت، فهناك أيضًا بعض المخاطر. ما يلي هو استعادة العملية.
الخطوة 1لأن واجهة برمجة التطبيقات الجديدة تمر عبر navigator.mediaDevices.getUserMedia وترجع وعدًا.
واجهة برمجة التطبيقات القديمة هي navigator.getUserMedia، لذلك تم التوافق. الرمز هو كما يلي:
// قد لا تقوم المتصفحات القديمة بتنفيذ أجهزة الوسائط على الإطلاق، لذا يمكننا تعيين كائن فارغ أولاً if (navigator.mediaDevices === uncategorized) { navigator.mediaDevices = {};}// تدعم بعض المتصفحات أجهزة الوسائط جزئيًا. لا يمكننا تعيين getUserMedia// مباشرة على الكائن لأن هذا قد يحل محل الخصائص الموجودة. سنضيف هنا سمة getUserMedia فقط في حالة عدم وجودها. if (navigator.mediaDevices.getUserMedia === unتعريف) { Let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||. navigator.mozGetUserMedia ||. أولاً، إذا كان هناك getUserMedia، فاحصل عليه // بعض المتصفحات لا تنفذه على الإطلاق - ثم قم بإرجاع خطأ إلى رفض الوعد للحفاظ على واجهة موحدة if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is لم يتم تنفيذه في هذا المتصفح') } // بخلاف ذلك، قم بلف وعد لطريقة navigator.getUserMedia القديمة وإرجاع Promise(function(resolve, رفض) { getUserMedia.call(navigator, القيود, العزم, رفض });الخطوة 2
هذه طريقة موجودة على الإنترنت، وهي تتضمن ملف HZRecorder. الرجوع أساسا إلى هذه الطريقة. يمكن استدعاء واجهة التسجيل عن طريق استدعاء HZRecorder.get. تمر هذه الطريقة في وظيفة رد اتصال بعد HZRecorder الجديد، يتم تنفيذ وظيفة رد الاتصال ويتم تمرير كائن HZRecorder الفعلي. يمكن تنفيذ وظائف مثل بدء التسجيل والإيقاف المؤقت والإيقاف والتشغيل من خلال أساليب هذا الكائن.
var HZRecorder = function (stream, config) { config = config ||. {}; config.sampleBits = config.sampleBits ||. // معدل أخذ العينات (1/6 44100) // إنشاء كائن بيئة صوتية audioContext = window.AudioContext ||. window.webkitAudioContext; var context = new audioContext(); // إدخال الصوت في هذا الكائن var audioInput = context.createMediaStreamSource(stream); .connect(volume); // إنشاء ذاكرة تخزين مؤقت لتخزين الصوت مؤقتًا var bufferSize = 4096; // إنشاء عقدة ذاكرة تخزين مؤقت للصوت، وطريقة createScriptProcessor // تشير المعلمتان الثانية والثالثة إلى كون المدخلات والمخرجات بكلتا الأذنين. var Recorder = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 // طول ملف التسجيل، المخزن المؤقت: [] // ذاكرة التخزين المؤقت للتسجيل، inputSampleRate: context.sampleRate // معدل أخذ عينات الإدخال، inputSampleBits: 16 // أرقام أخذ عينات الإدخال 8، 16،oututSampleRate: config.sampleRate //معدل أخذ عينات الإخراج، oututSampleBits: config.sampleBits // بتات عينة الإخراج 8، 16، الإدخال: function (data) { this.buffer.push(new Float32Array(data)); this.size += data. length },ضغط: 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 Compression = parseInt(this.inputSampleRate / this.outputSampleRate); / ضغط var result = new Float32Array(length); var Index = 0, j = 0; Index++; } return result; } , encodeWAV: function () { var SampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); .compress(); var dataLength = bytes. length * (sampleBits / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); varchannelCount = 1; //Mono var offset = 0; i++) { data.setUint8(offset + i, str.charCodeAt(i) } }; // معرف ملف تبادل الموارد writeString('RIFF'); Offset += 4; // إجمالي عدد البايتات من العنوان التالي إلى نهاية الملف، أي حجم الملف -8 data.setUint32(offset, 36 + dataLength, true offset += 4); / علامة ملف WAV writeString(' WAVE'); Offset += 4; // علامة تنسيق الشكل الموجي writeString('fmt '); data.setUint32(offset, 16, true); offset += 4; // فئة التنسيق (بيانات أخذ عينات تنسيق PCM) data.setUint16(offset, 1, true); // عدد القنوات data.setUint16 (إزاحة، رقم القناة، صحيح)؛ إزاحة += 2 // معدل أخذ العينات، عدد العينات في الثانية، يمثل سرعة التشغيل لكل قناة data.setUint32(offset, SampleRate, صحيح)؛ Offset += 4; // معدل نقل بيانات الشكل الموجي (متوسط البايتات في الثانية) أحادية × بتات البيانات في الثانية × بتات البيانات لكل عينة/8 data.setUint32(offset,channelCount * SampleRate * (sampleBits / 8)، صحيح)؛ Offset += 4; // عدد البايتات التي تشغلها عملية أخذ عينات أرقام ضبط البيانات السريعة في وقت واحد هو أحادي × عدد بتات البيانات لكل عينة/8 data.setUint16(offset,channelCount * (sampleBits / 8)، true)؛ offset += 2; // عدد بتات البيانات لكل عينة data.setUint16(offset, SampleBits, true); // معرف البيانات writeString('data'); += 4; // العدد الإجمالي للبيانات التي تم أخذ عينات منها، أي إجمالي حجم البيانات -44 data.setUint32(offset, dataLength, true offset += 4); === 8) { for (var i = 0; i < bytes.length; i++, offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); var val = 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' } } }); Connect(context.destination); }; //Stop this.stop = function () { Recorder.disconnect() }; }; // تابع this.again = function() { Recorder.connect(context.destination }); }; // تشغيل this.play = function (audio) { audio.src = window.URL.createObjectURL(this.getBlob()) }; { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); ) { رد الاتصال ('uploading'، e }، false)؛ callback('ok', e); }, false); xhr.addEventListener('error', function (e) { callback('error', e); }, xhr.addEventListener('abort', function (e) { رد الاتصال ('cancel'، e }، false } xhr.open('POST', url); // مجموعة الصوت Recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0) }; .throwError = function (message) { throw new function () { this.toString = function () { return message };} }; // ما إذا كان التسجيل مدعومًا 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 وwindow.URL.createObjectURL لتمرير بيانات blob والإبلاغ عن خطأ على جانب اللوحة.
1. قم بتعديل كود كائن audioData في HZRecorder. وإدخال ملف 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: [], الحجم : 0، // المخزن المؤقت لطول ملف التسجيل: []، // تسجيل المخزن المؤقت inputSampleRate: context.sampleRate، // معدل أخذ العينات المدخلات inputSampleBits: 16، // أرقام عينة الإدخال 8، 16 OutputSampleRate: config.sampleRate، // معدل عينة الإخراج inputSampleBits: config.sampleBits، // أرقام عينة الإخراج 8، 16 ConvertBuffer: function(arrayBuffer) { Let data = new Float32Array(arrayBuffer); length; 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); Let left = 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; } }, النهاية: الوظيفة() { 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 },ضغط: function() { // دمج الضغط // دمج البيانات = 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 } // السماح بالضغط = parseInt(this.inputSampleRate / this.outputSampleRate, 10); Float32Array(length); Let Index = 0; while (index < length) { result[index] = data[j]; { Let SampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); this.oututSampleBits); Let bytes = this.compress(); Let dataLength = bytes. length * (sampleBits / 8); 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); Offset += 4; // علامة تنسيق الموجة writeString('fmt'); // تصفية البايتات، بشكل عام 0x10 = 16 data.setUint32(offset, 16, true); // فئة التنسيق (أخذ عينات من نموذج PCM data) data.setUint16(offset, 1, true); offset += 2; // رقم القناة data.setUint16(offset,channelCount, true); += 2; // معدل أخذ العينات، عدد العينات في الثانية، مما يشير إلى سرعة تشغيل كل قناة data.setUint32(offset, SampleRate, true); Offset += 4; في الثانية) أحادية × بتات البيانات في الثانية × بتات البيانات لكل عينة / 8 data.setUint32(offset,channelCount * SampleRate * (sampleBits / 8, true offset += 4); عدد البايتات الذي يتم ضبطه بسرعة بواسطة قناة أحادية لعينة واحدة 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, بايت[i])); Let val = s < 0 ? s * 0x8000 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); , إزاحة += 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. طريقة getBlob الخاصة بـ HZRecord.
this.getBlob = function() { this.stop(); return audioData.finish();};
4. طريقة اللعب في HZRecord. تحويل النقطة إلى base64url.
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { Let fileReader = new FileReader(); fileReader.onload = function(e) { callback(e .target.result); fileReader.readAsDataURL(data);}
وحتى الآن، تم حل المشكلتين المذكورتين أعلاه.
الخطوة 4نقدم هنا بشكل أساسي كيفية إنشاء تأثيرات ديناميكية أثناء التسجيل. أحد متطلبات الرسوم المتحركة لدينا هو:
وفقًا للحجم الوارد، يتم توسيع القوس الدائري ديناميكيًا.
// أنشئ عقدة محلل واحصل على بيانات الوقت والتردد الصوتي const analyser = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const WrapEle = $this.refs['wrap'] Let ctx = WrapEle.getContext('2d');const width = WrapEle.width;const height = WrapEle.height;const center = { x: width / 2, y: height / 2}; function drawArc(ctx, color, x, y, radius, beginAngle, endAngle) { ctx.beginPath(); ctx.strokeStyle = color; ctx.arc(x, y, radius, (Math.PI * beginAngle) / 180, (Math.PI * endAngle) / 180); ctx.stroke();}(function drawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // الحصول على بيانات مجال التردد ctx.clearRect(0, 0, width, height); // ارسم خطوطًا for (let i = 0; i < 1; i++) { Let value = inputAnalyser[i] / 3; <===احصل على البيانات، دع الألوان = []؛ if (value <= 16) {colors = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4'] } else if (value <= 32) {colors = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4']; } else {colors = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631'] } drawArc(ctx,colors[0], center.x, center.y, 52 + 16, -30, 30); drawArc(ctx,colors[1], center.x, center.y , 52 + 16, 150, 210); drawArc(ctx,colors[2], center.x, center.y, 52 + 32, -22.5, 22.5); drawArc(ctx,colors[3], center.x, center.y, 52 + 32, 157.5, 202.5); drawArc(ctx,colors[4], center.x, center.y, 52 + 48, -13, 13); drawArc(ctx,colors[5], center.x, center.y, 52 + 48, 167, 193); } // طلب الإطار التالي requestAnimationFrame(drawSpectrum);})();نهاية القدر
في هذه المرحلة، تم الانتهاء من الحل الكامل لوظيفة تسجيل html5. إذا كان هناك أي شيء يحتاج إلى إضافته، يرجى ترك رسالة إذا كان هناك أي شيء غير معقول.
ملاحظة: يمكن أن يشير lamejs إلى هذا github
ما ورد أعلاه هو المحتوى الكامل لهذه المقالة وآمل أن يكون مفيدًا لدراسة الجميع وآمل أيضًا أن يدعم الجميع شبكة VeVb Wulin.