diff --git a/.gitignore b/.gitignore index 42c8c3c..7713d52 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ node_modules re_trace.json + +config +contacts +current_contact diff --git a/CMakeLists.txt b/CMakeLists.txt index 83de084..c5addee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ set(REM_LIBRARY rem CACHE STRING "rem_library") set(STATIC ON CACHE BOOL "Build static") set(APP_MODULES_DIR ${CMAKE_SOURCE_DIR}/modules) set(APP_MODULES amix vmix) -set(MODULES ice dtls_srtp turn opus vp8 avcodec fakevideo auresamp CACHE STRING "") +set(MODULES ice dtls_srtp turn opus vp8 avcodec fakevideo auresamp contact CACHE STRING "") add_subdirectory(external/re EXCLUDE_FROM_ALL) add_subdirectory(external/baresip EXCLUDE_FROM_ALL) @@ -116,6 +116,7 @@ set(SRCS_LIB src/social.c src/source.c src/stats.c + src/tracks.c src/users.c src/ws.c ${CMAKE_CURRENT_BINARY_DIR}/version.c diff --git a/include/mix.h b/include/mix.h index 5732148..8ec783a 100644 --- a/include/mix.h +++ b/include/mix.h @@ -1,7 +1,7 @@ #include #include -#define METRICS_URL "http://127.0.0.1:9091/metrics/job/rtc" +#define METRICS_URL "http://127.0.0.1:6969/push/atrium" enum { ROOM_SZ = 128, @@ -134,6 +134,7 @@ int slmix_update_room(void); */ int slmix_sip_init(struct mix *mix); int slmix_sip_close(void); +struct ua *slmix_sip_ua(void); /****************************************************************************** @@ -199,14 +200,17 @@ int user_event_json(char **json, enum user_event event, struct session *sess); /****************************************************************************** * ws.c */ +enum ws_type { WS_USERS, WS_TRACKS }; int sl_ws_init(void); int sl_ws_close(void); -int sl_ws_open(struct http_conn *httpc, const struct http_msg *msg, - struct mix *mix, struct session *sess); -void sl_ws_send_event(struct session *sess, char *json); -void sl_ws_send_event_self(struct session *sess, char *json); -void sl_ws_send_event_host(char *json); -void sl_ws_send_event_all(char *json); +int sl_ws_open(enum ws_type type, struct http_conn *httpc, + const struct http_msg *msg, struct mix *mix, + struct session *sess); +void sl_ws_send_event(enum ws_type type, struct session *sess, char *json); +void sl_ws_send_event_self(enum ws_type type, struct session *sess, + char *json); +void sl_ws_send_event_host(enum ws_type type, char *json); +void sl_ws_send_event_all(enum ws_type type, char *json); void sl_ws_dummyh(const struct websock_hdr *hdr, struct mbuf *mb, void *arg); void sl_ws_users_auth(const struct websock_hdr *hdr, struct mbuf *mb, void *arg); @@ -281,6 +285,7 @@ int slmix_handle_ice_candidate(struct peer_connection *pc, /****************************************************************************** * stats.c */ +int sl_mix_stats_append(struct mbuf *mb); int slmix_stats_init(void); void slmix_stats_close(void); @@ -289,3 +294,61 @@ void slmix_stats_close(void); */ int social_request(struct http_conn *conn, const struct http_msg *msg, struct session *sess); + +/****************************************************************************** + * tracks.c + */ +/* Local audio device track */ +struct sl_local { + struct slaudio *slaudio; +}; + +/* Remote audio call track */ +struct sl_remote { + struct call *call; + struct session *sess; +}; +enum { SL_MAX_TRACKS = 16 }; +enum sl_track_type { SL_TRACK_REMOTE, SL_TRACK_REMOTE_RTC, SL_TRACK_LOCAL }; +enum sl_track_status { + SL_TRACK_INVALID = -1, + SL_TRACK_IDLE = 0, + SL_TRACK_LOCAL_REGISTERING = 1, + SL_TRACK_LOCAL_REGISTER_OK = 2, + SL_TRACK_LOCAL_REGISTER_FAIL = 3, + SL_TRACK_LOCAL_AUDIO_READY = 4, + SL_TRACK_REMOTE_CONNECTED = 5, + SL_TRACK_REMOTE_CALLING = 6, + SL_TRACK_REMOTE_INCOMING = 7, +}; +struct sl_track { + struct le le; + uint16_t id; + enum sl_track_type type; + char name[64]; + char error[128]; + enum sl_track_status status; + bool muted; + bool focus; + union + { + struct sl_local local; + struct sl_remote remote; + } u; +}; +int sl_tracks_init(void); +int sl_tracks_close(void); +const struct list *sl_tracks(void); +int sl_track_next_id(void); +int sl_track_add(struct sl_track **trackp, enum sl_track_type type); +int sl_track_del(int id); +void sl_track_focus(int id); +enum sl_track_status sl_track_status(int id); +int sl_tracks_json(struct re_printf *pf, void *arg); +struct sl_track *sl_track_by_id(int id); +struct slaudio *sl_track_audio(struct sl_track *track); +int sl_track_dial(struct sl_track *track, struct pl *peer); +void sl_track_accept(struct sl_track *track); +void sl_track_hangup(struct sl_track *track); +void sl_track_toggle_mute(int id); +void sl_track_ws_send(void); diff --git a/modules/vmix/codec.c b/modules/vmix/codec.c index 4cdbafc..5ed572b 100644 --- a/modules/vmix/codec.c +++ b/modules/vmix/codec.c @@ -457,7 +457,7 @@ int vmix_codec_init(void) if (err) return err; -#if 0 +#if 1 proxy_codec_alloc("H264", "packetization-mode=0"); proxy_codec_alloc("H264", "packetization-mode=1"); #else diff --git a/modules/vmix/vmix.c b/modules/vmix/vmix.c index 408afbc..742833d 100644 --- a/modules/vmix/vmix.c +++ b/modules/vmix/vmix.c @@ -113,8 +113,10 @@ static uint32_t disp_enable_h(const char *device, bool enable) return 0; src = vmix_src_find(device); - if (!src) + if (!src) { + warning("disp_enable: device %s not found\n", device); return 0; + } vidmix_source_enable(src->vidmix_src, enable); diff --git a/src/chat.c b/src/chat.c index 21ba8e5..936e2e6 100644 --- a/src/chat.c +++ b/src/chat.c @@ -47,7 +47,7 @@ int chat_save(struct user *user, struct mix *mix, const struct http_msg *msg) if (err) return err; - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); mem_deref(json); diff --git a/src/http.c b/src/http.c index ebd42dd..db70ddf 100644 --- a/src/http.c +++ b/src/http.c @@ -232,7 +232,156 @@ static void http_req_handler(struct http_conn *conn, * Websocket Request */ if (0 == pl_strcasecmp(&msg->path, "/ws/v1/users")) { - sl_ws_open(conn, msg, mix, sess); + sl_ws_open(WS_USERS, conn, msg, mix, sess); + return; + } + + if (0 == pl_strcasecmp(&msg->path, "/ws/v1/tracks")) { + sl_ws_open(WS_TRACKS, conn, msg, mix, sess); + return; + } + + ROUTE("/api/v1/tracks/remote", "POST") + { + if (!sess->user->host) + goto auth; + + struct sl_track *track; + + err = sl_track_add(&track, SL_TRACK_REMOTE); + if (err) + goto err; + + sl_track_ws_send(); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + ROUTE("/api/v1/tracks/togglemute", "POST") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + err = re_regex((char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), "[0-9]+", &pltrack); + if (err) + goto err; + + int32_t id = pl_i32(&pltrack); + + sl_track_toggle_mute(id); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + + ROUTE("/api/v1/tracks/focus", "POST") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + err = re_regex((char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), "[0-9]+", &pltrack); + if (err) + goto err; + + int32_t id = pl_i32(&pltrack); + + sl_track_focus(id); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + + ROUTE("/api/v1/tracks", "DELETE") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + err = re_regex((char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), "[0-9]+", &pltrack); + if (err) + goto err; + int32_t id = pl_i32(&pltrack); + + err = sl_track_del(id); + if (err) + goto notfound; + + sl_track_ws_send(); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + ROUTE("/api/v1/tracks/accept", "POST") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + + err = re_regex(msg->prm.p, msg->prm.l, "track=[0-9]+", + &pltrack); + if (err) + goto err; + + sl_track_accept(sl_track_by_id(pl_i32(&pltrack))); + + sl_track_ws_send(); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + ROUTE("/api/v1/tracks/hangup", "POST") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + + err = re_regex(msg->prm.p, msg->prm.l, "track=[0-9]+", + &pltrack); + if (err) + goto err; + + sl_track_hangup(sl_track_by_id(pl_i32(&pltrack))); + + sl_track_ws_send(); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); + return; + } + + ROUTE("/api/v1/tracks/dial", "POST") + { + if (!sess->user->host) + goto auth; + + struct pl pltrack = PL_INIT; + struct pl peer; + + pl_set_mbuf(&peer, msg->mb); + + err = re_regex(msg->prm.p, msg->prm.l, "track=[0-9]+", + &pltrack); + if (err) + goto err; + + err = sl_track_dial(sl_track_by_id(pl_i32(&pltrack)), &peer); + if (err) + goto err; + + sl_track_ws_send(); + + http_sreply(conn, 204, "OK", "text/html", "", 0, sess); return; } @@ -552,19 +701,21 @@ static void http_req_handler(struct http_conn *conn, ROUTE("/api/v1/webrtc/focus", "PUT") { - char user[USERID_SZ] = {0}; - struct pl user_id = PL_INIT; + char user[64] = {0}; + struct pl user_id = PL_INIT; /* check permission */ if (!sess->user->host) goto err; err = re_regex((char *)mbuf_buf(msg->mb), - mbuf_get_left(msg->mb), "[a-zA-Z0-9@:]+", + mbuf_get_left(msg->mb), "[a-zA-Z0-9@.:]+", &user_id); if (err) goto err; + warning("focus %r\n", &user_id); + pl_strcpy(&user_id, user, sizeof(user)); vmix_disp_focus(user); @@ -575,8 +726,8 @@ static void http_req_handler(struct http_conn *conn, ROUTE("/api/v1/webrtc/solo/enable", "PUT") { - char user[USERID_SZ] = {0}; - struct pl user_id = PL_INIT; + char user[64] = {0}; + struct pl user_id = PL_INIT; /* check permission */ if (!sess->user->host) @@ -585,8 +736,10 @@ static void http_req_handler(struct http_conn *conn, err = re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), "[a-zA-Z0-9@:]+", &user_id); - if (err) + if (err) { + warning("solo regex err %m\n", err); goto err; + } pl_strcpy(&user_id, user, sizeof(user)); @@ -710,9 +863,9 @@ static void http_req_handler(struct http_conn *conn, ROUTE("/api/v1/webrtc/stats", "POST") { +#if 0 struct sl_httpconn *http_conn; char metric_url[URL_SZ] = {0}; - err = sl_httpc_alloc(&http_conn, NULL, NULL, NULL); if (err) goto err; @@ -721,18 +874,8 @@ static void http_req_handler(struct http_conn *conn, METRICS_URL "/instance/%s/user/%s", mix->room, sess->user->id); - struct mbuf *body = mbuf_alloc(mbuf_get_left(msg->mb)); - mbuf_write_mem(body, mbuf_buf(msg->mb), - mbuf_get_left(msg->mb)); - - struct pl header_gzip; - pl_set_str(&header_gzip, "Content-Encoding: gzip"); - http_reqconn_add_header(http_conn->conn, &header_gzip); - - err = sl_httpc_req(http_conn, SL_HTTP_POST, metric_url, body); - mem_deref(http_conn); - mem_deref(body); - +#endif + err = sl_mix_stats_append(msg->mb); if (err) goto err; @@ -831,7 +974,7 @@ static void http_req_handler(struct http_conn *conn, re_snprintf(json, sizeof(json), "{\"type\": \"emoji\", \"id\": %r }", &id); - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); http_sreply(conn, 204, "OK", "text/html", "", 0, NULL); return; } diff --git a/src/main.c b/src/main.c index 8927619..bc68db0 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ static const char *modv[] = { "ice", "dtls_srtp", + "contact", /* audio */ "amix", @@ -13,7 +14,7 @@ static const char *modv[] = { "auresamp", /* video */ - "vp8", + /*"vp8", */ "avcodec", "vmix", }; @@ -118,7 +119,7 @@ int main(int argc, char *const argv[]) struct mix *mix = slmix(); const char *conf = - "#sip_listen 0.0.0.0:5060\n" + "sip_listen 0.0.0.0:5060\n" "call_max_calls 10\n" /* SIP incoming only */ "sip_verify_server yes\n" "audio_buffer 40-100\n" @@ -132,10 +133,10 @@ int main(int argc, char *const argv[]) "opus_bitrate 64000\n" "ice_policy all\n" "video_size 1920x1080\n" - "video_bitrate 2500000\n" - "video_sendrate 10000000\n" /* max burst send */ + "video_bitrate 4500000\n" + "video_sendrate 30000000\n" /* max burst send */ "video_burst_bit 1000000\n" /* max burst send */ - "video_fps 24\n" + "video_fps 30\n" "avcodec_keyint 10\n" "avcodec_h265enc nil\n" "avcodec_h265dec nil\n" @@ -226,8 +227,12 @@ int main(int argc, char *const argv[]) if (err) return err; + sl_tracks_init(); + re_main(signal_handler); + sl_tracks_close(); + sl_ws_close(); slmix_close(); diff --git a/src/mix.c b/src/mix.c index 2c84718..c7d3b44 100644 --- a/src/mix.c +++ b/src/mix.c @@ -89,7 +89,7 @@ void slmix_refresh_rooms(void *arg) re_sdprintf(&json, "{\"type\": \"rooms\", \"rooms\": {%b}}", mjson.buf, mjson.end - 1); - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); mem_deref(json); slmix_db_cur_close(cur); @@ -213,6 +213,8 @@ int slmix_config(char *file) err = 0; } + conf_path_set(mix.path); + info("slmix_config path: %s\n", mix.path); out: diff --git a/src/sess.c b/src/sess.c index 0a2d1f8..041db40 100644 --- a/src/sess.c +++ b/src/sess.c @@ -138,7 +138,7 @@ static void pc_estab_handler(struct media_track *media, void *arg) stream_enable_tx(media_get_stream(media), true); /* Enable/Disable Source view */ -#if 0 +#if 1 if (!sess->user->host) slmix_source_append_all(sess->mix, NULL, sess->user->id); @@ -229,6 +229,9 @@ int slmix_session_alloc(struct session **sessp, struct mix *mix, struct session *sess; struct user *user; + if (!sessp) + return EINVAL; + sess = mem_zalloc(sizeof(*sess), destructor); if (!sess) return ENOMEM; @@ -246,6 +249,11 @@ int slmix_session_alloc(struct session **sessp, struct mix *mix, pl_strcpy(user_id, user->id, sizeof(user->id)); sess->auth = true; } + else if (user_id) { + info("session: create new with user %r\n", user_id); + rand_str(sess->id, sizeof(sess->id)); + pl_strcpy(user_id, user->id, sizeof(user->id)); + } else { /* generate a unique session and user id */ info("session: create new\n"); @@ -446,7 +454,7 @@ int slmix_session_user_updated(struct session *sess) if (err) return err; - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); json = mem_deref(json); return 0; diff --git a/src/sip.c b/src/sip.c index 3583791..2a126c3 100644 --- a/src/sip.c +++ b/src/sip.c @@ -1,7 +1,27 @@ -#include #include static struct ua *sip_ua; +static struct list sip_sessl = LIST_INIT; + +struct sip_sess_e { + struct le le; + struct call *call; + struct session *sess; +}; + + +static void destruct_sess_e(void *arg) +{ + struct sip_sess_e *e = arg; + + char *json = NULL; + user_event_json(&json, USER_DELETED, e->sess); + sl_ws_send_event(WS_USERS, e->sess, json); + mem_deref(json); + + list_unlink(&e->le); + mem_deref(e->sess); +} static void ua_event_handler(enum bevent_ev ev, struct bevent *event, @@ -10,6 +30,8 @@ static void ua_event_handler(enum bevent_ev ev, struct bevent *event, struct mix *mix = arg; struct call *call = bevent_get_call(event); const struct sip_msg *msg = bevent_get_msg(event); + struct sip_sess_e *e = NULL; + int err = 0; switch (ev) { @@ -28,8 +50,21 @@ static void ua_event_handler(enum bevent_ev ev, struct bevent *event, pl_set_str(&peer_pl, peer); - slmix_session_alloc(&sess, mix, NULL, NULL, &peer_pl, false, - true); + err = slmix_session_alloc(&sess, mix, NULL, NULL, &peer_pl, + false, true); + if (err) + goto hangup; + + e = mem_zalloc(sizeof(struct sip_sess_e), destruct_sess_e); + if (!e) { + mem_deref(sess); + goto hangup; + } + + e->call = call; + e->sess = sess; + + list_append(&sip_sessl, &e->le, e); pl_strcpy(&peer_pl, sess->user->id, sizeof(sess->user->id)); @@ -48,7 +83,8 @@ static void ua_event_handler(enum bevent_ev ev, struct bevent *event, sess->connected = true; sess->user->video = true; - slmix_disp_enable(mix, peer, true); + sess->user->pidx = slmix_disp_enable(mix, peer, true); + slmix_source_append_all(mix, call, peer); @@ -56,12 +92,33 @@ static void ua_event_handler(enum bevent_ev ev, struct bevent *event, case BEVENT_CALL_CLOSED: { slmix_source_deref(mix, call, NULL); + struct le *le; + struct le *le_tmp; + LIST_FOREACH_SAFE(&sip_sessl, le, le_tmp) + { + e = le->data; + + if (e->call != call) + continue; + + mem_deref(e); + } break; } default: break; } + + return; +hangup: + call_hangup(call, 500, "Server Error"); +} + + +struct ua *slmix_sip_ua(void) +{ + return sip_ua; } diff --git a/src/source.c b/src/source.c index c27fa63..8192c97 100644 --- a/src/source.c +++ b/src/source.c @@ -12,7 +12,7 @@ static int ws_json(struct session *sess, const struct odict *od) if (err) goto out; - sl_ws_send_event_self(sess, buf); + sl_ws_send_event_self(WS_USERS, sess, buf); out: mem_deref(buf); @@ -156,18 +156,29 @@ static void source_dealloc(void *arg) static int32_t source_id_next(struct source_pc *src) { - if (!src || !src->sess) - return -1; + int32_t next_id; + for (next_id = 0; next_id < SL_MAX_TRACKS; next_id++) { + struct sl_track *track = sl_track_by_id(next_id + 1); + if (!track) { + sl_track_add(&track, SL_TRACK_REMOTE_RTC); + track->status = SL_TRACK_REMOTE_CONNECTED; + re_snprintf(track->name, sizeof(track->name), "%s", + src->dev); + info("track/source add %d/%d, RTC\n", track->id, + next_id); - if (!src->sess->source_pcl.tail) - return 0; + goto out; + } - struct source_pc *last = src->sess->source_pcl.tail->data; + if (str_cmp(track->name, src->dev) == 0) + goto out; + } - if (!last) - return -1; + next_id = -1; - return last->id + 1; +out: + sl_track_ws_send(); + return next_id; } diff --git a/src/stats.c b/src/stats.c index 76a23fb..91149ee 100644 --- a/src/stats.c +++ b/src/stats.c @@ -5,6 +5,7 @@ static struct tmr tmr_jitter; static uint64_t jitter_last = 0; static int64_t max_jitter = 0; /* Mainloop jitter */ enum { JITTER_INTERVAL = 10 }; +static struct mbuf *mb_stats; static void jitter_stats(void *arg) @@ -35,17 +36,16 @@ static void slmix_metrics(void *arg) struct jbuf_stat video_jstat; bool types = true; (void)arg; - struct mix *mix = slmix(); - struct mbuf *mb = mbuf_alloc(512); - if (!mb) + if (mbuf_pos(mb_stats) == 0) goto out; - re_snprintf(metric_url, sizeof(metric_url), METRICS_URL "/instance/%s", - mix->room); + struct mix *mix = slmix(); + + re_snprintf(metric_url, sizeof(metric_url), METRICS_URL, mix->room); - mbuf_printf(mb, "# TYPE mix_jitter gauge\n"); - mbuf_printf(mb, "mix_jitter %lld\n", max_jitter); + mbuf_printf(mb_stats, "# TYPE mix_jitter gauge\n"); + mbuf_printf(mb_stats, "mix_jitter %lld\n", max_jitter); max_jitter = 0; LIST_FOREACH(&mix->sessl, le) @@ -60,115 +60,158 @@ static void slmix_metrics(void *arg) sess->user->id); if (types) { - mbuf_printf(mb, "# TYPE mix_rtt gauge\n"); - mbuf_printf(mb, "# TYPE mix_tx_sent counter\n"); - mbuf_printf(mb, "# TYPE mix_tx_lost counter\n"); - mbuf_printf(mb, "# TYPE mix_tx_jit gauge\n"); - mbuf_printf(mb, "# TYPE mix_rx_sent counter\n"); - mbuf_printf(mb, "# TYPE mix_rx_lost counter\n"); - mbuf_printf(mb, "# TYPE mix_rx_jit gauge\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_delay gauge\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_skew gauge\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_late counter\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_late_lost counter\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_lost counter\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_gnacks counter\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_jitter gauge\n"); - mbuf_printf(mb, "# TYPE mix_jbuf_packets gauge\n"); + mbuf_printf(mb_stats, "# TYPE mix_rtt gauge\n"); + mbuf_printf(mb_stats, "# TYPE mix_tx_sent counter\n"); + mbuf_printf(mb_stats, "# TYPE mix_tx_lost counter\n"); + mbuf_printf(mb_stats, "# TYPE mix_tx_jit gauge\n"); + mbuf_printf(mb_stats, "# TYPE mix_rx_sent counter\n"); + mbuf_printf(mb_stats, "# TYPE mix_rx_lost counter\n"); + mbuf_printf(mb_stats, "# TYPE mix_rx_jit gauge\n"); + mbuf_printf(mb_stats, "# TYPE mix_jbuf_delay gauge\n"); + mbuf_printf(mb_stats, "# TYPE mix_jbuf_skew gauge\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_late counter\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_late_lost counter\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_lost counter\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_gnacks counter\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_jitter gauge\n"); + mbuf_printf(mb_stats, + "# TYPE mix_jbuf_packets gauge\n"); types = false; } - audio_stat = stream_rtcp_stats(media_get_stream(sess->maudio)); + struct stream *audio_stream; + struct stream *video_stream; + + if (sess->call) { + audio_stream = audio_strm(call_audio(sess->call)); + video_stream = video_strm(call_video(sess->call)); + } + else { + audio_stream = media_get_stream(sess->maudio); + video_stream = media_get_stream(sess->mvideo); + } + + audio_stat = stream_rtcp_stats(audio_stream); if (audio_stat) { - mbuf_printf(mb, "mix_rtt{%s,kind=\"audio\"} %u\n", - labels, audio_stat->rtt / 1000); - mbuf_printf(mb, "mix_tx_sent{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, + "mix_rtt{%s,kind=\"audio\"} %u\n", labels, + audio_stat->rtt / 1000); + mbuf_printf(mb_stats, + "mix_tx_sent{%s,kind=\"audio\"} %u\n", labels, audio_stat->tx.sent); - mbuf_printf(mb, "mix_tx_lost{%s,kind=\"audio\"} %d\n", + mbuf_printf(mb_stats, + "mix_tx_lost{%s,kind=\"audio\"} %d\n", labels, audio_stat->tx.lost); - mbuf_printf(mb, "mix_tx_jit{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, + "mix_tx_jit{%s,kind=\"audio\"} %u\n", labels, audio_stat->tx.jit); - mbuf_printf(mb, "mix_rx_sent{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, + "mix_rx_sent{%s,kind=\"audio\"} %u\n", labels, audio_stat->rx.sent); - mbuf_printf(mb, "mix_rx_lost{%s,kind =\"audio\"} %d\n", + mbuf_printf(mb_stats, + "mix_rx_lost{%s,kind =\"audio\"} %d\n", labels, audio_stat->rx.lost); - mbuf_printf(mb, "mix_rx_jit{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, + "mix_rx_jit{%s,kind=\"audio\"} %u\n", labels, audio_stat->rx.jit); } - video_stat = stream_rtcp_stats(media_get_stream(sess->mvideo)); + video_stat = stream_rtcp_stats(video_stream); if (video_stat) { - mbuf_printf(mb, "mix_rtt{%s,kind=\"video\"} %u\n", - labels, video_stat->rtt / 1000); - mbuf_printf(mb, "mix_tx_sent{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_rtt{%s,kind=\"video\"} %u\n", labels, + video_stat->rtt / 1000); + mbuf_printf(mb_stats, + "mix_tx_sent{%s,kind=\"video\"} %u\n", labels, video_stat->tx.sent); - mbuf_printf(mb, "mix_tx_lost{%s,kind=\"video\"} %d\n", + mbuf_printf(mb_stats, + "mix_tx_lost{%s,kind=\"video\"} %d\n", labels, video_stat->tx.lost); - mbuf_printf(mb, "mix_tx_jit{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_tx_jit{%s,kind=\"video\"} %u\n", labels, video_stat->tx.jit); - mbuf_printf(mb, "mix_rx_sent{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_rx_sent{%s,kind=\"video\"} %u\n", labels, video_stat->rx.sent); - mbuf_printf(mb, "mix_rx_lost{%s,kind=\"video\"} %d\n", + mbuf_printf(mb_stats, + "mix_rx_lost{%s,kind=\"video\"} %d\n", labels, video_stat->rx.lost); - mbuf_printf(mb, "mix_rx_jit{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_rx_jit{%s,kind=\"video\"} %u\n", labels, video_stat->rx.jit); } - err = stream_jbuf_stats(media_get_stream(sess->maudio), - &audio_jstat); - err |= stream_jbuf_stats(media_get_stream(sess->mvideo), - &video_jstat); + err = stream_jbuf_stats(audio_stream, &audio_jstat); + err |= stream_jbuf_stats(video_stream, &video_jstat); if (err) continue; - mbuf_printf(mb, "mix_jbuf_delay{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, "mix_jbuf_delay{%s,kind=\"audio\"} %u\n", labels, audio_jstat.c_delay); - mbuf_printf(mb, "mix_jbuf_skew{%s,kind=\"audio\"} %d\n", + mbuf_printf(mb_stats, "mix_jbuf_skew{%s,kind=\"audio\"} %d\n", labels, audio_jstat.c_skew); - mbuf_printf(mb, "mix_jbuf_late{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, "mix_jbuf_late{%s,kind=\"audio\"} %u\n", labels, audio_jstat.n_late); - mbuf_printf(mb, "mix_jbuf_late_lost{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, + "mix_jbuf_late_lost{%s,kind=\"audio\"} %u\n", labels, audio_jstat.n_late_lost); - mbuf_printf(mb, "mix_jbuf_lost{%s,kind=\"audio\"} %u\n", + mbuf_printf(mb_stats, "mix_jbuf_lost{%s,kind=\"audio\"} %u\n", labels, audio_jstat.n_lost); - mbuf_printf(mb, "mix_jbuf_jitter{%s,kind=\"audio\"} %u\n", - labels, audio_jstat.c_jitter); - mbuf_printf(mb, "mix_jbuf_packets{%s,kind=\"audio\"} %u\n", - labels, audio_jstat.c_packets); - mbuf_printf(mb, "mix_jbuf_gnacks{%s,kind=\"audio\"} %u\n", - labels, audio_jstat.n_gnacks); - - mbuf_printf(mb, "mix_jbuf_delay{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_jbuf_jitter{%s,kind=\"audio\"} %u\n", labels, + audio_jstat.c_jitter); + mbuf_printf(mb_stats, + "mix_jbuf_packets{%s,kind=\"audio\"} %u\n", labels, + audio_jstat.c_packets); + mbuf_printf(mb_stats, + "mix_jbuf_gnacks{%s,kind=\"audio\"} %u\n", labels, + audio_jstat.n_gnacks); + + mbuf_printf(mb_stats, "mix_jbuf_delay{%s,kind=\"video\"} %u\n", labels, video_jstat.c_delay); - mbuf_printf(mb, "mix_jbuf_skew{%s,kind=\"video\"} %d\n", + mbuf_printf(mb_stats, "mix_jbuf_skew{%s,kind=\"video\"} %d\n", labels, video_jstat.c_skew); - mbuf_printf(mb, "mix_jbuf_late{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, "mix_jbuf_late{%s,kind=\"video\"} %u\n", labels, video_jstat.n_late); - mbuf_printf(mb, "mix_jbuf_late_lost{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, + "mix_jbuf_late_lost{%s,kind=\"video\"} %u\n", labels, video_jstat.n_late_lost); - mbuf_printf(mb, "mix_jbuf_lost{%s,kind=\"video\"} %u\n", + mbuf_printf(mb_stats, "mix_jbuf_lost{%s,kind=\"video\"} %u\n", labels, video_jstat.n_lost); - mbuf_printf(mb, "mix_jbuf_jitter{%s,kind=\"video\"} %u\n", - labels, video_jstat.c_jitter); - mbuf_printf(mb, "mix_jbuf_packets{%s,kind=\"video\"} %u\n", - labels, video_jstat.c_packets); - mbuf_printf(mb, "mix_jbuf_gnacks{%s,kind=\"video\"} %u\n", - labels, video_jstat.n_gnacks); + mbuf_printf(mb_stats, + "mix_jbuf_jitter{%s,kind=\"video\"} %u\n", labels, + video_jstat.c_jitter); + mbuf_printf(mb_stats, + "mix_jbuf_packets{%s,kind=\"video\"} %u\n", labels, + video_jstat.c_packets); + mbuf_printf(mb_stats, + "mix_jbuf_gnacks{%s,kind=\"video\"} %u\n", labels, + video_jstat.n_gnacks); } - if (mbuf_pos(mb) == 0) - goto out; err = sl_httpc_alloc(&http_conn, NULL, NULL, NULL); if (err) goto out; - sl_httpc_req(http_conn, SL_HTTP_POST, metric_url, mb); + sl_httpc_req(http_conn, SL_HTTP_POST, metric_url, mb_stats); mem_deref(http_conn); + mbuf_rewind(mb_stats); + out: - mem_deref(mb); - tmr_start(&tmr_metrics, 2000, slmix_metrics, NULL); + tmr_start(&tmr_metrics, 6000, slmix_metrics, NULL); +} + + +int sl_mix_stats_append(struct mbuf *mb) +{ + return mbuf_write_mem(mb_stats, mbuf_buf(mb), mbuf_get_left(mb)); } @@ -179,6 +222,10 @@ int slmix_stats_init(void) tmr_start(&tmr_metrics, 2000, slmix_metrics, NULL); tmr_start(&tmr_jitter, JITTER_INTERVAL, jitter_stats, NULL); + mb_stats = mbuf_alloc(512); + if (!mb_stats) + return ENOMEM; + return 0; } @@ -187,4 +234,5 @@ void slmix_stats_close(void) { tmr_cancel(&tmr_metrics); tmr_cancel(&tmr_jitter); + mb_stats = mem_deref(mb_stats); } diff --git a/src/tracks.c b/src/tracks.c new file mode 100644 index 0000000..0cf603d --- /dev/null +++ b/src/tracks.c @@ -0,0 +1,528 @@ +#include +#include +#include + + +static struct list tracks = LIST_INIT; + +/* TODO: refactor allow multiple local tracks */ +static struct sl_track *local_track = NULL; + + +const struct list *sl_tracks(void) +{ + return &tracks; +} + + +int sl_tracks_json(struct re_printf *pf, void *arg) +{ + struct le *le; + struct odict *o_tracks; + struct odict *o_track; + char id[ITOA_BUFSZ]; + int err; + (void)arg; + + if (!pf) + return EINVAL; + + err = odict_alloc(&o_tracks, 32); + if (err) + return ENOMEM; + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + if (!track) + continue; + + err = odict_alloc(&o_track, 32); + if (err) + return ENOMEM; + + if (track->type == SL_TRACK_REMOTE || + track->type == SL_TRACK_REMOTE_RTC) { + odict_entry_add(o_track, "type", ODICT_STRING, + "remote"); + } + + odict_entry_add(o_track, "name", ODICT_STRING, track->name); + odict_entry_add(o_track, "status", ODICT_INT, track->status); + odict_entry_add(o_track, "error", ODICT_STRING, track->error); + odict_entry_add(o_track, "muted", ODICT_BOOL, track->muted); + odict_entry_add(o_track, "focus", ODICT_BOOL, track->focus); + odict_entry_add(o_tracks, str_itoa(track->id, id, 10), + ODICT_OBJECT, o_track); + o_track = mem_deref(o_track); + } + + err = json_encode_odict(pf, o_tracks); + mem_deref(o_tracks); + + return err; +} + + +static bool sort_handler(struct le *le1, struct le *le2, void *arg) +{ + struct sl_track *track1 = le1->data; + struct sl_track *track2 = le2->data; + (void)arg; + + /* NOTE: important to use less than OR equal to, otherwise + the list_sort function may be stuck in a loop */ + return track1->id <= track2->id; +} + + +int sl_track_next_id(void) +{ + int id = 1; + struct le *le; + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + if (track->id == id) { + ++id; + continue; + } + break; + } + + return id; +} + + +static void track_destructor(void *data) +{ + struct sl_track *track = data; + + list_unlink(&track->le); + + if (track->type == SL_TRACK_LOCAL) + mem_deref(track->u.local.slaudio); + + if (track->type == SL_TRACK_REMOTE) { + if (track->u.remote.call) + ua_hangup(slmix_sip_ua(), track->u.remote.call, 0, + NULL); + + track->u.remote.sess = mem_deref(track->u.remote.sess); + } +} + + +int sl_track_add(struct sl_track **trackp, enum sl_track_type type) +{ + struct sl_track *track; + + if (!trackp) + return EINVAL; + + if (list_count(&tracks) >= SL_MAX_TRACKS) { + warning("sl_track_add: max. %d tracks reached\n", + SL_MAX_TRACKS); + return E2BIG; + } + + track = mem_zalloc(sizeof(struct sl_track), track_destructor); + if (!track) + return ENOMEM; + + track->id = sl_track_next_id(); + track->type = type; + track->status = SL_TRACK_IDLE; + track->muted = true; + + list_append(&tracks, &track->le, track); + list_sort(&tracks, sort_handler, NULL); + + *trackp = track; + + return 0; +} + + +void sl_track_focus(int id) +{ + struct le *le; + struct mix *mix = slmix(); + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + + track->focus = false; + slmix_disp_enable(mix, track->name, false); + + if (track->id == id) { + slmix_disp_enable(mix, track->name, true); + track->focus = true; + } + } + + sl_track_ws_send(); +} + + +int sl_track_del(int id) +{ + struct le *le; + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + if (track->id == id) { + mem_deref(track); + return 0; + } + } + return ENOENT; +} + + +struct sl_track *sl_track_by_id(int id) +{ + struct le *le; + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + if (track->id == id) { + return track; + } + } + return NULL; +} + + +enum sl_track_status sl_track_status(int id) +{ + struct le *le; + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + if (track->id == id) + return track->status; + } + + return SL_TRACK_INVALID; +} + + +struct slaudio *sl_track_audio(struct sl_track *track) +{ + if (!track || track->type != SL_TRACK_LOCAL) + return NULL; + + return track->u.local.slaudio; +} + + +int sl_track_dial(struct sl_track *track, struct pl *peer) +{ + int err; + char *peerc = NULL; + + if (!track) + return EINVAL; + + track->type = SL_TRACK_REMOTE; + track->error[0] = '\0'; + + const struct contacts *cs = baresip_contacts(); + + for (struct le *le = list_head(contact_list(cs)); le; le = le->next) { + struct contact *c = le->data; + struct sip_addr *a = contact_addr(c); + if (!a) + continue; + + if (pl_casecmp(peer, &a->dname) == 0) { + str_dup(&peerc, contact_str(c)); + break; + } + } + + if (!peerc) { + err = account_uri_complete_strdup(ua_account(slmix_sip_ua()), + &peerc, peer); + if (err) + goto out; + } + + err = ua_connect(slmix_sip_ua(), &track->u.remote.call, NULL, peerc, + VIDMODE_ON); + if (err) + goto out; + + track->status = SL_TRACK_REMOTE_CALLING; + pl_strcpy(peer, track->name, sizeof(track->name)); + +out: + if (err) + re_snprintf(track->error, sizeof(track->error), "%m", err); + + if (err == EINVAL) + str_ncpy(track->error, "Invalid ID", sizeof(track->error)); + + + sl_track_ws_send(); + + mem_deref(peerc); + + return err; +} + + +void sl_track_accept(struct sl_track *track) +{ + if (!track || track->type != SL_TRACK_REMOTE) + return; + + ua_answer(call_get_ua(track->u.remote.call), track->u.remote.call, + VIDMODE_OFF); +} + + +void sl_track_hangup(struct sl_track *track) +{ + if (!track) + return; + + warning("hangup %d\n", track->type); + + if (track->type == SL_TRACK_REMOTE) { + track->u.remote.sess = mem_deref(track->u.remote.sess); + ua_hangup(call_get_ua(track->u.remote.call), + track->u.remote.call, 0, ""); + } + + if (track->type == SL_TRACK_REMOTE_RTC) { + track->status = SL_TRACK_IDLE; + struct mix *mix = slmix(); + struct pl user_id = PL_INIT; + + pl_set_str(&user_id, track->name); + struct session *sess = + slmix_session_lookup_user_id(&mix->sessl, &user_id); + if (!sess) + goto out; + + slmix_session_speaker(sess, false); + } + +out: + track->name[0] = '\0'; + track->u.remote.call = NULL; +} + + +void sl_track_toggle_mute(int id) +{ + struct sl_track *track = sl_track_by_id(id); + if (!track) + return; + + track->muted = !track->muted; + + amix_mute(track->name, track->muted, 0); + + sl_track_ws_send(); +} + + +void sl_track_ws_send(void) +{ + char *json_str; + re_sdprintf(&json_str, "%H", sl_tracks_json, NULL); + sl_ws_send_event_host(WS_TRACKS, json_str); + mem_deref(json_str); +} + + +static void call_incoming(struct call *call) +{ + struct le *le; + struct sl_track *track; + + if (!call) + return; + + LIST_FOREACH(&tracks, le) + { + track = le->data; + + if (track->type != SL_TRACK_REMOTE) + continue; + + if (track->u.remote.call) + continue; + + goto out; + } + + /* Add new track if no empty remote track is found */ + sl_track_add(&track, SL_TRACK_REMOTE); + +out: + track->u.remote.call = call; + track->status = SL_TRACK_REMOTE_INCOMING; + str_ncpy(track->name, call_peeruri(call), sizeof(track->name)); + char buf[ITOA_BUFSZ]; + char *id = str_itoa(track->id, buf, 10); + audio_set_devicename(call_audio(call), id, id); +} + + +static void eventh(enum bevent_ev ev, struct bevent *event, void *arg) +{ + struct le *le; + bool changed = false; + struct call *call = bevent_get_call(event); + const char *prm = bevent_get_text(event); + const struct sip_msg *msg = bevent_get_msg(event); + (void)arg; + + if (ev == BEVENT_SIPSESS_CONN) { + struct ua *ua = uag_find_msg(msg); + ua_accept(ua, msg); + bevent_stop(event); + return; + } + + if (ev == BEVENT_CALL_INCOMING) { + call_incoming(call); + sl_track_ws_send(); + return; + } + + if (ev == BEVENT_REGISTERING) { + if (local_track) { + str_ncpy(local_track->name, + account_aor(ua_account(slmix_sip_ua())), + sizeof(local_track->name)); + local_track->status = SL_TRACK_LOCAL_REGISTERING; + sl_track_ws_send(); + } + return; + } + + if (ev == BEVENT_REGISTER_OK) { + if (local_track) { + local_track->status = SL_TRACK_LOCAL_REGISTER_OK; + sl_track_ws_send(); + } + return; + } + + if (ev == BEVENT_REGISTER_FAIL) { + if (local_track) { + local_track->status = SL_TRACK_LOCAL_REGISTER_FAIL; + sl_track_ws_send(); + } + return; + } + + if (ev == BEVENT_SHUTDOWN) { + list_flush(&tracks); + return; + } + + struct mix *mix = slmix(); + + LIST_FOREACH(&tracks, le) + { + struct sl_track *track = le->data; + const char *peername = NULL; + struct pl peername_pl = PL_INIT; + + if (track->type != SL_TRACK_REMOTE) + continue; + + if (track->u.remote.call != call) + continue; + + switch (ev) { + case BEVENT_CALL_RINGING: + track->status = SL_TRACK_REMOTE_CALLING; + changed = true; + peername = call_peername(call); + pl_set_str(&peername_pl, peername); + + audio_set_devicename(call_audio(call), peername, + peername); + video_set_devicename(call_video(call), peername, + peername); + break; + + case BEVENT_CALL_ESTABLISHED: + track->status = SL_TRACK_REMOTE_CONNECTED; + changed = true; + peername = call_peername(call); + pl_set_str(&peername_pl, peername); + + int err = slmix_session_alloc( + &track->u.remote.sess, mix, NULL, &peername_pl, + &peername_pl, false, true); + if (err) + break; + + slmix_source_append_all(mix, call, peername); + + amix_mute(peername, track->muted, + ++mix->next_speaker_id); + track->u.remote.sess->call = call; + track->u.remote.sess->connected = true; + track->u.remote.sess->user->video = true; + + track->u.remote.sess->user->pidx = + slmix_disp_enable(mix, peername, true); + slmix_disp_enable(mix, peername, true); + + warning("track: call with '%s' established\n", + peername); + break; + + case BEVENT_CALL_CLOSED: + track->u.remote.sess = mem_deref(track->u.remote.sess); + track->status = SL_TRACK_IDLE; + track->u.remote.call = NULL; + track->name[0] = '\0'; + if (call_scode(call) != 200) + str_ncpy(track->error, prm, + sizeof(track->error)); + changed = true; + break; + + default: + } + } + + if (!changed) + return; + + sl_track_ws_send(); +} + + +int sl_tracks_init(void) +{ + bevent_register(eventh, NULL); + + return 0; +} + + +int sl_tracks_close(void) +{ + bevent_unregister(eventh); + list_flush(&tracks); + + local_track = NULL; + + return 0; +} diff --git a/src/ws.c b/src/ws.c index 687f036..1888b96 100644 --- a/src/ws.c +++ b/src/ws.c @@ -9,6 +9,7 @@ struct ws_conn { struct websock_conn *c; struct mix *mix; struct session *sess; + enum ws_type type; }; enum { KEEPALIVE = 30 * 1000 }; @@ -57,7 +58,7 @@ static void conn_destroy(void *arg) wsc->sess->user->video = false; if (0 == user_event_json(&json, USER_DELETED, wsc->sess)) { - sl_ws_send_event(wsc->sess, json); + sl_ws_send_event(WS_USERS, wsc->sess, json); json = mem_deref(json); } @@ -99,8 +100,9 @@ static void ws_recv_h(const struct websock_hdr *hdr, struct mbuf *mb, } -int sl_ws_open(struct http_conn *httpc, const struct http_msg *msg, - struct mix *mix, struct session *sess) +int sl_ws_open(enum ws_type type, struct http_conn *httpc, + const struct http_msg *msg, struct mix *mix, + struct session *sess) { struct ws_conn *ws_conn; int err; @@ -119,9 +121,16 @@ int sl_ws_open(struct http_conn *httpc, const struct http_msg *msg, ws_conn->sess = mem_ref(sess); mem_ref(ws_conn->sess->user); ws_conn->sess->connected = true; + ws_conn->type = type; list_append(&wsl, &ws_conn->le, ws_conn); + if (type == WS_TRACKS) + sl_track_ws_send(); + + if (type != WS_USERS) + goto out; + char *json = NULL; if (0 == users_json(&json, ws_conn->mix)) { websock_send(ws_conn->c, WEBSOCK_TEXT, "%s", json); @@ -133,7 +142,7 @@ int sl_ws_open(struct http_conn *httpc, const struct http_msg *msg, slmix_refresh_rooms(&force); if (0 == user_event_json(&json, USER_ADDED, ws_conn->sess)) { - sl_ws_send_event(ws_conn->sess, json); + sl_ws_send_event(WS_USERS, ws_conn->sess, json); json = mem_deref(json); } @@ -145,7 +154,7 @@ int sl_ws_open(struct http_conn *httpc, const struct http_msg *msg, } -void sl_ws_send_event(struct session *sess, char *json) +void sl_ws_send_event(enum ws_type type, struct session *sess, char *json) { struct le *le; @@ -157,12 +166,14 @@ void sl_ws_send_event(struct session *sess, char *json) struct ws_conn *ws_conn = le->data; if (ws_conn->sess == sess) continue; + if (ws_conn->type != type) + continue; websock_send(ws_conn->c, WEBSOCK_TEXT, "%s", json); } } -void sl_ws_send_event_self(struct session *sess, char *json) +void sl_ws_send_event_self(enum ws_type type, struct session *sess, char *json) { struct le *le; @@ -174,12 +185,14 @@ void sl_ws_send_event_self(struct session *sess, char *json) struct ws_conn *ws_conn = le->data; if (ws_conn->sess != sess) continue; + if (ws_conn->type != type) + continue; websock_send(ws_conn->c, WEBSOCK_TEXT, "%s", json); } } -void sl_ws_send_event_host(char *json) +void sl_ws_send_event_host(enum ws_type type, char *json) { struct le *le; @@ -191,12 +204,14 @@ void sl_ws_send_event_host(char *json) struct ws_conn *ws_conn = le->data; if (!ws_conn->sess->user->host) continue; + if (ws_conn->type != type) + continue; websock_send(ws_conn->c, WEBSOCK_TEXT, "%s", json); } } -void sl_ws_send_event_all(char *json) +void sl_ws_send_event_all(enum ws_type type, char *json) { struct le *le; @@ -206,6 +221,8 @@ void sl_ws_send_event_all(char *json) LIST_FOREACH(&wsl, le) { struct ws_conn *ws_conn = le->data; + if (ws_conn->type != type) + continue; websock_send(ws_conn->c, WEBSOCK_TEXT, "%s", json); } } @@ -240,7 +257,7 @@ static void ws_send_rtcp_stats(struct mix *mix) "}}", sess->user->speaker_id, audio_stat->rtt / 1000, video_stat->rtt / 1000); - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); } } @@ -262,7 +279,7 @@ static void update_handler(void *arg) re_snprintf(json, sizeof(json), "{\"type\": \"rec\", \"t\": %lu, \"s\": %u}", secs, mix->talk_detect_h()); - sl_ws_send_event_all(json); + sl_ws_send_event_all(WS_USERS, json); ws_send_rtcp_stats(mix); diff --git a/webui/index.html b/webui/index.html index 0188ddc..560d1ca 100644 --- a/webui/index.html +++ b/webui/index.html @@ -1,5 +1,5 @@ - + diff --git a/webui/package-lock.json b/webui/package-lock.json index da086bf..3f85956 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -17,6 +17,7 @@ "@vueuse/core": "^14.2.1", "cropperjs": "2.1.0", "markdown-it": "^14.0.0", + "typeface-roboto-mono": "^1.1.13", "vue": "^3.2.41", "vue-router": "^5.0.3", "vue3-avataaars": "^1.0.12", @@ -4176,6 +4177,12 @@ "node": ">= 0.8.0" } }, + "node_modules/typeface-roboto-mono": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/typeface-roboto-mono/-/typeface-roboto-mono-1.1.13.tgz", + "integrity": "sha512-pnzDc70b7ywJHin/BUFL7HZX8DyOTBLT2qxlJ92eH1UJOFcENIBXa9IZrxsJX/gEKjbEDKhW5vz/TKRBNk/ufQ==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", diff --git a/webui/package.json b/webui/package.json index 804a618..36980d0 100644 --- a/webui/package.json +++ b/webui/package.json @@ -20,6 +20,7 @@ "@vueuse/core": "^14.2.1", "cropperjs": "2.1.0", "markdown-it": "^14.0.0", + "typeface-roboto-mono": "^1.1.13", "vue": "^3.2.41", "vue-router": "^5.0.3", "vue3-avataaars": "^1.0.12", @@ -33,11 +34,11 @@ "@vitejs/plugin-vue": "^6.0.2", "@vue/eslint-config-prettier": "^10.1.0", "@vue/eslint-config-typescript": "^14.1.4", + "@vue/tsconfig": "^0.8.1", "eslint-plugin-vue": "^10.8.0", "tailwindcss": "^4.0.8", "typescript": "~5.9.3", "vite": "^7.3.1", - "vue-tsc": "^3.1.5", - "@vue/tsconfig": "^0.8.1" + "vue-tsc": "^3.1.5" } } diff --git a/webui/src/api.ts b/webui/src/api.ts index 02ad0b9..01a3284 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -2,6 +2,7 @@ import config from './config' import { Webrtc } from './webrtc' import router from './router' import { State, RecordType } from './ws/state' +import { Tracks } from './ws/tracks' import { Error } from './error' @@ -96,6 +97,7 @@ export default { async websocket() { State.websocket() + Tracks.websocket() }, async call() { @@ -167,7 +169,7 @@ export default { const blob = await compressedResponse.blob(); - await api_fetch('POST', '/webrtc/stats', blob, false, true) + await api_fetch('POST', '/webrtc/stats', data, false, true) }, async record_switch(type: RecordType) { @@ -204,5 +206,33 @@ export default { if (!resp?.ok) return return JSON.parse(await resp?.text()!) + }, + + async track_add(type: string) { + api_fetch('POST', '/tracks/' + type, null); + }, + + async track_togglemute(track: number) { + api_fetch('POST', '/tracks/togglemute', String(track), false) + }, + + async track_del(track: number) { + api_fetch('DELETE', '/tracks', String(track), false) + }, + + async track_focus(track: number) { + api_fetch('POST', '/tracks/focus', String(track), false) + }, + + async track_accept(track: number) { + api_fetch('POST', '/tracks/accept?track=' + String(track), null) + }, + + async track_hangup(track: number) { + api_fetch('POST', '/tracks/hangup?track=' + String(track), null); + }, + + async track_dial(track: number, peer: string) { + api_fetch('POST', '/tracks/dial?track=' + String(track), peer, false); } } diff --git a/webui/src/components/BottomActions.vue b/webui/src/components/BottomActions.vue index 521aab7..0d04fe0 100644 --- a/webui/src/components/BottomActions.vue +++ b/webui/src/components/BottomActions.vue @@ -1,5 +1,6 @@