import { IWebrtcTextSimple } from "../type"; import { base64ToArray } from "../utils"; import SignalSimple from "./signal-simple"; export default class WebRtcTextSimple { private _peerConnection?: RTCPeerConnection; private _pcConfig?: any; private _signal?: SignalSimple; private _imageElement?: HTMLImageElement; private buildTextOfferMessage?: (desc: RTCSessionDescriptionInit) => any; private buildTextCandidateMessage?: (candidate: any) => any; private _imageStreamChannel?: RTCDataChannel; private readonly _earlyCandidates: RTCIceCandidate[]; public constructor(option: IWebrtcTextSimple) { this._pcConfig = option?.iceConfig; this._imageElement = option.imageElement; this._earlyCandidates = []; this.buildTextCandidateMessage = option.buildTextCandidateMessage?.bind(this); this.buildTextOfferMessage = option.buildTextOfferMessage?.bind(this); } public peerConnection() { return this._peerConnection; } // 设置信令 public setSignal = (signal: SignalSimple) => { this._signal = signal; } get imageStreamChannel() { return this._imageStreamChannel; } // gatherCandidate作为CandidateFromWeb然后由信令发送 private _gatherCandidate = (event: RTCPeerConnectionIceEvent) => { if (event.candidate && this._peerConnection) { if (this._peerConnection.currentRemoteDescription) { this._signal && this.buildTextCandidateMessage && this._signal?.sendMsg(this.buildTextCandidateMessage(event.candidate)); } else { this._earlyCandidates.push(event.candidate); } } else { console.log('WebrtcText: IceCandidate 发送完毕'); } }; // 创建发送Offer private _handleCreateOffer() { this._peerConnection && this._peerConnection.createOffer().then( sessionDescription => { this._peerConnection && this._peerConnection .setLocalDescription(sessionDescription) .then(() => { this._signal && this.buildTextOfferMessage && this._signal?.sendMsg(this.buildTextOfferMessage(sessionDescription)); }) .catch(error => console.error(`WebrtcText: SetLocalDescription 失败${JSON.stringify(error, undefined, 2)}`) ); }, function (error) { console.error(`WebrtcText: 创建Offer失败${JSON.stringify(error, undefined, 2)}`); } ); } // 初始化peerConnection并创建发送Offer public initPeer = () => { this._peerConnection = new RTCPeerConnection(); // 如果需要createDataChannel, 需要在setDescription之前先create至少一个channel,否则后续一直无法open this._peerConnection.createDataChannel('preserveChannel'); if (this._pcConfig) { this._peerConnection.setConfiguration(this._pcConfig); } else { this._peerConnection.setConfiguration({ "iceServers":[{"urls":"turn:test.udt.woa.com:3478?transport=udp","credential":"udt@wetest","username":"udt"},{"urls":"turn:test.udt.woa.com:3478?transport=tcp","credential":"udt@wetest","username":"udt"},{"urls":"turn:119.29.18.254:3478?transport=udp","credential":"fEcGCPLqhYcj3JlYQnMgQpaLnXMhyQGF","username":"udt-public"}]}) } if (this._peerConnection?.iceConnectionState !== 'connected') { this._handleCreateOffer(); } this._peerConnection.onicecandidate = this._gatherCandidate; } // 收到Answer后,SetRemoteDescription并开始发送Candidate public handleReceiveAnswer = (descriptionInitDict: RTCSessionDescriptionInit) => { const descr = new RTCSessionDescription(descriptionInitDict); if (this._peerConnection?.iceConnectionState !== 'connected') { this._peerConnection && this._peerConnection .setRemoteDescription(descr) .then(() => { while (this._earlyCandidates.length) { const candidate = this._earlyCandidates.shift(); this._peerConnection && this._signal && this.buildTextCandidateMessage && this._signal?.sendMsg(this.buildTextCandidateMessage(candidate)); } }) .catch(error => { console.error(`WebrtcText: 设置 RemoteDescription 失败${JSON.stringify(error, undefined, 2)}`); }); } } // 收到CandidateFromDevice后,回调addIceCandidate public handleReceiveCandidate = (candidate: any) => { const candidateNew = new RTCIceCandidate(candidate); this._peerConnection && this._peerConnection .addIceCandidate(candidateNew) .then(() => { console.log('WebrtcText: 添加 Ice Candidate 成功'); }) .catch(error => { console.error(`WebrtcText: 添加 Ice Candidate 失败 ${JSON.stringify(error, undefined, 2)}`); }); } public requestImageChannel = () => { if (this._imageStreamChannel) { return; } this._imageStreamChannel = this._peerConnection?.createDataChannel('wsctrl'); this._imageStreamChannel && this._imageStreamChannel.addEventListener('open', () => { console.log('imageChannel-----------------------', 'imageChannel open'); }); let toRead = 0; let pendingDataView: any; let hasRead = 0; const decoder = new TextDecoder(); this._imageStreamChannel && this._imageStreamChannel.addEventListener('message', (e) => { const { data } = e; if (data.byteLength === 4 && !toRead) { const view = new DataView(data); const targetRead = view.getInt32(0, false); toRead = targetRead; console.log(targetRead, 'toRead'); pendingDataView = new Uint8Array(new ArrayBuffer(toRead)); } else if (toRead - hasRead >= data.byteLength) { const curretDataView = new Uint8Array(data); (pendingDataView as Uint8Array).set(curretDataView, hasRead); hasRead += data.byteLength; } if (hasRead === toRead) { toRead = 0; hasRead = 0; const result = decoder.decode(pendingDataView); const json = JSON.parse(result); if (json['type'] == '0') { const image = json['image']; const blob = base64ToArray(image); const url = URL.createObjectURL(blob); if (this._imageElement) { this._imageElement.src = url; } } pendingDataView = undefined; } }); } }