Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent init new peer connection, in case of it already established #440

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
77 changes: 43 additions & 34 deletions src/main/js/webrtc_adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class WebRTCAdaptor {

/**
* Degradation Preference
*
*
* maintain-framerate, maintain-resolution, or balanced
*/
this.degradationPreference = "maintain-resolution";
Expand Down Expand Up @@ -650,8 +650,8 @@ export class WebRTCAdaptor {

this.stop(this.publishStreamId);
setTimeout(() => {
//publish about some time later because server may not drop the connection yet
//it may trigger already publishing error
//publish about some time later because server may not drop the connection yet
//it may trigger already publishing error
Logger.log("Trying publish again for stream: " + this.publishStreamId);
this.publish(this.publishStreamId, this.publishToken, this.publishSubscriberId, this.publishSubscriberCode, this.publishStreamName, this.publishMainTrack, this.publishMetaData);
}, 500);
Expand All @@ -673,8 +673,8 @@ export class WebRTCAdaptor {
Logger.log("It will try to play again for stream: " + streamId + " because it is not stopped on purpose")
this.stop(streamId);
setTimeout(() => {
//play about some time later because server may not drop the connection yet
//it may trigger already playing error
//play about some time later because server may not drop the connection yet
//it may trigger already playing error
Logger.log("Trying play again for stream: " + streamId);
this.play(streamId, this.playToken, this.playRoomId, this.playEnableTracks, this.playSubscriberId, this.playSubscriberCode, this.playMetaData);
}, 500);
Expand Down Expand Up @@ -948,7 +948,7 @@ export class WebRTCAdaptor {
*/
sanitizeHTML(text) {
if(text.includes("script"))
return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
return text
}

Expand Down Expand Up @@ -1043,10 +1043,10 @@ export class WebRTCAdaptor {
this.iceCandidateList[streamId] = new Array();
if (!this.playStreamId.includes(streamId)) {
if (this.mediaManager.localStream != null) {
this.mediaManager.localStream.getTracks().forEach(track => {
this.mediaManager.localStream.getTracks().forEach(track => {

let rtpSender = this.remotePeerConnection[streamId].addTrack(track, this.mediaManager.localStream);
if (track.kind == 'video')
if (track.kind == 'video')
{
let parameters = rtpSender.getParameters();
parameters.degradationPreference = this.degradationPreference;
Expand Down Expand Up @@ -1130,7 +1130,7 @@ export class WebRTCAdaptor {
}

}

return this.remotePeerConnection[streamId];
}

Expand Down Expand Up @@ -1376,6 +1376,15 @@ export class WebRTCAdaptor {
startPublishing(idOfStream) {
let streamId = idOfStream;

if (typeof this.remotePeerConnection[streamId] !== 'undefined'
&& this.remotePeerConnection[streamId] !== null
&& (this.remotePeerConnection[streamId].iceConnectionState !== "new"
|| this.remotePeerConnection[streamId].iceConnectionState !== "failed"
|| this.remotePeerConnection[streamId].iceConnectionState !== "disconnected")) {
Logger.debug("We already established peer connection, no need to create offer");
return;
}

let peerConnection = this.initPeerConnection(streamId, "publish");

//this.remotePeerConnection[streamId]
Expand Down Expand Up @@ -1544,8 +1553,8 @@ export class WebRTCAdaptor {
if (typeof value.jitterBufferDelay != "undefined" && typeof value.jitterBufferEmittedCount != "undefined") {
videoJitterAverageDelay = value.jitterBufferDelay / value.jitterBufferEmittedCount;
}
}
}

else if (value.type == "remote-inbound-rtp" && typeof value.kind != "undefined") {
//this is coming when webrtc publishing

Expand Down Expand Up @@ -1922,27 +1931,27 @@ export class WebRTCAdaptor {

this.webSocketAdaptor.send(JSON.stringify(jsCmd));
}

/**
* Register user push notification token to Ant Media Server according to subscriberId and authToken
* @param {string} subscriberId: subscriber id it can be anything that defines the user
* @param {string} authToken: JWT token with the issuer field is the subscriberId and secret is the application's subscriberAuthenticationKey,
* @param {string} authToken: JWT token with the issuer field is the subscriberId and secret is the application's subscriberAuthenticationKey,
* It's used to authenticate the user - token should be obtained from Ant Media Server Push Notification REST Service
* or can be generated with JWT by using the secret and issuer fields
*
*
* @param {string} pushNotificationToken: Push Notification Token that is obtained from the Firebase or APN
* @param {string} tokenType: It can be "fcm" or "apn" for Firebase Cloud Messaging or Apple Push Notification
*
*
* @returns Server responds this message with a result.
* Result message is something like
* Result message is something like
* {
* "command":"notification",
* "success":true or false
* "definition":"If success is false, it gives the error message",
* "information":"If succeess is false, it gives more information to debug if available"
*
* }
*
*
* }
*
*/
registerPushNotificationToken(subscriberId, authToken, pushNotificationToken, tokenType) {
let jsCmd = {
Expand All @@ -1954,8 +1963,8 @@ export class WebRTCAdaptor {
};
this.webSocketAdaptor.send(JSON.stringify(jsCmd));
}


/**
* Send push notification to subscribers
* @param {string} subscriberId: subscriber id it can be anything(email, username, id) that defines the user in your applicaiton
Expand All @@ -1964,31 +1973,31 @@ export class WebRTCAdaptor {
* or can be generated with JWT by using the secret and issuer fields
* @param {string} pushNotificationContent: JSON Format - Push Notification Content. If it's not JSON, it will not parsed
* @param {Array} subscriberIdsToNotify: Array of subscriber ids to notify
*
*
* @returns Server responds this message with a result.
* Result message is something like
* Result message is something like
* {
* "command":"notification",
* "success":true or false
* "definition":"If success is false, it gives the error message",
* "information":"If succeess is false, it gives more information to debug if available"
*
* }
*
* }
*/
sendPushNotification(subscriberId, authToken, pushNotificationContent, subscriberIdsToNotify) {

//type check for pushNotificationContent if json
if (typeof pushNotificationContent !== "object") {
Logger.error("Push Notification Content is not JSON format");
throw new Error("Push Notification Content is not JSON format");
}

//type check if subscriberIdsToNotify is array
if (!Array.isArray(subscriberIdsToNotify)) {
Logger.error("subscriberIdsToNotify is not an array. Please put the subscriber ids to notify in an array such as [user1], [user1, user2]");
throw new Error("subscriberIdsToNotify is not an array. Please put the subscriber ids to notify in an array such as [user1], [user1, user2]");
}

let jsCmd = {
command: "sendPushNotification",
subscriberId: subscriberId,
Expand All @@ -1998,16 +2007,16 @@ export class WebRTCAdaptor {
};
this.webSocketAdaptor.send(JSON.stringify(jsCmd));
}

/**
* Send push notification to topic
* @param {string} subscriberId: subscriber id it can be anything(email, username, id) that defines the user in your applicaiton
* @param {string} authToken: JWT token with the issuer field is the subscriberId and secret is the application's subscriberAuthenticationKey,
* @param {string} authToken: JWT token with the issuer field is the subscriberId and secret is the application's subscriberAuthenticationKey,
* It's used to authenticate the user - token should be obtained from Ant Media Server Push Notification REST Service
* or can be generated with JWT by using the secret and issuer fields
* @param {string} pushNotificationContent:JSON Format - Push Notification Content. If it's not JSON, it will not parsed
* @param {string} topic: Topic to send push notification
*
*
* @returns Server responds this message with a result.
* Result message is something like
* {
Expand All @@ -2016,15 +2025,15 @@ export class WebRTCAdaptor {
* "definition":"If success is false, it gives the error message",
* "information":"If succeess is false, it gives more information to debug if available"
* }
*
*
*/
sendPushNotificationToTopic(subscriberId, authToken, pushNotificationContent, topic) {
//type check for pushNotificationContent if json
if (typeof pushNotificationContent !== "object") {
Logger.error("Push Notification Content is not JSON format");
throw new Error("Push Notification Content is not JSON format");
}

let jsCmd = {
command: "sendPushNotification",
subscriberId: subscriberId,
Expand Down
99 changes: 99 additions & 0 deletions src/test/js/webrtc_adaptor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ describe("WebRTCAdaptor", function () {
sandbox.restore();
});

afterEach(() => {
// Restore the default sandbox here
sinon.restore();
clock.restore();
sandbox.restore();
mockRTCPeerConnection.restore();
});

// Create a mock for the RTCPeerConnection
const mockRTCPeerConnection = sinon.stub(window, 'RTCPeerConnection');

// Define the behavior of the mock object
mockRTCPeerConnection.returns({
createOffer: sinon.stub().returns(Promise.resolve()),
setLocalDescription: sinon.stub().returns(Promise.resolve()),
addIceCandidate: sinon.stub().returns(Promise.resolve()),
close: sinon.stub(),
// Add any other methods you want to mock
});

it("Initialize", async function () {

Expand Down Expand Up @@ -769,6 +788,86 @@ describe("WebRTCAdaptor", function () {

});

it("startPublishing with existing peer connection", async function() {
let adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
isPlayMode: true
});

let streamId = "stream123";
let peerConnection = new RTCPeerConnection();
sandbox.replaceGetter(peerConnection,"iceConnectionState", sinon.fake.returns("connected"));
adaptor.remotePeerConnection[streamId] = peerConnection;

let initPeerConnection = sinon.replace(adaptor, "initPeerConnection", sinon.fake.returns(peerConnection));
let createOfferFake = sinon.replace(peerConnection, "createOffer", sinon.fake.returns(Promise.reject("this is on purpose")));

adaptor.startPublishing(streamId);

expect(initPeerConnection.called).to.be.false;
expect(createOfferFake.called).to.be.false;
});

it("startPublishing with new peer connection", async function() {
let adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
isPlayMode: true
});

let streamId = "stream123";
let peerConnection = new RTCPeerConnection();
sandbox.replaceGetter(peerConnection,"iceConnectionState", sinon.fake.returns("new"));
adaptor.remotePeerConnection[streamId] = peerConnection;

let initPeerConnection = sinon.replace(adaptor, "initPeerConnection", sinon.fake.returns(peerConnection));
let createOfferFake = sinon.replace(peerConnection, "createOffer", sinon.fake.returns(Promise.resolve()));

adaptor.startPublishing(streamId);

expect(initPeerConnection.calledWithExactly(streamId, "publish")).to.be.false;
expect(createOfferFake.called).to.be.false;
});

it("startPublishing with failed peer connection", async function() {
let adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
isPlayMode: true
});

let streamId = "stream123";
let peerConnection = new RTCPeerConnection();
sandbox.replaceGetter(peerConnection,"iceConnectionState", sinon.fake.returns("failed"));
adaptor.remotePeerConnection[streamId] = peerConnection;

let initPeerConnection = sinon.replace(adaptor, "initPeerConnection", sinon.fake.returns(peerConnection));
let createOfferFake = sinon.replace(peerConnection, "createOffer", sinon.fake.returns(Promise.resolve()));

adaptor.startPublishing(streamId);

expect(initPeerConnection.calledWithExactly(streamId, "publish")).to.be.false;
expect(createOfferFake.called).to.be.false;
});

it("startPublishing with disconnected peer connection", async function() {
let adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
isPlayMode: true
});

let streamId = "stream123";
let peerConnection = new RTCPeerConnection();
sandbox.replaceGetter(peerConnection,"iceConnectionState", sinon.fake.returns("disconnected"));
adaptor.remotePeerConnection[streamId] = peerConnection;

let initPeerConnection = sinon.replace(adaptor, "initPeerConnection", sinon.fake.returns(peerConnection));
let createOfferFake = sinon.replace(peerConnection, "createOffer", sinon.fake.returns(Promise.resolve()));

adaptor.startPublishing(streamId);

expect(initPeerConnection.calledWithExactly(streamId, "publish")).to.be.false;
expect(createOfferFake.called).to.be.false;
});

it("onTrack", async function () {

{
Expand Down
Loading