index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <view>
  3. <u-toast ref="uToast" />
  4. </view>
  5. </template>
  6. <script>
  7. import { videoStart,videoControl } from "@/api/core/sendOrder";
  8. //录音对象
  9. var Recorder = function (stream) {
  10. var sampleBits = 16; //输出采样数位 8, 16
  11. var sampleRate = 8000; //输出采样率
  12. var context = new AudioContext();
  13. var audioInput = context.createMediaStreamSource(stream);
  14. var recorder = context.createScriptProcessor(4096, 1, 1);
  15. var audioData = {
  16. size: 0, //录音文件长度
  17. buffer: [], //录音缓存
  18. inputSampleRate: 48000, //输入采样率
  19. inputSampleBits: 16, //输入采样数位 8, 16
  20. outputSampleRate: sampleRate,
  21. oututSampleBits: sampleBits,
  22. clear: function () {
  23. this.buffer = [];
  24. this.size = 0;
  25. },
  26. input: function (data) {
  27. this.buffer.push(new Float32Array(data));
  28. this.size += data.length;
  29. },
  30. compress: function () {
  31. //合并压缩
  32. //合并
  33. var data = new Float32Array(this.size);
  34. var offset = 0;
  35. for (var i = 0; i < this.buffer.length; i++) {
  36. data.set(this.buffer[i], offset);
  37. offset += this.buffer[i].length;
  38. }
  39. //压缩
  40. var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
  41. var length = data.length / compression;
  42. var result = new Float32Array(length);
  43. var index = 0,
  44. j = 0;
  45. while (index < length) {
  46. result[index] = data[j];
  47. j += compression;
  48. index++;
  49. }
  50. return result;
  51. },
  52. encodePCM: function () {
  53. //这里不对采集到的数据进行其他格式处理,如有需要均交给服务器端处理。
  54. var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
  55. var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
  56. var bytes = this.compress();
  57. var dataLength = bytes.length * (sampleBits / 8);
  58. var buffer = new ArrayBuffer(dataLength);
  59. var data = new DataView(buffer);
  60. var offset = 0;
  61. for (var i = 0; i < bytes.length; i++, offset += 2) {
  62. var s = Math.max(-1, Math.min(1, bytes[i]));
  63. data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  64. }
  65. return new Blob([data]);
  66. },
  67. };
  68. this.start = function () {
  69. audioInput.connect(recorder);
  70. recorder.connect(context.destination);
  71. };
  72. this.stop = function () {
  73. recorder.disconnect();
  74. };
  75. this.getBlob = function () {
  76. return audioData.encodePCM();
  77. };
  78. this.clear = function () {
  79. audioData.clear();
  80. };
  81. recorder.onaudioprocess = function (e) {
  82. audioData.input(e.inputBuffer.getChannelData(0));
  83. };
  84. };
  85. export default {
  86. data() {
  87. return {
  88. ws: null,
  89. deviceId:'',
  90. wsUrl: "wss://80.5000v.com/websocket", // websocket地址
  91. record: null, //多媒体对象,用来处理音频
  92. sendingTimes: 0, // 发送设备号的次数
  93. timeInte: null, // 定时任务 定时发送
  94. audo: new Audio(),
  95. openIntercom:false
  96. };
  97. },
  98. created() {
  99. },
  100. methods: {
  101. init () {
  102. navigator.mediaDevices
  103. .getUserMedia({ audio: true})
  104. .then( (mediaStream)=> {
  105. /* 使用这个 stream stream */
  106. console.log('使用这个',mediaStream)
  107. this.record = new Recorder(mediaStream);
  108. })
  109. .catch(function (err) {
  110. /* 处理 error */
  111. console.log('处理 error',err)
  112. uni.showToast({
  113. title: '浏览器不支持音频输入'+err,
  114. duration: 2000,
  115. icon: 'none'
  116. });
  117. });
  118. // navigator.getUserMedia(
  119. // {
  120. // audio: true,
  121. // },
  122. // (mediaStream) => {
  123. // console.log('2使用这个',mediaStream)
  124. // this.record = new Recorder(mediaStream);
  125. // },
  126. // (error) => {
  127. // });
  128. // console.log('方法',getUserMedia)
  129. // let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
  130. // navigator.getUserMedia = getUserMedia
  131. // if (!navigator.getUserMedia) {
  132. // uni.showToast({
  133. // title: '浏览器不支持音频输入'+getUserMedia,
  134. // duration: 2000,
  135. // icon: 'none'
  136. // });
  137. // console.log('浏览器不支持音频输入')
  138. // } else {
  139. // navigator.getUserMedia(
  140. // {
  141. // audio: true,
  142. // },
  143. // (mediaStream) => {
  144. // this.record = new Recorder(mediaStream);
  145. // console.log('2121',this.record)
  146. // },
  147. // (error) => {
  148. // }
  149. // );
  150. // }
  151. },
  152. async toIntercom(deviceId) {
  153. console.log(deviceId,'itemdeviceId')
  154. if(this.isNull(deviceId)) {
  155. uni.showToast({
  156. title: 'deviceId为空',
  157. duration: 2000,
  158. icon: 'none'
  159. });
  160. return false
  161. }
  162. this.init()
  163. this.deviceId = deviceId;
  164. // #ifdef H5
  165. if(this.openIntercom){
  166. //打开中 执行关闭
  167. let obj = {
  168. audioVideoType: 0,
  169. channel: 1,
  170. controll: 4,
  171. dataRate: 0,
  172. deviceId: deviceId, //设备号
  173. };
  174. let res = await videoControl(obj);
  175. if(res.code == '0'){
  176. uni.closeSocket();
  177. this.openIntercom = false;
  178. this.ws.close();
  179. this.record.stop();
  180. clearInterval(this.timeInte);
  181. this.ws = null;
  182. this.timeInte = null;
  183. }else{
  184. uni.showToast({
  185. title:res.desc,
  186. duration: 2000,
  187. icon: 'none'
  188. });
  189. }
  190. }else {
  191. // 关闭中 执行打开
  192. let obj = {
  193. channel: 1,
  194. dataRate: 1,
  195. dataType: 2, // 对讲
  196. deviceId: deviceId, // 设备号
  197. };
  198. let res = await videoStart(obj);
  199. if(res.code == '0'){
  200. // 成功以后连接socket
  201. // 命令下发成功
  202. let wsUrl;
  203. if (window.location.hostname != "localhost") {
  204. wsUrl = "wss://" + window.location.host + "/websocket";
  205. } else {
  206. wsUrl = "wss://tq.5000v.cn:443/websocket"
  207. }
  208. this.ws = new WebSocket(wsUrl); // 连接websocket
  209. this.ws.binaryType = "arraybuffer"; //传输的是 ArrayBuffer 类型的数据
  210. this.ws.onopen = (event) => {
  211. // 连接成功后
  212. console.log("websocket连接成功");
  213. // 连接成功后发送设备号
  214. this.websocketSend();
  215. };
  216. this.ws.onclose = (event) => {
  217. // 监听websocket断开
  218. console.log("websocket连接关闭");
  219. this.$emit('oNstate',false)//回传状态
  220. };
  221. this.ws.onmessage = (evt) => {
  222. // 收到websocket传来的信息
  223. console.log('websocket传来的信息',evt.data)
  224. this.$emit('oNstate',true)//回传状态
  225. if(typeof evt.data == 'string'){
  226. if(evt.data.indexOf(':1') > -1){
  227. // :1 代表接通 :0代表未接通
  228. this.record.start(); //开始录音
  229. this.receive();
  230. }else {
  231. if(this.sendingTimes < 3) {
  232. // 重发次数小于3 继续发
  233. // 间隔一秒发
  234. setTimeout(()=>{
  235. this.websocketSend();
  236. this.sendingTimes++
  237. }, 1000);
  238. }else{
  239. // 重发3次 提升无法连接 并且下发关闭指令 关闭连接
  240. // this.$message.error('设备无法链接');
  241. // this.$refs.uToast.show({
  242. // title: '设备无法链接',
  243. // type: "error",
  244. // });
  245. uni.showToast({
  246. title:'设备无法链接',
  247. duration: 2000,
  248. icon: 'none'
  249. });
  250. // this.closeIntercom();
  251. this.toIntercom(this.deviceId);
  252. this.sendingTimes = 0;
  253. }
  254. }
  255. } else {
  256. this.playAudio(evt.data);
  257. }
  258. };
  259. this.openIntercom = true;
  260. }else{
  261. // this.$refs.uToast.show({
  262. // title: res.desc,
  263. // type: "error",
  264. // });
  265. uni.showToast({
  266. title:res.desc,
  267. duration: 2000,
  268. icon: 'none'
  269. });
  270. }
  271. }
  272. this.$emit('oNstate',this.openIntercom)
  273. // #endif
  274. },
  275. websocketSend() {
  276. this.ws.send(this.deviceId);
  277. },
  278. receive() {
  279. // 对讲
  280. this.timeInte = setInterval(()=>{
  281. if(this.ws.readyState==1){ // ws进入连接状态,则每隔500毫秒发送一包数据
  282. console.log(this.record.getBlob());
  283. this.ws.send(this.record.getBlob()); //发送音频数据
  284. this.record.clear();//每次发送完成则清理掉旧数据
  285. }
  286. },200); //每隔200ms发送一次,定时器
  287. },
  288. playAudio(data) {
  289. // 收听
  290. let _this = this;
  291. var buffer = (new Response(data)).arrayBuffer();
  292. buffer.then(function(buf){
  293. var audioContext = new ( window.AudioContext || window.webkitAudioContext )();
  294. var fileResult = _this.addWavHeader(buf, '8000', '16', '1'); // 解析数据转码wav
  295. audioContext.decodeAudioData(fileResult, function(buffer) {
  296. _this.visualize(audioContext, buffer); // 播放
  297. });
  298. });
  299. },
  300. visualize(audioContext, buffer) {
  301. var audioBufferSouceNode = audioContext.createBufferSource();
  302. var analyser = audioContext.createAnalyser();
  303. //将信号源连接到分析仪
  304. audioBufferSouceNode.connect(analyser);
  305. //将分析仪连接到目的地(扬声器),否则我们将听不到声音
  306. analyser.connect(audioContext.destination);
  307. //然后将缓冲区分配给缓冲区源节点
  308. audioBufferSouceNode.buffer = buffer;
  309. //发挥作用
  310. if (!audioBufferSouceNode.start) {
  311. audioBufferSouceNode.start = audioBufferSouceNode.noteOn //在旧浏览器中使用noteOn方法
  312. audioBufferSouceNode.stop = audioBufferSouceNode.noteOff //在旧浏览器中使用noteOff方法
  313. };
  314. //如果有的话,停止前一个声音
  315. if (this.animationId !== null) {
  316. cancelAnimationFrame(this.animationId);
  317. }
  318. audioBufferSouceNode.start(0);
  319. this.audo.source = audioBufferSouceNode;
  320. this.audo.audioContext = audioContext;
  321. },
  322. //处理音频流,转码wav
  323. addWavHeader(samples, sampleRateTmp, sampleBits, channelCount) {
  324. var dataLength = samples.byteLength;
  325. var buffer = new ArrayBuffer(44 + dataLength);
  326. var view = new DataView(buffer);
  327. function writeString(view, offset, string){
  328. for (var i = 0; i < string.length; i++){
  329. view.setUint8(offset + i, string.charCodeAt(i));
  330. }
  331. }
  332. var offset = 0;
  333. /* 资源交换文件标识符 */
  334. writeString(view, offset, 'RIFF'); offset += 4;
  335. /* 下个地址开始到文件尾总字节数,即文件大小-8 */
  336. view.setUint32(offset, /*32*/ 36 + dataLength, true); offset += 4;
  337. /* WAV文件标志 */
  338. writeString(view, offset, 'WAVE'); offset += 4;
  339. /* 波形格式标志 */
  340. writeString(view, offset, 'fmt '); offset += 4;
  341. /* 过滤字节,一般为 0x10 = 16 */
  342. view.setUint32(offset, 16, true); offset += 4;
  343. /* 格式类别 (PCM形式采样数据) */
  344. view.setUint16(offset, 1, true); offset += 2;
  345. /* 通道数 */
  346. view.setUint16(offset, channelCount, true); offset += 2;
  347. /* 采样率,每秒样本数,表示每个通道的播放速度 */
  348. view.setUint32(offset, sampleRateTmp, true); offset += 4;
  349. /* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
  350. view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset +=4;
  351. /* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
  352. view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
  353. /* 每样本数据位数 */
  354. view.setUint16(offset, sampleBits, true); offset += 2;
  355. /* 数据标识符 */
  356. writeString(view, offset, 'data'); offset += 4;
  357. /* 采样数据总数,即数据总大小-44 */
  358. view.setUint32(offset, dataLength, true); offset += 4;
  359. function floatTo32BitPCM(output, offset, input){
  360. input = new Int32Array(input);
  361. for (var i = 0; i < input.length; i++, offset+=4){
  362. output.setInt32(offset,input[i],true);
  363. }
  364. }
  365. function floatTo16BitPCM(output, offset, input){
  366. input = new Int16Array(input);
  367. for (var i = 0; i < input.length; i++, offset+=2){
  368. output.setInt16(offset,input[i],true);
  369. }
  370. }
  371. function floatTo8BitPCM(output, offset, input){
  372. input = new Int8Array(input);
  373. for (var i = 0; i < input.length; i++, offset++){
  374. output.setInt8(offset,input[i],true);
  375. }
  376. }
  377. if(sampleBits == 16){
  378. floatTo16BitPCM(view, 44, samples);
  379. }else if(sampleBits == 8){
  380. floatTo8BitPCM(view, 44, samples);
  381. }else{
  382. floatTo32BitPCM(view, 44, samples);
  383. }
  384. return view.buffer;
  385. }
  386. }
  387. }
  388. </script>