Unity WebGL基于js通信实现网页录音
Unity WebGL基于js通信实现网页录音
由于Unity发布WebGL后无法使用Unity中的Microphone,所以无法进行录音,只能借助与网页进行录音操作,因此基于Unity新版页面对接方式jslib实现页面录音。可以将录音数据回传至unity,并在unity中进行播放。理论上支持无限制录音。
UnityWebGLMicrophone1.0.1 插件下载地址
插件介绍
在请求网页权限后,可以选择录制时间或者循环录制,然后开始录制。之后会在页面中进行分段录制,并回传至unity,结束录制后进行整合,并转换成AudioClip进行播放。
运行效果图
主要代码
js部分:
- 获取权限
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");}},
- 发送数据
//发送数据$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);},
- 停止录制
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#部分
- 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通信实现网页录音相关推荐
- Unity,WebGL, 页面JS调用Unity方法
与WebPlayer类似,在JS中用SendMessage 比如在Unity场景中有一个GameObject,叫A, A上有C#脚本,里面有个方法 public void F(string str) ...
- Unity WebGL/WebPlayer与html通信对比,在html添加网页端按钮直接测试
Unity WebPlayer与html通信,在html添加网页端按钮直接测试 由于现在各大浏览器的升级,UnityWebPlayer的网页方式逐渐被摒弃掉,但是很多老项目或者追求稳定的开发者还在选择 ...
- Unity Webgl内嵌网页页面
Unity Webgl端有时候会有这样一个需求,在Unity界面上内嵌一个网页,并且可以在界面上把这个网页关掉(不是重新打开新的标签页) 效果如下: 现在来实现这个功能: 1.在Assets文件夹下新 ...
- 一款基于Webgl实现的3D类网页游戏
摘要:本文主要介绍运用webgl的第三方框架three.js实现的一款简单的3D类网页游戏.主要内容包括介绍three.js,如何运用three.js摆放相机,设置相机角度,相机视觉角度转移,场景中物 ...
- 零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐!(DoTween动画 | WebGL视频 | 大文件上传GitHub)
零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐! 前言 一,环境搭建 1.1 安装Unity 1.2 添加WebGl模块 二,开发项目 2.1 导入插件 2.2 项目搭建 2.3 逻辑处理 ...
- 基于babylon.js的3D网页游戏从零教程
3D 游戏的 javascript 框架: 在很久一段时间 web 端的 3D 游戏引擎一直是 nothing,但现在却如雨后春笋. Unity (Unity 2018.2 开始已经彻底弃用 js,使 ...
- Unity WebGL(一)Unity和JS交互
UNITY与JavaScript交互 前言 一.从 Unity 脚本调用 JavaScript 函数 1.在工程文件夹里新建"Plugins"文件夹,在内部创建一个.jslib扩展 ...
- Unity WebGL打包网页端运行显示Not allowed to access uv4 on mesh
项目场景:Unity WebGl网页连接后台服务器 提示:这里简述项目相关背景: 该项目实现的是Unity Web端联网后台读取数据主要功能 问题描述 提示:这里描述项目中遇到的问题: 在该项目中在U ...
- unity webgl自适应网页尺寸
我使用的是unity 2019.4 LTS版 一共有两步 1 在Index.html中修改 <!DOCTYPE html> <html lang="en-us"& ...
- Unity WebGL 窗口自适应
unity 打包好WebGL后,用文本编辑器编辑打包生成的 index.html 文件 在生成的html里面修改代码 <script type="text/javascript&quo ...
最新文章
- Django web框架-----Django连接现有mysql数据库
- geth安装失败,双击后不显示或等待很久后报错
- 记对一个key file crackme的破解
- Extjs中给同一个GridPanel中的事件添加参数的方法
- 【C语言】C语言的数据类型
- 第8章 面向对象高级编程与网络编程
- diff与patch操作
- CCNA学习指南 第五章 下载
- 另一半是程序员,一定要好好爱ta
- 量化指标公式源码_五行量化副图指标 源码 通达信
- 古罗马花园石头雕像喷泉原理
- Python如何换pip的源(阿里云或清华云等源)
- 计算机最大数最小数的函数,Excel函数教程: 求最大、最小、中间数、最多数-excel技巧-电脑技巧收藏家...
- 第四章第四节数据资产盘点-数据资产梳理
- C语言中6HZ怎么算,C语言基础课First作业
- 每日工作记录——ERROR:Simulator:793 - Unable to elaborate instantiated module work
- Pytest如何查找用例耗时最长的N个
- cumulus(浑天仪)使用手册
- (二十五)struts2.x中的转换器
- 学习记录——SIMPACK生成轨道谱文件的方法
热门文章
- 4大MQ消息队列的比较
- oppo android root工具箱,oppo R11(全网通 安卓8.1)手机完美获取root教程,最强root工具,亲测可用!...
- 黑马程序员—我的面试,我的学习,我的经历
- 三星on 7刷android7,三星On7(G6100)安卓8.0版官方固件rom刷机包
- python找不到解释器_为什么pycharm找不到python解释器
- 8psk信号的载波调制 matlab,EDGE系统中GMSK和8psk调制的应用原理
- 视频倍速插件(火狐,谷歌)
- 计算机五笔是什么时候学的吗,电脑五笔输入法怎么学
- kdj买卖指标公式源码_买卖点KDJ版指标详解 通达信通达信KDJ
- 骚操作 Python爬你要的网站数据