From 9f40c11e8a1ade4ef33040982edca5588323e81c Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Thu, 25 Jun 2020 17:10:58 +0200 Subject: [PATCH 001/105] Set last sending timestamp for the first packet sent (avoid #2217 overflow issue). --- ice.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ice.c b/ice.c index b6885cc79b..d170796b56 100644 --- a/ice.c +++ b/ice.c @@ -4421,7 +4421,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu component->out_stats.audio.bytes_lastsec_temp += pkt->length; struct timeval tv; gettimeofday(&tv, NULL); - if ((gint32)(timestamp - stream->audio_last_rtp_ts) > 0) { + if(stream->audio_last_ntp_ts == 0 || (gint32)(timestamp - stream->audio_last_rtp_ts) > 0) { stream->audio_last_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; stream->audio_last_rtp_ts = timestamp; } @@ -4452,7 +4452,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu component->out_stats.video[0].bytes_lastsec_temp += pkt->length; struct timeval tv; gettimeofday(&tv, NULL); - if ((gint32)(timestamp - stream->video_last_rtp_ts) > 0) { + if(stream->video_last_ntp_ts == 0 || (gint32)(timestamp - stream->video_last_rtp_ts) > 0) { stream->video_last_ntp_ts = (gint64)tv.tv_sec*G_USEC_PER_SEC + tv.tv_usec; stream->video_last_rtp_ts = timestamp; } From 922b3926e3b9ed2e50b6e6f36b6f018025dadf8b Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Thu, 25 Jun 2020 17:23:59 +0200 Subject: [PATCH 002/105] Add more checks on validity of NUA before using it in SIP plugin (#2247) --- plugins/janus_sip.c | 73 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/plugins/janus_sip.c b/plugins/janus_sip.c index 318cc915e8..1d363e9e34 100644 --- a/plugins/janus_sip.c +++ b/plugins/janus_sip.c @@ -2114,8 +2114,12 @@ void janus_sip_destroy_session(janus_plugin_session *handle, int *error) { session->refer_id = 0; } /* Shutdown the NUA */ - if(session->stack && session->stack->s_nua) - nua_shutdown(session->stack->s_nua); + if(session->stack) { + janus_mutex_lock(&session->stack->smutex); + if(session->stack->s_nua) + nua_shutdown(session->stack->s_nua); + janus_mutex_unlock(&session->stack->smutex); + } g_hash_table_remove(sessions, handle); janus_mutex_unlock(&sessions_mutex); return; @@ -2978,7 +2982,16 @@ static void *janus_sip_handler(void *data) { char custom_params[2048]; janus_sip_parse_custom_contact_params(root, (char *)&custom_params, sizeof(custom_params)); /* Create a new NUA handle */ + janus_mutex_lock(&session->stack->smutex); + if(session->stack->s_nua == NULL) { + janus_mutex_unlock(&session->stack->smutex); + JANUS_LOG(LOG_ERR, "NUA destroyed while registering?\n"); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } session->stack->s_nh_r = nua_handle(session->stack->s_nua, session, TAG_END()); + janus_mutex_unlock(&session->stack->smutex); if(session->stack->s_nh_r == NULL) { JANUS_LOG(LOG_ERR, "NUA Handle for REGISTER still null??\n"); error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; @@ -3090,16 +3103,34 @@ static void *janus_sip_handler(void *data) { nh = g_hash_table_lookup(session->stack->subscriptions, (char *)event_type); if(nh == NULL) { /* We don't, create one now */ - if(!session->helper) + if(!session->helper) { + janus_mutex_lock(&session->stack->smutex); + if(session->stack->s_nua == NULL) { + janus_mutex_unlock(&session->stack->smutex); + JANUS_LOG(LOG_ERR, "NUA destroyed while subscribing?\n"); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } nh = nua_handle(session->stack->s_nua, session, TAG_END()); - else { + janus_mutex_unlock(&session->stack->smutex); + } else { /* This is a helper, we need to use the master's SIP stack */ if(session->master == NULL || session->master->stack == NULL) { error_code = JANUS_SIP_ERROR_HELPER_ERROR; g_snprintf(error_cause, 512, "Invalid master SIP stack"); goto error; } + janus_mutex_lock(&session->master->stack->smutex); + if(session->master->stack->s_nua == NULL) { + janus_mutex_unlock(&session->master->stack->smutex); + JANUS_LOG(LOG_ERR, "NUA destroyed while subscribing?\n"); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } nh = nua_handle(session->master->stack->s_nua, session, TAG_END()); + janus_mutex_unlock(&session->master->stack->smutex); } if(session->stack->subscriptions == NULL) { /* We still need a table for mapping these subscriptions as well */ @@ -3328,7 +3359,16 @@ static void *janus_sip_handler(void *data) { if(session->stack->s_nh_i != NULL) nua_handle_destroy(session->stack->s_nh_i); if(!session->helper) { + janus_mutex_lock(&session->stack->smutex); + if(session->stack->s_nua == NULL) { + janus_mutex_unlock(&session->stack->smutex); + JANUS_LOG(LOG_ERR, "NUA destroyed while calling?\n"); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } session->stack->s_nh_i = nua_handle(session->stack->s_nua, session, TAG_END()); + janus_mutex_unlock(&session->stack->smutex); if(session->account.display_name) { g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>", session->account.display_name, session->account.identity); } else { @@ -3344,7 +3384,18 @@ static void *janus_sip_handler(void *data) { g_snprintf(error_cause, 512, "Invalid master SIP stack"); goto error; } + janus_mutex_lock(&session->master->stack->smutex); + if(session->master->stack->s_nua == NULL) { + janus_mutex_unlock(&session->master->stack->smutex); + g_free(sdp); + session->sdp = NULL; + janus_sdp_destroy(parsed_sdp); + error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR; + g_snprintf(error_cause, 512, "Invalid NUA"); + goto error; + } session->stack->s_nh_i = nua_handle(session->master->stack->s_nua, session, TAG_END()); + janus_mutex_unlock(&session->master->stack->smutex); if(session->master->account.display_name) { g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>", session->master->account.display_name, session->master->account.identity); } else { @@ -6516,7 +6567,19 @@ gpointer janus_sip_sofia_thread(gpointer user_data) { TAG_NULL()); su_root_run(session->stack->s_root); /* When we get here, we're done */ - nua_destroy(session->stack->s_nua); + janus_mutex_lock(&session->stack->smutex); + nua_t *s_nua = session->stack->s_nua; + session->stack->s_nua = NULL; + janus_mutex_unlock(&session->stack->smutex); + if(session->stack->s_nh_r != NULL) { + nua_handle_destroy(session->stack->s_nh_r); + session->stack->s_nh_r = NULL; + } + if(session->stack->s_nh_i != NULL) { + nua_handle_destroy(session->stack->s_nh_i); + session->stack->s_nh_i = NULL; + } + nua_destroy(s_nua); su_root_destroy(session->stack->s_root); session->stack->s_root = NULL; janus_refcount_decrease(&session->ref); From 88df9449ac54f27afa29672cf092fac65a695c29 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Mon, 29 Jun 2020 10:37:47 +0200 Subject: [PATCH 003/105] Added timeout to connections in HTTP transport (120s) --- transports/janus_http.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transports/janus_http.c b/transports/janus_http.c index 02c234b912..fe4f8d366e 100644 --- a/transports/janus_http.c +++ b/transports/janus_http.c @@ -475,6 +475,7 @@ static struct MHD_Daemon *janus_http_create_daemon(gboolean admin, char *path, admin ? &janus_http_admin_handler : &janus_http_handler, path, MHD_OPTION_NOTIFY_COMPLETED, &janus_http_request_completed, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, 120, MHD_OPTION_END); } else { /* Bind to the interface that was specified */ @@ -490,6 +491,7 @@ static struct MHD_Daemon *janus_http_create_daemon(gboolean admin, char *path, path, MHD_OPTION_NOTIFY_COMPLETED, &janus_http_request_completed, NULL, MHD_OPTION_SOCK_ADDR, ipv6 ? (struct sockaddr *)&addr6 : (struct sockaddr *)&addr, + MHD_OPTION_CONNECTION_TIMEOUT, 120, MHD_OPTION_END); } } else { @@ -514,6 +516,7 @@ static struct MHD_Daemon *janus_http_create_daemon(gboolean admin, char *path, MHD_OPTION_HTTPS_MEM_CERT, cert_pem_bytes, MHD_OPTION_HTTPS_MEM_KEY, cert_key_bytes, MHD_OPTION_HTTPS_KEY_PASSWORD, password, + MHD_OPTION_CONNECTION_TIMEOUT, 120, MHD_OPTION_END); } else { /* Bind to the interface that was specified */ @@ -533,6 +536,7 @@ static struct MHD_Daemon *janus_http_create_daemon(gboolean admin, char *path, MHD_OPTION_HTTPS_MEM_KEY, cert_key_bytes, MHD_OPTION_HTTPS_KEY_PASSWORD, password, MHD_OPTION_SOCK_ADDR, ipv6 ? (struct sockaddr *)&addr6 : (struct sockaddr *)&addr, + MHD_OPTION_CONNECTION_TIMEOUT, 120, MHD_OPTION_END); } } From 56e3af69fb658ac35d22fa3f7fb14d525f9bee47 Mon Sep 17 00:00:00 2001 From: Neil Kinnish Date: Tue, 30 Jun 2020 13:48:53 +0100 Subject: [PATCH 004/105] Fix opus silence potential to generate huge files (#2250) --- postprocessing/pp-opus.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/postprocessing/pp-opus.c b/postprocessing/pp-opus.c index d188bde66f..459d51b0f1 100644 --- a/postprocessing/pp-opus.c +++ b/postprocessing/pp-opus.c @@ -70,7 +70,7 @@ int janus_pp_opus_process(FILE *file, janus_pp_frame_packet *list, int *working) janus_pp_frame_packet *tmp = list; long int offset = 0; int bytes = 0, len = 0, steps = 0, last_seq = 0; - uint64_t pos = 0; + uint64_t pos = 0, nextPos = 0; uint8_t *buffer = g_malloc0(1500); while(*working && tmp != NULL) { if(tmp->prev != NULL && ((tmp->ts - tmp->prev->ts)/48/20 > 1)) { @@ -85,6 +85,12 @@ int janus_pp_opus_process(FILE *file, janus_pp_frame_packet *list, int *working) int i=0; for(i=0; iprev->ts - list->ts) / 48 / 20 + i + 1; + if(tmp->next != NULL) + nextPos = (tmp->next->ts - list->ts) / 48 / 20; + if(pos >= nextPos) { + JANUS_LOG(LOG_WARN, "[SKIP] pos: %06" SCNu64 ", skipping remaining silence\n", pos); + break; + } op->granulepos = 960*(pos); /* FIXME: get this from the toc byte */ ogg_stream_packetin(stream, op); ogg_write(); From 1312661c8648094321a6924c0845ac216e0b7efc Mon Sep 17 00:00:00 2001 From: Jan Willamowius Date: Wed, 1 Jul 2020 12:45:34 +0200 Subject: [PATCH 005/105] fix documentation for mute_room/unmute_room (#2257) --- plugins/janus_audiobridge.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index ee931e2492..3455ffe5fa 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -697,7 +697,7 @@ room-: { * \verbatim { - "request" : "", + "request" : "", "secret" : "", "room" : , "id" : @@ -716,10 +716,9 @@ room-: { * \verbatim { - "request" : "", + "request" : "", "secret" : "", - "room" : , - "id" : + "room" : } \endverbatim * From 7b9cf73ac016ebea8b698c7170250b51e978adec Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Wed, 1 Jul 2020 12:51:06 +0200 Subject: [PATCH 006/105] Fixed typo preventing SRTP support in static AudioBridge RTP forwarders (fixes #2258) --- plugins/janus_audiobridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index 3455ffe5fa..a8cc23fda1 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -1891,8 +1891,8 @@ static int janus_audiobridge_create_static_rtp_forwarder(janus_config_category * /* We may need to SRTP-encrypt this stream */ int srtp_suite = 0; const char *srtp_crypto = NULL; - janus_config_item *s_suite = janus_config_get(config, cat, janus_config_type_item, "srtp_suite"); - janus_config_item *s_crypto = janus_config_get(config, cat, janus_config_type_item, "srtp_crypto"); + janus_config_item *s_suite = janus_config_get(config, cat, janus_config_type_item, "rtp_forward_srtp_suite"); + janus_config_item *s_crypto = janus_config_get(config, cat, janus_config_type_item, "rtp_forward_srtp_crypto"); if(s_suite && s_suite->value) { srtp_suite = atoi(s_suite->value); if(srtp_suite != 32 && srtp_suite != 80) { From 139da3cf3f89c309b800f4474623a905168aca8a Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Wed, 1 Jul 2020 15:56:13 +0200 Subject: [PATCH 007/105] Fixed typo (SSRC outbound for RTP forwarders) --- plugins/janus_audiobridge.c | 2 +- plugins/janus_videoroom.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index a8cc23fda1..daee185c54 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -1446,7 +1446,7 @@ static guint32 janus_audiobridge_rtp_forwarder_add_helper(janus_audiobridge_room } else if(srtp_suite == 80) { srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp)); } - policy->ssrc.type = ssrc_any_inbound; + policy->ssrc.type = ssrc_any_outbound; policy->key = decoded; policy->next = NULL; /* Create SRTP context */ diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c index 0986e4b77e..4ea710d24a 100644 --- a/plugins/janus_videoroom.c +++ b/plugins/janus_videoroom.c @@ -1920,7 +1920,7 @@ static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_publishe } else if(srtp_suite == 80) { srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp)); } - policy->ssrc.type = ssrc_any_inbound; + policy->ssrc.type = ssrc_any_outbound; policy->key = decoded; policy->next = NULL; /* Create SRTP context */ From 535a3642f81585bb3622d8431421f20c270404f3 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Wed, 1 Jul 2020 16:27:39 +0200 Subject: [PATCH 008/105] Added missing SRTP support to AudioBridge RTP forwarders (see #2258) --- plugins/janus_audiobridge.c | 50 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index daee185c54..8cd87d7fdc 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -1419,7 +1419,23 @@ typedef struct janus_audiobridge_rtp_forwarder { gboolean is_srtp; srtp_t srtp_ctx; srtp_policy_t srtp_policy; + /* Reference */ + volatile gint destroyed; + janus_refcount ref; } janus_audiobridge_rtp_forwarder; +static void janus_audiobridge_rtp_forwarder_destroy(janus_audiobridge_rtp_forwarder *rf) { + if(rf && g_atomic_int_compare_and_exchange(&rf->destroyed, 0, 1)) { + janus_refcount_decrease(&rf->ref); + } +} +static void janus_audiobridge_rtp_forwarder_free(const janus_refcount *f_ref) { + janus_audiobridge_rtp_forwarder *rf = janus_refcount_containerof(f_ref, janus_audiobridge_rtp_forwarder, ref); + if(rf->is_srtp) { + srtp_dealloc(rf->srtp_ctx); + g_free(rf->srtp_policy.key); + } + g_free(rf); +} static guint32 janus_audiobridge_rtp_forwarder_add_helper(janus_audiobridge_room *room, const gchar *host, uint16_t port, uint32_t ssrc, int pt, janus_audiocodec codec, int srtp_suite, const char *srtp_crypto, @@ -1495,6 +1511,7 @@ static guint32 janus_audiobridge_rtp_forwarder_add_helper(janus_audiobridge_room while(g_hash_table_lookup(room->rtp_forwarders, GUINT_TO_POINTER(actual_stream_id)) != NULL) { actual_stream_id = janus_random_uint32(); } + janus_refcount_init(&rf->ref, janus_audiobridge_rtp_forwarder_free); g_hash_table_insert(room->rtp_forwarders, GUINT_TO_POINTER(actual_stream_id), rf); janus_mutex_unlock(&room->rtp_mutex); @@ -2136,7 +2153,7 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { audiobridge->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); g_atomic_int_set(&audiobridge->destroyed, 0); janus_mutex_init(&audiobridge->mutex); - audiobridge->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_free); + audiobridge->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_audiobridge_rtp_forwarder_destroy); audiobridge->rtp_encoder = NULL; audiobridge->rtp_udp_sock = -1; janus_mutex_init(&audiobridge->rtp_mutex); @@ -2608,7 +2625,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s } g_atomic_int_set(&audiobridge->destroyed, 0); janus_mutex_init(&audiobridge->mutex); - audiobridge->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_free); + audiobridge->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_audiobridge_rtp_forwarder_destroy); audiobridge->rtp_encoder = NULL; audiobridge->rtp_udp_sock = -1; janus_mutex_init(&audiobridge->rtp_mutex); @@ -6294,6 +6311,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* RTP */ gint16 seq = 0; gint32 ts = 0; + /* SRTP buffer, if needed */ + char sbuf[1500]; /* Loop */ int i=0; @@ -6623,16 +6642,31 @@ static void *janus_audiobridge_mixer_thread(void *data) { rtph->seq_number = htons(forwarder->seq_number); forwarder->timestamp += (forwarder->codec == JANUS_AUDIOCODEC_OPUS ? OPUS_SAMPLES : G711_SAMPLES); rtph->timestamp = htonl(forwarder->timestamp); - /* Send RTP packet */ + /* Check if this packet needs to be encrypted */ + char *payload = (char *)rtph; + int plen = (JANUS_AUDIOCODEC_OPUS ? (length+12) : 172); + if(forwarder->is_srtp) { + memcpy(sbuf, payload, plen); + int protected = plen; + int res = srtp_protect(forwarder->srtp_ctx, sbuf, &protected); + if(res != srtp_err_status_ok) { + janus_rtp_header *header = (janus_rtp_header *)sbuf; + guint32 timestamp = ntohl(header->timestamp); + guint16 seq = ntohs(header->seq_number); + JANUS_LOG(LOG_ERR, "Error encrypting RTP packet for room %s... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n", + audiobridge->room_id_str, janus_srtp_error_str(res), plen, protected, timestamp, seq); + } else { + payload = (char *)&sbuf; + plen = protected; + } + } + /* No encryption, send the RTP packet as it is */ struct sockaddr *address = (forwarder->serv_addr.sin_family == AF_INET ? (struct sockaddr *)&forwarder->serv_addr : (struct sockaddr *)&forwarder->serv_addr6); size_t addrlen = (forwarder->serv_addr.sin_family == AF_INET ? sizeof(forwarder->serv_addr) : sizeof(forwarder->serv_addr6)); - if(sendto(audiobridge->rtp_udp_sock, (char *)rtph, - (forwarder->codec == JANUS_AUDIOCODEC_OPUS ? (length+12) : 172), - 0, address, addrlen) < 0) { + if(sendto(audiobridge->rtp_udp_sock, payload, plen, 0, address, addrlen) < 0) { JANUS_LOG(LOG_HUGE, "Error forwarding mixed RTP packet for room %s... %s (len=%d)...\n", - audiobridge->room_id_str, strerror(errno), - (forwarder->codec == JANUS_AUDIOCODEC_OPUS ? (length+12) : 172)); + audiobridge->room_id_str, strerror(errno), plen); } } } From 9e2ebaa1a8e7ac801a2f5243aac80f2072c3492f Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Thu, 2 Jul 2020 17:25:53 +0200 Subject: [PATCH 009/105] New demo to use canvas element with EchoTest plugin (#2261) --- html/canvas.html | 159 +++++++++++ html/canvas.js | 585 ++++++++++++++++++++++++++++++++++++++ html/demos.html | 4 + html/janus-logo-small.png | Bin 0 -> 20535 bytes html/navbar.html | 1 + 5 files changed, 749 insertions(+) create mode 100644 html/canvas.html create mode 100644 html/canvas.js create mode 100644 html/janus-logo-small.png diff --git a/html/canvas.html b/html/canvas.html new file mode 100644 index 0000000000..1cd020b31c --- /dev/null +++ b/html/canvas.html @@ -0,0 +1,159 @@ + + + + + + +Janus WebRTC Server: Canvas Capture + + + + + + + + + + + + + + + + + +Fork me on GitHub + + + +
+
+
+ +
+
+
+

Demo details

+

This is a variant of the Echo Test demo meant to showcase how + you can use an HTML5 canvas element as a WebRTC media + source: everything is exactly the same in term of available controls, + features, and the like, with the substantial difference that we'll + play a bit with what we'll send on the video stream.

+

More precisely, the demo captures the webcam feed via a + getUserMedia call to use as a background in a + canvas element, and then presents some basic controls + to add some text dynamically; an image is also statically added + to the element as well. The canvas element is then used + as the actual source of media for our PeerConnection, which means the + video we get back from the EchoTest plugin should reflect the + tweaks we've made on the stream.

+

Notice that this is a very naive implementation, with many + hardcoded assumptions about video resolution and other things, and may not + perform very well either: it's only meant to showcase an interesting + approach that can be used for WebRTC, so you're encouraged to dig + deeper yourself to see how to make the canvas + processing more efficient and cooler. The code for this demo comes from a + Dangerous Demo + we showed during a past edition of Kamailio World; you can read more details in a + blog post + we wrote on the Meetecho blog after the fact.

+

Press the Start button above to launch the demo.

+
+
+
+
+
+
+
+
+
+

Local Stream +
+ + + +
+

+
+
+ + +
+
+
+
+
+
+

Tweaks

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+

Remote Stream

+
+
+
+
+
+
+
+
+ +
+ +
+ + + diff --git a/html/canvas.js b/html/canvas.js new file mode 100644 index 0000000000..a7e34dead3 --- /dev/null +++ b/html/canvas.js @@ -0,0 +1,585 @@ +// We make use of this 'server' variable to provide the address of the +// REST Janus API. By default, in this example we assume that Janus is +// co-located with the web server hosting the HTML pages but listening +// on a different port (8088, the default for HTTP in Janus), which is +// why we make use of the 'window.location.hostname' base address. Since +// Janus can also do HTTPS, and considering we don't really want to make +// use of HTTP for Janus if your demos are served on HTTPS, we also rely +// on the 'window.location.protocol' prefix to build the variable, in +// particular to also change the port used to contact Janus (8088 for +// HTTP and 8089 for HTTPS, if enabled). +// In case you place Janus behind an Apache frontend (as we did on the +// online demos at http://janus.conf.meetecho.com) you can just use a +// relative path for the variable, e.g.: +// +// var server = "/janus"; +// +// which will take care of this on its own. +// +// +// If you want to use the WebSockets frontend to Janus, instead, you'll +// have to pass a different kind of address, e.g.: +// +// var server = "ws://" + window.location.hostname + ":8188"; +// +// Of course this assumes that support for WebSockets has been built in +// when compiling the server. WebSockets support has not been tested +// as much as the REST API, so handle with care! +// +// +// If you have multiple options available, and want to let the library +// autodetect the best way to contact your server (or pool of servers), +// you can also pass an array of servers, e.g., to provide alternative +// means of access (e.g., try WebSockets first and, if that fails, fall +// back to plain HTTP) or just have failover servers: +// +// var server = [ +// "ws://" + window.location.hostname + ":8188", +// "/janus" +// ]; +// +// This will tell the library to try connecting to each of the servers +// in the presented order. The first working server will be used for +// the whole session. +// +var server = null; +if(window.location.protocol === 'http:') + server = "http://" + window.location.hostname + ":8088/janus"; +else + server = "https://" + window.location.hostname + ":8089/janus"; + +var janus = null; +var echotest = null; +var opaqueId = "canvas-"+Janus.randomString(12); + +var bitrateTimer = null; +var spinner = null; + +var audioenabled = false; +var videoenabled = false; + +var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true"); +var doSimulcast2 = (getQueryStringValue("simulcast2") === "yes" || getQueryStringValue("simulcast2") === "true"); +var acodec = (getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null); +var vcodec = (getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null); +var vprofile = (getQueryStringValue("vprofile") !== "" ? getQueryStringValue("vprofile") : null); +var simulcastStarted = false; + +// We'll try to do 15 frames per second: should be relatively fluid, and +// most important should be doable in JavaScript on lower end machines too +var fps = 15; +// Let's add some placeholders for the tweaks we can configure +var myText = "Hi there!"; +var myColor = "white"; +var myFont = "20pt Calibri"; +var myX = 15, myY = 223; +// As the "watermark", we'll use a smaller version of the Janus logo +var logoUrl = "./janus-logo-small.png"; +var logoW = 340, logoH = 110; +var logoS = 0.4; +var logoX = 432 - logoW*logoS - 5, logoY = 5; + + +$(document).ready(function() { + // Initialize the library (all console debuggers enabled) + Janus.init({debug: "all", callback: function() { + // Use a button to start the demo + $('#start').one('click', function() { + $(this).attr('disabled', true).unbind('click'); + // Make sure the browser supports WebRTC + if(!Janus.isWebrtcSupported()) { + bootbox.alert("No WebRTC support... "); + return; + } + // Create session + janus = new Janus( + { + server: server, + // No "iceServers" is provided, meaning janus.js will use a default STUN server + // Here are some examples of how an iceServers field may look like to support TURN + // iceServers: [{urls: "turn:yourturnserver.com:3478", username: "janususer", credential: "januspwd"}], + // iceServers: [{urls: "turn:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}], + // iceServers: [{urls: "turns:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}], + // Should the Janus API require authentication, you can specify either the API secret or user token here too + // token: "mytoken", + // or + // apisecret: "serversecret", + success: function() { + // Attach to EchoTest plugin + janus.attach( + { + plugin: "janus.plugin.echotest", + opaqueId: opaqueId, + success: function(pluginHandle) { + $('#details').remove(); + echotest = pluginHandle; + Janus.log("Plugin attached! (" + echotest.getPlugin() + ", id=" + echotest.getId() + ")"); + // We're connected to the plugin, create and populate the canvas element + createCanvas(); + $('#start').removeAttr('disabled').html("Stop") + .click(function() { + $(this).attr('disabled', true); + if(bitrateTimer) + clearInterval(bitrateTimer); + bitrateTimer = null; + janus.destroy(); + }); + }, + error: function(error) { + console.error(" -- Error attaching plugin...", error); + bootbox.alert("Error attaching plugin... " + error); + }, + consentDialog: function(on) { + Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now"); + if(on) { + // Darken screen and show hint + $.blockUI({ + message: '
', + css: { + border: 'none', + padding: '15px', + backgroundColor: 'transparent', + color: '#aaa', + top: '10px', + left: (navigator.mozGetUserMedia ? '-100px' : '300px') + } }); + } else { + // Restore screen + $.unblockUI(); + } + }, + iceState: function(state) { + Janus.log("ICE state changed to " + state); + }, + mediaState: function(medium, on) { + Janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium); + }, + webrtcState: function(on) { + Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now"); + $("#videoleft").parent().unblock(); + }, + slowLink: function(uplink, lost) { + Janus.warn("Janus reports problems " + (uplink ? "sending" : "receiving") + + " packets on this PeerConnection (" + lost + " lost packets)"); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::", msg); + if(jsep) { + Janus.debug("Handling SDP as well...", jsep); + echotest.handleRemoteJsep({ jsep: jsep }); + } + var result = msg["result"]; + if(result) { + if(result === "done") { + // The plugin closed the echo test + bootbox.alert("The Echo Test is over"); + if(spinner) + spinner.stop(); + spinner = null; + $('#myvideo').remove(); + $('#waitingvideo').remove(); + $('#peervideo').remove(); + $('#toggleaudio').attr('disabled', true); + $('#togglevideo').attr('disabled', true); + $('#bitrate').attr('disabled', true); + $('#curbitrate').hide(); + $('#curres').hide(); + return; + } + // Any loss? + var status = result["status"]; + if(status === "slow_link") { + toastr.warning("Janus apparently missed many packets we sent, maybe we should reduce the bitrate", "Packet loss?", {timeOut: 2000}); + } + } + // Is simulcast in place? + var substream = msg["substream"]; + var temporal = msg["temporal"]; + if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) { + if(!simulcastStarted) { + simulcastStarted = true; + addSimulcastButtons(msg["videocodec"] === "vp8" || msg["videocodec"] === "h264"); + } + // We just received notice that there's been a switch, update the buttons + updateSimulcastButtons(substream, temporal); + } + }, + onlocalstream: function(stream) { + // We ignore the stream we got here, we're using the canvas to render it + if(echotest.webrtcStuff.pc.iceConnectionState !== "completed" && + echotest.webrtcStuff.pc.iceConnectionState !== "connected") { + $("#videoleft").parent().block({ + message: 'Publishing...', + css: { + border: 'none', + backgroundColor: 'transparent', + color: 'white' + } + }); + } + }, + onremotestream: function(stream) { + Janus.debug(" ::: Got a remote stream :::", stream); + var addButtons = false; + if($('#peervideo').length === 0) { + addButtons = true; + $('#videos').removeClass('hide').show(); + $('#videoright').append('