优化目录结构
This commit is contained in:
224
client/test/mocks/mocksignaling.js
Normal file
224
client/test/mocks/mocksignaling.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import { sleep } from "../helpers/testutils.js";
|
||||
|
||||
/** @type {MockPrivateSignalingManager | MockPublicSignalingManager} */
|
||||
let manager;
|
||||
|
||||
export function reset(isPrivate) {
|
||||
manager = isPrivate ? new MockPrivateSignalingManager() : new MockPublicSignalingManager();
|
||||
}
|
||||
|
||||
export class MockSignaling extends EventTarget {
|
||||
|
||||
constructor(interval = 1000) {
|
||||
super();
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
async start() {
|
||||
await manager.add(this);
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await manager.remove(this);
|
||||
}
|
||||
|
||||
async createConnection(connectionId) {
|
||||
await manager.openConnection(this, connectionId);
|
||||
}
|
||||
|
||||
async deleteConnection(connectionId) {
|
||||
await manager.closeConnection(this, connectionId);
|
||||
}
|
||||
|
||||
async sendOffer(connectionId, sdp) {
|
||||
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
||||
await manager.offer(this, data);
|
||||
}
|
||||
|
||||
async sendAnswer(connectionId, sdp) {
|
||||
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
||||
await manager.answer(this, data);
|
||||
}
|
||||
|
||||
async sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid) {
|
||||
const data = {
|
||||
'candidate': candidate,
|
||||
'sdpMLineIndex': sdpMLineIndex,
|
||||
'sdpMid': sdpMid,
|
||||
'connectionId': connectionId
|
||||
};
|
||||
await manager.candidate(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
class MockPublicSignalingManager {
|
||||
constructor() {
|
||||
this.list = new Set();
|
||||
this.delay = async () => await sleep(10);
|
||||
}
|
||||
|
||||
async add(signaling) {
|
||||
await this.delay();
|
||||
this.list.add(signaling);
|
||||
signaling.dispatchEvent(new Event("start"));
|
||||
}
|
||||
|
||||
async remove(signaling) {
|
||||
await this.delay();
|
||||
this.list.delete(signaling);
|
||||
signaling.dispatchEvent(new Event("end"));
|
||||
}
|
||||
|
||||
async openConnection(signaling, connectionId) {
|
||||
await this.delay();
|
||||
const data = { connectionId: connectionId, polite: true };
|
||||
signaling.dispatchEvent(new CustomEvent("connect", { detail: data }));
|
||||
}
|
||||
|
||||
async closeConnection(signaling, connectionId) {
|
||||
await this.delay();
|
||||
const data = { connectionId: connectionId };
|
||||
for (const element of this.list) {
|
||||
element.dispatchEvent(new CustomEvent("disconnect", { detail: data }));
|
||||
}
|
||||
}
|
||||
|
||||
async offer(owner, data) {
|
||||
await this.delay();
|
||||
data.polite = false;
|
||||
for (const signaling of this.list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("offer", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async answer(owner, data) {
|
||||
await this.delay();
|
||||
for (const signaling of this.list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("answer", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async candidate(owner, data) {
|
||||
await this.delay();
|
||||
for (const signaling of this.list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("candidate", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockPrivateSignalingManager {
|
||||
constructor() {
|
||||
// structure Map<string:connectionId, Set<MockSignaling>> connectionIds
|
||||
this.connectionIds = new Map();
|
||||
this.delay = async () => await sleep(10);
|
||||
}
|
||||
|
||||
async add(signaling) {
|
||||
await this.delay();
|
||||
signaling.dispatchEvent(new Event("start"));
|
||||
}
|
||||
|
||||
async remove(signaling) {
|
||||
await this.delay();
|
||||
signaling.dispatchEvent(new Event("end"));
|
||||
}
|
||||
|
||||
async openConnection(signaling, connectionId) {
|
||||
await this.delay();
|
||||
const peerExists = this.connectionIds.has(connectionId);
|
||||
if (!peerExists) {
|
||||
this.connectionIds.set(connectionId, new Set());
|
||||
}
|
||||
|
||||
const list = this.connectionIds.get(connectionId);
|
||||
list.add(signaling);
|
||||
|
||||
const data = { connectionId: connectionId, polite: peerExists };
|
||||
signaling.dispatchEvent(new CustomEvent("connect", { detail: data }));
|
||||
}
|
||||
|
||||
async closeConnection(signaling, connectionId) {
|
||||
await this.delay();
|
||||
const peerExists = this.connectionIds.has(connectionId);
|
||||
const list = this.connectionIds.get(connectionId);
|
||||
if (!peerExists || !list.has(signaling)) {
|
||||
console.error(`${connectionId} This connection id is not used.`);
|
||||
}
|
||||
|
||||
const data = { connectionId: connectionId };
|
||||
for (const element of list) {
|
||||
element.dispatchEvent(new CustomEvent("disconnect", { detail: data }));
|
||||
}
|
||||
|
||||
list.delete(signaling);
|
||||
if (list.size == 0) {
|
||||
this.connectionIds.delete(connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
findList(owner, connectionId) {
|
||||
if (!this.connectionIds.has(connectionId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const list = new Set(this.connectionIds.get(connectionId));
|
||||
list.delete(owner);
|
||||
if (list.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
async offer(owner, data) {
|
||||
await this.delay();
|
||||
const list = this.findList(owner, data.connectionId);
|
||||
if (list == null) {
|
||||
console.warn(`${data.connectionId} This connection id is not ready other session.`);
|
||||
return;
|
||||
}
|
||||
|
||||
data.polite = true;
|
||||
for (const signaling of list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("offer", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async answer(owner, data) {
|
||||
await this.delay();
|
||||
const list = this.findList(owner, data.connectionId);
|
||||
if (list == null) {
|
||||
console.warn(`${data.connectionId} This connection id is not ready other session.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const signaling of list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("answer", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async candidate(owner, data) {
|
||||
await this.delay();
|
||||
const list = this.findList(owner, data.connectionId);
|
||||
if (list == null) {
|
||||
console.warn(`${data.connectionId} This connection id is not ready other session.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const signaling of list) {
|
||||
if (signaling != owner) {
|
||||
signaling.dispatchEvent(new CustomEvent("candidate", { detail: data }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
316
client/test/mocks/peerconnectionmock.js
Normal file
316
client/test/mocks/peerconnectionmock.js
Normal file
@@ -0,0 +1,316 @@
|
||||
import { sleep, getUniqueId } from '../helpers/testutils.js';
|
||||
|
||||
export class PeerConnectionMock extends EventTarget {
|
||||
constructor(config) {
|
||||
super();
|
||||
this.delay = async () => await sleep(10);
|
||||
this.config = config;
|
||||
this.ontrack = undefined;
|
||||
this.ondatachannel = undefined;
|
||||
this.onicecandidate = undefined;
|
||||
this.onnegotiationneeded = undefined;
|
||||
this.onsignalingstatechange = undefined;
|
||||
this.oniceconnectionstatechange = undefined;
|
||||
this.onicegatheringstatechange = undefined;
|
||||
this.pendingLocalDescription = null;
|
||||
this.currentLocalDescription = null;
|
||||
this.pendingRemoteDescription = null;
|
||||
this.currentRemoteDescription = null;
|
||||
this.candidates = [];
|
||||
this.signalingState = "stable";
|
||||
this.iceConnectionState = "new";
|
||||
this.iceGatheringState = "new";
|
||||
this.audioTracks = new Map();
|
||||
this.videoTracks = new Map();
|
||||
this.channels = new Map();
|
||||
this.transceiverCount = 0;
|
||||
this.transceivers = new Map();
|
||||
}
|
||||
|
||||
get localDescription() {
|
||||
if (this.pendingLocalDescription) {
|
||||
return this.pendingLocalDescription;
|
||||
}
|
||||
|
||||
return this.currentLocalDescription;
|
||||
}
|
||||
|
||||
get remoteDescription() {
|
||||
if (this.pendingRemoteDescription) {
|
||||
return this.pendingRemoteDescription;
|
||||
}
|
||||
|
||||
return this.currentRemoteDescription;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.ontrack = undefined;
|
||||
this.ondatachannel = undefined;
|
||||
this.onicecandidate = undefined;
|
||||
this.onnegotiationneeded = undefined;
|
||||
this.onsignalingstatechange = undefined;
|
||||
this.oniceconnectionstatechange = undefined;
|
||||
this.onicegatheringstatechange = undefined;
|
||||
this.pendingLocalDescription = null;
|
||||
this.currentLocalDescription = null;
|
||||
this.pendingRemoteDescription = null;
|
||||
this.currentRemoteDescription = null;
|
||||
this.candidates = [];
|
||||
this.signalingState = "close";
|
||||
this.iceConnectionState = "closed";
|
||||
this.audioTracks.clear();
|
||||
this.videoTracks.clear();
|
||||
this.channels.clear();
|
||||
this.transceiverCount = 0;
|
||||
this.transceivers.clear();
|
||||
}
|
||||
|
||||
fireOnNegotiationNeeded() {
|
||||
if (this.onnegotiationneeded) {
|
||||
this.onnegotiationneeded();
|
||||
}
|
||||
}
|
||||
|
||||
getTransceivers() {
|
||||
return Array.from(this.transceivers.values());
|
||||
}
|
||||
|
||||
addTrack(track) {
|
||||
if (track.kind == "audio") {
|
||||
this.audioTracks.set(track.id, track);
|
||||
} else {
|
||||
this.videoTracks.set(track.id, track);
|
||||
}
|
||||
const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
|
||||
this.transceivers.set(this.transceiverCount++, transceiver);
|
||||
this.fireOnNegotiationNeeded();
|
||||
return transceiver.sender;
|
||||
}
|
||||
|
||||
addTransceiver(trackOrKind) {
|
||||
if (typeof trackOrKind == "string") {
|
||||
const track = { id: getUniqueId(), kind: trackOrKind };
|
||||
if (track.kind == "audio") {
|
||||
this.audioTracks.set(track.id, track);
|
||||
} else {
|
||||
this.videoTracks.set(track.id, track);
|
||||
}
|
||||
const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
|
||||
this.transceivers.set(this.transceiverCount++, transceiver);
|
||||
this.fireOnNegotiationNeeded();
|
||||
return transceiver;
|
||||
}
|
||||
|
||||
if (trackOrKind.kind == "audio") {
|
||||
this.audioTracks.set(trackOrKind.id, trackOrKind);
|
||||
} else {
|
||||
this.videoTracks.set(trackOrKind.id, trackOrKind);
|
||||
}
|
||||
const transceiver = { direction: "sendrecv", sender: { track: trackOrKind }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
|
||||
this.transceivers.set(this.transceiverCount++, transceiver);
|
||||
this.fireOnNegotiationNeeded();
|
||||
return transceiver;
|
||||
}
|
||||
|
||||
createDataChannel(label) {
|
||||
const channel = { id: getUniqueId(), label: label };
|
||||
this.channels.set(channel.id, channel);
|
||||
this.fireOnNegotiationNeeded();
|
||||
return channel;
|
||||
}
|
||||
|
||||
async setLocalDescription(description = null) {
|
||||
if (description == null) {
|
||||
description = this._createSessionDescription();
|
||||
}
|
||||
await this.delay();
|
||||
this._setSessionDescription(description, false);
|
||||
}
|
||||
|
||||
async setRemoteDescription(description) {
|
||||
await this.delay();
|
||||
if (description.type == "offer" && this.signalingState == "have-local-offer") {
|
||||
this._setSessionDescription({ type: "rollback", sdp: "" }, true);
|
||||
}
|
||||
this._setSessionDescription(description, true);
|
||||
}
|
||||
|
||||
_createSessionDescription() {
|
||||
let dummySdp = "testsdp";
|
||||
if (this.videoTracks.size > 0) {
|
||||
dummySdp += "videotrack";
|
||||
}
|
||||
if (this.audioTracks.size > 0) {
|
||||
dummySdp += "audiotrack";
|
||||
}
|
||||
if (this.channels.size > 0) {
|
||||
dummySdp += "datachannel";
|
||||
}
|
||||
|
||||
if (this.signalingState == "stable" || this.signalingState == "have-local-offer" || this.signalingState == "have-remote-pranswer") {
|
||||
return { type: "offer", sdp: dummySdp };
|
||||
}
|
||||
return { type: "answer", sdp: dummySdp };
|
||||
}
|
||||
|
||||
_setSessionDescription(description, remote) {
|
||||
if (description.type == "rollback"
|
||||
&& (this.signalingState == "stable" || this.signalingState == "have-local-pranswer" || this.signalingState == "have-remote-pranswer")) {
|
||||
throw "InvalidStateError";
|
||||
}
|
||||
|
||||
if (description.type != "rollback") {
|
||||
if (remote) {
|
||||
if (description.type == "offer") {
|
||||
this.pendingRemoteDescription = description;
|
||||
this.signalingState = "have-remote-offer";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
// if sdp contains track string, create dummy track
|
||||
if (description.sdp.includes("track")) {
|
||||
const isVideo = description.sdp.includes("video");
|
||||
const kind = isVideo ? "video" : "audio";
|
||||
this._createTrackAndTransceiver(kind);
|
||||
}
|
||||
if (description.sdp.includes("datachannel")) {
|
||||
const channel = { id: getUniqueId(), label: "dummychannel" };
|
||||
this.channels.set(channel.id, channel);
|
||||
}
|
||||
}
|
||||
if (description.type == "answer") {
|
||||
this.currentRemoteDescription = description;
|
||||
this.currentLocalDescription = this.pendingLocalDescription;
|
||||
this.pendingLocalDescription = null;
|
||||
this.pendingRemoteDescription = null;
|
||||
this.signalingState = "stable";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
}
|
||||
if (description.type == "pranswer") {
|
||||
this.pendingRemoteDescription = description;
|
||||
this.signalingState = "have-remote-pranswer";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
}
|
||||
} else {
|
||||
if (description.type == "offer") {
|
||||
this.pendingLocalDescription = description;
|
||||
this.signalingState = "have-local-offer";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
}
|
||||
if (description.type == "answer") {
|
||||
this.currentLocalDescription = description;
|
||||
this.currentRemoteDescription = this.pendingRemoteDescription;
|
||||
this.pendingLocalDescription = null;
|
||||
this.pendingRemoteDescription = null;
|
||||
this.signalingState = "stable";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
// if sdp contains track string, create dummy track
|
||||
if (description.sdp.includes("track")) {
|
||||
const isVideo = description.sdp.includes("video");
|
||||
const kind = isVideo ? "video" : "audio";
|
||||
this._createTrackAndTransceiver(kind);
|
||||
}
|
||||
if (description.sdp.includes("datachannel")) {
|
||||
const channel = { id: getUniqueId(), label: "dummychannel" };
|
||||
this.channels.set(channel.id, channel);
|
||||
}
|
||||
}
|
||||
if (description.type == "pranswer") {
|
||||
this.pendingLocalDescription = description;
|
||||
this.signalingState = "have-local-pranswer";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.pendingLocalDescription = null;
|
||||
this.pendingRemoteDescription = null;
|
||||
this.signalingState = "stable";
|
||||
this.onsignalingstatechange(this.signalingState);
|
||||
}
|
||||
|
||||
if (this.videoTracks.size != 0 || this.audioTracks.size != 0) {
|
||||
this._mockGatheringIceCandidate(this.videoTracks.size + this.audioTracks.size);
|
||||
}
|
||||
|
||||
//fire ontrack with new tracks, after using tracks clear.
|
||||
if (this.ontrack) {
|
||||
for (const track of this.videoTracks.values()) {
|
||||
this.ontrack({ track: track });
|
||||
}
|
||||
this.videoTracks.clear();
|
||||
|
||||
for (const track of this.audioTracks.values()) {
|
||||
this.ontrack({ track: track });
|
||||
}
|
||||
this.audioTracks.clear();
|
||||
}
|
||||
|
||||
if (this.ondatachannel) {
|
||||
for (const channel of this.channels.values()) {
|
||||
this.ondatachannel({ channel: channel });
|
||||
}
|
||||
this.channels.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async _mockGatheringIceCandidate(count) {
|
||||
this.iceGatheringState = "gathering";
|
||||
if (this.onicegatheringstatechange) {
|
||||
this.onicegatheringstatechange(this.iceGatheringState);
|
||||
}
|
||||
for (let index = 0; index < count; index++) {
|
||||
await this.delay();
|
||||
const newCandidate = { candidate: getUniqueId(), sdpMLineIndex: index, sdpMid: index };
|
||||
if (this.onicecandidate) {
|
||||
this.onicecandidate(newCandidate);
|
||||
}
|
||||
}
|
||||
this.iceGatheringState = "complete";
|
||||
if (this.onicegatheringstatechange) {
|
||||
this.onicegatheringstatechange(this.iceGatheringState);
|
||||
}
|
||||
if (this.onicecandidate) {
|
||||
this.onicecandidate({ candidate: null, sdpMLineIndex: null, sdpMid: null });
|
||||
}
|
||||
}
|
||||
|
||||
async addIceCandidate(candidate) {
|
||||
await this.delay();
|
||||
if (this.remoteDescription == null) {
|
||||
throw "InvalidStateError";
|
||||
}
|
||||
this.candidates.push(candidate);
|
||||
}
|
||||
|
||||
_createTrackAndTransceiver(kind) {
|
||||
const track = { id: getUniqueId(), kind: kind };
|
||||
if (kind == "video") {
|
||||
this.videoTracks.set(track.id, track);
|
||||
} else {
|
||||
this.audioTracks.set(track.id, track);
|
||||
}
|
||||
const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
|
||||
this.transceivers.set(this.transceiverCount++, transceiver);
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionDescriptionMock {
|
||||
|
||||
constructor(object) {
|
||||
this.sdp = object.sdp;
|
||||
this.type = object.type;
|
||||
}
|
||||
|
||||
sdp;
|
||||
type;
|
||||
}
|
||||
|
||||
export class IceCandidateMock {
|
||||
constructor(object) {
|
||||
this.candidate = object.candidate;
|
||||
this.sdpMLineIndex = object.sdpMLineIndex;
|
||||
this.sdpMid = object.sdpMid;
|
||||
}
|
||||
|
||||
candidate;
|
||||
sdpMLineIndex;
|
||||
sdpMid;
|
||||
}
|
||||
Reference in New Issue
Block a user