【m】分窗渲染测试没问题
This commit is contained in:
@@ -5,26 +5,26 @@ function uuid4() {
|
||||
var temp_url = URL.createObjectURL(new Blob());
|
||||
var uuid = temp_url.toString();
|
||||
URL.revokeObjectURL(temp_url);
|
||||
return uuid.split(/[:/]/g).pop().toLowerCase(); // remove prefixes
|
||||
return uuid.split(/[:/]/g).pop().toLowerCase();
|
||||
}
|
||||
|
||||
export class RenderStreaming {
|
||||
/**
|
||||
* @param signaling signaling class
|
||||
* @param {RTCConfiguration} config
|
||||
*/
|
||||
constructor(signaling, config) {
|
||||
this._peer = null;
|
||||
this._peer = null; // participant端:单一peer
|
||||
this._peers = new Map(); // host端:多peer Map (participantId → Peer)
|
||||
this._connectionId = null;
|
||||
this.onConnect = function (connectionId) { Logger.log(`Connect peer on ${connectionId}.`); };
|
||||
this._participantId = null; // 自己的participantId
|
||||
this._isHost = false;
|
||||
this.onConnect = function (connectionId, data) { Logger.log(`Connect peer on ${connectionId}.`); };
|
||||
this.onDisconnect = function (connectionId) { Logger.log(`Disconnect peer on ${connectionId}.`); };
|
||||
this.onGotOffer = function (connectionId) { Logger.log(`On got Offer on ${connectionId}.`); };
|
||||
this.onGotAnswer = function (connectionId) { Logger.log(`On got Answer on ${connectionId}.`); };
|
||||
this.onTrackEvent = function (data) { Logger.log(`OnTrack event peer with data:${data}`); };
|
||||
this.onAddChannel = function (data) { Logger.log(`onAddChannel event peer with data:${data}`); };
|
||||
this.onMessage = function (data) { Logger.log(`On message: ${data}`); };
|
||||
this.onParticipantLeft = function (connectionId) { Logger.log(`Participant left on ${connectionId}.`); };
|
||||
this.onNewPeer = function (connectionId) { Logger.log(`New peer created for ${connectionId}.`); };
|
||||
this.onParticipantLeft = function (participantId) { Logger.log(`Participant left: ${participantId}.`); };
|
||||
this.onParticipantJoined = function (participantId) { Logger.log(`Participant joined: ${participantId}.`); };
|
||||
this.onNewPeer = function (participantId) { Logger.log(`New peer created for ${participantId}.`); };
|
||||
this._config = config;
|
||||
this._signaling = signaling;
|
||||
this._signaling.addEventListener('connect', this._onConnect.bind(this));
|
||||
@@ -34,12 +34,21 @@ export class RenderStreaming {
|
||||
this._signaling.addEventListener('candidate', this._onIceCandidate.bind(this));
|
||||
this._signaling.addEventListener('on-message', this._onMessage.bind(this));
|
||||
this._signaling.addEventListener('participant-left', this._onParticipantLeft.bind(this));
|
||||
this._signaling.addEventListener('participant-joined', this._onParticipantJoined.bind(this));
|
||||
}
|
||||
|
||||
async _onConnect(e) {
|
||||
const data = e.detail;
|
||||
if (this._connectionId == data.connectionId) {
|
||||
this._preparePeerConnection(this._connectionId, data.polite);
|
||||
this._participantId = data.participantId;
|
||||
this._isHost = data.role === 'host';
|
||||
|
||||
if (!this._isHost) {
|
||||
// participant端:立即创建单一peer并开始协商
|
||||
this._preparePeerConnection(this._connectionId, data.polite, null);
|
||||
}
|
||||
// host端:不在connect时创建peer,等participant加入后再创建
|
||||
|
||||
this.onConnect(data.connectionId, data);
|
||||
}
|
||||
}
|
||||
@@ -52,50 +61,91 @@ export class RenderStreaming {
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
}
|
||||
// 关闭所有host端peers
|
||||
this._peers.forEach((peer, participantId) => {
|
||||
peer.close();
|
||||
});
|
||||
this._peers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async _onOffer(e) {
|
||||
const offer = e.detail;
|
||||
// 如果已有Peer但ICE连接已断开,需要重建Peer
|
||||
if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') {
|
||||
Logger.log('ICE disconnected, resetting PeerConnection for new offer');
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
}
|
||||
if (!this._peer) {
|
||||
this._preparePeerConnection(offer.connectionId, offer.polite);
|
||||
}
|
||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||
try {
|
||||
await this._peer.onGotDescription(offer.connectionId, desc);
|
||||
} catch (error) {
|
||||
Logger.warn(`Error happen on GotDescription that description.\n Message: ${error}\n RTCSdpType:${desc.type}\n sdp:${desc.sdp}`);
|
||||
return;
|
||||
const participantId = offer.participantId;
|
||||
|
||||
if (this._isHost) {
|
||||
// host端:为该participant创建或复用peer
|
||||
let peer = this._peers.get(participantId);
|
||||
if (!peer || (peer.pc && peer.pc.iceConnectionState === 'disconnected')) {
|
||||
if (peer) peer.close();
|
||||
peer = this._preparePeerConnection(this._connectionId, offer.polite, participantId);
|
||||
}
|
||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||
try {
|
||||
await peer.onGotDescription(this._connectionId, desc);
|
||||
} catch (error) {
|
||||
Logger.warn(`Error on GotDescription for participant ${participantId}: ${error}`);
|
||||
}
|
||||
} else {
|
||||
// participant端:使用单一peer
|
||||
if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') {
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
}
|
||||
if (!this._peer) {
|
||||
this._preparePeerConnection(offer.connectionId, offer.polite, null);
|
||||
}
|
||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||
try {
|
||||
await this._peer.onGotDescription(offer.connectionId, desc);
|
||||
} catch (error) {
|
||||
Logger.warn(`Error on GotDescription: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _onAnswer(e) {
|
||||
const answer = e.detail;
|
||||
const participantId = answer.participantId;
|
||||
const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" });
|
||||
if (this._peer) {
|
||||
|
||||
if (this._isHost && participantId) {
|
||||
// host端:路由到对应participant的peer
|
||||
const peer = this._peers.get(participantId);
|
||||
if (peer) {
|
||||
try {
|
||||
await peer.onGotDescription(this._connectionId, desc);
|
||||
} catch (error) {
|
||||
Logger.warn(`Error on GotDescription answer for ${participantId}: ${error}`);
|
||||
}
|
||||
}
|
||||
} else if (this._peer) {
|
||||
// participant端
|
||||
try {
|
||||
await this._peer.onGotDescription(answer.connectionId, desc);
|
||||
} catch (error) {
|
||||
Logger.warn(`Error happen on GotDescription that description.\n Message: ${error}\n RTCSdpType:${desc.type}\n sdp:${desc.sdp}`);
|
||||
return;
|
||||
Logger.warn(`Error on GotDescription answer: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _onIceCandidate(e) {
|
||||
const candidate = e.detail;
|
||||
const participantId = candidate.participantId;
|
||||
const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex });
|
||||
if (this._peer) {
|
||||
|
||||
if (this._isHost && participantId) {
|
||||
// host端:路由到对应participant的peer
|
||||
const peer = this._peers.get(participantId);
|
||||
if (peer) {
|
||||
await peer.onGotCandidate(this._connectionId, iceCandidate);
|
||||
}
|
||||
} else if (this._peer) {
|
||||
// participant端
|
||||
await this._peer.onGotCandidate(candidate.connectionId, iceCandidate);
|
||||
}
|
||||
}
|
||||
// 在 RenderStreaming 类中添加
|
||||
|
||||
async _onMessage(e) {
|
||||
const data = e.detail;
|
||||
this.onMessage(data);
|
||||
@@ -103,13 +153,32 @@ export class RenderStreaming {
|
||||
|
||||
async _onParticipantLeft(e) {
|
||||
const data = e.detail;
|
||||
Logger.log(`Participant left: ${data.connectionId}`);
|
||||
this.onParticipantLeft(data.connectionId);
|
||||
const participantId = data.participantId;
|
||||
Logger.log(`Participant left: ${participantId}`);
|
||||
|
||||
// 关闭该participant的peer
|
||||
if (this._peers.has(participantId)) {
|
||||
const peer = this._peers.get(participantId);
|
||||
peer.close();
|
||||
this._peers.delete(participantId);
|
||||
}
|
||||
|
||||
this.onParticipantLeft(participantId);
|
||||
}
|
||||
/**
|
||||
* if not set argument, a generated uuid is used.
|
||||
* @param {string | null} connectionId
|
||||
*/
|
||||
|
||||
async _onParticipantJoined(e) {
|
||||
const data = e.detail;
|
||||
const participantId = data.participantId;
|
||||
Logger.log(`Participant joined: ${participantId}`);
|
||||
|
||||
// host端:为新participant创建peer
|
||||
if (this._isHost && !this._peers.has(participantId)) {
|
||||
this._preparePeerConnection(this._connectionId, false, participantId);
|
||||
}
|
||||
|
||||
this.onParticipantJoined(participantId);
|
||||
}
|
||||
|
||||
async createConnection(connectionId) {
|
||||
this._connectionId = connectionId ? connectionId : uuid4();
|
||||
await this._signaling.createConnection(this._connectionId);
|
||||
@@ -118,90 +187,106 @@ export class RenderStreaming {
|
||||
async deleteConnection() {
|
||||
await this._signaling.deleteConnection(this._connectionId);
|
||||
}
|
||||
// 在 RenderStreaming 类中添加
|
||||
|
||||
_preparePeerConnection(connectionId, polite) {
|
||||
if (this._peer) {
|
||||
Logger.log('Close current PeerConnection');
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
_preparePeerConnection(connectionId, polite, participantId) {
|
||||
// host端多peer模式:participantId标识目标participant
|
||||
// participant端单peer模式:participantId为null
|
||||
|
||||
const peer = new Peer(connectionId, polite, this._config);
|
||||
|
||||
// 保存peer
|
||||
if (participantId) {
|
||||
if (this._peers.has(participantId)) {
|
||||
const oldPeer = this._peers.get(participantId);
|
||||
oldPeer.close();
|
||||
}
|
||||
this._peers.set(participantId, peer);
|
||||
} else {
|
||||
if (this._peer) {
|
||||
this._peer.close();
|
||||
}
|
||||
this._peer = peer;
|
||||
}
|
||||
|
||||
// Create peerConnection with proxy server and set up handlers
|
||||
this._peer = new Peer(connectionId, polite, this._config);
|
||||
// this._peer.addEventListener('disconnect', () => {
|
||||
// this.onDisconnect(`Receive disconnect message from peer. connectionId:${connectionId}`);
|
||||
// });
|
||||
this._peer.addEventListener('trackevent', (e) => {
|
||||
// 事件处理:附加participantId用于路由
|
||||
peer.addEventListener('trackevent', (e) => {
|
||||
const data = e.detail;
|
||||
data.participantId = participantId;
|
||||
this.onTrackEvent(data);
|
||||
});
|
||||
this._peer.addEventListener('adddatachannel', (e) => {
|
||||
|
||||
peer.addEventListener('adddatachannel', (e) => {
|
||||
const data = e.detail;
|
||||
this.onAddChannel(data);
|
||||
});
|
||||
this._peer.addEventListener('ongotoffer', (e) => {
|
||||
|
||||
peer.addEventListener('ongotoffer', (e) => {
|
||||
const id = e.detail.connectionId;
|
||||
this.onGotOffer(id);
|
||||
});
|
||||
this._peer.addEventListener('ongotanswer', (e) => {
|
||||
|
||||
peer.addEventListener('ongotanswer', (e) => {
|
||||
const id = e.detail.connectionId;
|
||||
this.onGotAnswer(id);
|
||||
});
|
||||
this._peer.addEventListener('sendoffer', (e) => {
|
||||
|
||||
peer.addEventListener('sendoffer', (e) => {
|
||||
const offer = e.detail;
|
||||
this._signaling.sendOffer(offer.connectionId, offer.sdp);
|
||||
this._signaling.sendOffer(offer.connectionId, offer.sdp, participantId);
|
||||
});
|
||||
this._peer.addEventListener('sendanswer', (e) => {
|
||||
|
||||
peer.addEventListener('sendanswer', (e) => {
|
||||
const answer = e.detail;
|
||||
this._signaling.sendAnswer(answer.connectionId, answer.sdp);
|
||||
this._signaling.sendAnswer(answer.connectionId, answer.sdp, participantId);
|
||||
});
|
||||
this._peer.addEventListener('sendcandidate', (e) => {
|
||||
|
||||
peer.addEventListener('sendcandidate', (e) => {
|
||||
const candidate = e.detail;
|
||||
this._signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex);
|
||||
this._signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex, participantId);
|
||||
});
|
||||
this.onNewPeer(connectionId);
|
||||
return this._peer;
|
||||
|
||||
this.onNewPeer(participantId || connectionId);
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<RTCStatsReport> | null}
|
||||
*/
|
||||
async getStats() {
|
||||
return await this._peer.getStats(this._connectionId);
|
||||
async getStats(participantId) {
|
||||
if (this._isHost && participantId) {
|
||||
const peer = this._peers.get(participantId);
|
||||
return peer ? await peer.getStats(this._connectionId) : null;
|
||||
}
|
||||
return this._peer ? await this._peer.getStats(this._connectionId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} label
|
||||
* @returns {RTCDataChannel | null}
|
||||
*/
|
||||
createDataChannel(label) {
|
||||
return this._peer.createDataChannel(this._connectionId, label);
|
||||
createDataChannel(label, participantId) {
|
||||
if (this._isHost && participantId) {
|
||||
const peer = this._peers.get(participantId);
|
||||
return peer ? peer.createDataChannel(this._connectionId, label) : null;
|
||||
}
|
||||
return this._peer ? this._peer.createDataChannel(this._connectionId, label) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MediaStreamTrack} track
|
||||
* @returns {RTCRtpSender | null}
|
||||
*/
|
||||
addTrack(track) {
|
||||
return this._peer.addTrack(this._connectionId, track);
|
||||
addTrack(track, participantId) {
|
||||
if (this._isHost && participantId) {
|
||||
const peer = this._peers.get(participantId);
|
||||
return peer ? peer.addTrack(this._connectionId, track) : null;
|
||||
}
|
||||
return this._peer ? this._peer.addTrack(this._connectionId, track) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MediaStreamTrack | string} trackOrKind
|
||||
* @param {RTCRtpTransceiverInit | null} init
|
||||
* @returns {RTCRtpTransceiver | null}
|
||||
*/
|
||||
addTransceiver(trackOrKind, init) {
|
||||
return this._peer.addTransceiver(this._connectionId, trackOrKind, init);
|
||||
addTransceiver(trackOrKind, init, participantId) {
|
||||
if (this._isHost && participantId) {
|
||||
const peer = this._peers.get(participantId);
|
||||
return peer ? peer.addTransceiver(this._connectionId, trackOrKind, init) : null;
|
||||
}
|
||||
return this._peer ? this._peer.addTransceiver(this._connectionId, trackOrKind, init) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @returns {RTCRtpTransceiver[] | null}
|
||||
*/
|
||||
getTransceivers() {
|
||||
return this._peer.getTransceivers(this._connectionId);
|
||||
getTransceivers(participantId) {
|
||||
if (this._isHost && participantId) {
|
||||
const peer = this._peers.get(participantId);
|
||||
return peer ? peer.getTransceivers(this._connectionId) : null;
|
||||
}
|
||||
return this._peer ? this._peer.getTransceivers(this._connectionId) : null;
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
@@ -209,6 +294,7 @@ export class RenderStreaming {
|
||||
this._signaling.sendMessage(this._connectionId, message);
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
await this._signaling.start();
|
||||
}
|
||||
@@ -218,10 +304,14 @@ export class RenderStreaming {
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
}
|
||||
this._peers.forEach((peer) => {
|
||||
peer.close();
|
||||
});
|
||||
this._peers.clear();
|
||||
|
||||
if (this._signaling) {
|
||||
await this._signaling.stop();
|
||||
this._signaling = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,13 +182,13 @@ export class WebSocketSignaling extends EventTarget {
|
||||
this.dispatchEvent(new CustomEvent('disconnect', { detail: msg }));
|
||||
break;
|
||||
case "offer":
|
||||
this.dispatchEvent(new CustomEvent('offer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, polite: msg.data.polite } }));
|
||||
this.dispatchEvent(new CustomEvent('offer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, polite: msg.data.polite, participantId: msg.participantId } }));
|
||||
break;
|
||||
case "answer":
|
||||
this.dispatchEvent(new CustomEvent('answer', { detail: { connectionId: msg.from, sdp: msg.data.sdp } }));
|
||||
this.dispatchEvent(new CustomEvent('answer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, participantId: msg.participantId } }));
|
||||
break;
|
||||
case "candidate":
|
||||
this.dispatchEvent(new CustomEvent('candidate', { detail: { connectionId: msg.from, candidate: msg.data.candidate, sdpMLineIndex: msg.data.sdpMLineIndex, sdpMid: msg.data.sdpMid } }));
|
||||
this.dispatchEvent(new CustomEvent('candidate', { detail: { connectionId: msg.from, candidate: msg.data.candidate, sdpMLineIndex: msg.data.sdpMLineIndex, sdpMid: msg.data.sdpMid, participantId: msg.participantId } }));
|
||||
break;
|
||||
case "on-message":
|
||||
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.data }));
|
||||
@@ -196,8 +196,10 @@ export class WebSocketSignaling extends EventTarget {
|
||||
case "participant-left":
|
||||
this.dispatchEvent(new CustomEvent('participant-left', { detail: msg }));
|
||||
break;
|
||||
case "participant-joined":
|
||||
this.dispatchEvent(new CustomEvent('participant-joined', { detail: msg }));
|
||||
break;
|
||||
case "broadcast":
|
||||
// 处理服务器广播的消息
|
||||
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message }));
|
||||
break;
|
||||
default:
|
||||
@@ -231,28 +233,28 @@ export class WebSocketSignaling extends EventTarget {
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendOffer(connectionId, sdp) {
|
||||
sendOffer(connectionId, sdp, participantId) {
|
||||
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
||||
const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data });
|
||||
const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data, participantId: participantId || '' });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendAnswer(connectionId, sdp) {
|
||||
sendAnswer(connectionId, sdp, participantId) {
|
||||
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
||||
const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data });
|
||||
const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data, participantId: participantId || '' });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid) {
|
||||
sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid, participantId) {
|
||||
const data = {
|
||||
'candidate': candidate,
|
||||
'sdpMLineIndex': sdpMLineIndex,
|
||||
'sdpMid': sdpMid,
|
||||
'connectionId': connectionId
|
||||
};
|
||||
const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data });
|
||||
const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data, participantId: participantId || '' });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user