Skip to content
Open
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