UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

534 lines 24.8 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { callOnSvg, callOffSvg } from "./icons"; import { EventEmitter } from "events"; import PCMPlayer from 'pcm-player'; import { alaw, mulaw } from 'alawmulaw'; import httpClient from "../core/httpClient"; import PCMProcessor from './pcm-processor'; import AudioEncodeProcessor from './AudioEncodeProcessor.worklet.js'; import StreamWebsocket from "../stream/ws-loader"; import { ERRORMSG } from "./codemsg"; var code = { OK: 0, ARGUMENTS: 1, // 参数有误 UNSUPPORTED: 2, // 不支持 UNEXPECTED_STATUS: 3, // 意外的状态 CONNECT: 4, // 无法建立Websocket连接, URL有误,端口未开通,或者证书未安装 INTERRUPT: 5, // 流中断 }; var audioDefaultConfig = { // 非压缩 'pcm': { audioSample: 8000, audioBitrate: 32000, audioChannel: 1, audioBitwidth: 16 }, //压缩(欧洲使用) 'g711a': { audioSample: 8000, audioBitrate: 64000, audioChannel: 1, audioBitwidth: 16 }, //压缩(北美和日本) 'g711u': { audioSample: 8000, audioBitrate: 64000, audioChannel: 1, audioBitwidth: 16 }, }; var TalkCtrl = /** @class */ (function () { function TalkCtrl(stream, requestInfo, prefixName, videoBox) { if (prefixName === void 0) { prefixName = ''; } this.autoTalk = false; this._isCompatible = false; this._pcmPlayer = null; this._processor = null; this._prefixName = ''; this._talkDemo = null; this._talkTitle = null; this._videoBox = null; this._talkBody = null; this._playUrl = ''; this._talkStream = null; this._decoder = null; this._animationId = 0; this._capturer = { audioStream: null, audioContext: null, audioSource: null, audioNode: null, scriptProcessor: null, gainNode: null }; this._streamAudioInfo = { audioType: 'g711a', audioSample: 8000, audioBitrate: 64000, audioBitwidth: 16, audioChannel: 1, }; this.eventList = { stopPropagation: function (e) { e.stopPropagation(); } }; this.emitter = new EventEmitter(); this.requestInfo = requestInfo; this._talkTitle = stream.title || ''; this._prefixName = prefixName; this._videoBox = videoBox; this._isCompatible = !!(window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia); } TalkCtrl.prototype.on = function (event, listener) { this.emitter.addListener(event, listener); }; TalkCtrl.prototype.off = function (event, listener) { this.emitter.removeListener(event, listener); }; // 创建文件 TalkCtrl.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { var className, callWrapper, talkDemo, talkCard, talkHead, talkBody, talkFoot; return __generator(this, function (_a) { // 浏览器不支持 if (!this._isCompatible) { className = '.jh-talk-btn'; callWrapper = this._videoBox.querySelector(className); callWrapper.setAttribute('aria-controls', '浏览器不支持对讲'); return [2 /*return*/]; } // 已存在demo if (this._talkDemo) { return [2 /*return*/]; } talkDemo = document.createElement('div'); talkDemo.classList.add(this._prefixName + '-talk-wrapper'); this._videoBox.appendChild(talkDemo); talkDemo.style.top = (this._videoBox.clientHeight - 126) + 'px'; talkDemo.style.left = (this._videoBox.clientWidth - 314) + 'px'; talkCard = document.createElement('div'); talkCard.classList.add("".concat(this._prefixName, "-talk-card")); talkHead = document.createElement('div'); talkHead.classList.add("".concat(this._prefixName, "-talk-head")); talkHead.innerHTML = "<h3 class=\"talk-title\">\n <span class=\"talk-svg\">".concat(callOffSvg, "</span>\n ").concat(this._talkTitle, "\n </h3>"); talkBody = document.createElement('div'); talkBody.classList.add("".concat(this._prefixName, "-talk-body")); talkBody.innerHTML = '数据请求中....'; talkFoot = document.createElement('div'); talkFoot.classList.add("".concat(this._prefixName, "-talk-foot")); talkFoot.innerHTML = "<button class=\"talk-start\">\u505C\u6B62</button>\n <button class=\"talk-close\">\u5173\u95ED</button>"; talkCard.appendChild(talkHead); talkCard.appendChild(talkBody); talkCard.appendChild(talkFoot); talkDemo.appendChild(talkCard); this._talkBody = talkBody; this._talkDemo = talkDemo; this.addEvent(talkHead, talkFoot); this.open(); return [2 /*return*/]; }); }); }; // 添加页面事件 TalkCtrl.prototype.addEvent = function (talkHead, talkFoot) { var _this = this; var isHead = false, isBar = false; var dy = 0, sy = 0, dx = 0, sx = 0; var rec = this._talkDemo; var videoBox = this._videoBox; rec.addEventListener('click', function (e) { e.stopPropagation(); }); videoBox.addEventListener('mousemove', function (e) { e.stopPropagation(); // 录音文本框移动 if (isHead) { rec.style.top = e.clientY - (dy - sy) + 'px'; rec.style.left = e.clientX - (dx - sx) + 'px'; } }); videoBox.addEventListener('mouseup', function (e) { e.stopPropagation(); if (isHead) { isHead = false; } if (isBar) { isBar = false; } }); talkHead.addEventListener('mousedown', function (e) { dx = e.clientX; dy = e.clientY; sx = parseInt(rec.style.left); sy = parseInt(rec.style.top); if (!isHead) { isHead = true; } }); talkFoot.querySelector(".talk-start").addEventListener('click', function () { _this.autoTalk ? _this.stop(true) : _this.open(); }); talkFoot.querySelector(".talk-close").addEventListener('click', function () { _this.destroy(true); }); }; TalkCtrl.prototype.removeEvent = function () { var talkDemo = this._talkDemo; var videoBox = this._videoBox; var talkHead = talkDemo.querySelector("".concat(this._prefixName, "-talk-head")); var talkFoot = talkDemo.querySelector("".concat(this._prefixName, "-talk-foot")); var talkStart = talkFoot.querySelector(".talk-start"); var talkClose = talkFoot.querySelector(".talk-close"); }; // 创建音频获取录音 TalkCtrl.prototype._loadCapturer = function () { return __awaiter(this, void 0, void 0, function () { var constraints, _a, err_1, audioContext, audioSource, processorOptions, audioNode, audioSource, scriptProcessor; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: constraints = { audio: { noiseSuppression: true, // 降噪 echoCancellation: true // 回声消除 }, video: false }; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); _a = this._capturer; return [4 /*yield*/, window.navigator.mediaDevices.getUserMedia(constraints)]; // 获取麦克风权限 case 2: _a.audioStream = _b.sent(); // 获取麦克风权限 console.log('capturer.audioStream:', this._capturer.audioStream); return [3 /*break*/, 4]; case 3: err_1 = _b.sent(); console.error(err_1); this.emitter.emit('error', code.UNSUPPORTED); return [3 /*break*/, 4]; case 4: audioContext = this._capturer.audioContext = new window.AudioContext(); if (!(window.AudioWorklet && window.AudioWorkletNode)) return [3 /*break*/, 6]; console.info('0:'); // new-AudioWorkletNode return [4 /*yield*/, audioContext.audioWorklet.addModule(AudioEncodeProcessor)]; case 5: // new-AudioWorkletNode _b.sent(); audioSource = this._capturer.audioSource = audioContext.createMediaStreamSource(this._capturer.audioStream); processorOptions = __assign({ fromSampleRate: audioContext.sampleRate }, this._streamAudioInfo); audioNode = this._capturer.audioNode = new AudioWorkletNode(audioContext, 'AudioEncodeProcessor', { processorOptions: processorOptions }); audioNode.port.onmessage = function (evt) { // 处理编码后的音频数据 var bFileWrite = false; if (evt.data && bFileWrite && evt.data.type === 'pcm') { // this._root._fileStorage.inputData(evt.data.pcmSamples) } else if (evt.data && evt.data instanceof ArrayBuffer) { _this.emitter.emit('stream.output', evt.data); } else { console.log('got message from worklet', evt.type, evt.data); } }; audioSource.connect(audioNode); audioNode.connect(audioContext.destination); console.log('talk-success'); this.emitter.emit('success'); return [3 /*break*/, 7]; case 6: // old-ScriptProcessorNode this._loadProcessor(); audioSource = this._capturer.audioSource = audioContext.createMediaStreamSource(this._capturer.audioStream); scriptProcessor = this._capturer.scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1); scriptProcessor.onaudioprocess = function (evt) { // console.log(evt) // PCM原始音频可在此录制 // write file... /* 加工处理PCM音频数据,如消除白噪音,重采样,编码等 */ _this._processor.input(evt.inputBuffer.getChannelData(0)); }; audioSource.connect(scriptProcessor); scriptProcessor.connect(audioContext.destination); this.emitter.emit('success'); _b.label = 7; case 7: // 绘制音频动效 this.audioAnimation(); return [2 /*return*/]; } }); }); }; // 创建音频动效 TalkCtrl.prototype.audioAnimation = function () { return __awaiter(this, void 0, void 0, function () { var audioCtx, source, analyser, talkBody, canvas, ctx, draw; var _this = this; return __generator(this, function (_a) { audioCtx = this._capturer.audioContext; source = this._capturer.audioSource; analyser = audioCtx.createAnalyser(); analyser.fftSize = 512; // 将 MediaStreamAudioSourceNode 连接到 AnalyserNode source.connect(analyser); talkBody = this._talkBody; canvas = document.createElement("canvas"); ctx = canvas.getContext('2d'); canvas.width = talkBody.clientWidth; canvas.height = talkBody.clientHeight; talkBody.appendChild(canvas); draw = function () { // 获取音频数据 var dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteTimeDomainData(dataArray); // 清除 Canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制音频动画 ctx.beginPath(); for (var i = 0; i < dataArray.length; i++) { var v = dataArray[i] / 96.0; var y = v * canvas.height / 2; if (i === 0) { ctx.moveTo(i, y); } else { ctx.lineTo(i, y); } } ctx.lineTo(canvas.width, canvas.height / 2); ctx.strokeStyle = '#1aff8c'; ctx.stroke(); // 请求下一帧 _this._animationId = requestAnimationFrame(draw); }; draw(); return [2 /*return*/]; }); }); }; // 页面处理 TalkCtrl.prototype._loadProcessing = function () { var _a = this, _talkDemo = _a._talkDemo, autoTalk = _a.autoTalk; var start = _talkDemo.querySelector(".talk-start"); var svg = _talkDemo.querySelector(".talk-svg"); if (autoTalk) { start.innerHTML = '停止'; svg.innerHTML = callOnSvg; } else { start.innerHTML = '开始'; svg.innerHTML = callOffSvg; } }; /** * pcm 音频数据加工处理 */ TalkCtrl.prototype._loadProcessor = function () { var _this = this; this._processor = new PCMProcessor(__assign({ fromSampleRate: this._capturer.audioContext.sampleRate }, this._streamAudioInfo), function (samples) { _this.emitter.emit('stream.output', samples); }); }; TalkCtrl.prototype._loadPlayer = function () { var _a = this._streamAudioInfo, audioType = _a.audioType, audioSample = _a.audioSample, audioChannel = _a.audioChannel; switch (audioType) { case 'g711a': this._decoder = alaw; break; case 'g711u': this._decoder = mulaw; break; default: break; } var obj = { inputCodec: 'Int16', channels: audioChannel, sampleRate: audioSample, flushTime: 500 }; this._pcmPlayer = new PCMPlayer(obj); }; // 开流 TalkCtrl.prototype.open = function () { return __awaiter(this, void 0, void 0, function () { var _a; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = this; return [4 /*yield*/, this.getUrl() // console.log('this._playUrl:',this._playUrl) ]; case 1: _a._playUrl = _b.sent(); // console.log('this._playUrl:',this._playUrl) this._talkStream = new StreamWebsocket(this._playUrl, 'talk'); this._talkStream.on('stream.input', function (inputBuf) { if (_this._pcmPlayer) { var pcmSamples = _this._decoder.decode(new Uint8Array(inputBuf)); _this._pcmPlayer.feed(pcmSamples); } }); this.emitter.on('stream.output', function (outputBuf) { _this._talkStream && _this._talkStream.sendArrayBuffer(outputBuf); }); this._talkStream.open().then(function (_a) { var code = _a.code, mediaInfo = _a.mediaInfo; var searchParams = new URLSearchParams(mediaInfo); var audioType = _this._streamAudioInfo['audioType']; if (searchParams.has('audioType')) { audioType = _this._streamAudioInfo['audioType'] = searchParams.get('audioType').toLocaleLowerCase(); } for (var _i = 0, _b = searchParams.keys(); _i < _b.length; _i++) { var key = _b[_i]; if (key === 'audioType') continue; var value = searchParams.get(key); _this._streamAudioInfo[key] = /^\d+$/.test(value) ? parseInt(value) : value; if (_this._streamAudioInfo[key] === 0) { _this._streamAudioInfo[key] = audioDefaultConfig[audioType][key]; } } // console.log('_streamAudioInfo:',this._streamAudioInfo) _this.autoTalk = true; _this._loadPlayer(); _this._loadCapturer(); _this._loadProcessing(); }).catch(function (errCode) { _this.stop(true); _this._talkBody.innerHTML = ERRORMSG[errCode]; console.error(errCode); }); return [2 /*return*/]; } }); }); }; TalkCtrl.prototype.getUrl = function () { return __awaiter(this, void 0, void 0, function () { var _a, url, aisleId, protocol, headers, res; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = this.requestInfo, url = _a.url, aisleId = _a.aisleId, protocol = _a.protocol, headers = _a.headers; return [4 /*yield*/, httpClient.get(url, { aisleId: aisleId, protocol: protocol }, headers)]; case 1: res = _b.sent(); if (res.code !== 200) { return [2 /*return*/, res.data]; } else { return [2 /*return*/, Promise.reject(res.msg)]; } return [2 /*return*/]; } }); }); }; // 停止喊会 TalkCtrl.prototype.stop = function (en) { var _this = this; if (en === void 0) { en = false; } this.autoTalk = false; if (en) { this._loadProcessing(); } if (this._talkStream) { this._talkStream.destroy(); this._talkStream = null; } if (this._capturer.audioNode) { this._capturer.audioSource.disconnect(this._capturer.audioNode); this._capturer.audioNode.disconnect(this._capturer.audioContext.destination); } if (this._capturer.scriptProcessor) { this._capturer.audioSource.disconnect(this._capturer.scriptProcessor); this._capturer.scriptProcessor.disconnect(this._capturer.audioContext.destination); } this._capturer.audioContext && this._capturer.audioContext.close(); this._capturer.audioStream && this._capturer.audioStream.getTracks().forEach(function (track) { return track.stop(); }); Object.keys(this._capturer).forEach(function (key) { _this._capturer[key] = null; }); if (this._animationId) { cancelAnimationFrame(this._animationId); } if (this._talkBody && en) { var canvas = this._talkBody.querySelector('canvas'); canvas && canvas.parentNode.removeChild(canvas); } }; // 销毁 TalkCtrl.prototype.destroy = function (en) { if (en === void 0) { en = false; } this.emitter && this.emitter.removeAllListeners(); this.stop(en); if (this._pcmPlayer) { this._pcmPlayer.destroy(); this._pcmPlayer = null; } this._talkDemo && this._talkDemo.parentNode.removeChild(this._talkDemo); this._talkDemo = null; }; // 浏览器是否支持 TalkCtrl.talkIsSupported = function () { return !(window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia); }; return TalkCtrl; }()); export default TalkCtrl; //# sourceMappingURL=talk.js.map