yjz-web-sdk 1.0.8-beta.14 → 1.0.8-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/rtc/WebRTCClient.d.ts +1 -2
- package/lib/yjz-web-sdk.js +54 -37
- package/lib/yjz-web-sdk.umd.cjs +5 -5
- package/package.json +1 -1
|
@@ -23,7 +23,6 @@ export declare class WebRTCClient extends EventEmitter {
|
|
|
23
23
|
private rafId;
|
|
24
24
|
private currentMedia;
|
|
25
25
|
private fileImage?;
|
|
26
|
-
private ua;
|
|
27
26
|
constructor(config: WebRTCConfig);
|
|
28
27
|
startPush(useBackCamera?: boolean): Promise<void>;
|
|
29
28
|
handleOffer(offerSdp: string): void;
|
|
@@ -43,7 +42,7 @@ export declare class WebRTCClient extends EventEmitter {
|
|
|
43
42
|
private configDataChannel;
|
|
44
43
|
private handleDataChannelMessage;
|
|
45
44
|
private checkStats;
|
|
46
|
-
private
|
|
45
|
+
private processStatsOptimized;
|
|
47
46
|
private getConnectionType;
|
|
48
47
|
/** 获取或创建 video 元素(懒初始化) */
|
|
49
48
|
private getFileVideo;
|
package/lib/yjz-web-sdk.js
CHANGED
|
@@ -3568,6 +3568,7 @@ class FileTypeUtils {
|
|
|
3568
3568
|
}
|
|
3569
3569
|
}
|
|
3570
3570
|
class WebRTCClient extends EventEmitter {
|
|
3571
|
+
// private isFireFox: boolean = false;
|
|
3571
3572
|
constructor(config) {
|
|
3572
3573
|
super();
|
|
3573
3574
|
__publicField(this, "config");
|
|
@@ -3591,7 +3592,6 @@ class WebRTCClient extends EventEmitter {
|
|
|
3591
3592
|
__publicField(this, "rafId", 0);
|
|
3592
3593
|
__publicField(this, "currentMedia", null);
|
|
3593
3594
|
__publicField(this, "fileImage");
|
|
3594
|
-
__publicField(this, "ua", "");
|
|
3595
3595
|
__publicField(this, "startPushLocal", async (file) => {
|
|
3596
3596
|
var _a;
|
|
3597
3597
|
if (this.isPushingLocalStream || !file) return;
|
|
@@ -3624,7 +3624,6 @@ class WebRTCClient extends EventEmitter {
|
|
|
3624
3624
|
}
|
|
3625
3625
|
});
|
|
3626
3626
|
this.config = config;
|
|
3627
|
-
this.ua = navigator.userAgent;
|
|
3628
3627
|
}
|
|
3629
3628
|
async startPush(useBackCamera = true) {
|
|
3630
3629
|
if (this.isPushingLocalStream) {
|
|
@@ -3771,7 +3770,7 @@ class WebRTCClient extends EventEmitter {
|
|
|
3771
3770
|
const rotatedStream = await this.getRotatedStream(this.localStream, useBackCamera);
|
|
3772
3771
|
this.rotatedStream = rotatedStream;
|
|
3773
3772
|
rotatedStream.getTracks().forEach((track) => {
|
|
3774
|
-
track.contentHint = "
|
|
3773
|
+
track.contentHint = "motion";
|
|
3775
3774
|
const sender = this.peerConnection.addTrack(track, rotatedStream);
|
|
3776
3775
|
senders.push(sender);
|
|
3777
3776
|
});
|
|
@@ -3844,15 +3843,26 @@ class WebRTCClient extends EventEmitter {
|
|
|
3844
3843
|
}
|
|
3845
3844
|
async getRotatedStream(localStream, useBackCamera) {
|
|
3846
3845
|
const canvas = document.createElement("canvas");
|
|
3846
|
+
canvas.width = 640;
|
|
3847
|
+
canvas.height = 480;
|
|
3848
|
+
canvas.style.position = "absolute";
|
|
3849
|
+
canvas.style.top = "-9999px";
|
|
3850
|
+
canvas.style.left = "-9999px";
|
|
3851
|
+
canvas.style.width = "1px";
|
|
3852
|
+
canvas.style.height = "1px";
|
|
3853
|
+
canvas.style.opacity = "0";
|
|
3854
|
+
document.body.appendChild(canvas);
|
|
3847
3855
|
const ctx = canvas.getContext("2d");
|
|
3848
3856
|
const video = document.createElement("video");
|
|
3849
3857
|
video.srcObject = localStream;
|
|
3850
3858
|
video.muted = true;
|
|
3859
|
+
video.playsInline = true;
|
|
3860
|
+
video.autoplay = true;
|
|
3861
|
+
video.setAttribute("playsinline", "true");
|
|
3862
|
+
video.setAttribute("webkit-playsinline", "true");
|
|
3851
3863
|
await video.play();
|
|
3852
3864
|
const width = video.videoWidth;
|
|
3853
3865
|
const height = video.videoHeight;
|
|
3854
|
-
canvas.width = 640;
|
|
3855
|
-
canvas.height = 480;
|
|
3856
3866
|
const drawFrame = () => {
|
|
3857
3867
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
3858
3868
|
ctx.save();
|
|
@@ -3876,7 +3886,10 @@ class WebRTCClient extends EventEmitter {
|
|
|
3876
3886
|
rafId = requestAnimationFrame(drawLoop);
|
|
3877
3887
|
const stream = canvas.captureStream(fps);
|
|
3878
3888
|
stream.getTracks().forEach((track) => {
|
|
3879
|
-
const stopLoop = () =>
|
|
3889
|
+
const stopLoop = () => {
|
|
3890
|
+
cancelAnimationFrame(rafId);
|
|
3891
|
+
canvas.remove();
|
|
3892
|
+
};
|
|
3880
3893
|
track.addEventListener("ended", stopLoop);
|
|
3881
3894
|
track.addEventListener("stop", stopLoop);
|
|
3882
3895
|
});
|
|
@@ -3993,13 +4006,19 @@ class WebRTCClient extends EventEmitter {
|
|
|
3993
4006
|
}
|
|
3994
4007
|
}
|
|
3995
4008
|
checkStats() {
|
|
3996
|
-
this.statsTimer = setInterval(() => this.
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4009
|
+
this.statsTimer = setInterval(() => this.processStatsOptimized(), 1e3);
|
|
4010
|
+
}
|
|
4011
|
+
async processStatsOptimized() {
|
|
4012
|
+
const pc = this.peerConnection;
|
|
4013
|
+
if (!pc) return;
|
|
4014
|
+
const ua = navigator.userAgent;
|
|
4015
|
+
const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua);
|
|
4016
|
+
const isMobile = /iPhone|iPad|Android/i.test(ua);
|
|
4017
|
+
const minInterval = isSafari || isMobile ? 3e3 : 1e3;
|
|
4018
|
+
const now = Date.now();
|
|
4019
|
+
if (this.lastReportTime && now - this.lastReportTime < minInterval) return;
|
|
4020
|
+
try {
|
|
4021
|
+
const statsReport = await pc.getStats(null);
|
|
4003
4022
|
let videoFps = 0;
|
|
4004
4023
|
let totalDecodeTime = 0;
|
|
4005
4024
|
let roundTripTime = 0;
|
|
@@ -4007,22 +4026,19 @@ class WebRTCClient extends EventEmitter {
|
|
|
4007
4026
|
let packetsReceived = 0;
|
|
4008
4027
|
let bytesReceived = 0;
|
|
4009
4028
|
let framesDecoded = 0;
|
|
4010
|
-
let pliCount = 0;
|
|
4011
4029
|
let framesReceived = 0;
|
|
4030
|
+
let pliCount = 0;
|
|
4012
4031
|
let connectionType = "";
|
|
4013
|
-
const
|
|
4014
|
-
|
|
4015
|
-
if (report.type === "inbound-rtp") {
|
|
4032
|
+
for (const report of statsReport.values()) {
|
|
4033
|
+
if (report.type === "inbound-rtp" && report.kind === "video") {
|
|
4016
4034
|
bytesReceived += report.bytesReceived || 0;
|
|
4017
4035
|
packetsLost += report.packetsLost || 0;
|
|
4018
4036
|
packetsReceived += report.packetsReceived || 0;
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
pliCount = (report.pliCount || 0) + (report.firCount || 0);
|
|
4025
|
-
}
|
|
4037
|
+
videoFps = report.framesPerSecond || 0;
|
|
4038
|
+
totalDecodeTime = report.totalDecodeTime || 0;
|
|
4039
|
+
framesDecoded = report.framesDecoded || 0;
|
|
4040
|
+
framesReceived = report.framesReceived || 0;
|
|
4041
|
+
pliCount = (report.pliCount || 0) + (report.firCount || 0);
|
|
4026
4042
|
}
|
|
4027
4043
|
if (report.type === "candidate-pair" && report.state === "succeeded") {
|
|
4028
4044
|
roundTripTime = typeof report.currentRoundTripTime !== "undefined" ? report.currentRoundTripTime : report.responsesReceived > 0 ? report.totalRoundTripTime / report.responsesReceived : 0;
|
|
@@ -4032,28 +4048,30 @@ class WebRTCClient extends EventEmitter {
|
|
|
4032
4048
|
connectionType = this.getConnectionType(local, remote);
|
|
4033
4049
|
}
|
|
4034
4050
|
}
|
|
4035
|
-
});
|
|
4036
|
-
if (!this.ua.includes("Firefox")) {
|
|
4037
|
-
roundTripTime = Math.floor((roundTripTime || 0) * 1e3);
|
|
4038
4051
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4052
|
+
if (roundTripTime > 1e3) {
|
|
4053
|
+
roundTripTime = Math.floor(roundTripTime / 1e3);
|
|
4054
|
+
} else if (roundTripTime < 1) {
|
|
4055
|
+
roundTripTime = Math.floor(roundTripTime * 1e3);
|
|
4056
|
+
}
|
|
4057
|
+
const deltaTime = now - (this.lastReportTime || now);
|
|
4058
|
+
const bytesDiff = bytesReceived - (this.lastBytesReceived || 0);
|
|
4041
4059
|
const bytesPerSecond = deltaTime > 0 ? bytesDiff * 1e3 / deltaTime : 0;
|
|
4042
4060
|
const kilobytesPerSecond = bytesPerSecond / 1024;
|
|
4043
4061
|
const megabytesPerSecond = kilobytesPerSecond / 1024;
|
|
4044
4062
|
this.lastBytesReceived = bytesReceived;
|
|
4045
|
-
this.lastReportTime =
|
|
4063
|
+
this.lastReportTime = now;
|
|
4064
|
+
const lostPackets = packetsLost - (this.lastPacketsLost || 0);
|
|
4065
|
+
const receivedPackets = packetsReceived - (this.lastPacketsReceived || 0);
|
|
4046
4066
|
let lossRate = 0;
|
|
4047
|
-
const lostPackets = packetsLost - this.lastPacketsLost;
|
|
4048
|
-
const receivedPackets = packetsReceived - this.lastPacketsReceived;
|
|
4049
4067
|
if (lostPackets > 0 && receivedPackets > 0) {
|
|
4050
4068
|
lossRate = lostPackets / (lostPackets + receivedPackets);
|
|
4051
4069
|
this.maxLostRate = Math.max(this.maxLostRate || 0, lossRate);
|
|
4052
|
-
this.lostPacketCount
|
|
4070
|
+
this.lostPacketCount = (this.lostPacketCount || 0) + 1;
|
|
4053
4071
|
}
|
|
4054
4072
|
this.lastPacketsLost = packetsLost;
|
|
4055
4073
|
this.lastPacketsReceived = packetsReceived;
|
|
4056
|
-
const fps =
|
|
4074
|
+
const fps = videoFps || framesDecoded - (this.lastSecondDecodedCount || 0);
|
|
4057
4075
|
this.lastSecondDecodedCount = framesDecoded;
|
|
4058
4076
|
const bitrate = kilobytesPerSecond < 1024 ? `${Math.floor(kilobytesPerSecond)} KB/s` : `${Math.floor(megabytesPerSecond)} MB/s`;
|
|
4059
4077
|
const averageDecodeTime = framesDecoded > 0 ? Math.floor(1e4 * totalDecodeTime / framesDecoded) : 0;
|
|
@@ -4062,7 +4080,6 @@ class WebRTCClient extends EventEmitter {
|
|
|
4062
4080
|
framesPerSecond: fps,
|
|
4063
4081
|
currentRoundTripTime: roundTripTime,
|
|
4064
4082
|
lostRate: Math.floor(lossRate * 100),
|
|
4065
|
-
// percent
|
|
4066
4083
|
bitrate,
|
|
4067
4084
|
pliCount,
|
|
4068
4085
|
averageDecodeTime,
|
|
@@ -4070,9 +4087,9 @@ class WebRTCClient extends EventEmitter {
|
|
|
4070
4087
|
framesReceived
|
|
4071
4088
|
};
|
|
4072
4089
|
this.emit(EmitType.statisticInfo, screenInfo);
|
|
4073
|
-
}
|
|
4090
|
+
} catch (error) {
|
|
4074
4091
|
this.emit(EmitType.webrtcError, createWebRtcError(FailCode.STREAM_STATE, error));
|
|
4075
|
-
}
|
|
4092
|
+
}
|
|
4076
4093
|
}
|
|
4077
4094
|
getConnectionType(local, remote) {
|
|
4078
4095
|
if (local.candidateType === "host" && remote.candidateType === "host") return "直连";
|
package/lib/yjz-web-sdk.umd.cjs
CHANGED
|
@@ -66,10 +66,12 @@ sdp:Re};switch(i.browser){case"chrome":if(!q||!H||!t.shimChrome)return n("Chrome
|
|
|
66
66
|
* @param data 清晰度数据
|
|
67
67
|
*/static clarity(e){return new He("ActionClarity",this.formatData(e))}static switchAudio(e){return new He("ActionCommandEvent",this.formatData(e))}static changeSender(e){return new He("ActionTrack",this.formatData(e))}}class $e{
|
|
68
68
|
/** 读取文件头并判断类型 */
|
|
69
|
-
static async detectFileType(e){const t=await e.slice(0,16).arrayBuffer(),n=new Uint8Array(t);return 255===n[0]&&216===n[1]&&255===n[2]||137===n[0]&&80===n[1]&&78===n[2]&&71===n[3]||71===n[0]&&73===n[1]&&70===n[2]&&56===n[3]||82===n[0]&&73===n[1]&&70===n[2]&&70===n[3]&&87===n[8]&&69===n[9]&&66===n[10]&&80===n[11]?"image":"ftyp"===String.fromCharCode(...n.slice(4,8))||26===n[0]&&69===n[1]&&223===n[2]&&163===n[3]||79===n[0]&&103===n[1]&&103===n[2]&&83===n[3]?"video":"unknown"}}class qe extends d{
|
|
69
|
+
static async detectFileType(e){const t=await e.slice(0,16).arrayBuffer(),n=new Uint8Array(t);return 255===n[0]&&216===n[1]&&255===n[2]||137===n[0]&&80===n[1]&&78===n[2]&&71===n[3]||71===n[0]&&73===n[1]&&70===n[2]&&56===n[3]||82===n[0]&&73===n[1]&&70===n[2]&&70===n[3]&&87===n[8]&&69===n[9]&&66===n[10]&&80===n[11]?"image":"ftyp"===String.fromCharCode(...n.slice(4,8))||26===n[0]&&69===n[1]&&223===n[2]&&163===n[3]||79===n[0]&&103===n[1]&&103===n[2]&&83===n[3]?"video":"unknown"}}class qe extends d{
|
|
70
|
+
// private isFireFox: boolean = false;
|
|
71
|
+
constructor(e){super(),i(this,"config"),i(this,"peerConnection",null),i(this,"localStream",null),i(this,"rotatedStream",null),i(this,"isPushingStream",!1),i(this,"isPushingLocalStream",!1),i(this,"dataChannel",null),i(this,"statsTimer"),i(this,"lastReportTime",0),i(this,"lastBytesReceived",0),i(this,"lastPacketsLost",0),i(this,"lastPacketsReceived",0),i(this,"lostPacketCount",0),i(this,"maxLostRate",0),i(this,"lastSecondDecodedCount",0),i(this,"fileVideo"),i(this,"canvas"),i(this,"canvasStream",null),i(this,"rafId",0),i(this,"currentMedia",null),i(this,"fileImage"),i(this,"startPushLocal",async e=>{var t;if(!this.isPushingLocalStream&&e)try{await this.loadMedia(e),this.startCanvasStream();const n=[];null==(t=this.canvasStream)||t.getTracks().forEach(e=>{e.contentHint="detail";const t=this.peerConnection.addTrack(e,this.canvasStream);n.push(t)}),n.forEach(e=>this.setVideoParams(e)),await xe(this.peerConnection,e=>{this.emit(y.sendOffer,e)}),this.isPushingLocalStream=!0}catch(n){let e;this.isPushingLocalStream=!1,e=n instanceof Error?n.message:String(n),this.emit(y.cameraError,C(m.LOCAL_STREAM_FAIL,e))}}),this.config=e}async startPush(e=!0){this.isPushingLocalStream&&this.stopLocal();try{this.isPushingStream=!0,await this.readyCapture(e)}catch(t){let e;this.isPushingStream=!1,e=t instanceof Error?t.message:String(t),this.emit(y.cameraError,C(m.CAMERA_STREAM_FAIL,e))}}handleOffer(e){this.resetPeerConnection(),((e,t,n,i)=>{w.info("信息日志:","设置远程offer Description=======>");const r=new RTCSessionDescription({type:"offer",sdp:t});e.setRemoteDescription(r).then(()=>Me(e,n,i)).catch(e=>null==i?void 0:i(g(u.HANDLE_OFFER,e)))})(this.peerConnection,e,e=>{this.emit(y.sendAnswer,e)},e=>this.emit(y.webrtcError,e))}handleAnswer(e){((e,t,n)=>{const i=new RTCSessionDescription({type:"answer",sdp:t});e.setRemoteDescription(i).catch(e=>null==n?void 0:n(g(u.REMOTE_DES,e)))})(this.peerConnection,e??"",e=>this.emit(y.webrtcError,e))}handleIceCandidate(e){((e,t,n)=>{w.info("信息日志:","接收远程ice 并设置=======>"),e.addIceCandidate(t).catch(e=>null==n?void 0:n(g(u.HANDLE_ICE,e)))})(this.peerConnection,e,e=>this.emit(y.webrtcError,e))}sendChannelData(e,t){var n;try{let i=null;switch(e){case je.ClickData:i=He.click(t);break;case je.ClipboardData:i=He.clipboard(t);break;case je.ActionInput:i=He.input(t,this.config.myId);break;case je.ActionChinese:i=He.chinese(t);break;case je.ActionRequestCloudDeviceInfo:i=He.requestCloudDeviceInfo();break;case je.ActionClarity:i=He.clarity(t);break;case je.ActionWheel:i=He.wheel(t);break;case je.ActionGesture:i=He.gesture(t);break;case je.ActionCommand:i=He.action(t);break;case je.ActionCommandEvent:i=He.switchAudio(t);break;case je.ActionTrack:i=He.changeSender(t)}if(i){const e=JSON.stringify(i),t=(new TextEncoder).encode(e).buffer;null==(n=this.dataChannel)||n.send(t),i=null}}catch(i){this.emit(y.webrtcError,g(u.DATACHANNEL_ERR,i))}}closeConnection(){w.info("信息日志:","关闭webrtc连接=======>"),this.statsTimer&&(clearInterval(this.statsTimer),this.statsTimer=void 0),this.stopPush(),this.stopLocal(),this.peerConnection&&("function"==typeof this.peerConnection.getSenders&&this.peerConnection.getSenders&&this.peerConnection.getSenders().forEach(e=>{var t,n;e.track&&(null==(n=(t=e.track).stop)||n.call(t))}),"function"==typeof this.peerConnection.getReceivers&&this.peerConnection.getReceivers&&this.peerConnection.getReceivers().forEach(e=>{var t,n;e.track&&(null==(n=(t=e.track).stop)||n.call(t))}),this.peerConnection.getTransceivers&&this.peerConnection.getTransceivers().forEach(e=>{var t;null==(t=e.stop)||t.call(e)}),this.removeAllListeners(),this.dataChannel=null,this.peerConnection.onicecandidate=null,this.peerConnection.ontrack=null,this.peerConnection.ondatachannel=null,this.peerConnection.onconnectionstatechange=null,this.peerConnection.oniceconnectionstatechange=null,this.peerConnection.onsignalingstatechange=null,this.peerConnection.onnegotiationneeded=null,this.peerConnection.onicegatheringstatechange=null,this.peerConnection.close(),this.peerConnection=null)}async readyCapture(e){this.resetPeerConnection(),this.stopPush(),w.info("信息日志:","启用摄像头推流到云机=======>",e);try{this.localStream=await navigator.mediaDevices.getUserMedia({video:{width:{ideal:640},height:{ideal:480},facingMode:e?{ideal:"environment"}:{ideal:"user"},frameRate:{ideal:20,max:30}},audio:!0});const t=[],n=await this.getRotatedStream(this.localStream,e);this.rotatedStream=n,n.getTracks().forEach(e=>{e.contentHint="motion";const i=this.peerConnection.addTrack(e,n);t.push(i)}),await xe(this.peerConnection,e=>{this.emit(y.sendOffer,e)}),t.forEach(e=>this.setVideoParams(e))}catch(t){if(t instanceof DOMException)switch(t.name){case"NotAllowedError":throw new Error("用户拒绝了摄像头或麦克风权限");case"NotFoundError":throw new Error("未找到摄像头或麦克风设备");case"OverconstrainedError":throw new Error("设备无法满足指定约束");case"NotReadableError":throw new Error("设备忙或无法访问");default:throw new Error(`其他错误: ${t.message}`)}throw new Error(`mediaDevices 异常: ${t}`)}}
|
|
70
72
|
/**
|
|
71
73
|
* ✅ 切换前后摄像头(无缝)
|
|
72
|
-
*/async switchCamera(e){var t;try{w.info("信息日志:","切换摄像头 => "+(e?"后置":"前置")),this.isPushingStream=!0,this.localStream=await navigator.mediaDevices.getUserMedia({video:{width:{ideal:640},height:{ideal:480},facingMode:e?{ideal:"environment"}:{ideal:"user"},frameRate:{ideal:20,max:30}},audio:!1});const n=await this.getRotatedStream(this.localStream,e);this.rotatedStream=n;const i=this.rotatedStream.getVideoTracks()[0],r=null==(t=this.peerConnection)?void 0:t.getSenders().find(e=>e.track&&"video"===e.track.kind);r&&await r.replaceTrack(i)}catch(n){if(this.isPushingStream=!1,n instanceof DOMException)switch(n.name){case"NotAllowedError":throw new Error("用户拒绝了摄像头或麦克风权限");case"NotFoundError":throw new Error("未找到摄像头或麦克风设备");case"OverconstrainedError":throw new Error("设备无法满足指定约束");case"NotReadableError":throw new Error("设备忙或无法访问");default:throw new Error(`其他错误: ${n.message}`)}throw new Error(`mediaDevices 异常: ${n}`)}}async getRotatedStream(e,t){const n=document.createElement("canvas"),i=n.getContext("2d"),r=document.createElement("video");r.srcObject=e,r.muted=!0,await r.play();const o=r.videoWidth,s=r.videoHeight;
|
|
74
|
+
*/async switchCamera(e){var t;try{w.info("信息日志:","切换摄像头 => "+(e?"后置":"前置")),this.isPushingStream=!0,this.localStream=await navigator.mediaDevices.getUserMedia({video:{width:{ideal:640},height:{ideal:480},facingMode:e?{ideal:"environment"}:{ideal:"user"},frameRate:{ideal:20,max:30}},audio:!1});const n=await this.getRotatedStream(this.localStream,e);this.rotatedStream=n;const i=this.rotatedStream.getVideoTracks()[0],r=null==(t=this.peerConnection)?void 0:t.getSenders().find(e=>e.track&&"video"===e.track.kind);r&&await r.replaceTrack(i)}catch(n){if(this.isPushingStream=!1,n instanceof DOMException)switch(n.name){case"NotAllowedError":throw new Error("用户拒绝了摄像头或麦克风权限");case"NotFoundError":throw new Error("未找到摄像头或麦克风设备");case"OverconstrainedError":throw new Error("设备无法满足指定约束");case"NotReadableError":throw new Error("设备忙或无法访问");default:throw new Error(`其他错误: ${n.message}`)}throw new Error(`mediaDevices 异常: ${n}`)}}async getRotatedStream(e,t){const n=document.createElement("canvas");n.width=640,n.height=480,n.style.position="absolute",n.style.top="-9999px",n.style.left="-9999px",n.style.width="1px",n.style.height="1px",n.style.opacity="0",document.body.appendChild(n);const i=n.getContext("2d"),r=document.createElement("video");r.srcObject=e,r.muted=!0,r.playsInline=!0,r.autoplay=!0,r.setAttribute("playsinline","true"),r.setAttribute("webkit-playsinline","true"),await r.play();const o=r.videoWidth,s=r.videoHeight;let a,c=0;const d=e=>{e-c>=40&&((()=>{i.clearRect(0,0,n.width,n.height),i.save(),i.translate(n.width/2,n.height/2);const e=t?-Math.PI/2:Math.PI/2;i.rotate(e),i.drawImage(r,-o/2,-s/2,o,s),i.restore()})(),c=e),a=requestAnimationFrame(d)};a=requestAnimationFrame(d);const l=n.captureStream(25);return l.getTracks().forEach(e=>{const t=()=>{cancelAnimationFrame(a),n.remove()};e.addEventListener("ended",t),e.addEventListener("stop",t)}),l}
|
|
73
75
|
// private async setVideoParams(sender: RTCRtpSender) {
|
|
74
76
|
// Logger.info('信息日志:', '设置推流视频参数=======>')
|
|
75
77
|
// const params = sender.getParameters();
|
|
@@ -82,9 +84,7 @@ static async detectFileType(e){const t=await e.slice(0,16).arrayBuffer(),n=new U
|
|
|
82
84
|
// });
|
|
83
85
|
// await sender.setParameters(params);
|
|
84
86
|
// }
|
|
85
|
-
async setVideoParams(e){const t=e.getParameters(),n=/Android|iPhone|iPad/i.test(navigator.userAgent)?6e5:18e5;t.degradationPreference="maintain-resolution",t.encodings&&0!==t.encodings.length||(t.encodings=[{}]),t.encodings.forEach(e=>{e.maxBitrate=n,e.maxFramerate=30,e.scaleResolutionDownBy=1,e.priority="high"});try{await e.setParameters(t),w.info("信息日志:",`设置推流视频参数成功:${n/1e3}kbps / 30fps`)}catch(i){w.error("设置推流参数失败:",i)}}stopPush(){var e,t,n;this.isPushingStream&&(this.isPushingStream=!1,w.info("信息日志:","停止推流到云机=======>"),null==(e=this.localStream)||e.getTracks().forEach(e=>e.stop()),this.localStream=null,null==(t=this.rotatedStream)||t.getTracks().forEach(e=>e.stop()),this.rotatedStream=null,null==(n=this.peerConnection)||n.getSenders().forEach(e=>{var t;try{null==(t=this.peerConnection)||t.removeTrack(e)}catch(n){}}))}resetPeerConnection(){var e,t,n,i,r;this.peerConnection||(this.peerConnection=(e=>{w.info("信息日志:","初始化 Webrtc PeerConnection=======>");const t=[{urls:e.stunServerUri},{urls:e.turnServerUri,username:e.turnServerUserName,credential:e.turnServerPassword}];return new RTCPeerConnection({iceServers:t,iceTransportPolicy:"all",bundlePolicy:"max-bundle"})})(this.config),e=this.peerConnection,t=e=>{this.emit(y.sendICEMessage,e)},n=e=>{this.emit(y.streamTrack,e)},i=e=>{this.emit(y.iceConnectionState,e),"connected"===e&&this.checkStats()},r=e=>this.emit(y.webrtcError,e),e.onicecandidate=e=>{if(!e.candidate)return;const n={sdp:e.candidate.candidate,sdpMid:e.candidate.sdpMid,sdpMLineIndex:e.candidate.sdpMLineIndex};w.debug("信息日志:",`webrtc 生成的icecandidate===>${JSON.stringify(n)}`),t(JSON.stringify(n))},e.onconnectionstatechange=()=>{const t=e.iceConnectionState;t&&(w.debug("信息日志:","webrtc p2p连接状态===>",t),"failed"!==t&&"disconnected"!==t&&"closed"!==t||null==r||r(g(u.ICE_STATE,"failed")),null==i||i(t))},e.ontrack=e=>{w.debug("信息日志:","webrtc p2p连接后获取的音视频track");const t=e.track;t.contentHint="motion",null==n||n(t)},this.configDataChannel())}configDataChannel(){this.peerConnection.ondatachannel=e=>{this.dataChannel=e.channel,this.dataChannel.onmessage=e=>this.handleDataChannelMessage(e),this.dataChannel.onerror=e=>this.emit(y.webrtcError,g(u.DATACHANNEL_ERR,e)),this.sendChannelData(je.ActionRequestCloudDeviceInfo,"")}}handleDataChannelMessage(e){const t=JSON.parse(e.data);if(t.type===je.ActionCommandEvent){const{action:e,value:n,cameraId:i}=JSON.parse(t.data);"ACTION_CONTROL_VIDEO"===e&&("ENABLE"===n?this.isPushingStream?this.switchCamera(0===Number(i)):this.startPush(0===Number(i)):(this.stopPush(),this.stopLocal()))}else if(t.type===je.ActionUpdateCloudStatus){const{rotation:e,screenWidth:n,screenHeight:i,gestureMode:r,level:o,isClarity:s}=JSON.parse(t.data),a={direction:[Ve.ROTATION_0,Ve.ROTATION_180].includes(e)?ze.Vertical:ze.Horizontal,screenWidth:n,screenHeight:i,gestureMode:r,clarityLevel:o,isClarity:s};this.emit(y.cloudStatusChanged,a)}else t.type===je.CloudClipData&&this.emit(y.cloudClipData,t.data)}checkStats(){this.statsTimer=setInterval(()=>this.
|
|
86
|
-
// percent
|
|
87
|
-
bitrate:T,pliCount:c,averageDecodeTime:R,framesDecoded:a,framesReceived:d};this.emit(y.statisticInfo,b)}).catch(e=>{this.emit(y.webrtcError,g(u.STREAM_STATE,e))})}getConnectionType(e,t){return"host"===e.candidateType&&"host"===t.candidateType?"直连":"relay"===e.candidateType||"relay"===t.candidateType?"中继":"NAT"}
|
|
87
|
+
async setVideoParams(e){const t=e.getParameters(),n=/Android|iPhone|iPad/i.test(navigator.userAgent)?6e5:18e5;t.degradationPreference="maintain-resolution",t.encodings&&0!==t.encodings.length||(t.encodings=[{}]),t.encodings.forEach(e=>{e.maxBitrate=n,e.maxFramerate=30,e.scaleResolutionDownBy=1,e.priority="high"});try{await e.setParameters(t),w.info("信息日志:",`设置推流视频参数成功:${n/1e3}kbps / 30fps`)}catch(i){w.error("设置推流参数失败:",i)}}stopPush(){var e,t,n;this.isPushingStream&&(this.isPushingStream=!1,w.info("信息日志:","停止推流到云机=======>"),null==(e=this.localStream)||e.getTracks().forEach(e=>e.stop()),this.localStream=null,null==(t=this.rotatedStream)||t.getTracks().forEach(e=>e.stop()),this.rotatedStream=null,null==(n=this.peerConnection)||n.getSenders().forEach(e=>{var t;try{null==(t=this.peerConnection)||t.removeTrack(e)}catch(n){}}))}resetPeerConnection(){var e,t,n,i,r;this.peerConnection||(this.peerConnection=(e=>{w.info("信息日志:","初始化 Webrtc PeerConnection=======>");const t=[{urls:e.stunServerUri},{urls:e.turnServerUri,username:e.turnServerUserName,credential:e.turnServerPassword}];return new RTCPeerConnection({iceServers:t,iceTransportPolicy:"all",bundlePolicy:"max-bundle"})})(this.config),e=this.peerConnection,t=e=>{this.emit(y.sendICEMessage,e)},n=e=>{this.emit(y.streamTrack,e)},i=e=>{this.emit(y.iceConnectionState,e),"connected"===e&&this.checkStats()},r=e=>this.emit(y.webrtcError,e),e.onicecandidate=e=>{if(!e.candidate)return;const n={sdp:e.candidate.candidate,sdpMid:e.candidate.sdpMid,sdpMLineIndex:e.candidate.sdpMLineIndex};w.debug("信息日志:",`webrtc 生成的icecandidate===>${JSON.stringify(n)}`),t(JSON.stringify(n))},e.onconnectionstatechange=()=>{const t=e.iceConnectionState;t&&(w.debug("信息日志:","webrtc p2p连接状态===>",t),"failed"!==t&&"disconnected"!==t&&"closed"!==t||null==r||r(g(u.ICE_STATE,"failed")),null==i||i(t))},e.ontrack=e=>{w.debug("信息日志:","webrtc p2p连接后获取的音视频track");const t=e.track;t.contentHint="motion",null==n||n(t)},this.configDataChannel())}configDataChannel(){this.peerConnection.ondatachannel=e=>{this.dataChannel=e.channel,this.dataChannel.onmessage=e=>this.handleDataChannelMessage(e),this.dataChannel.onerror=e=>this.emit(y.webrtcError,g(u.DATACHANNEL_ERR,e)),this.sendChannelData(je.ActionRequestCloudDeviceInfo,"")}}handleDataChannelMessage(e){const t=JSON.parse(e.data);if(t.type===je.ActionCommandEvent){const{action:e,value:n,cameraId:i}=JSON.parse(t.data);"ACTION_CONTROL_VIDEO"===e&&("ENABLE"===n?this.isPushingStream?this.switchCamera(0===Number(i)):this.startPush(0===Number(i)):(this.stopPush(),this.stopLocal()))}else if(t.type===je.ActionUpdateCloudStatus){const{rotation:e,screenWidth:n,screenHeight:i,gestureMode:r,level:o,isClarity:s}=JSON.parse(t.data),a={direction:[Ve.ROTATION_0,Ve.ROTATION_180].includes(e)?ze.Vertical:ze.Horizontal,screenWidth:n,screenHeight:i,gestureMode:r,clarityLevel:o,isClarity:s};this.emit(y.cloudStatusChanged,a)}else t.type===je.CloudClipData&&this.emit(y.cloudClipData,t.data)}checkStats(){this.statsTimer=setInterval(()=>this.processStatsOptimized(),1e3)}async processStatsOptimized(){const e=this.peerConnection;if(!e)return;const t=navigator.userAgent,n=/Safari/.test(t)&&!/Chrome/.test(t),i=/iPhone|iPad|Android/i.test(t),r=n||i?3e3:1e3,o=Date.now();if(!(this.lastReportTime&&o-this.lastReportTime<r))try{const t=await e.getStats(null);let n=0,i=0,r=0,s=0,a=0,c=0,d=0,l=0,h=0,p="";for(const e of t.values())if("inbound-rtp"===e.type&&"video"===e.kind&&(c+=e.bytesReceived||0,s+=e.packetsLost||0,a+=e.packetsReceived||0,n=e.framesPerSecond||0,i=e.totalDecodeTime||0,d=e.framesDecoded||0,l=e.framesReceived||0,h=(e.pliCount||0)+(e.firCount||0)),"candidate-pair"===e.type&&"succeeded"===e.state){r=void 0!==e.currentRoundTripTime?e.currentRoundTripTime:e.responsesReceived>0?e.totalRoundTripTime/e.responsesReceived:0;const n=t.get(e.localCandidateId),i=t.get(e.remoteCandidateId);n&&i&&(p=this.getConnectionType(n,i))}r>1e3?r=Math.floor(r/1e3):r<1&&(r=Math.floor(1e3*r));const u=o-(this.lastReportTime||o),f=c-(this.lastBytesReceived||0),m=(u>0?1e3*f/u:0)/1024,g=m/1024;this.lastBytesReceived=c,this.lastReportTime=o;const C=s-(this.lastPacketsLost||0),v=a-(this.lastPacketsReceived||0);let S=0;C>0&&v>0&&(S=C/(C+v),this.maxLostRate=Math.max(this.maxLostRate||0,S),this.lostPacketCount=(this.lostPacketCount||0)+1),this.lastPacketsLost=s,this.lastPacketsReceived=a;const T=n||d-(this.lastSecondDecodedCount||0);this.lastSecondDecodedCount=d;const R=m<1024?`${Math.floor(m)} KB/s`:`${Math.floor(g)} MB/s`,b=d>0?Math.floor(1e4*i/d):0,w={connectionType:p,framesPerSecond:T,currentRoundTripTime:r,lostRate:Math.floor(100*S),bitrate:R,pliCount:h,averageDecodeTime:b,framesDecoded:d,framesReceived:l};this.emit(y.statisticInfo,w)}catch(s){this.emit(y.webrtcError,g(u.STREAM_STATE,s))}}getConnectionType(e,t){return"host"===e.candidateType&&"host"===t.candidateType?"直连":"relay"===e.candidateType||"relay"===t.candidateType?"中继":"NAT"}
|
|
88
88
|
/** 获取或创建 video 元素(懒初始化) */getFileVideo(){return this.fileVideo||(this.fileVideo=document.createElement("video"),this.fileVideo.muted=!0,this.fileVideo.playsInline=!0),this.fileVideo}
|
|
89
89
|
/** 获取或创建 canvas 元素(懒初始化) */getCanvas(){return this.canvas||(this.canvas=document.createElement("canvas"),this.canvas.width=640,this.canvas.height=480),this.canvas}async loadMedia(e){var t;const n=this.getFileVideo(),i=this.getFileImage(),r=e.type.toLowerCase(),o=null==(t=e.name.split(".").pop())?void 0:t.toLowerCase();let s="unknown";if(r.startsWith("video/")||o&&["mp4","webm","ogg","mov","mkv"].includes(o)?s="video":(r.startsWith("image/")||o&&["jpg","jpeg","png","gif","bmp","webp"].includes(o))&&(s="image"),"unknown"===s&&(s=await $e.detectFileType(e)),"video"===s)return new Promise((t,i)=>{const r=URL.createObjectURL(e);n.src=r,n.onloadedmetadata=()=>t(),n.onerror=()=>i(new Error(`视频文件加载失败: ${e.name}`)),n.play().catch(e=>i(new Error(`视频播放失败: ${e}`))),this.currentMedia=n});if("image"===s)return new Promise((t,n)=>{const r=URL.createObjectURL(e);i.src=r,i.onload=()=>t(),i.onerror=()=>n(new Error(`图片文件加载失败: ${e.name}`)),this.currentMedia=i});throw new Error(`不支持的文件类型: ${r||o}`)}startCanvasStream(e=30){w.info("信息日志:","初始化,使用本地文件推流到云机=======>");const t=this.getCanvas(),n=t.getContext("2d"),i=this.currentMedia;if(!i)throw new Error("请先加载媒体文件");let r=0;const o=1e3/e;let s=1;const a=e=>{if(e-r>=o){let o,s;if(n.clearRect(0,0,t.width,t.height),n.save(),n.translate(t.width/2,t.height/2),n.rotate(Math.PI/2),n.scale(-1,1),i instanceof HTMLVideoElement)o=i.videoWidth,s=i.videoHeight;else{if(!(i instanceof HTMLImageElement))throw new Error("不支持的媒体类型");o=i.width,s=i.height}const a=Math.min(t.height/o,t.width/s),c=o*a,d=s*a;n.drawImage(i,-c/2,-d/2,c,d),n.restore(),r=e}s=requestAnimationFrame(a)};return a(0),this.rafId=requestAnimationFrame(a),this.canvasStream=t.captureStream(e),this.canvasStream.getTracks().forEach(e=>{const t=()=>cancelAnimationFrame(s);e.addEventListener("ended",t),e.addEventListener("stop",t)}),this.canvasStream}getFileImage(){if(!this.fileImage){const e=document.createElement("img");e.style.display="none",document.body.appendChild(e),this.fileImage=e}return this.fileImage}
|
|
90
90
|
/** 停止推流并释放资源 */stopLocal(){var e,t;this.isPushingLocalStream&&(this.isPushingLocalStream=!1,cancelAnimationFrame(this.rafId),null==(e=this.canvasStream)||e.getTracks().forEach(e=>e.stop()),this.canvasStream=null,null==(t=this.peerConnection)||t.getSenders().forEach(e=>{var t;try{null==(t=this.peerConnection)||t.removeTrack(e)}catch(n){w.error("错误日志:","移除音视频轨道失败=====>",n)}}),this.fileVideo&&(this.fileVideo.pause(),this.fileVideo.src=""))}}const Xe=async(e,t=600)=>{const n=e.map(e=>({urls:e,username:"yangyj",credential:"hb@2025@168"})).map(e=>((e,t=600)=>new Promise(n=>{const i=performance.now();let r=!1;const o=new RTCPeerConnection({iceServers:[e],iceTransportPolicy:"relay"});o.createDataChannel("test"),o.createOffer().then(e=>o.setLocalDescription(e)).catch(()=>{r||(r=!0,o.close(),n({...e,rtt:1/0}))}),o.onicecandidate=t=>{if(!r){if(t.candidate&&t.candidate.candidate.includes("relay")){const t=Math.trunc(performance.now()-i);r=!0,o.close(),n({...e,rtt:t})}null===t.candidate&&(r=!0,o.close(),n({...e,rtt:1/0}))}},setTimeout(()=>{r||(r=!0,o.close(),n({...e,rtt:1/0}))},t)}))(e,t).then(e=>e).catch(t=>(w.warn("警告日志:","中继计算超时=====>",t),{...e,rtt:1/0}))),i=await Promise.all(n);w.debug("调试日志:","信令计算结果======>",i);const r=i.filter(e=>e.rtt!==1/0);if(0===r.length)throw new Error("All TURN servers are unreachable or slow (RTT = Infinity).");const o=r.sort((e,t)=>e.rtt-t.rtt)[0];return{best:o?Ye(o):void 0,all:i.map(Ye)}},Ye=e=>({urls:e.urls,rtt:e.rtt}),Qe=e=>!(e.hostTurn&&0!==e.hostTurn.length||e.spareTurn&&0!==e.spareTurn.length);class Ze extends d{
|