diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c index a847fb4ea2..863e4c727b 100644 --- a/src/mod/applications/mod_conference/mod_conference.c +++ b/src/mod/applications/mod_conference/mod_conference.c @@ -100,6 +100,7 @@ static struct { uint32_t id_pool; int32_t running; uint32_t threads; + switch_event_channel_id_t event_channel_id; } globals; /* forward declaration for conference_obj and caller_control */ @@ -115,6 +116,7 @@ typedef struct conference_cdr_node_s { uint32_t flags; uint32_t id; conference_member_t *member; + switch_event_t *var_event; struct conference_cdr_node_s *next; } conference_cdr_node_t; @@ -204,7 +206,9 @@ typedef enum { CFLAG_ENDCONF_FORCED = (1 << 16), CFLAG_RFC4579 = (1 << 17), CFLAG_FLOOR_CHANGE = (1 << 18), - CFLAG_VID_FLOOR_LOCK = (1 << 19) + CFLAG_VID_FLOOR_LOCK = (1 << 19), + CFLAG_JSON_EVENTS = (1 << 20), + CFLAG_LIVEARRAY_SYNC = (1 << 21) } conf_flag_t; typedef enum { @@ -300,6 +304,8 @@ typedef struct conference_record { /* Conference Object */ typedef struct conference_obj { char *name; + char *la_name; + char *la_event_channel; char *desc; char *timer_name; char *tts_engine; @@ -338,6 +344,7 @@ typedef struct conference_obj { char *domain; char *caller_controls; char *moderator_controls; + switch_live_array_t *la; uint32_t flags; member_flag_t mflags; switch_call_cause_t bridge_hangup_cause; @@ -466,6 +473,8 @@ struct conference_member { char *kicked_sound; switch_queue_t *dtmf_queue; switch_thread_t *input_thread; + cJSON *json; + cJSON *status_field; }; typedef enum { @@ -564,6 +573,9 @@ static switch_status_t conf_api_sub_clear_vid_floor(conference_obj_t *conference static void conference_cdr_del(conference_member_t *member) { + if (member->channel) { + switch_channel_get_variables(member->channel, &member->cdr_node->var_event); + } member->cdr_node->leave_time = switch_epoch_time_now(NULL); member->cdr_node->flags = member->flags; member->cdr_node->member = NULL; @@ -1086,6 +1098,218 @@ static void conference_cdr_render(conference_obj_t *conference) switch_xml_free(cdr); } +static cJSON *conference_json_render(conference_obj_t *conference, cJSON *req) +{ + char tmp[30]; + const char *domain; const char *name; + char *dup_domain = NULL; + char *uri; + conference_cdr_node_t *np; + char *tmpp = tmp; + cJSON *json = cJSON_CreateObject(), *jusers = NULL, *jold_users = NULL, *juser = NULL, *jvars = NULL; + + switch_assert(json); + + switch_mutex_lock(conference->mutex); + switch_snprintf(tmp, sizeof(tmp), "%u", conference->doc_version); + conference->doc_version++; + switch_mutex_unlock(conference->mutex); + + if (!(name = conference->name)) { + name = "conference"; + } + + if (!(domain = conference->domain)) { + dup_domain = switch_core_get_domain(SWITCH_TRUE); + if (!(domain = dup_domain)) { + domain = "cluecon.com"; + } + } + + + uri = switch_mprintf("%s@%s", name, domain); + json_add_child_string(json, "entity", uri); + json_add_child_string(json, "conferenceDescription", conference->desc ? conference->desc : "FreeSWITCH Conference"); + json_add_child_string(json, "conferenceState", "active"); + switch_snprintf(tmp, sizeof(tmp), "%u", conference->count); + json_add_child_string(json, "userCount", tmp); + + jusers = json_add_child_array(json, "users"); + jold_users = json_add_child_array(json, "oldUsers"); + + switch_mutex_lock(conference->member_mutex); + + for (np = conference->cdr_nodes; np; np = np->next) { + char *user_uri = NULL; + switch_channel_t *channel = NULL; + switch_time_exp_t tm; + switch_size_t retsize; + const char *fmt = "%Y-%m-%dT%H:%M:%S%z"; + char *p; + + if (np->record_path || !np->cp) { + continue; + } + + //if (!np->cp || (np->member && !np->member->session) || np->leave_time) { /* for now we'll remove participants when they leave */ + //continue; + //} + + if (np->member && np->member->session) { + channel = switch_core_session_get_channel(np->member->session); + } + + juser = cJSON_CreateObject(); + + if (channel) { + const char *uri = switch_channel_get_variable_dup(channel, "conference_invite_uri", SWITCH_FALSE, -1); + + if (uri) { + user_uri = strdup(uri); + } + } + + if (np->cp) { + + if (!user_uri) { + user_uri = switch_mprintf("%s@%s", np->cp->caller_id_number, domain); + } + + json_add_child_string(juser, "entity", user_uri); + json_add_child_string(juser, "displayText", np->cp->caller_id_name); + } + + //if (np->record_path) { + //json_add_child_string(juser, "recordingPATH", np->record_path); + //} + + json_add_child_string(juser, "status", np->leave_time ? "disconnected" : "connected"); + + switch_time_exp_lt(&tm, (switch_time_t) conference->start_time * 1000000); + switch_strftime_nocheck(tmp, &retsize, sizeof(tmp), fmt, &tm); + p = end_of_p(tmpp) -1; + snprintf(p, 4, ":00"); + + json_add_child_string(juser, "joinTime", tmpp); + + snprintf(tmp, sizeof(tmp), "%u", np->id); + json_add_child_string(juser, "memberId", tmp); + + jvars = cJSON_CreateObject(); + + if (!np->member && np->var_event) { + switch_json_add_presence_data_cols(np->var_event, jvars, "PD-"); + } else if (np->member) { + const char *var; + const char *prefix = NULL; + switch_event_t *var_event = NULL; + switch_event_header_t *hp; + int all = 0; + + switch_channel_get_variables(channel, &var_event); + + if ((prefix = switch_event_get_header(var_event, "json_conf_var_prefix"))) { + all = strcasecmp(prefix, "__all__"); + } else { + prefix = "json_"; + } + + for(hp = var_event->headers; hp; hp = hp->next) { + if (all || !strncasecmp(hp->name, prefix, strlen(prefix))) { + json_add_child_string(jvars, hp->name, hp->value); + } + } + + switch_json_add_presence_data_cols(var_event, jvars, "PD-"); + + switch_event_destroy(&var_event); + + if ((var = switch_channel_get_variable(channel, "rtp_use_ssrc"))) { + json_add_child_string(juser, "rtpAudioSSRC", var); + } + + json_add_child_string(juser, "rtpAudioDirection", switch_channel_test_flag(channel, CF_HOLD) ? "sendonly" : "sendrecv"); + + + if (switch_channel_test_flag(channel, CF_VIDEO)) { + if ((var = switch_channel_get_variable(channel, "rtp_use_video_ssrc"))) { + json_add_child_string(juser, "rtpVideoSSRC", var); + } + + json_add_child_string(juser, "rtpVideoDirection", switch_channel_test_flag(channel, CF_HOLD) ? "sendonly" : "sendrecv"); + } + } + + if (jvars) { + json_add_child_obj(juser, "variables", jvars); + } + + cJSON_AddItemToArray(np->leave_time ? jold_users : jusers, juser); + + switch_safe_free(user_uri); + } + + switch_mutex_unlock(conference->member_mutex); + + switch_safe_free(dup_domain); + switch_safe_free(uri); + + return json; +} + +static void conference_la_event_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id) +{ + switch_live_array_parse_json(json, globals.event_channel_id); +} + +static void conference_event_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id) +{ + char *domain = NULL, *name = NULL; + conference_obj_t *conference = NULL; + cJSON *data, *reply = NULL, *conf_desc = NULL; + const char *action = NULL; + + if ((data = cJSON_GetObjectItem(json, "data"))) { + action = cJSON_GetObjectCstr(data, "action"); + } + + if (!action) action = ""; + + reply = cJSON_Duplicate(json, 1); + cJSON_DeleteItemFromObject(reply, "data"); + + if ((name = strchr(event_channel, '.'))) { + char *tmp = strdup(name + 1); + switch_assert(tmp); + name = tmp; + + if ((domain = strchr(name, '@'))) { + *domain++ = '\0'; + } + } + + if (!strcasecmp(action, "bootstrap")) { + if (!zstr(name) && (conference = conference_find(name, domain))) { + conf_desc = conference_json_render(conference, json); + } else { + conf_desc = cJSON_CreateObject(); + json_add_child_string(conf_desc, "conferenceDescription", "FreeSWITCH Conference"); + json_add_child_string(conf_desc, "conferenceState", "inactive"); + json_add_child_array(conf_desc, "users"); + json_add_child_array(conf_desc, "oldUsers"); + } + } else { + conf_desc = cJSON_CreateObject(); + json_add_child_string(conf_desc, "error", "Invalid action"); + } + + json_add_child_string(conf_desc, "action", "conferenceDescription"); + + cJSON_AddItemToObject(reply, "data", conf_desc); + + switch_event_channel_broadcast(event_channel, &reply, modname, globals.event_channel_id); + +} static switch_status_t conference_add_event_data(conference_obj_t *conference, switch_event_t *event) @@ -1340,6 +1564,42 @@ static switch_status_t member_del_relationship(conference_member_t *member, uint return status; } +static void send_json_event(conference_obj_t *conference) +{ + cJSON *event, *conf_desc = NULL; + char *name = NULL, *domain = NULL, *dup_domain = NULL; + char *event_channel = NULL; + + if (!switch_test_flag(conference, CFLAG_JSON_EVENTS)) { + return; + } + + conf_desc = conference_json_render(conference, NULL); + + if (!(name = conference->name)) { + name = "conference"; + } + + if (!(domain = conference->domain)) { + dup_domain = switch_core_get_domain(SWITCH_TRUE); + if (!(domain = dup_domain)) { + domain = "cluecon.com"; + } + } + + event_channel = switch_mprintf("conference.%q@%q", name, domain); + + event = cJSON_CreateObject(); + + json_add_child_string(event, "eventChannel", event_channel); + cJSON_AddItemToObject(event, "data", conf_desc); + + switch_event_channel_broadcast(event_channel, &event, modname, globals.event_channel_id); + + switch_safe_free(dup_domain); + switch_safe_free(event_channel); +} + static void send_rfc_event(conference_obj_t *conference) { switch_event_t *event; @@ -1422,6 +1682,73 @@ static void send_conference_notify(conference_obj_t *conference, const char *sta } +static void member_update_status_field(conference_member_t *member) +{ + char *str, *vstr = "", display[128] = ""; + + if (!member->conference->la) { + return; + } + + switch_live_array_lock(member->conference->la); + + if (!switch_test_flag(member, MFLAG_CAN_SPEAK)) { + str = "MUTE"; + } else if (switch_channel_test_flag(member->channel, CF_HOLD)) { + str = "HOLD"; + } else if (member == member->conference->floor_holder) { + if (switch_test_flag(member, MFLAG_TALKING)) { + str = "TALKING (FLOOR)"; + } else { + str = "FLOOR"; + } + } else if (switch_test_flag(member, MFLAG_TALKING)) { + str = "TALKING"; + } else { + str = "ACTIVE"; + } + + if (switch_channel_test_flag(member->channel, CF_VIDEO)) { + vstr = " VIDEO"; + if (member == member->conference->video_floor_holder) { + vstr = " VIDEO (FLOOR)"; + } + } + + switch_snprintf(display, sizeof(display), "%s%s", str, vstr); + + + free(member->status_field->valuestring); + member->status_field->valuestring = strdup(display); + + switch_live_array_add(member->conference->la, switch_core_session_get_uuid(member->session), -1, &member->json, SWITCH_FALSE); + switch_live_array_unlock(member->conference->la); +} + +static void adv_la(conference_obj_t *conference, conference_member_t *member, switch_bool_t join) +{ + if (conference && conference->la && member->session) { + cJSON *msg, *data; + const char *uuid = switch_core_session_get_uuid(member->session); + const char *cookie = switch_channel_get_variable(member->channel, "event_channel_cookie"); + + msg = cJSON_CreateObject(); + data = json_add_child_obj(msg, "pvtData", NULL); + + cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(uuid)); + cJSON_AddItemToObject(msg, "eventType", cJSON_CreateString("channelPvtData")); + + cJSON_AddItemToObject(data, "action", cJSON_CreateString(join ? "conference-liveArray-join" : "conference-liveArray-part")); + cJSON_AddItemToObject(data, "laChannel", cJSON_CreateString(conference->la_event_channel)); + cJSON_AddItemToObject(data, "laName", cJSON_CreateString(conference->la_name)); + + if (cookie) { + switch_event_channel_permission_modify(cookie, conference->la_event_channel, join); + } + + switch_event_channel_broadcast(uuid, &msg, modname, globals.event_channel_id); + } +} /* Gain exclusive access and add the member to the list */ static switch_status_t conference_add_member(conference_obj_t *conference, conference_member_t *member) @@ -1585,7 +1912,29 @@ static switch_status_t conference_add_member(conference_obj_t *conference, confe switch_mutex_unlock(member->audio_out_mutex); switch_mutex_unlock(member->audio_in_mutex); + if (conference->la && member->channel) { + member->json = cJSON_CreateArray(); + cJSON_AddItemToArray(member->json, cJSON_CreateStringPrintf("%0.8d", member->id)); + cJSON_AddItemToArray(member->json, cJSON_CreateString(switch_channel_get_variable(member->channel, "caller_id_number"))); + cJSON_AddItemToArray(member->json, cJSON_CreateString(switch_channel_get_variable(member->channel, "caller_id_name"))); + + cJSON_AddItemToArray(member->json, cJSON_CreateStringPrintf("%s@%s", + switch_channel_get_variable(member->channel, "original_read_codec"), + switch_channel_get_variable(member->channel, "original_read_rate") + )); + + member->status_field = cJSON_CreateString(""); + cJSON_AddItemToArray(member->json, member->status_field); + member_update_status_field(member); + //switch_live_array_add_alias(conference->la, switch_core_session_get_uuid(member->session), "conference"); + adv_la(conference, member, SWITCH_TRUE); + switch_live_array_add(conference->la, switch_core_session_get_uuid(member->session), -1, &member->json, SWITCH_FALSE); + + } + + send_rfc_event(conference); + send_json_event(conference); switch_mutex_unlock(conference->mutex); status = SWITCH_STATUS_SUCCESS; @@ -1637,12 +1986,14 @@ static void conference_set_video_floor_holder(conference_obj_t *conference, conf //switch_channel_set_flag(member->channel, CF_VIDEO_PASSIVE); switch_core_session_refresh_video(member->session); conference->video_floor_holder = member; + member_update_status_field(member); } else { conference->video_floor_holder = NULL; } if (old_member) { old_id = old_member->id; + member_update_status_field(old_member); //switch_channel_clear_flag(old_member->channel, CF_VIDEO_PASSIVE); } @@ -1714,6 +2065,7 @@ static void conference_set_floor_holder(conference_obj_t *conference, conference switch_channel_get_name(member->channel)); conference->floor_holder = member; + member_update_status_field(member); } else { conference->floor_holder = NULL; } @@ -1721,6 +2073,7 @@ static void conference_set_floor_holder(conference_obj_t *conference, conference if (old_member) { old_id = old_member->id; + member_update_status_field(old_member); } switch_set_flag(conference, CFLAG_FLOOR_CHANGE); @@ -1881,8 +2234,14 @@ static switch_status_t conference_del_member(conference_obj_t *conference, confe switch_mutex_unlock(member->audio_in_mutex); + if (conference->la && member->session) { + switch_live_array_del(conference->la, switch_core_session_get_uuid(member->session)); + //switch_live_array_clear_alias(conference->la, switch_core_session_get_uuid(member->session), "conference"); + adv_la(conference, member, SWITCH_FALSE); + } + send_rfc_event(conference); - + send_json_event(conference); switch_mutex_unlock(conference->mutex); status = SWITCH_STATUS_SUCCESS; @@ -2062,6 +2421,11 @@ static void *SWITCH_THREAD_FUNC conference_video_thread_run(switch_thread_t *thr return NULL; } +static void conference_command_handler(switch_live_array_t *la, const char *cmd, const char *sessid, cJSON *jla, void *user_data) +{ + +} + /* Main monitor thread (1 per distinct conference room) */ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, void *obj) { @@ -2079,6 +2443,7 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v int32_t z = 0; int member_score_sum = 0; int divisor = 0; + conference_cdr_node_t *np; if (!(divisor = conference->rate / 8000)) { divisor = 1; @@ -2108,6 +2473,26 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "conference-create"); switch_event_fire(&event); + if (switch_test_flag(conference, CFLAG_LIVEARRAY_SYNC)) { + char *p; + + if (strchr(conference->name, '@')) { + conference->la_event_channel = switch_core_sprintf(conference->pool, "conference-liveArray.%s", conference->name); + } else { + conference->la_event_channel = switch_core_sprintf(conference->pool, "conference-liveArray.%s@%s", conference->name, conference->domain); + } + + conference->la_name = switch_core_strdup(conference->pool, conference->name); + if ((p = strchr(conference->la_name, '@'))) { + *p = '\0'; + } + + switch_live_array_create(conference->la_event_channel, conference->la_name, globals.event_channel_id, &conference->la); + switch_live_array_set_user_data(conference->la, conference); + switch_live_array_set_command_handler(conference->la, conference_command_handler); + } + + while (globals.running && !switch_test_flag(conference, CFLAG_DESTRUCT)) { switch_size_t file_sample_len = samples; switch_size_t file_data_len = samples * 2; @@ -2505,6 +2890,14 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v switch_mutex_lock(conference->mutex); conference_stop_file(conference, FILE_STOP_ASYNC); conference_stop_file(conference, FILE_STOP_ALL); + + for (np = conference->cdr_nodes; np; np = np->next) { + if (np->var_event) { + switch_event_destroy(&np->var_event); + } + } + + /* Close Unused Handles */ if (conference->fnode) { conference_file_node_t *fnode, *cur; @@ -2596,6 +2989,10 @@ static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, v switch_thread_rwlock_unlock(conference->rwlock); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Write Lock OFF\n"); + if (conference->la) { + switch_live_array_destroy(&conference->la); + } + if (conference->sh) { switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_NONE; switch_core_speech_close(&conference->lsh, &flags); @@ -3241,7 +3638,6 @@ static void *SWITCH_THREAD_FUNC conference_loop_input(switch_thread_t *thread, v break; } - /* if we have caller digits, feed them to the parser to find an action */ if (switch_channel_has_dtmf(channel)) { char dtmf[128] = ""; @@ -3251,7 +3647,12 @@ static void *SWITCH_THREAD_FUNC conference_loop_input(switch_thread_t *thread, v if (switch_test_flag(member, MFLAG_DIST_DTMF)) { conference_send_all_dtmf(member, member->conference, dtmf); } else if (member->dmachine) { - switch_ivr_dmachine_feed(member->dmachine, dtmf, NULL); + char *p; + char str[2] = ""; + for (p = dtmf; p && *p; p++) { + str[0] = *p; + switch_ivr_dmachine_feed(member->dmachine, str, NULL); + } } } else if (member->dmachine) { switch_ivr_dmachine_ping(member->dmachine, NULL); @@ -3280,6 +3681,7 @@ static void *SWITCH_THREAD_FUNC conference_loop_input(switch_thread_t *thread, v if (++hangover_hits >= hangover) { hangover_hits = hangunder_hits = 0; switch_clear_flag_locked(member, MFLAG_TALKING); + member_update_status_field(member); check_agc_levels(member); clear_avg(member); member->score_iir = 0; @@ -3398,7 +3800,7 @@ static void *SWITCH_THREAD_FUNC conference_loop_input(switch_thread_t *thread, v if (!switch_test_flag(member, MFLAG_TALKING)) { switch_set_flag_locked(member, MFLAG_TALKING); - + member_update_status_field(member); if (test_eflag(member->conference, EFLAG_START_TALKING) && switch_test_flag(member, MFLAG_CAN_SPEAK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) { conference_add_event_member_data(member, event); @@ -3435,6 +3837,7 @@ static void *SWITCH_THREAD_FUNC conference_loop_input(switch_thread_t *thread, v if (++hangover_hits >= hangover) { hangover_hits = hangunder_hits = 0; switch_clear_flag_locked(member, MFLAG_TALKING); + member_update_status_field(member); check_agc_levels(member); clear_avg(member); @@ -3763,6 +4166,15 @@ static void conference_loop_output(conference_member_t *member) switch_mutex_lock(member->write_mutex); + + if (switch_channel_test_flag(member->channel, CF_CONFERENCE_ADV)) { + if (member->conference->la) { + adv_la(member->conference, member, SWITCH_TRUE); + } + switch_channel_clear_flag(member->channel, CF_CONFERENCE_ADV); + } + + if (switch_core_session_dequeue_event(member->session, &event, SWITCH_FALSE) == SWITCH_STATUS_SUCCESS) { if (event->event_id == SWITCH_EVENT_MESSAGE) { char *from = switch_event_get_header(event, "from"); @@ -4864,6 +5276,8 @@ static switch_status_t conf_api_sub_mute(conference_member_t *member, switch_str switch_event_fire(&event); } + member_update_status_field(member); + return SWITCH_STATUS_SUCCESS; } @@ -4948,6 +5362,8 @@ static switch_status_t conf_api_sub_unmute(conference_member_t *member, switch_s switch_event_fire(&event); } + member_update_status_field(member); + return SWITCH_STATUS_SUCCESS; } @@ -7260,6 +7676,10 @@ static void set_cflags(const char *flags, uint32_t *f) *f |= CFLAG_VIDEO_BRIDGE; } else if (!strcasecmp(argv[i], "audio-always")) { *f |= CFLAG_AUDIO_ALWAYS; + } else if (!strcasecmp(argv[i], "json-events")) { + *f |= CFLAG_JSON_EVENTS; + } else if (!strcasecmp(argv[i], "livearray-sync")) { + *f |= CFLAG_LIVEARRAY_SYNC; } else if (!strcasecmp(argv[i], "rfc-4579")) { *f |= CFLAG_RFC4579; } @@ -9308,6 +9728,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_conference_load) switch_console_add_complete_func("::conference::list_conferences", list_conferences); + switch_event_channel_bind("conference", conference_event_channel_handler, &globals.event_channel_id); + switch_event_channel_bind("conference-liveArray", conference_la_event_channel_handler, &globals.event_channel_id); + /* build api interface help ".syntax" field string */ p = strdup(""); for (i = 0; i < CONFFUNCAPISIZE; i++) { @@ -9389,6 +9812,9 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_conference_shutdown) /* signal all threads to shutdown */ globals.running = 0; + switch_event_channel_unbind(NULL, conference_event_channel_handler); + switch_event_channel_unbind(NULL, conference_la_event_channel_handler); + switch_console_del_complete_func("::conference::list_conferences"); /* wait for all threads */