Unity WebGL基于js通信实现网页录音

由于Unity发布WebGL后无法使用Unity中的Microphone,所以无法进行录音,只能借助与网页进行录音操作,因此基于Unity新版页面对接方式jslib实现页面录音。可以将录音数据回传至unity,并在unity中进行播放。理论上支持无限制录音。

UnityWebGLMicrophone1.0.1 插件下载地址

插件介绍

在请求网页权限后,可以选择录制时间或者循环录制,然后开始录制。之后会在页面中进行分段录制,并回传至unity,结束录制后进行整合,并转换成AudioClip进行播放。

运行效果图

主要代码

js部分:

  1. 获取权限
 Init: function (_GameObjectName, _recordTime) {//console.log("MicrophoneInit:");unityGameObjectName = Pointer_stringify(_GameObjectName);//转换unity传递过来的字符串navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;window.AudioContext = window.AudioContext || window.webkitAudioContext;var maxRecordTime = _recordTime;if (navigator.getUserMedia) {const constraints = { audio: true };navigator.getUserMedia(constraints,function (stream) {console.log("授权成功!");isUserMedia = "true";recorderState = "inactive";//  bufferSize: the onaudioprocess event is called when the buffer is fullvar bufferSize = 4096;//channelCount = 2;channelCount = 1;sampleBits = 16;// creates the audio contextaudioContext = new AudioContext();originalSampleRate = audioContext.sampleRate;//48000// creates an audio node from the microphone incoming streammediaStream = audioContext.createMediaStreamSource(stream);volume = audioContext.createGain();for (var channel = 0; channel < channelCount; channel++) {inputBuffer[channel] = [];}recordingLength = 0;recordedLength = 0;recordedResultLength = 0;var maxRecordLength = maxRecordTime * originalSampleRate;if (audioContext.createScriptProcessor) {//  创建一个可以通过JavaScript直接处理音频的ScriptProcessorNode.scriptProcessor = audioContext.createScriptProcessor(bufferSize,channelCount,channelCount);}else {// 创建一个 JavaScriptNode, 用于javascript直接处理音频。 这个方法已经被AudioContext.createScriptProcessor()替代并且废弃。scriptProcessor = audioContext.createJavaScriptNode(bufferSize,channelCount,channelCount);}scriptProcessor.onaudioprocess = function (audioProcessingEvent) {for (var channel = 0; channel < channelCount; channel++) {inputBuffer[channel].push(audioProcessingEvent.inputBuffer.getChannelData(channel));}recordingLength += bufferSize;recordedLength += bufferSize;//录制长度超出界限multipleRecording(maxRecordLength);};},function (err) {console.error("授权失败!", err);isUserMedia = "false";recorderState = "Don'tGetUserMedia";switch (error.code || error.name) {case 'PERMISSION_DENIED':case 'PermissionDeniedError':console.error('用户拒绝提供信息。');break;case 'NOT_SUPPORTED_ERROR':case 'NotSupportedError':console.error('浏览器不支持硬件设备。');break;case 'MANDATORY_UNSATISFIED_ERROR':case 'MandatoryUnsatisfiedError':console.error('无法发现指定的硬件设备。');break;default:console.error('无法打开麦克风。异常信息:' + (error.code || error.name));break;}});}else {isUserMedia = "false";recorderState = "Don'tGetUserMedia";console.error("浏览器不支持 getUserMedia");}},
  1. 发送数据
  //发送数据$sendWAVData: function (blob, sign) {var reader = new FileReader();reader.onload = function (e) {var _value = reader.result;var _partLength = 8192;var _length = parseInt(_value.length / _partLength);if (_length * _partLength < _value.length)_length += 1;var _head = "Head|" + _length.toString() + "|" + _value.length.toString() + "|" + sign;SendMessage(unityGameObjectName, "GetAudioData", _head);for (var i = 0; i < _length; i++) {var _sendValue = "";if (i < _length - 1) {_sendValue = _value.substr(i * _partLength, _partLength);}else {_sendValue = _value.substr(i * _partLength, _value.length - i * _partLength);}_sendValue = "Part|" + i.toString() + "|" + _sendValue;SendMessage(unityGameObjectName, "GetAudioData", _sendValue);}if (sign === "end")recorderState = "inactive";_value = null;}reader.readAsDataURL(blob);},
  1. 停止录制
StopRecording: function () {if (isUserMedia !== "true") {recorderState = "Don'tGetUserMedia";SendMessage(unityGameObjectName, "OnWarning", recorderState);return;}if (recorderState === "completed")return;if (recorderState !== "inactive") {mediaStream.disconnect();scriptProcessor.disconnect();recorderState = "completed";//console.log("停止录音,转码数据中..." + recorderState);SendMessage(unityGameObjectName, "OnStop", recorderState);var _data = getPureWavData(0);sendWAVData(_data, "part");_data = [];var _headerData = getWAVHeaderData(recordedResultLength);sendWAVData(_headerData, "end");_headerData = [];}elseconsole.log("未开始录音..." + recorderState);},

4.自动结束录制/重复录制

 //超出限制自动结束录制$autoStopRecording: function () {mediaStream.disconnect();scriptProcessor.disconnect();recorderState = "autoCompleted";//console.log("自动停止录音,转码数据中..." + recorderState);SendMessage(unityGameObjectName, "OnStop", recorderState);var _data = getPureWavData(0);sendWAVData(_data, "part");_data = [];var _headerData = getWAVHeaderData(recordedResultLength);sendWAVData(_headerData, "end");_headerData = [];},//重复录制 - 发送数据$multipleRecording: function (maxRecordLength) {if (!loop) {if (maxRecordLength >= totalRecordLength) {if (recordingLength >= totalRecordLength) {autoStopRecording();}}else {if (recordingLength >= currentRecordLength) {autoStopRecording();}else if (recordingLength >= maxRecordLength) {var _data = getPureWavData(0);currentRecordLength = currentRecordLength - maxRecordLength;sendWAVData(_data, "part");_data = [];}}}else {maxRecordLength = maxRecordLength > totalRecordLength ? totalRecordLength : maxRecordLength;if (recordingLength >= maxRecordLength) {var _data = getPureWavData(0);sendWAVData(_data, "part");_data = [];}}},

C#部分

  1. WebGL Microphone
 public sealed class Microphone{/// <summary>/// 录制回调/// </summary>public class MicrophoneEvent : UnityEvent { }/// <summary>/// 停止录制回调/// </summary>public class MicrophoneStopEvent : UnityEvent<bool> { }public Microphone(){}#if UNITY_WEBGL && !UNITY_EDITOR#region/// <summary>/// 初始化音频/// </summary>/// <param name="_unityName">数据交互物体名</param>/// <param name="_recordTime">单次最大可录制时间</param>[DllImport("__Internal")]internal static extern void Init(string _unityName,int _recordTime);/// <summary>/// 是否可录制/// </summary>[DllImport("__Internal")]private static extern bool IsCanRecording();/// <summary>/// 正在录制/// </summary>/// <returns></returns>[DllImport("__Internal")]private static extern bool IsRecord();/// <summary>/// 暂停录制/// </summary>/// <returns></returns>[DllImport("__Internal")]private static extern bool IsPauseRecording();/// <summary>/// 已录制时间(/s)/// Tip: 开始录制时重置为0,取消录制时重置为0/// </summary>/// <returns></returns>[DllImport("__Internal")]private static extern float GetRecordedTime();/// <summary>/// 开始录制/// </summary>/// <param name="loop">是否循环</param>/// <param name="secondLength">录制长度(/s)</param>/// <param name="sampleRate">采样率</param>[DllImport("__Internal")]private static extern void StartRecording(int loop, int secondLength, int sampleRate);/// <summary>/// 暂停录制/// </summary>[DllImport("__Internal")]private static extern void PauseRecording();/// <summary>/// 继续录制/// </summary>[DllImport("__Internal")]private static extern void ResumeRecording();/// <summary>/// 停止录制/// </summary>[DllImport("__Internal")]private static extern void StopRecording();/// <summary>///取消录制/// </summary>[DllImport("__Internal")]private static extern void CancelRecording();/// <summary>///关闭录制权限/// </summary>[DllImport("__Internal")]private static extern void CloseRecording();/// <summary>/// 当前录制的名字/// </summary>public static string m_currentAudioName = string.Empty;/// <summary>/// 录制的结果/// </summary>public static AudioClip AudioClipResult { get { return WebGLMicrophoneManager.m_audioClip ?? null; } }/// <summary>/// 是否可录制/// </summary>public static bool IsRecordable { get { return IsCanRecording(); } }/// <summary>/// 正在录制/// </summary>/// <returns></returns>public static bool IsRecording { get { return IsRecord(); } }/// <summary>/// 暂停录制/// </summary>/// <returns></returns>public static bool IsPause { get { return IsPauseRecording(); } }/// <summary>/// 已录制时间(/s)/// Tip: 开始录制时重置为0,取消录制时重置为0/// </summary>/// <returns></returns>public static float RecordedTime { get { return GetRecordedTime(); } }/// <summary>///  请求权限,录制前必须请求权限/// </summary>public static void GetUserMedia(){WebGLMicrophoneManager.Instance.Init();}/// <summary>/// 开始录制/// </summary>/// <param name="audioName">生成的音频名字</param>/// <param name="loop">是否循环</param>/// <param name="secondLength">录制长度(/s)</param>/// <param name="sampleRate">采样率 8000/11025/22050/32000/44100/48000</param>//public static AudioClip Start(string audioName, bool loop = false, int secondLength = 60, int sampleRate = 44100)//{//    m_currentAudioName = audioName;//    StartRecording(loop == false ? 0 : 1, secondLength, sampleRate);//    Debug.Log("开始录制");//    Debug.Log(WebGLMicrophoneManager.m_audioClip != null ? WebGLMicrophoneManager.m_audioClip.length : -100);//    return WebGLMicrophoneManager.m_audioClip;//}public static void Start(string audioName, bool loop = false, int secondLength = 60, int sampleRate = 44100){m_currentAudioName = audioName;StartRecording(loop == false ? 0 : 1, secondLength, sampleRate);//Debug.Log("开始录制");}/// <summary>/// 暂停录制/// </summary>public static void Pause() { PauseRecording(); }/// <summary>/// 继续录制/// </summary>public static void Resume() { ResumeRecording(); }/// <summary>/// 停止录制/// </summary>public static void Stop() { StopRecording(); }/// <summary>///取消录制/// </summary>public static void Cancel() { CancelRecording(); }/// <summary>///关闭录制权限/// </summary>public static void Close() { CloseRecording(); }#endregion
#else#region/// <summary>/// 是否可录制/// </summary>public static bool IsRecordable;/// <summary>/// 正在录制/// </summary>/// <returns></returns>public static bool IsRecording;/// <summary>/// 暂停录制/// </summary>/// <returns></returns>public static bool IsPause;/// <summary>/// 已录制时间(/s)/// Tip: 开始录制时重置为0,取消录制时重置为0/// </summary>/// <returns></returns>public static float RecordedTime;/// <summary>/// 录制的结果/// </summary>public static AudioClip AudioClipResult { get; private set; }/// <summary>///  请求权限,录制前必须请求权限/// </summary>public static void GetUserMedia() { }/// <summary>/// 开始录制/// Tip: 当不循环录制时超时自动结束录制/// </summary>/// <param name="audioName">生成的音频名字</param>/// <param name="loop">是否循环</param>/// <param name="secondLength">录制长度(/s)</param>/// <param name="sampleRate">采样率 8000/11025/22050/32000/44100/48000</param>//public static AudioClip Start(string audioName, bool loop = false, int secondLength = 60, int sampleRate = 44100) { return null; }public static void Start(string audioName, bool loop = false, int secondLength = 60, int sampleRate = 44100) { }/// <summary>/// 暂停录制/// </summary>public static void Pause() { }/// <summary>/// 继续录制/// </summary>public static void Resume() { }/// <summary>/// 停止录制/// </summary>public static void Stop() { }/// <summary>///取消录制/// </summary>public static void Cancel() { }/// <summary>///关闭录制权限/// </summary>public static void Close() { }#endregion
#endif/// <summary>/// 开始录制回调/// </summary>public static MicrophoneEvent OnStartEvent = new MicrophoneEvent();/// <summary>/// 暂停录制回调/// </summary>public static MicrophoneEvent OnPauseEvent = new MicrophoneEvent();/// <summary>/// 重开始录制回调/// </summary>public static MicrophoneEvent OnResumeEvent = new MicrophoneEvent();/// <summary>/// 结束录制回调/// value:true 自动结束录制/// </summary>public static MicrophoneStopEvent OnStopEvent = new MicrophoneStopEvent();/// <summary>/// 获取录制数据后回调/// </summary>public static MicrophoneEvent OnEndEvent = new MicrophoneEvent();/// <summary>/// 取消录制回调/// </summary>public static MicrophoneEvent OnCancelEvent = new MicrophoneEvent();/// <summary>/// 关闭录制回调/// </summary>public static MicrophoneEvent OnCloseEvent = new MicrophoneEvent();/// <summary>/// 录制错误回调/// </summary>/// <param name="recorderState"></param>public static MicrophoneEvent OnWarningEvent = new MicrophoneEvent();}

2.数据处理

        /// <summary>/// 结束录制获取数据/// </summary>/// <param name="_audioDataString"></param>public void GetAudioData(string _audioDataString){if (_audioDataString.Contains("Head")){string[] _headValue = _audioDataString.Split('|');m_valuePartCount = int.Parse(_headValue[1]);m_audioLength = int.Parse(_headValue[2]);m_currentRecorderSign = _headValue[3];m_audioData = new string[m_valuePartCount];m_getDataLength = 0;//Debug.Log("接收数据头:" + m_valuePartCount + "   " + m_audioLength);}else if (_audioDataString.Contains("Part")){string[] _headValue = _audioDataString.Split('|');int _dataIndex = int.Parse(_headValue[1]);m_audioData[_dataIndex] = _headValue[2];m_getDataLength++;if (m_getDataLength == m_valuePartCount){StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < m_audioData.Length; i++){stringBuilder.Append(m_audioData[i]);}string _audioDataValue = stringBuilder.ToString();//Debug.Log("接收长度:" + _audioDataValue.Length + " 需接收长度:" + m_audioLength);int _index = _audioDataValue.LastIndexOf(',');string _value = _audioDataValue.Substring(_index + 1, _audioDataValue.Length - _index - 1);byte[] data = Convert.FromBase64String(_value);//Debug.Log("已接收长度 :" + data.Length);if (m_currentRecorderSign == "end"){int _audioLength = data.Length;for (int i = 0; i < m_audioClipDataList.Count; i++){_audioLength += m_audioClipDataList[i].Length;}byte[] _audioData = new byte[_audioLength];//Debug.Log("总长度 :" + _audioLength);int _audioIndex = 0;data.CopyTo(_audioData, _audioIndex);_audioIndex += data.Length;//Debug.Log("已赋值0:" + _audioIndex);for (int i = 0; i < m_audioClipDataList.Count; i++){m_audioClipDataList[i].CopyTo(_audioData, _audioIndex);_audioIndex += m_audioClipDataList[i].Length;//Debug.Log("已赋值 :" + _audioIndex);}WAV wav = new WAV(_audioData);AudioClip _audioClip = AudioClip.Create(Microphone.m_currentAudioName, wav.SampleCount, 1, wav.Frequency, false);_audioClip.SetData(wav.LeftChannel, 0);m_audioClip = _audioClip;_audioClip = null;//Debug.Log("音频设置成功,已设置到unity。"+m_audioClip.length+"  "+m_audioClip.name);m_audioClipDataList.Clear();if (Microphone.OnEndEvent != null)Microphone.OnEndEvent.Invoke();}elsem_audioClipDataList.Add(data);m_audioData = null;}}

Unity WebGL基于js通信实现网页录音相关推荐

  1. Unity,WebGL, 页面JS调用Unity方法

    与WebPlayer类似,在JS中用SendMessage 比如在Unity场景中有一个GameObject,叫A, A上有C#脚本,里面有个方法 public void F(string str) ...

  2. Unity WebGL/WebPlayer与html通信对比,在html添加网页端按钮直接测试

    Unity WebPlayer与html通信,在html添加网页端按钮直接测试 由于现在各大浏览器的升级,UnityWebPlayer的网页方式逐渐被摒弃掉,但是很多老项目或者追求稳定的开发者还在选择 ...

  3. Unity Webgl内嵌网页页面

    Unity Webgl端有时候会有这样一个需求,在Unity界面上内嵌一个网页,并且可以在界面上把这个网页关掉(不是重新打开新的标签页) 效果如下: 现在来实现这个功能: 1.在Assets文件夹下新 ...

  4. 一款基于Webgl实现的3D类网页游戏

    摘要:本文主要介绍运用webgl的第三方框架three.js实现的一款简单的3D类网页游戏.主要内容包括介绍three.js,如何运用three.js摆放相机,设置相机角度,相机视觉角度转移,场景中物 ...

  5. 零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐!(DoTween动画 | WebGL视频 | 大文件上传GitHub)

    零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐! 前言 一,环境搭建 1.1 安装Unity 1.2 添加WebGl模块 二,开发项目 2.1 导入插件 2.2 项目搭建 2.3 逻辑处理 ...

  6. 基于babylon.js的3D网页游戏从零教程

    3D 游戏的 javascript 框架: 在很久一段时间 web 端的 3D 游戏引擎一直是 nothing,但现在却如雨后春笋. Unity (Unity 2018.2 开始已经彻底弃用 js,使 ...

  7. Unity WebGL(一)Unity和JS交互

    UNITY与JavaScript交互 前言 一.从 Unity 脚本调用 JavaScript 函数 1.在工程文件夹里新建"Plugins"文件夹,在内部创建一个.jslib扩展 ...

  8. Unity WebGL打包网页端运行显示Not allowed to access uv4 on mesh

    项目场景:Unity WebGl网页连接后台服务器 提示:这里简述项目相关背景: 该项目实现的是Unity Web端联网后台读取数据主要功能 问题描述 提示:这里描述项目中遇到的问题: 在该项目中在U ...

  9. unity webgl自适应网页尺寸

    我使用的是unity 2019.4 LTS版 一共有两步 1 在Index.html中修改 <!DOCTYPE html> <html lang="en-us"& ...

  10. Unity WebGL 窗口自适应

    unity 打包好WebGL后,用文本编辑器编辑打包生成的 index.html 文件 在生成的html里面修改代码 <script type="text/javascript&quo ...

最新文章

  1. Django web框架-----Django连接现有mysql数据库
  2. geth安装失败,双击后不显示或等待很久后报错
  3. 记对一个key file crackme的破解
  4. Extjs中给同一个GridPanel中的事件添加参数的方法
  5. 【C语言】C语言的数据类型
  6. 第8章 面向对象高级编程与网络编程
  7. diff与patch操作
  8. CCNA学习指南 第五章 下载
  9. 另一半是程序员,一定要好好爱ta
  10. 量化指标公式源码_五行量化副图指标 源码 通达信
  11. 古罗马花园石头雕像喷泉原理
  12. Python如何换pip的源(阿里云或清华云等源)
  13. 计算机最大数最小数的函数,Excel函数教程: 求最大、最小、中间数、最多数-excel技巧-电脑技巧收藏家...
  14. 第四章第四节数据资产盘点-数据资产梳理
  15. C语言中6HZ怎么算,C语言基础课First作业
  16. 每日工作记录——ERROR:Simulator:793 - Unable to elaborate instantiated module work
  17. Pytest如何查找用例耗时最长的N个
  18. cumulus(浑天仪)使用手册
  19. (二十五)struts2.x中的转换器
  20. 学习记录——SIMPACK生成轨道谱文件的方法

热门文章

  1. 4大MQ消息队列的比较
  2. oppo android root工具箱,oppo R11(全网通 安卓8.1)手机完美获取root教程,最强root工具,亲测可用!...
  3. 黑马程序员—我的面试,我的学习,我的经历
  4. 三星on 7刷android7,三星On7(G6100)安卓8.0版官方固件rom刷机包
  5. python找不到解释器_为什么pycharm找不到python解释器
  6. 8psk信号的载波调制 matlab,EDGE系统中GMSK和8psk调制的应用原理
  7. 视频倍速插件(火狐,谷歌)
  8. 计算机五笔是什么时候学的吗,电脑五笔输入法怎么学
  9. kdj买卖指标公式源码_买卖点KDJ版指标详解 通达信通达信KDJ
  10. 骚操作 Python爬你要的网站数据