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

Added support for real-time text (T.140 with or without red) to the SIP plugin #1898

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
68f26ec
Added support for T.140 real-time text to SIP plugin
lminiero Dec 17, 2019
875d86a
Added support for T.140 redundancy via red
lminiero Dec 17, 2019
4bb93c2
Removed fence on datachannels for outgoing calls
lminiero Dec 18, 2019
c2135b7
Added support for subprotocol in data channels
lminiero Dec 19, 2019
2258b40
Aligned to latest changes in master
lminiero Jan 17, 2020
d9a7d25
Aligned with latest changes in master (0.9.0)
lminiero Feb 6, 2020
ea4abbf
Aligned with latest changes in master (0.9.1)
lminiero Feb 24, 2020
e1b203c
Fixed conflict with master
lminiero Mar 2, 2020
e3864b8
Aligned with latest changes in master (0.9.2)
lminiero Mar 26, 2020
e073c4e
Re-aligned with master
lminiero Mar 27, 2020
46be8b8
Aligned with latest changes in master (0.9.3 tag)
lminiero Apr 22, 2020
a34824e
Aligned to latest changes in master
lminiero May 7, 2020
34de5a8
Fixed conflicts with master
lminiero May 18, 2020
b227fe7
Aligned with latest changes in master
lminiero May 19, 2020
ca10ceb
Fixed conflicts with master
lminiero Jun 1, 2020
52b9ad0
Aligned with master
lminiero Sep 1, 2020
de26fe2
Aligned to latest changes in master
lminiero Oct 13, 2020
4941fd1
Aligned to most recent changes in master
lminiero Dec 11, 2020
9adc570
Aligned to latest changes in master
lminiero Feb 23, 2021
368e76e
Use on() instead of deprecated live() (see #2648)
lminiero Apr 28, 2021
54c3f82
Aligned to latest master and fixed conflicts
lminiero Apr 28, 2021
98b879d
Aligned to latest master and fixed conflicts
lminiero Jul 29, 2021
751d21a
Fixed conflict with master
lminiero Nov 23, 2021
4ed385c
Fixed conflicts with master
lminiero Feb 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion html/siptest.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ <h3>Demo details</h3>
</div>
</div>
</div>
<div id="videos" class="hide">
<div id="videos" class="row hide">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
Expand All @@ -130,6 +130,23 @@ <h3 class="panel-title">Remote UA</h3>
</div>
</div>
</div>
<div id="texts" class="row hide">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Real-time text</h3>
</div>
<div class="panel-body relative" style="overflow-x: auto;" id="messages">
</div>
<div class="panel-footer">
<div class="input-group margin-bottom-sm">
<span class="input-group-addon"><i class="fa fa-cloud-upload fa-fw"></i></span>
<input class="form-control" type="text" placeholder="Write some text" autocomplete="off" id="datasend" disabled></input>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Expand Down
169 changes: 165 additions & 4 deletions html/siptest.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ $(document).ready(function() {
if(event === 'registered') {
Janus.log("Successfully registered as " + result["username"] + "!");
$('#you').removeClass('hide').show().text("Registered as '" + result["username"] + "'");
// TODO Enable buttons to call now
// Enable buttons to call now
if(!registered) {
registered = true;
masterId = result["master_id"];
Expand Down Expand Up @@ -226,6 +226,7 @@ $(document).ready(function() {
} else if(event === 'incomingcall') {
Janus.log("Incoming call from " + result["username"] + "!");
sipcall.callId = callId;
sipcall.peer = result["username"];
var doAudio = true, doVideo = true;
var offerlessInvite = false;
if(jsep) {
Expand Down Expand Up @@ -277,7 +278,7 @@ $(document).ready(function() {
sipcallAction(
{
jsep: jsep,
media: { audio: doAudio, video: doVideo },
media: { audio: doAudio, video: doVideo, data: !offerlessInvite },
success: function(jsep) {
Janus.debug("Got SDP " + jsep.type + "! audio=" + doAudio + ", video=" + doVideo + ":", jsep);
var body = { request: "accept" };
Expand Down Expand Up @@ -632,16 +633,139 @@ $(document).ready(function() {
$('#remotevideo').removeClass('hide').show();
}
},
ondataopen: function(label, protocol) {
Janus.log("The DataChannel is available! " + label + " (protocol=" + protocol + ")");
if(sipcall.localMessages)
return;
$('#texts').removeClass('hide').show();
// We'll store the history of local and remote messages
// in an array, in case we need to edit those again
sipcall.localMessages = [];
sipcall.remoteMessages = [];
// The "typing" remote text is in a separate string instead
sipcall.remoteText = "";
// Send the BOM
var array = new Uint8Array(3);
array[0] = 239;
array[1] = 187;
array[2] = 191;
sipcall.data({ data: array, label: "RTT", protocol: "t140" });
// Prepare the input area
$('#datasend').removeAttr('disabled')
.keypress(function(event) {
if(event.which == 13) {
// Enter key was pressed, send a line separator code
var array = new Uint8Array(3);
array[0] = 226;
array[1] = 128;
array[2] = 168;
sipcall.data({ data: array, label: "RTT", protocol: "t140" });
// Now push the text to the local messages and empty the input
sipcall.localMessages.push({
timestamp: new Date(),
text: $(this).val()
});
$(this).val('');
// Update the contents of the chatbox
updateRealtimeChat(sipcall);
return;
}
var array = new Uint8Array(1);
array[0] = event.keyCode;
sipcall.data({ data: array, label: "RTT", protocol: "t140" });
}).keydown(function(event) {
if(event.which == 8) {
// Backspace pressed, send the related code
var array = new Uint8Array(1);
array[0] = event.keyCode;
sipcall.data({ data: array, label: "RTT", protocol: "t140" });
// Delete in our input as well
if($(this).val().length > 0) {
var text = $(this).val().slice(0, -1);
$(this).focus().val('').val(text);
} else {
// Empty input, start editing previously completed message
var text = "";
if(sipcall.localMessages.length > 0) {
text = sipcall.localMessages.pop().text;
}
$(this).focus().val('').val(text);
// Update the contents of the chatbox
updateRealtimeChat(sipcall);
}
return false;
}
// Prevent cursor positioning
var text = $(this).val();
$(this).focus().val('').val(text);
}).on("click", function(event) {
// Prevent cursor positioning
var text = $(this).val();
$(this).focus().val('').val(text);
});
},
ondata: function(data) {
Janus.debug("We got data from the DataChannel!", data);
// Parse the text, and update the string
var array = new Uint8Array(data);
var str = "";
for(var i=0, len=array.length; i<len; ++i) {
if(array[i] === 8) {
// Backspace
str = decodeURIComponent(str);
sipcall.remoteText += str;
str = "";
if(sipcall.remoteText.length > 0) {
sipcall.remoteText = sipcall.remoteText.slice(0, -1);
// Update the typing text
updateRealtimeChat(sipcall, true);
} else {
// User wasn't typing, start editing previously completed message
if(sipcall.remoteMessages.length > 0) {
str = sipcall.remoteMessages.pop().text;
}
sipcall.remoteText = str;
// Update the contents of the chatbox
updateRealtimeChat(sipcall);
}
continue;
} else if(array[i] === 226 && (len-i > 2) && array[i+1] === 128 && array[i+2] === 168) {
// Line separator, push the current text to the remote messages
sipcall.remoteMessages.push({
timestamp: new Date(),
sender: sipcall.peer,
text: sipcall.remoteText
});
sipcall.remoteText = "";
// Update the contents of the chatbox
updateRealtimeChat(sipcall);
i += 2;
continue;
}
str += ( "%" + pad(array[i].toString(16)));
}
str = decodeURIComponent(str);
sipcall.remoteText += str;
// Update the typing text
updateRealtimeChat(sipcall, true);
},
oncleanup: function() {
Janus.log(" ::: Got a cleanup notification :::");
$('#myvideo').remove();
$('#waitingvideo').remove();
$('#remotevideo').remove();
$('#videos .no-video-container').remove();
$('#videos').hide();
$('#texts').hide();
$('#messages').empty();
$('#datasend').attr('disabled', true).val('');
$('#dtmf').parent().html("Remote UA");
if(sipcall)
sipcall.callId = null;
delete sipcall.peer;
delete sipcall.localMessages;
delete sipcall.remoteMessages;
delete sipcall.remoteText;
}
});
},
Expand Down Expand Up @@ -869,11 +993,14 @@ function doCall(ev) {
actuallyDoCall(handle, $('#peer' + suffix).val(), doVideo);
}
function actuallyDoCall(handle, uri, doVideo, referId) {
handle.peer = uri;
handle.createOffer(
{
media: {
audioSend: true, audioRecv: true, // We DO want audio
videoSend: doVideo, videoRecv: doVideo // We MAY want video
// Should you want to negotiate support for real-time text
// as well, all you need to do is pass data:true here
},
success: function(jsep) {
Janus.debug("Got SDP!", jsep);
Expand Down Expand Up @@ -1008,7 +1135,7 @@ function addHelper(helperCreated) {
success: function(pluginHandle) {
helpers[helperId].sipcall = pluginHandle;
Janus.log("[Helper #" + helperId + "] Plugin attached! (" + helpers[helperId].sipcall.getPlugin() + ", id=" + helpers[helperId].sipcall.getId() + ")");
// TODO Send the "register"
// Send the "register"
helpers[helperId].sipcall.send({
message: {
request: "register",
Expand Down Expand Up @@ -1144,7 +1271,7 @@ function addHelper(helperCreated) {
sipcallAction(
{
jsep: jsep,
media: { audio: doAudio, video: doVideo },
media: { audio: doAudio, video: doVideo, data: !offerlessInvite },
success: function(jsep) {
Janus.debug("[Helper #" + helperId + "] Got SDP " + jsep.type + "! audio=" + doAudio + ", video=" + doVideo + ":", jsep);
var body = { request: "accept" };
Expand Down Expand Up @@ -1539,3 +1666,37 @@ function removeHelper(helperId) {
$('#sipcall'+helperId).remove();
}
}

// Helper to update the real-time chat content (both completed and ongoing messages)
function updateRealtimeChat(handle, onlyTyping) {
if(!onlyTyping) {
// Prepare a list of messages and sort them by timestamp
var messages = [];
for(var lm of handle.localMessages)
messages.push(lm);
for(var rm of handle.remoteMessages)
messages.push(rm);
messages.sort(function(a, b) {
return a.timestamp-b.timestamp;
});
// Add all messages
$('#messages').empty();
for(var m of messages) {
$('#messages').append(
'<p>[' + m.timestamp.toLocaleTimeString() + '] <b>' +
(m.sender ? m.sender : 'You') + ':</b> <span>' + m.text + '</span></p>'
);
}
}
// Add a line for the "currently typing" text
if($('#typing').length === 0) {
$('#messages').append('<p><b>' + handle.peer + ' typing:</b> <span id="typing"></span>');
$('#messages').get(0).scrollTop = $('#messages').get(0).scrollHeight;
}
$('#typing').html(handle.remoteText);
}

// Helper to pad strings when parsing T.140 blocks for real-time text
function pad(n) {
return n.length < 2 ? "0" + n : n;
}
Loading