@jxstjh/jhvideo
Version:
HTML5 jhvideo base on MPEG2-TS Stream Player
534 lines • 24.8 kB
JavaScript
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