2025-04-16 02:20:40 +01:00

1800 lines
62 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
})((function () { 'use strict';
class Emitter {
on(name, fn, ctx) {
const e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn,
ctx
});
return this;
}
once(name, fn, ctx) {
const self = this;
function listener() {
self.off(name, listener);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
fn.apply(ctx, args);
}
listener._ = fn;
return this.on(name, listener, ctx);
}
emit(name) {
const evtArr = ((this.e || (this.e = {}))[name] || []).slice();
for (var _len2 = arguments.length, data = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
data[_key2 - 1] = arguments[_key2];
}
for (let i = 0; i < evtArr.length; i += 1) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
}
off(name, callback) {
const e = this.e || (this.e = {});
if (!name) {
Object.keys(e).forEach(key => {
delete e[key];
});
delete this.e;
return;
}
const evts = e[name];
const liveEvents = [];
if (evts && callback) {
for (let i = 0, len = evts.length; i < len; i += 1) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]);
}
}
if (liveEvents.length) {
e[name] = liveEvents;
} else {
delete e[name];
}
return this;
}
}
// 播放协议
const DEBUG_LEVEL = {
debug: 'debug',
warn: 'warn'
};
const URL_OBJECT_CLEAR_TIME = 10 * 1000; // url object clear time
// inner events
const EVENTS = {
fullscreen: 'fullscreen$2',
webFullscreen: 'webFullscreen',
decoderWorkerInit: 'decoderWorkerInit',
play: 'play',
playing: 'playing',
pause: 'pause',
mute: 'mute',
load: 'load',
loading: 'loading',
zooming: 'zooming',
videoInfo: 'videoInfo',
timeUpdate: 'timeUpdate',
audioInfo: "audioInfo",
log: 'log',
error: "error",
kBps: 'kBps',
timeout: 'timeout',
delayTimeout: 'delayTimeout',
delayTimeoutRetryEnd: 'delayTimeoutRetryEnd',
loadingTimeout: 'loadingTimeout',
loadingTimeoutRetryEnd: 'loadingTimeoutRetryEnd',
stats: 'stats',
performance: "performance",
videoSmooth: 'videoSmooth',
faceDetectActive: 'faceDetectActive',
objectDetectActive: 'objectDetectActive',
occlusionDetectActive: 'occlusionDetectActive',
imageDetectActive: 'imageDetectActive',
// record
record: 'record',
recording: 'recording',
recordingTimestamp: 'recordingTimestamp',
recordStart: 'recordStart',
recordEnd: 'recordEnd',
recordCreateError: 'recordCreateError',
recordBlob: 'recordBlob',
buffer: 'buffer',
videoFrame: 'videoFrame',
videoSEI: 'videoSEI',
start: 'start',
metadata: 'metadata',
resize: 'resize',
volumechange: 'volumechange',
destroy: 'destroy',
beforeDestroy: 'beforeDestroy',
// stream
streamEnd: 'streamEnd',
streamRate: 'streamRate',
streamAbps: 'streamAbps',
streamVbps: 'streamVbps',
streamDts: 'streamDts',
streamSuccess: 'streamSuccess',
streamMessage: 'streamMessage',
streamError: 'streamError',
streamStats: 'streamStats',
// MSE
mseSourceOpen: 'mseSourceOpen',
mseSourceClose: 'mseSourceClose',
mseSourceended: 'mseSourceended',
mseSourceStartStreaming: 'mseSourceStartStreaming',
mseSourceEndStreaming: 'mseSourceEndStreaming',
mseSourceBufferError: 'mseSourceBufferError',
mseAddSourceBufferError: 'mseAddSourceBufferError',
mseSourceBufferBusy: 'mseSourceBufferBusy',
mseSourceBufferFull: 'mseSourceBufferFull',
// VIDEO
videoWaiting: 'videoWaiting',
videoTimeUpdate: 'videoTimeUpdate',
videoSyncAudio: 'videoSyncAudio',
//
playToRenderTimes: 'playToRenderTimes',
playbackTime: 'playbackTime',
playbackTimestamp: 'playbackTimestamp',
playbackTimeScroll: 'playbackTimeScroll',
playbackPrecision: 'playbackPrecision',
// inner
playbackShowPrecisionChange: 'playbackShowPrecisionChange',
playbackJustTime: 'playbackJustTime',
playbackStats: 'playbackStats',
playbackSeek: 'playbackSeek',
playbackPause: 'playbackPause',
playbackPauseOrResume: 'playbackPauseOrResume',
playbackRateChange: 'playbackRateChange',
playbackPreRateChange: 'playbackPreRateChange',
ptz: 'ptz',
streamQualityChange: 'streamQualityChange',
visibilityChange: "visibilityChange",
netBuf: 'netBuf',
close: 'close',
networkDelayTimeout: 'networkDelayTimeout',
togglePerformancePanel: 'togglePerformancePanel',
viewResizeChange: 'viewResizeChange',
flvDemuxBufferSizeTooLarge: 'flvDemuxBufferSizeTooLarge',
// talk
talkGetUserMediaSuccess: 'talkGetUserMediaSuccess',
talkGetUserMediaFail: 'talkGetUserMediaFail',
talkGetUserMediaTimeout: 'talkGetUserMediaTimeout',
talkStreamStart: 'talkStreamStart',
talkStreamOpen: 'talkStreamOpen',
talkStreamClose: 'talkStreamClose',
talkStreamError: 'talkStreamError',
talkStreamInactive: 'talkStreamInactive',
webrtcDisconnect: 'webrtcDisconnect',
webrtcFailed: 'webrtcFailed',
webrtcClosed: 'webrtcClosed',
webrtcOnConnectionStateChange: 'webrtcOnConnectionStateChange',
webrtcOnIceConnectionStateChange: 'webrtcOnIceConnectionStateChange',
// crash
crashLog: 'crashLog',
// dom
focus: 'focus',
blur: 'blur',
visibilityHiddenTimeout: 'visibilityHiddenTimeout',
// websocket
websocketOpen: 'websocketOpen',
websocketClose: 'websocketClose',
websocketError: 'websocketError',
websocketMessage: 'websocketMessage',
// ai
aiObjectDetectorInfo: 'aiObjectDetectorInfo',
aiFaceDetectorInfo: 'aiFaceDetectorInfo',
aiOcclusionDetectResult: 'aiOcclusionDetectResult',
aiImageDetectResult: 'aiImageDetectResult',
// 异常暂停
playFailedAndPaused: 'playFailedAndPaused',
// audio
audioResumeState: 'audioResumeState',
// webrtc
webrtcStreamH265: 'webrtcStreamH265',
// flv
flvMetaData: 'flvMetaData',
// talk
talkFailedAndStop: 'talkFailedAndStop',
removeLoadingBgImage: 'removeLoadingBgImage',
memoryLog: 'memoryLog',
downloadMemoryLog: 'downloadMemoryLog',
pressureObserverCpu: 'pressureObserverCpu',
currentPts: 'currentPts'
};
const TALK_EVENTS = {
talkStreamClose: EVENTS.talkStreamClose,
talkStreamError: EVENTS.talkStreamError,
talkStreamInactive: EVENTS.talkStreamInactive,
talkGetUserMediaTimeout: EVENTS.talkGetUserMediaTimeout,
talkFailedAndStop: EVENTS.talkFailedAndStop
};
const TALK_EVENTS_ERROR = {
talkStreamError: EVENTS.talkStreamError,
talkStreamClose: EVENTS.talkStreamClose
};
const EVENTS_ERROR = {
playError: 'playIsNotPauseOrUrlIsNull',
fetchError: "fetchError",
websocketError: 'websocketError',
webcodecsH265NotSupport: 'webcodecsH265NotSupport',
webcodecsDecodeError: 'webcodecsDecodeError',
webcodecsUnsupportedConfigurationError: 'webcodecsUnsupportedConfigurationError',
webcodecsDecodeConfigureError: 'webcodecsDecodeConfigureError',
mediaSourceH265NotSupport: 'mediaSourceH265NotSupport',
mediaSourceAudioG711NotSupport: 'mediaSourceAudioG711NotSupport',
mediaSourceAudioInitTimeout: 'mediaSourceAudioInitTimeout',
mediaSourceAudioNoDataTimeout: 'mediaSourceAudioNoDataTimeout',
mediaSourceDecoderConfigurationError: 'mediaSourceDecoderConfigurationError',
mediaSourceFull: EVENTS.mseSourceBufferFull,
mseSourceBufferError: EVENTS.mseSourceBufferError,
mseAddSourceBufferError: EVENTS.mseAddSourceBufferError,
mediaSourceAppendBufferError: 'mediaSourceAppendBufferError',
mediaSourceTsIsMaxDiff: 'mediaSourceTsIsMaxDiff',
mediaSourceUseCanvasRenderPlayFailed: 'mediaSourceUseCanvasRenderPlayFailed',
mediaSourceBufferedIsZeroError: 'mediaSourceBufferedIsZeroError',
wasmDecodeError: 'wasmDecodeError',
wasmUseVideoRenderError: 'wasmUseVideoRenderError',
hlsError: 'hlsError',
webrtcError: 'webrtcError',
webrtcClosed: EVENTS.webrtcClosed,
webrtcIceCandidateError: 'webrtcIceCandidateError',
webglAlignmentError: 'webglAlignmentError',
wasmWidthOrHeightChange: 'wasmWidthOrHeightChange',
mseWidthOrHeightChange: 'mseWidthOrHeightChange',
wcsWidthOrHeightChange: 'wcsWidthOrHeightChange',
widthOrHeightChange: 'widthOrHeightChange',
tallWebsocketClosedByError: 'tallWebsocketClosedByError',
flvDemuxBufferSizeTooLarge: EVENTS.flvDemuxBufferSizeTooLarge,
wasmDecodeVideoNoResponseError: 'wasmDecodeVideoNoResponseError',
audioChannelError: 'audioChannelError',
simdH264DecodeVideoWidthIsTooLarge: 'simdH264DecodeVideoWidthIsTooLarge',
simdDecodeError: 'simdDecodeError',
webglContextLostError: 'webglContextLostError',
videoElementPlayingFailed: 'videoElementPlayingFailed',
videoElementPlayingFailedForWebrtc: 'videoElementPlayingFailedForWebrtc',
decoderWorkerInitError: 'decoderWorkerInitError',
videoInfoError: 'videoInfoError',
videoCodecIdError: 'videoCodecIdError',
streamEnd: EVENTS.streamEnd,
delayTimeout: EVENTS.delayTimeout,
loadingTimeout: EVENTS.loadingTimeout,
networkDelayTimeout: EVENTS.networkDelayTimeout,
aliyunRtcError: 'aliyunRtcError',
...TALK_EVENTS_ERROR
};
const WEBSOCKET_STATUS = {
notConnect: 'notConnect',
open: 'open',
close: 'close',
error: 'error'
};
const TALK_ENC_TYPE = {
g711a: 'g711a',
g711u: 'g711u',
pcm: 'pcm',
opus: 'opus'
};
// RTP_PAYLOAD_TYPE_PCMU = 0, // g711u
// RTP_PAYLOAD_TYPE_PCMA = 8, // g711a
// RTP_PAYLOAD_TYPE_JPEG = 26,
// RTP_PAYLOAD_TYPE_H264 = 96,
// RTP_PAYLOAD_TYPE_H265 = 97,
// RTP_PAYLOAD_TYPE_OPUS = 98,
// RTP_PAYLOAD_TYPE_AAC = 99,
// RTP_PAYLOAD_TYPE_G726 = 100,
// RTP_PAYLOAD_TYPE_G726_16 = 101,
// RTP_PAYLOAD_TYPE_G726_24 = 102,
// RTP_PAYLOAD_TYPE_G726_32 = 103,
// RTP_PAYLOAD_TYPE_G726_40 = 104,
// RTP_PAYLOAD_TYPE_SPEEX = 105,
const RTP_PAYLOAD_TYPE = {
pcma: 8,
g711a: 8,
pcmu: 0,
g711u: 0,
jpeg: 26,
h264: 96,
h265: 97,
opus: 98,
aac: 99
};
const TALK_PACKET_TYPE = {
empty: 'empty',
// 裸的协议
rtp: 'rtp'
};
const TALK_PACKAGE_TCP_SEND_TYPE = {
tcp: 'tcp',
udp: 'udp'
};
const WEBSOCKET_EVENTS = {
open: 'open',
close: 'close',
error: 'error',
message: 'message'
};
const TALK_ENGINE = {
worklet: 'worklet',
script: 'script'
};
// default talk options
const DEFAULT_TALK_OPTIONS = {
encType: TALK_ENC_TYPE.g711a,
packetType: TALK_PACKET_TYPE.rtp,
// 默认的包个格式化
packetTcpSendType: TALK_PACKAGE_TCP_SEND_TYPE.tcp,
// 默认的包个格式化
rtpSsrc: '0000000000',
// 10 位
numberChannels: 1,
// 采样通道
sampleRate: 8000,
// 采样率
sampleBitsWidth: 16,
// 采样精度
sendInterval: 20,
// 发送间隔(ms)
debug: false,
debugLevel: DEBUG_LEVEL.warn,
// debug level
testMicrophone: false,
// 测试麦克风获取
saveRtpToFile: false,
// 保存 rtp 到文件
audioBufferLength: 160,
// 默认走的是 20ms 8000 采样率 16 位精度
engine: TALK_ENGINE.worklet,
//
checkGetUserMediaTimeout: false,
// 检测 getUserMedia 超时
getUserMediaTimeout: 10 * 1000,
// getUserMedia 超时时间 10s
audioConstraints: {
// deviceId: '', // 设备 id
latency: true,
//
noiseSuppression: true,
// 降噪
autoGainControl: true,
echoCancellation: true,
// 回声消除
sampleRate: 48000,
channelCount: 1
}
};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var screenfull = createCommonjsModule(function (module) {
/*!
* screenfull
* v5.1.0 - 2020-12-24
* (c) Sindre Sorhus; MIT License
*/
(function () {
var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
var isCommonjs = module.exports;
var fn = (function () {
var val;
var fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror'
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror'
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror'
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError'
]
];
var i = 0;
var l = fnMap.length;
var ret = {};
for (; i < l; i++) {
val = fnMap[i];
if (val && val[1] in document) {
for (i = 0; i < val.length; i++) {
ret[fnMap[0][i]] = val[i];
}
return ret;
}
}
return false;
})();
var eventNameMap = {
change: fn.fullscreenchange,
error: fn.fullscreenerror
};
var screenfull = {
request: function (element, options) {
return new Promise(function (resolve, reject) {
var onFullScreenEntered = function () {
this.off('change', onFullScreenEntered);
resolve();
}.bind(this);
this.on('change', onFullScreenEntered);
element = element || document.documentElement;
var returnPromise = element[fn.requestFullscreen](options);
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenEntered).catch(reject);
}
}.bind(this));
},
exit: function () {
return new Promise(function (resolve, reject) {
if (!this.isFullscreen) {
resolve();
return;
}
var onFullScreenExit = function () {
this.off('change', onFullScreenExit);
resolve();
}.bind(this);
this.on('change', onFullScreenExit);
var returnPromise = document[fn.exitFullscreen]();
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenExit).catch(reject);
}
}.bind(this));
},
toggle: function (element, options) {
return this.isFullscreen ? this.exit() : this.request(element, options);
},
onchange: function (callback) {
this.on('change', callback);
},
onerror: function (callback) {
this.on('error', callback);
},
on: function (event, callback) {
var eventName = eventNameMap[event];
if (eventName) {
document.addEventListener(eventName, callback, false);
}
},
off: function (event, callback) {
var eventName = eventNameMap[event];
if (eventName) {
document.removeEventListener(eventName, callback, false);
}
},
raw: fn
};
if (!fn) {
if (isCommonjs) {
module.exports = {isEnabled: false};
} else {
window.screenfull = {isEnabled: false};
}
return;
}
Object.defineProperties(screenfull, {
isFullscreen: {
get: function () {
return Boolean(document[fn.fullscreenElement]);
}
},
element: {
enumerable: true,
get: function () {
return document[fn.fullscreenElement];
}
},
isEnabled: {
enumerable: true,
get: function () {
// Coerce to boolean in case of old WebKit
return Boolean(document[fn.fullscreenEnabled]);
}
}
});
if (isCommonjs) {
module.exports = screenfull;
} else {
window.screenfull = screenfull;
}
})();
});
screenfull.isEnabled;
function now() {
return new Date().getTime();
}
(() => {
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module) return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
} catch (e) {}
return false;
})();
function clamp(num, a, b) {
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
}
function isFunction(fn) {
return typeof fn === "function";
}
function isSupportGetUserMedia() {
let result = false;
const navigator = window.navigator;
if (navigator) {
result = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
if (!result) {
result = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
}
}
return result;
}
function createWorkletModuleUrl(func) {
function functionToString(str) {
return str.trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1];
}
const funcStr = functionToString(func.toString());
const blob = new Blob([funcStr], {
type: 'application/javascript'
});
return URL.createObjectURL(blob);
}
function clone(obj) {
let result = '';
//
if (typeof obj === 'object') {
try {
result = JSON.stringify(obj);
result = JSON.parse(result);
} catch (e) {
result = obj;
}
} else {
result = obj;
}
return result;
}
/**
*
* @returns {object:DEFAULT_TALK_OPTIONS}
*/
function getDefaultTalkOptions() {
return clone(DEFAULT_TALK_OPTIONS);
}
function isTrue(value) {
return value === true || value === 'true';
}
class Resampler {
constructor(options) {
const {
fromSampleRate,
toSampleRate,
channels,
inputBufferSize
} = options;
if (!fromSampleRate || !toSampleRate || !channels) {
throw new Error("Invalid settings specified for the resampler.");
}
this.resampler = null;
this.fromSampleRate = fromSampleRate;
this.toSampleRate = toSampleRate;
this.channels = channels || 0;
this.inputBufferSize = inputBufferSize;
this.initialize();
}
initialize() {
if (this.fromSampleRate == this.toSampleRate) {
// Setup resampler bypass - Resampler just returns what was passed through
this.resampler = buffer => {
return buffer;
};
this.ratioWeight = 1;
} else {
if (this.fromSampleRate < this.toSampleRate) {
// Use generic linear interpolation if upsampling,
// as linear interpolation produces a gradient that we want
// and works fine with two input sample points per output in this case.
this.linearInterpolation();
this.lastWeight = 1;
} else {
// Custom resampler I wrote that doesn't skip samples
// like standard linear interpolation in high downsampling.
// This is more accurate than linear interpolation on downsampling.
this.multiTap();
this.tailExists = false;
this.lastWeight = 0;
}
// Initialize the internal buffer:
this.initializeBuffers();
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
}
}
bufferSlice(sliceAmount) {
//Typed array and normal array buffer section referencing:
try {
return this.outputBuffer.subarray(0, sliceAmount);
} catch (error) {
try {
//Regular array pass:
this.outputBuffer.length = sliceAmount;
return this.outputBuffer;
} catch (error) {
//Nightly Firefox 4 used to have the subarray function named as slice:
return this.outputBuffer.slice(0, sliceAmount);
}
}
}
initializeBuffers() {
this.outputBufferSize = Math.ceil(this.inputBufferSize * this.toSampleRate / this.fromSampleRate / this.channels * 1.000000476837158203125) + this.channels + this.channels;
try {
this.outputBuffer = new Float32Array(this.outputBufferSize);
this.lastOutput = new Float32Array(this.channels);
} catch (error) {
this.outputBuffer = [];
this.lastOutput = [];
}
}
linearInterpolation() {
this.resampler = buffer => {
let bufferLength = buffer.length,
channels = this.channels,
outLength,
ratioWeight,
weight,
firstWeight,
secondWeight,
sourceOffset,
outputOffset,
outputBuffer,
channel;
if (bufferLength % channels !== 0) {
throw new Error("Buffer was of incorrect sample length.");
}
if (bufferLength <= 0) {
return [];
}
outLength = this.outputBufferSize;
ratioWeight = this.ratioWeight;
weight = this.lastWeight;
firstWeight = 0;
secondWeight = 0;
sourceOffset = 0;
outputOffset = 0;
outputBuffer = this.outputBuffer;
for (; weight < 1; weight += ratioWeight) {
secondWeight = weight % 1;
firstWeight = 1 - secondWeight;
this.lastWeight = weight % 1;
for (channel = 0; channel < this.channels; ++channel) {
outputBuffer[outputOffset++] = this.lastOutput[channel] * firstWeight + buffer[channel] * secondWeight;
}
}
weight -= 1;
for (bufferLength -= channels, sourceOffset = Math.floor(weight) * channels; outputOffset < outLength && sourceOffset < bufferLength;) {
secondWeight = weight % 1;
firstWeight = 1 - secondWeight;
for (channel = 0; channel < this.channels; ++channel) {
outputBuffer[outputOffset++] = buffer[sourceOffset + (channel > 0 ? channel : 0)] * firstWeight + buffer[sourceOffset + (channels + channel)] * secondWeight;
}
weight += ratioWeight;
sourceOffset = Math.floor(weight) * channels;
}
for (channel = 0; channel < channels; ++channel) {
this.lastOutput[channel] = buffer[sourceOffset++];
}
return this.bufferSlice(outputOffset);
};
}
multiTap() {
this.resampler = buffer => {
let bufferLength = buffer.length,
outLength,
output_variable_list,
channels = this.channels,
ratioWeight,
weight,
channel,
actualPosition,
amountToNext,
alreadyProcessedTail,
outputBuffer,
outputOffset,
currentPosition;
if (bufferLength % channels !== 0) {
throw new Error("Buffer was of incorrect sample length.");
}
if (bufferLength <= 0) {
return [];
}
outLength = this.outputBufferSize;
output_variable_list = [];
ratioWeight = this.ratioWeight;
weight = 0;
actualPosition = 0;
amountToNext = 0;
alreadyProcessedTail = !this.tailExists;
this.tailExists = false;
outputBuffer = this.outputBuffer;
outputOffset = 0;
currentPosition = 0;
for (channel = 0; channel < channels; ++channel) {
output_variable_list[channel] = 0;
}
do {
if (alreadyProcessedTail) {
weight = ratioWeight;
for (channel = 0; channel < channels; ++channel) {
output_variable_list[channel] = 0;
}
} else {
weight = this.lastWeight;
for (channel = 0; channel < channels; ++channel) {
output_variable_list[channel] = this.lastOutput[channel];
}
alreadyProcessedTail = true;
}
while (weight > 0 && actualPosition < bufferLength) {
amountToNext = 1 + actualPosition - currentPosition;
if (weight >= amountToNext) {
for (channel = 0; channel < channels; ++channel) {
output_variable_list[channel] += buffer[actualPosition++] * amountToNext;
}
currentPosition = actualPosition;
weight -= amountToNext;
} else {
for (channel = 0; channel < channels; ++channel) {
output_variable_list[channel] += buffer[actualPosition + (channel > 0 ? channel : 0)] * weight;
}
currentPosition += weight;
weight = 0;
break;
}
}
if (weight === 0) {
for (channel = 0; channel < channels; ++channel) {
outputBuffer[outputOffset++] = output_variable_list[channel] / ratioWeight;
}
} else {
this.lastWeight = weight;
for (channel = 0; channel < channels; ++channel) {
this.lastOutput[channel] = output_variable_list[channel];
}
this.tailExists = true;
break;
}
} while (actualPosition < bufferLength && outputOffset < outLength);
return this.bufferSlice(outputOffset);
};
}
resample(buffer) {
if (this.fromSampleRate == this.toSampleRate) {
this.ratioWeight = 1;
} else {
if (this.fromSampleRate < this.toSampleRate) {
this.lastWeight = 1;
} else {
this.tailExists = false;
this.lastWeight = 0;
}
this.initializeBuffers();
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
}
return this.resampler(buffer);
}
}
/**
* 将无符号Float32Array数组转化成有符号的Int16Array数组
* @param {Float32Array} input unsinged Float32Array
* @return {Int16Array} singed int16
*/
function floatTo16BitPCM(input) {
let i = input.length;
let output = new Int16Array(i);
while (i--) {
let s = Math.max(-1, Math.min(1, input[i]));
output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
}
return output;
}
/**
* 将无符号Float32Array数组转化成有符号的Int8Array数组
* @param {Float32Array} input unsinged Float32Array
* @return {Int8Array} singed int8
*/
function floatTo8BitPCM(input) {
let i = input.length;
let output = new Int8Array(i);
while (i--) {
let s = Math.max(-1, Math.min(1, input[i]));
const temp = s < 0 ? s * 0x8000 : s * 0x7FFF;
output[i] = parseInt(255 / (65535 / (32768 + temp)), 10);
}
return output;
}
const QUANT_MASK = 0xf;
const SEG_SHIFT = 4;
const BIAS = 0x84;
const segEnd = [0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF];
function _search(val, table, size) {
for (let i = 0; i < size; i++) {
if (val <= table[i]) {
return i;
}
}
return size;
}
// alaw
function _linear2alaw(pcmVal) {
let mask;
let seg;
let aval;
if (pcmVal >= 0) {
mask = 0xD5;
} else {
mask = 0x55;
// pcmVal = -pcmVal - 8;
pcmVal = -pcmVal - 1;
if (pcmVal < 0) {
pcmVal = 32767;
}
}
/* Convert the scaled magnitude to segment number. */
seg = _search(pcmVal, segEnd, 8);
if (seg >= 8) {
return 0x7F ^ mask;
} else {
aval = seg << SEG_SHIFT;
if (seg < 2) {
aval |= pcmVal >> 4 & QUANT_MASK;
} else {
aval |= pcmVal >> seg + 3 & QUANT_MASK;
}
return aval ^ mask;
}
}
// ulaw
function _linear2ulaw(pcmVal) {
let mask = 0;
if (pcmVal < 0) {
pcmVal = BIAS - pcmVal;
mask = 0x7F;
} else {
pcmVal += BIAS;
mask = 0xFF;
}
let seg = _search(pcmVal, segEnd, 8);
if (seg >= 8) {
return 0x7F ^ mask;
} else {
let uval = seg << 4 | pcmVal >> seg + 3 & 0xF;
return uval ^ mask;
}
}
// pcm to g711a
function g711aEncoder(typedArray) {
const g711Array = [];
const tempArray = Array.prototype.slice.call(typedArray);
tempArray.forEach((i, index) => {
g711Array[index] = _linear2alaw(i);
});
return g711Array;
}
// g711u
function g711uEncoder(typedArray) {
const g711Array = [];
const tempArray = Array.prototype.slice.call(typedArray);
tempArray.forEach((i, index) => {
g711Array[index] = _linear2ulaw(i);
});
return g711Array;
}
class Debug {
constructor(master) {
this.log = function (name) {
if (master._opt.debug && master._opt.debugLevel == DEBUG_LEVEL.debug) {
const prefix = master._opt.debugUuid ? `[${master._opt.debugUuid}]` : '';
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
console.log(`JbPro${prefix}[\u2705\u2705\u2705][${name}]`, ...args);
}
};
this.warn = function (name) {
if (master._opt.debug && (master._opt.debugLevel == DEBUG_LEVEL.debug || master._opt.debugLevel == DEBUG_LEVEL.warn)) {
const prefix = master._opt.debugUuid ? `[${master._opt.debugUuid}]` : '';
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
console.log(`JbPro${prefix}[\u2757\u2757\u2757][${name}]`, ...args);
}
};
this.error = function (name) {
const prefix = master._opt.debugUuid ? `[${master._opt.debugUuid}]` : '';
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
console.error(`JbPro${prefix}[\u274C\u274C\u274C][${name}]`, ...args);
// if (master.addMemoryLog) {
// master.addMemoryLog(`[${name}]`, ...args);
// }
};
}
}
class Events {
constructor(master) {
this.destroys = [];
this.proxy = this.proxy.bind(this);
this.master = master;
}
proxy(target, name, callback) {
let option = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
if (!target) {
return;
}
if (Array.isArray(name)) {
return name.map(item => this.proxy(target, item, callback, option));
}
target.addEventListener(name, callback, option);
const destroy = () => {
if (isFunction(target.removeEventListener)) {
target.removeEventListener(name, callback, option);
}
};
this.destroys.push(destroy);
return destroy;
}
destroy() {
this.master.debug && this.master.debug.log(`Events`, 'destroy');
this.destroys.forEach(event => event());
this.destroys = [];
}
}
class Talk extends Emitter {
constructor(player) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
super();
/**@type {import('./constant').DEFAULT_TALK_OPTIONS}*/
this._opt = {};
if (player) {
this.player = player;
}
this.tag = 'talk';
const defaultOptions = getDefaultTalkOptions();
this._opt = Object.assign({}, defaultOptions, options);
this._opt.sampleRate = parseInt(this._opt.sampleRate, 10);
this._opt.sampleBitsWidth = parseInt(this._opt.sampleBitsWidth, 10);
this.audioContext = null;
this.gainNode = null;
this.recorder = null;
this.workletRecorder = null;
this.biquadFilter = null;
this.userMediaStream = null;
this.clearWorkletUrlTimeout = null;
// buffersize
this.bufferSize = 512; //
this._opt.audioBufferLength = this.calcAudioBufferLength();
this.audioBufferList = [];
// socket
this.socket = null;
this.socketStatus = WEBSOCKET_STATUS.notConnect;
//
this.mediaStreamSource = null;
this.heartInterval = null;
this.checkGetUserMediaTimeout = null;
this.wsUrl = null;
this.startTimestamp = 0;
// 报文数据
this.sequenceId = 0;
this.tempTimestamp = null;
this.tempRtpBufferList = [];
this.events = new Events(this);
this._initTalk();
//
if (!this.player) {
this.debug = new Debug(this);
}
if ((this._opt.encType === TALK_ENC_TYPE.g711a || this._opt.encType === TALK_ENC_TYPE.g711u) && !(this._opt.sampleRate === 8000 && this._opt.sampleBitsWidth === 16)) {
this.warn(this.tag, `
encType is ${this._opt.encType} and sampleBitsWidth is ${this._opt.sampleBitsWidth}, set sampleBitsWidth to ${this._opt.sampleBitsWidth}
${this._opt.encType} only support sampleRate 8000 and sampleBitsWidth 16`);
}
this.log(this.tag, 'init', JSON.stringify(this._opt));
}
destroy() {
if (this.clearWorkletUrlTimeout) {
clearTimeout(this.clearWorkletUrlTimeout);
this.clearWorkletUrlTimeout = null;
}
//
if (this.userMediaStream) {
this.userMediaStream.getTracks && this.userMediaStream.getTracks().forEach(track => {
track.stop();
});
this.userMediaStream = null;
}
if (this.mediaStreamSource) {
this.mediaStreamSource.disconnect();
this.mediaStreamSource = null;
}
if (this.recorder) {
this.recorder.disconnect();
this.recorder.onaudioprocess = null;
this.recorder = null;
}
if (this.biquadFilter) {
this.biquadFilter.disconnect();
this.biquadFilter = null;
}
if (this.gainNode) {
this.gainNode.disconnect();
this.gainNode = null;
}
if (this.workletRecorder) {
this.workletRecorder.disconnect();
this.workletRecorder = null;
}
if (this.socket) {
if (this.socketStatus === WEBSOCKET_STATUS.open) {
this._sendClose();
}
this.socket.close();
this.socket = null;
}
this._stopHeartInterval();
this._stopCheckGetUserMediaTimeout();
this.audioContext = null;
this.gainNode = null;
this.recorder = null;
this.audioBufferList = [];
this.sequenceId = 0;
this.wsUrl = null;
this.tempTimestamp = null;
this.tempRtpBufferList = [];
this.startTimestamp = 0;
this.log('talk', 'destroy');
}
addRtpToBuffer(rtp) {
const len = rtp.length + this.tempRtpBufferList.length;
const buffer = new Uint8Array(len);
buffer.set(this.tempRtpBufferList, 0);
buffer.set(rtp, this.tempRtpBufferList.length);
this.tempRtpBufferList = buffer;
//console.log('addRtpToBuffer length and byteLength ', this.tempRtpBufferList.length, this.tempRtpBufferList.byteLength)
}
downloadRtpFile() {
const blob = new Blob([this.tempRtpBufferList]);
try {
const oa = document.createElement('a');
oa.href = window.URL.createObjectURL(blob);
oa.download = Date.now() + '.rtp';
oa.click();
window.URL.revokeObjectURL(oa.href);
} catch (e) {
console.error('downloadRtpFile', e);
}
}
calcAudioBufferLength() {
const {
sampleRate,
sampleBitsWidth
} = this._opt;
// 默认走的是 20ms 8000 采样率 16 位精度
return sampleRate * 8 * (20 / 1000) / 8;
}
get socketStatusOpen() {
return this.socketStatus === WEBSOCKET_STATUS.open;
}
log() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
this._log('log', ...args);
}
warn() {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
this._log('warn', ...args);
}
error() {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
this._log('error', ...args);
}
_log(type) {
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
args[_key4 - 1] = arguments[_key4];
}
if (this.player) {
this.player.debug[type](...args);
} else if (this.debug) {
this.debug[type](...args);
} else {
console[type](...args);
}
}
_getSequenceId() {
return ++this.sequenceId;
}
_createWebSocket() {
return new Promise((resolve, reject) => {
const proxy = this.events.proxy;
this.socket = new WebSocket(this.wsUrl);
this.socket.binaryType = 'arraybuffer';
this.emit(EVENTS.talkStreamStart);
proxy(this.socket, WEBSOCKET_EVENTS.open, () => {
this.socketStatus = WEBSOCKET_STATUS.open;
this.log(this.tag, 'websocket open -> do talk');
this.emit(EVENTS.talkStreamOpen);
resolve();
this._doTalk();
});
proxy(this.socket, WEBSOCKET_EVENTS.message, event => {
this.log(this.tag, 'websocket message', event.data);
});
proxy(this.socket, WEBSOCKET_EVENTS.close, e => {
this.socketStatus = WEBSOCKET_STATUS.close;
this.warn(this.tag, 'websocket close -> reject', e);
this.emit(EVENTS.talkStreamClose);
reject(e);
});
proxy(this.socket, WEBSOCKET_EVENTS.error, error => {
this.socketStatus = WEBSOCKET_STATUS.error;
this.error(this.tag, 'websocket error -> reject', error);
this.emit(EVENTS.talkStreamError, error);
reject(error);
});
});
}
_sendClose() {}
_initTalk() {
this._initMethods();
if (this._opt.engine === TALK_ENGINE.worklet) {
this._initWorklet();
} else if (this._opt.engine === TALK_ENGINE.script) {
this._initScriptProcessor();
}
this.log(this.tag, 'audioContext samplerate', this.audioContext.sampleRate);
}
_initMethods() {
//
this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
sampleRate: 48000
});
this.gainNode = this.audioContext.createGain();
// default 1
this.gainNode.gain.value = 1;
// 消音器
this.biquadFilter = this.audioContext.createBiquadFilter();
this.biquadFilter.type = "lowpass";
this.biquadFilter.frequency.value = 3000;
this.resampler = new Resampler({
fromSampleRate: this.audioContext.sampleRate,
toSampleRate: this._opt.sampleRate,
channels: this._opt.numberChannels,
inputBufferSize: this.bufferSize
});
}
_initScriptProcessor() {
//
const createScript = this.audioContext.createScriptProcessor || this.audioContext.createJavaScriptNode;
this.recorder = createScript.apply(this.audioContext, [this.bufferSize, this._opt.numberChannels, this._opt.numberChannels]);
this.recorder.onaudioprocess = e => this._onaudioprocess(e);
}
_initWorklet() {
function workletProcess() {
class TalkProcessor extends AudioWorkletProcessor {
constructor(options) {
super();
this._cursor = 0;
this._bufferSize = options.processorOptions.bufferSize;
this._buffer = new Float32Array(this._bufferSize);
}
process(inputs, outputs, parameters) {
if (!inputs.length || !inputs[0].length) {
return true;
}
for (let i = 0; i < inputs[0][0].length; i++) {
this._cursor += 1;
if (this._cursor === this._bufferSize) {
this._cursor = 0;
this.port.postMessage({
eventType: 'data',
buffer: this._buffer
});
}
this._buffer[this._cursor] = inputs[0][0][i];
}
return true;
}
}
registerProcessor('talk-processor', TalkProcessor);
}
const workletUrl = createWorkletModuleUrl(workletProcess);
this.audioContext.audioWorklet && this.audioContext.audioWorklet.addModule(workletUrl).then(() => {
const workletNode = new AudioWorkletNode(this.audioContext, 'talk-processor', {
processorOptions: {
bufferSize: this.bufferSize
}
});
workletNode.connect(this.gainNode);
workletNode.port.onmessage = e => {
if (e.data.eventType === 'data') {
this._encodeAudioData(e.data.buffer);
}
};
this.workletRecorder = workletNode;
});
this.clearWorkletUrlTimeout = setTimeout(() => {
URL.revokeObjectURL(workletUrl);
this.clearWorkletUrlTimeout = null;
}, URL_OBJECT_CLEAR_TIME);
}
_onaudioprocess(e) {
// 数组里的每个数字都是32位的单精度浮点数
// 默认是单精度
const float32Array = e.inputBuffer.getChannelData(0);
// send 出去。
this._encodeAudioData(new Float32Array(float32Array));
}
_encodeAudioData(float32Array) {
// 没有说话
if (float32Array[0] === 0 && float32Array[1] === 0) {
this.log(this.tag, 'empty audio data');
return;
}
const resampleBuffer = this.resampler.resample(float32Array);
// default 32Bit
let tempArrayBuffer = resampleBuffer;
if (this._opt.sampleBitsWidth === 16) {
tempArrayBuffer = floatTo16BitPCM(resampleBuffer);
} else if (this._opt.sampleBitsWidth === 8) {
tempArrayBuffer = floatTo8BitPCM(resampleBuffer);
}
if (tempArrayBuffer.buffer !== null) {
let typedArray = null;
if (this._opt.encType === TALK_ENC_TYPE.g711a) {
typedArray = g711aEncoder(tempArrayBuffer);
} else if (this._opt.encType === TALK_ENC_TYPE.g711u) {
typedArray = g711uEncoder(tempArrayBuffer);
} else if (this._opt.encType === TALK_ENC_TYPE.pcm) {
typedArray = tempArrayBuffer;
}
// 变成了8位 array 了
const unit8Array = new Uint8Array(typedArray);
for (let i = 0; i < unit8Array.length; i++) {
let audioBufferLength = this.audioBufferList.length;
this.audioBufferList[audioBufferLength++] = unit8Array[i];
if (this.audioBufferList.length === this._opt.audioBufferLength) {
this._sendTalkMsg(new Uint8Array(this.audioBufferList));
this.audioBufferList = [];
}
}
}
}
_parseAudioMsg(typedArray) {
let typeArray2 = null;
// rtp reumx just support g711a or g711u or opus
if (this._opt.packetType === TALK_PACKET_TYPE.rtp && (this._opt.encType === TALK_ENC_TYPE.g711a || this._opt.encType === TALK_ENC_TYPE.g711u)) {
typeArray2 = this.rtpPacket(typedArray);
} else if (this._opt.packetType === TALK_PACKET_TYPE.empty) {
// 默认
typeArray2 = typedArray;
}
return typeArray2;
}
rtpPacket(typedArray) {
const rtpHeader = [];
//2 bits RTP的版本这里统一为2
const version = 2;
//1 bit 如果置1在packet的末尾被填充填充有时是方便一些针对固定长度的算法的封装
const padding = 0;
//1 bit 如果置1在RTP Header会跟着一个header extension
const extension = 0;
//4 bits 表示头部后 特约信源 的个数
const csrcCount = 0;
//1 bit 不同的有效载荷有不同的含义marker=1; 对于视频,标记一帧的结束;对于音频,标记会话的开始。
const marker = 1;
//7 bits 表示所传输的多媒体的类型,
let playloadType = 0;
//16 bits 每个RTP packet的sequence number会自动加一以便接收端检测丢包情况
let sequenceNumber = 0;
//32 bits 时间戳
let timestamp = 0;
//32 bits 同步源的id每两个同步源的id不能相同
const ssrc = this._opt.rtpSsrc;
//
const frameLen = typedArray.length;
if (this._opt.encType === TALK_ENC_TYPE.g711a) {
playloadType = RTP_PAYLOAD_TYPE.g711a;
} else if (this._opt.encType === TALK_ENC_TYPE.g711u) {
playloadType = RTP_PAYLOAD_TYPE.g711u;
} else if (this._opt.encType === TALK_ENC_TYPE.opus) {
playloadType = RTP_PAYLOAD_TYPE.opus;
}
if (!this.startTimestamp) {
this.startTimestamp = now();
}
timestamp = now() - this.startTimestamp;
sequenceNumber = this._getSequenceId();
// frame length
// 需要在rtp头前面加两个字节表示数据包长度整个rtp包长度
// 国标流udp不需要两个字节长度
// 国标流tcp需要两个字节长度
// websocket目前是按照tcp的做法做的
let index = 0;
if (this._opt.packetTcpSendType === TALK_PACKAGE_TCP_SEND_TYPE.tcp) {
const rtpFrameLen = frameLen + 12;
// 0
rtpHeader[index++] = 0xFF & rtpFrameLen >> 8;
// 1
rtpHeader[index++] = 0xFF & rtpFrameLen >> 0;
}
rtpHeader[index++] = (version << 6) + (padding << 5) + (extension << 4) + csrcCount;
rtpHeader[index++] = (marker << 7) + playloadType;
rtpHeader[index++] = sequenceNumber / (0xff + 1);
rtpHeader[index++] = sequenceNumber % (0xff + 1);
rtpHeader[index++] = timestamp / (0xffff + 1) / (0xff + 1);
rtpHeader[index++] = timestamp / (0xffff + 1) % (0xff + 1);
rtpHeader[index++] = timestamp % (0xffff + 1) / (0xff + 1);
rtpHeader[index++] = timestamp % (0xffff + 1) % (0xff + 1);
rtpHeader[index++] = ssrc / (0xffff + 1) / (0xff + 1);
rtpHeader[index++] = ssrc / (0xffff + 1) % (0xff + 1);
rtpHeader[index++] = ssrc % (0xffff + 1) / (0xff + 1);
rtpHeader[index++] = ssrc % (0xffff + 1) % (0xff + 1);
let typeArray2 = rtpHeader.concat([...typedArray]);
let binary = new Uint8Array(typeArray2.length);
for (let ii = 0; ii < typeArray2.length; ii++) {
binary[ii] = typeArray2[ii];
}
return binary;
}
opusPacket(typedArray) {
//TODO:待完成
return typedArray;
}
_sendTalkMsg(typedArray) {
if (this.tempTimestamp === null) {
this.tempTimestamp = now();
}
const timestamp = now();
const diff = timestamp - this.tempTimestamp;
const typedArray2 = this._parseAudioMsg(typedArray);
this.log(this.tag, `'send talk msg and diff is ${diff} and byteLength is ${typedArray2.byteLength} and length is ${typedArray2.length}, and g711 length is ${typedArray.length}`);
if (isTrue(this._opt.saveRtpToFile)) {
if (this._opt.packetType === TALK_PACKET_TYPE.rtp) {
this.addRtpToBuffer(typedArray2);
}
}
if (typedArray2) {
if (this.socketStatusOpen) {
this.socket.send(typedArray2.buffer);
} else {
this.emit(EVENTS_ERROR.tallWebsocketClosedByError);
}
}
this.tempTimestamp = timestamp;
}
_doTalk() {
this._getUserMedia();
// this._getUserMedia2();
// this._getUserMedia3();
}
_getUserMedia() {
this.log(this.tag, 'getUserMedia');
// 老的浏览器可能根本没有实现 mediaDevices所以我们可以先设置一个空的对象
if (window.navigator.mediaDevices === undefined) {
window.navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有 getUserMedia 属性的时候添加它。
if (window.navigator.mediaDevices.getUserMedia === undefined) {
this.log(this.tag, 'window.navigator.mediaDevices.getUserMedia is undefined and init function');
window.navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先,如果有 getUserMedia 的话,就获得它
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
// 一些浏览器根本没实现它 - 那么就返回一个 error 到 promise 的 reject 来保持一个统一的接口
// 由于受浏览器的限制navigator.mediaDevices.getUserMedia在https协议下是可以正常使用的
// 而在http协议下只允许localhost/127.0.0.1这两个域名访问,
// 因此在开发时应做好容灾处理上线时则需要确认生产环境是否处于https协议下。
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的 navigator.getUserMedia 方法包裹一个 Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
if (this._opt.checkGetUserMediaTimeout) {
this._startCheckGetUserMediaTimeout();
}
// 最后经过反复测试只有noiseSuppression+echoCancellation同时生效时打开录音后再播放音频系统音量一定会变小
// 很惨的是getUserMedia只要你没有配置这两个参数默认就是同时开启的只要你给这两参数任意一个设为false或者都设为false
// 就不会影响手机系统音量。
window.navigator.mediaDevices.getUserMedia({
audio: this._opt.audioConstraints,
video: false
}).then(stream => {
this.log(this.tag, 'getUserMedia success');
this.userMediaStream = stream;
this.mediaStreamSource = this.audioContext.createMediaStreamSource(stream);
this.mediaStreamSource.connect(this.biquadFilter);
if (this.recorder) {
this.biquadFilter.connect(this.recorder);
this.recorder.connect(this.gainNode);
} else if (this.workletRecorder) {
this.biquadFilter.connect(this.workletRecorder);
this.workletRecorder.connect(this.gainNode);
}
this.gainNode.connect(this.audioContext.destination);
this.emit(EVENTS.talkGetUserMediaSuccess);
// check stream inactive
if (stream.oninactive === null) {
stream.oninactive = e => {
this._handleStreamInactive(e);
};
}
}).catch(e => {
this.error(this.tag, 'getUserMedia error', e.toString());
this.emit(EVENTS.talkGetUserMediaFail, e.toString());
}).finally(() => {
this.log(this.tag, 'getUserMedia finally');
this._stopCheckGetUserMediaTimeout();
});
}
_getUserMedia2() {
this.log(this.tag, 'getUserMedia');
navigator.mediaDevices ? navigator.mediaDevices.getUserMedia({
audio: true
}).then(stream => {
this.log(this.tag, 'getUserMedia2 success');
}) : navigator.getUserMedia({
audio: true
}, this.log(this.tag, 'getUserMedia2 success'), this.log(this.tag, 'getUserMedia2 fail'));
}
async _getUserMedia3() {
this.log(this.tag, 'getUserMedia3');
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
latency: true,
noiseSuppression: true,
autoGainControl: true,
echoCancellation: true,
sampleRate: 48000,
channelCount: 1
},
video: false
});
console.log('getUserMedia() got stream:', stream);
this.log(this.tag, 'getUserMedia3 success');
} catch (e) {
this.log(this.tag, 'getUserMedia3 fail');
}
}
_handleStreamInactive(e) {
if (this.userMediaStream) {
this.warn(this.tag, 'stream oninactive', e);
this.emit(EVENTS.talkStreamInactive);
}
}
_startCheckGetUserMediaTimeout() {
this._stopCheckGetUserMediaTimeout();
this.checkGetUserMediaTimeout = setTimeout(() => {
this.log(this.tag, 'check getUserMedia timeout');
this.emit(EVENTS.talkGetUserMediaTimeout);
}, this._opt.getUserMediaTimeout);
}
_stopCheckGetUserMediaTimeout() {
if (this.checkGetUserMediaTimeout) {
this.log(this.tag, 'stop checkGetUserMediaTimeout');
clearTimeout(this.checkGetUserMediaTimeout);
this.checkGetUserMediaTimeout = null;
}
}
_startHeartInterval() {
// 定时发送心跳,
this.heartInterval = setInterval(() => {
this.log(this.tag, 'heart interval');
let data = [0x23, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
data = new Uint8Array(data);
this.socket.send(data.buffer);
}, 15 * 1000);
}
_stopHeartInterval() {
if (this.heartInterval) {
this.log(this.tag, 'stop heart interval');
clearInterval(this.heartInterval);
this.heartInterval = null;
}
}
startTalk(wsUrl) {
return new Promise((resolve, reject) => {
if (!isSupportGetUserMedia()) {
return reject('not support getUserMedia');
}
this.wsUrl = wsUrl;
if (this._opt.testMicrophone) {
this._doTalk();
} else {
if (!this.wsUrl) {
return reject('wsUrl is null');
}
this._createWebSocket().catch(e => {
reject(e);
});
}
// reject
this.once(EVENTS.talkGetUserMediaFail, () => {
reject('getUserMedia fail');
});
// only get user media success and resolve
this.once(EVENTS.talkGetUserMediaSuccess, () => {
resolve();
});
});
}
setVolume(volume) {
volume = parseFloat(volume).toFixed(2);
if (isNaN(volume)) {
return;
}
volume = clamp(volume, 0, 1);
this.gainNode.gain.value = volume;
}
getOption() {
return this._opt;
}
get volume() {
return this.gainNode ? parseFloat(this.gainNode.gain.value * 100).toFixed(0) : null;
}
}
class JessibucaProTalk extends Emitter {
constructor() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
super();
this.talk = null;
/**@type {import('./constant').DEFAULT_TALK_OPTIONS}*/
this._opt = options;
this.LOG_TAG = 'JbProTalk';
this.debug = new Debug(this);
this.debug.log(this.LOG_TAG, 'init', JSON.stringify(options));
}
destroy() {
this.debug.log(this.LOG_TAG, 'destroy()');
this.off();
if (this.talk) {
this.talk.destroy();
this.talk = null;
}
this.debug.log(this.LOG_TAG, 'destroy');
}
_initTalk() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (this.talk) {
this.debug.log(this.LOG_TAG, '_initTalk this.talk is not null and destroy');
this.talk.destroy();
this.talk = null;
}
const opt = Object.assign({}, clone(this._opt), options);
this.talk = new Talk(null, opt);
this.debug.log(this.LOG_TAG, '_initTalk', this.talk.getOption());
this._bindTalkEvents();
}
_bindTalkEvents() {
// 对外的事件
Object.keys(TALK_EVENTS).forEach(key => {
this.talk.on(TALK_EVENTS[key], value => {
this.emit(key, value);
});
});
}
/**
*
* @param wsUrl
* @param options
* @returns {Promise<unknown>}
*/
startTalk(wsUrl) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return new Promise((resolve, reject) => {
this.debug.log(this.LOG_TAG, 'startTalk', wsUrl, JSON.stringify(options));
this._initTalk(options);
this.talk.startTalk(wsUrl).then(() => {
resolve();
this.talk.once(EVENTS.talkStreamClose, () => {
this.debug.warn(this.LOG_TAG, 'talkStreamClose -> stopTalk');
this.stopTalk().catch(e => {
this.debug.warn(this.LOG_TAG, 'talkStreamClose stopTalk', e);
}).finally(() => {
this.emit(EVENTS.talkFailedAndStop, EVENTS.talkStreamClose);
});
});
this.talk.once(EVENTS.talkStreamError, e => {
this.debug.error(this.LOG_TAG, 'talkStreamError -> stopTalk');
this.stopTalk().catch(e => {
this.debug.warn(this.LOG_TAG, 'talkStreamError stopTalk', e);
}).finally(() => {
this.emit(EVENTS.talkFailedAndStop, EVENTS.talkStreamError);
});
});
this.talk.once(EVENTS.talkStreamInactive, () => {
this.debug.warn(this.LOG_TAG, 'talkStreamInactive -> stopTalk');
this.stopTalk().catch(e => {
this.debug.warn(this.LOG_TAG, 'talkStreamInactive stopTalk', e);
}).finally(() => {
this.emit(EVENTS.talkFailedAndStop, EVENTS.talkStreamInactive);
});
});
this.talk.once(EVENTS.talkGetUserMediaTimeout, () => {
this.debug.warn(this.LOG_TAG, 'talkGetUserMediaTimeout -> stopTalk');
this.stopTalk().catch(e => {
this.debug.warn(this.LOG_TAG, 'talkGetUserMediaTimeout stopTalk', e);
}).finally(() => {
this.emit(EVENTS.talkFailedAndStop, EVENTS.talkGetUserMediaTimeout);
});
});
}).catch(e => {
reject(e);
});
});
}
/**
*
* @returns {Promise<unknown>}
*/
stopTalk() {
return new Promise((resolve, reject) => {
this.debug.log(this.LOG_TAG, 'stopTalk()');
if (!this.talk) {
reject('talk is not init');
}
this.talk.destroy();
this.talk = null;
resolve();
});
}
/**
*
* @returns {Promise<unknown>}
*/
getTalkVolume() {
return new Promise((resolve, reject) => {
if (!this.talk) {
reject('talk is not init');
}
let result = this.talk.volume;
resolve(result);
});
}
/**
*
* @param volume
* @returns {Promise<unknown>}
*/
setTalkVolume(volume) {
return new Promise((resolve, reject) => {
this.debug.log(this.LOG_TAG, 'setTalkVolume', volume);
if (!this.talk) {
reject('talk is not init');
}
this.talk.setVolume(volume / 100);
resolve();
});
}
downloadTempRtpFile() {
return new Promise((resolve, reject) => {
if (this.talk) {
this.talk.downloadRtpFile();
resolve();
} else {
reject('talk is not init');
}
});
}
}
JessibucaProTalk.EVENTS = TALK_EVENTS;
window.JessibucaProTalk = JessibucaProTalk;
window.JbProTalk = JessibucaProTalk;
window.WebPlayerProTalk = JessibucaProTalk;
}));
//# sourceMappingURL=web-player-pro-talk.js.map