FS-7845 Refactor of mod_conference to improve compilation speed, and
logical organization.
This commit is contained in:
parent
d569b4ab98
commit
756ef77b7b
|
@ -2,7 +2,8 @@ include $(top_srcdir)/build/modmake.rulesam
|
|||
MODNAME=mod_conference
|
||||
|
||||
mod_LTLIBRARIES = mod_conference.la
|
||||
mod_conference_la_SOURCES = mod_conference.c
|
||||
mod_conference_la_SOURCES = mod_conference.c mod_conference_api.c mod_conference_loop.c mod_conference_al.c mod_conference_cdr.c mod_conference_video.c
|
||||
mod_conference_la_SOURCES += mod_conference_event.c mod_conference_member.c mod_conference_utils.c mod_conference_file.c mod_conference_record.c
|
||||
mod_conference_la_CFLAGS = $(AM_CFLAGS) -I.
|
||||
mod_conference_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
||||
mod_conference_la_LDFLAGS = -avoid-version -module -no-undefined -shared
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
runtime in one file, header(with all structures), apps, api commands, utilities
|
||||
|
||||
Currently mod_conference can compile with 'time make -j' in:
|
||||
real 0m9.709s
|
||||
user 0m9.040s
|
||||
sys 0m0.568s
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
|
||||
|
||||
al_handle_t *create_al(switch_memory_pool_t *pool)
|
||||
{
|
||||
al_handle_t *al;
|
||||
|
||||
al = switch_core_alloc(pool, sizeof(al_handle_t));
|
||||
switch_mutex_init(&al->mutex, SWITCH_MUTEX_NESTED, pool);
|
||||
|
||||
return al;
|
||||
}
|
||||
|
||||
#ifndef OPENAL_POSITIONING
|
||||
void gen_arc(conference_obj_t *conference, switch_stream_handle_t *stream)
|
||||
{
|
||||
}
|
||||
void process_al(al_handle_t *al, void *data, switch_size_t datalen, int rate)
|
||||
{
|
||||
}
|
||||
|
||||
#else
|
||||
void gen_arc(conference_obj_t *conference, switch_stream_handle_t *stream)
|
||||
{
|
||||
float offset;
|
||||
float pos;
|
||||
float radius;
|
||||
float x, z;
|
||||
float div = 3.14159f / 180;
|
||||
conference_member_t *member;
|
||||
uint32_t count = 0;
|
||||
|
||||
if (!conference->count) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
for (member = conference->members; member; member = member->next) {
|
||||
if (member->channel && member_test_flag(member, MFLAG_CAN_SPEAK) && !member_test_flag(member, MFLAG_NO_POSITIONAL)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count < 3) {
|
||||
for (member = conference->members; member; member = member->next) {
|
||||
if (member->channel && !member_test_flag(member, MFLAG_NO_POSITIONAL) && member->al) {
|
||||
|
||||
member->al->pos_x = 0;
|
||||
member->al->pos_y = 0;
|
||||
member->al->pos_z = 0;
|
||||
member->al->setpos = 1;
|
||||
|
||||
if (stream) {
|
||||
stream->write_function(stream, "Member %d (%s) 0.0:0.0:0.0\n", member->id, switch_channel_get_name(member->channel));
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Member %d (%s) 0.0:0.0:0.0\n",
|
||||
member->id, switch_channel_get_name(member->channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
offset = 180 / (count - 1);
|
||||
|
||||
radius = 1.0f;
|
||||
|
||||
pos = -90.0f;
|
||||
|
||||
for (member = conference->members; member; member = member->next) {
|
||||
|
||||
if (!member->channel || member_test_flag(member, MFLAG_NO_POSITIONAL) || !member_test_flag(member, MFLAG_CAN_SPEAK)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!member->al) {
|
||||
member->al = create_al(member->pool);
|
||||
}
|
||||
member_set_flag(member, MFLAG_POSITIONAL);
|
||||
|
||||
if (pos == 0) {
|
||||
x = 0;
|
||||
z = radius;
|
||||
} else if (pos == -90) {
|
||||
z = 0;
|
||||
x = radius * -1;
|
||||
} else if (pos == 90) {
|
||||
z = 0;
|
||||
x = radius;
|
||||
} else if (pos < 0) {
|
||||
z = cos((90+pos) * div) * radius;
|
||||
x = sin((90+pos) * div) * radius * -1.0f;
|
||||
} else {
|
||||
x = cos(pos * div) * radius;
|
||||
z = sin(pos * div) * radius;
|
||||
}
|
||||
|
||||
member->al->pos_x = x;
|
||||
member->al->pos_y = 0;
|
||||
member->al->pos_z = z;
|
||||
member->al->setpos = 1;
|
||||
|
||||
if (stream) {
|
||||
stream->write_function(stream, "Member %d (%s) %0.2f:0.0:%0.2f\n", member->id, switch_channel_get_name(member->channel), x, z);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Member %d (%s) %0.2f:0.0:%0.2f\n",
|
||||
member->id, switch_channel_get_name(member->channel), x, z);
|
||||
}
|
||||
|
||||
pos += offset;
|
||||
}
|
||||
|
||||
end:
|
||||
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void process_al(al_handle_t *al, void *data, switch_size_t datalen, int rate)
|
||||
{
|
||||
|
||||
if (rate != 48000) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Only 48khz is supported.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!al->device) {
|
||||
ALCint contextAttr[] = {
|
||||
ALC_FORMAT_CHANNELS_SOFT, ALC_STEREO_SOFT,
|
||||
ALC_FORMAT_TYPE_SOFT, ALC_SHORT_SOFT,
|
||||
ALC_FREQUENCY, rate,
|
||||
ALC_HRTF_SOFT, AL_TRUE,
|
||||
0
|
||||
};
|
||||
|
||||
switch_mutex_lock(mod_conference_globals.setup_mutex);
|
||||
if ((al->device = alcLoopbackOpenDeviceSOFT(NULL))) {
|
||||
const ALshort silence[16] = { 0 };
|
||||
float orient[6] = { /*fwd:*/ 0., 0., -1., /*up:*/ 0., 1., 0. };
|
||||
|
||||
al->context = alcCreateContext(al->device, contextAttr);
|
||||
alcSetThreadContext(al->context);
|
||||
|
||||
/* listener at origin, facing down -z (ears at 0.0m height) */
|
||||
alListener3f( AL_POSITION, 0. ,0, 0. );
|
||||
alListener3f( AL_VELOCITY, 0., 0., 0. );
|
||||
alListenerfv( AL_ORIENTATION, orient );
|
||||
|
||||
|
||||
alGenSources(1, &al->source);
|
||||
alSourcef( al->source, AL_PITCH, 1.);
|
||||
alSourcef( al->source, AL_GAIN, 1.);
|
||||
alGenBuffers(2, al->buffer_in);
|
||||
|
||||
alBufferData(al->buffer_in[0], AL_FORMAT_MONO16, data, datalen, rate);
|
||||
//alBufferData(al->buffer_in[0], AL_FORMAT_MONO16, NULL, 0, rate);
|
||||
alBufferData(al->buffer_in[1], AL_FORMAT_MONO16, silence, sizeof(silence), rate);
|
||||
alSourceQueueBuffers(al->source, 2, al->buffer_in);
|
||||
alSourcePlay(al->source);
|
||||
}
|
||||
switch_mutex_unlock(mod_conference_globals.setup_mutex);
|
||||
}
|
||||
|
||||
if (al->device) {
|
||||
ALint processed = 0, state = 0;
|
||||
|
||||
//alcSetThreadContext(al->context);
|
||||
alGetSourcei(al->source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &processed);
|
||||
|
||||
if (al->setpos) {
|
||||
al->setpos = 0;
|
||||
alSource3f(al->source, AL_POSITION, al->pos_x, al->pos_y, al->pos_z);
|
||||
//alSource3f(al->source, AL_VELOCITY, .01, 0., 0.);
|
||||
}
|
||||
|
||||
if (processed > 0) {
|
||||
ALuint bufid;
|
||||
alSourceUnqueueBuffers(al->source, 1, &bufid);
|
||||
alBufferData(bufid, AL_FORMAT_MONO16, data, datalen, rate);
|
||||
alSourceQueueBuffers(al->source, 1, &bufid);
|
||||
}
|
||||
|
||||
if (state != AL_PLAYING) {
|
||||
alSourcePlay(al->source);
|
||||
}
|
||||
|
||||
alcRenderSamplesSOFT(al->device, data, datalen / 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void check_agc_levels(conference_member_t *member)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
if (!member->avg_score) return;
|
||||
|
||||
if ((int)member->avg_score < member->conference->agc_level - 100) {
|
||||
member->agc_volume_in_level++;
|
||||
switch_normalize_volume_granular(member->agc_volume_in_level);
|
||||
x = 1;
|
||||
} else if ((int)member->avg_score > member->conference->agc_level + 100) {
|
||||
member->agc_volume_in_level--;
|
||||
switch_normalize_volume_granular(member->agc_volume_in_level);
|
||||
x = -1;
|
||||
}
|
||||
|
||||
if (x) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG7,
|
||||
"AGC %s:%d diff:%d level:%d cur:%d avg:%d vol:%d %s\n",
|
||||
member->conference->name,
|
||||
member->id, member->conference->agc_level - member->avg_score, member->conference->agc_level,
|
||||
member->score, member->avg_score, member->agc_volume_in_level, x > 0 ? "+++" : "---");
|
||||
|
||||
clear_avg(member);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef OPENAL_POSITIONING
|
||||
switch_status_t parse_position(al_handle_t *al, const char *data)
|
||||
{
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
#else
|
||||
switch_status_t parse_position(al_handle_t *al, const char *data)
|
||||
{
|
||||
char *args[3];
|
||||
int num;
|
||||
char *dup;
|
||||
|
||||
|
||||
dup = strdup((char *)data);
|
||||
switch_assert(dup);
|
||||
|
||||
if ((num = switch_split(dup, ':', args)) != 3) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
al->pos_x = atof(args[0]);
|
||||
al->pos_y = atof(args[1]);
|
||||
al->pos_z = atof(args[2]);
|
||||
al->setpos = 1;
|
||||
|
||||
switch_safe_free(dup);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef OPENAL_POSITIONING
|
||||
switch_status_t member_parse_position(conference_member_t *member, const char *data)
|
||||
{
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
#else
|
||||
switch_status_t member_parse_position(conference_member_t *member, const char *data)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_FALSE;
|
||||
|
||||
if (member->al) {
|
||||
status = parse_position(member->al, data);
|
||||
}
|
||||
|
||||
return status;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef OPENAL_POSITIONING
|
||||
void close_al(al_handle_t *al)
|
||||
{
|
||||
if (!al) return;
|
||||
|
||||
switch_mutex_lock(mod_conference_globals.setup_mutex);
|
||||
if (al->source) {
|
||||
alDeleteSources(1, &al->source);
|
||||
al->source = 0;
|
||||
}
|
||||
|
||||
if (al->buffer_in[0]) {
|
||||
alDeleteBuffers(2, al->buffer_in);
|
||||
al->buffer_in[0] = 0;
|
||||
al->buffer_in[1] = 0;
|
||||
}
|
||||
|
||||
if (al->context) {
|
||||
alcDestroyContext(al->context);
|
||||
al->context = 0;
|
||||
}
|
||||
|
||||
if (al->device) {
|
||||
alcCloseDevice(al->device);
|
||||
al->device = NULL;
|
||||
}
|
||||
switch_mutex_unlock(mod_conference_globals.setup_mutex);
|
||||
}
|
||||
#endif
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,761 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
|
||||
inline switch_bool_t cdr_test_mflag(conference_cdr_node_t *np, member_flag_t mflag)
|
||||
{
|
||||
return !!np->mflags[mflag];
|
||||
}
|
||||
|
||||
const char *audio_flow(conference_member_t *member)
|
||||
{
|
||||
const char *flow = "sendrecv";
|
||||
|
||||
if (!member_test_flag(member, MFLAG_CAN_SPEAK)) {
|
||||
flow = "recvonly";
|
||||
}
|
||||
|
||||
if (member->channel && switch_channel_test_flag(member->channel, CF_HOLD)) {
|
||||
flow = member_test_flag(member, MFLAG_CAN_SPEAK) ? "sendonly" : "inactive";
|
||||
}
|
||||
|
||||
return flow;
|
||||
}
|
||||
|
||||
|
||||
char *conference_rfc4579_render(conference_obj_t *conference, switch_event_t *event, switch_event_t *revent)
|
||||
{
|
||||
switch_xml_t xml, x_tag, x_tag1, x_tag2, x_tag3, x_tag4;
|
||||
char tmp[30];
|
||||
const char *domain; const char *name;
|
||||
char *dup_domain = NULL;
|
||||
char *uri;
|
||||
int off = 0, off1 = 0, off2 = 0, off3 = 0, off4 = 0;
|
||||
conference_cdr_node_t *np;
|
||||
char *tmpp = tmp;
|
||||
char *xml_text = NULL;
|
||||
|
||||
if (!(xml = switch_xml_new("conference-info"))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->mutex);
|
||||
switch_snprintf(tmp, sizeof(tmp), "%u", conference->doc_version);
|
||||
conference->doc_version++;
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
|
||||
if (!event || !(name = switch_event_get_header(event, "conference-name"))) {
|
||||
if (!(name = conference->name)) {
|
||||
name = "conference";
|
||||
}
|
||||
}
|
||||
|
||||
if (!event || !(domain = switch_event_get_header(event, "conference-domain"))) {
|
||||
if (!(domain = conference->domain)) {
|
||||
dup_domain = switch_core_get_domain(SWITCH_TRUE);
|
||||
if (!(domain = dup_domain)) {
|
||||
domain = "cluecon.com";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_xml_set_attr_d(xml, "version", tmpp);
|
||||
|
||||
switch_xml_set_attr_d(xml, "state", "full");
|
||||
switch_xml_set_attr_d(xml, "xmlns", "urn:ietf:params:xml:ns:conference-info");
|
||||
|
||||
|
||||
uri = switch_mprintf("sip:%s@%s", name, domain);
|
||||
switch_xml_set_attr_d(xml, "entity", uri);
|
||||
|
||||
if (!(x_tag = switch_xml_add_child_d(xml, "conference-description", off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_tag1 = switch_xml_add_child_d(x_tag, "display-text", off1++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag1, conference->desc ? conference->desc : "FreeSWITCH Conference");
|
||||
|
||||
|
||||
if (!(x_tag1 = switch_xml_add_child_d(x_tag, "conf-uris", off1++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_tag2 = switch_xml_add_child_d(x_tag1, "entry", off2++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "uri", off3++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag3, uri);
|
||||
|
||||
|
||||
|
||||
if (!(x_tag = switch_xml_add_child_d(xml, "conference-state", off++))) {
|
||||
abort();
|
||||
}
|
||||
if (!(x_tag1 = switch_xml_add_child_d(x_tag, "user-count", off1++))) {
|
||||
abort();
|
||||
}
|
||||
switch_snprintf(tmp, sizeof(tmp), "%u", conference->count);
|
||||
switch_xml_set_txt_d(x_tag1, tmpp);
|
||||
|
||||
#if 0
|
||||
if (conference->count == 0) {
|
||||
switch_event_add_header(revent, SWITCH_STACK_BOTTOM, "notfound", "true");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(x_tag1 = switch_xml_add_child_d(x_tag, "active", off1++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag1, "true");
|
||||
|
||||
off1 = off2 = off3 = off4 = 0;
|
||||
|
||||
if (!(x_tag = switch_xml_add_child_d(xml, "users", off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
|
||||
for (np = conference->cdr_nodes; np; np = np->next) {
|
||||
char *user_uri = NULL;
|
||||
switch_channel_t *channel = NULL;
|
||||
|
||||
if (!np->cp || (np->member && !np->member->session) || np->leave_time) { /* for now we'll remove participants when the leave */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (np->member && np->member->session) {
|
||||
channel = switch_core_session_get_channel(np->member->session);
|
||||
}
|
||||
|
||||
if (!(x_tag1 = switch_xml_add_child_d(x_tag, "user", off1++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (channel) {
|
||||
const char *uri = switch_channel_get_variable_dup(channel, "conference_invite_uri", SWITCH_FALSE, -1);
|
||||
|
||||
if (uri) {
|
||||
user_uri = strdup(uri);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user_uri) {
|
||||
user_uri = switch_mprintf("sip:%s@%s", np->cp->caller_id_number, domain);
|
||||
}
|
||||
|
||||
|
||||
switch_xml_set_attr_d(x_tag1, "state", "full");
|
||||
switch_xml_set_attr_d(x_tag1, "entity", user_uri);
|
||||
|
||||
if (!(x_tag2 = switch_xml_add_child_d(x_tag1, "display-text", off2++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag2, np->cp->caller_id_name);
|
||||
|
||||
|
||||
if (!(x_tag2 = switch_xml_add_child_d(x_tag1, "endpoint", off2++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_tag2, "entity", user_uri);
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "display-text", off3++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag3, np->cp->caller_id_name);
|
||||
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "status", off3++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag3, np->leave_time ? "disconnected" : "connected");
|
||||
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "joining-info", off3++))) {
|
||||
abort();
|
||||
}
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "when", off4++))) {
|
||||
abort();
|
||||
} else {
|
||||
switch_time_exp_t tm;
|
||||
switch_size_t retsize;
|
||||
const char *fmt = "%Y-%m-%dT%H:%M:%S%z";
|
||||
char *p;
|
||||
|
||||
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");
|
||||
|
||||
|
||||
switch_xml_set_txt_d(x_tag4, tmpp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** ok so this is in the rfc but not the xsd
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "joining-method", off3++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag3, np->cp->direction == SWITCH_CALL_DIRECTION_INBOUND ? "dialed-in" : "dialed-out");
|
||||
*/
|
||||
|
||||
if (np->member) {
|
||||
const char *var;
|
||||
//char buf[1024];
|
||||
|
||||
//switch_snprintf(buf, sizeof(buf), "conf_%s_%s_%s", conference->name, conference->domain, np->cp->caller_id_number);
|
||||
//switch_channel_set_variable(channel, "conference_call_key", buf);
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "media", off3++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%ua", np->member->id);
|
||||
switch_xml_set_attr_d(x_tag3, "id", tmpp);
|
||||
|
||||
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "type", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, "audio");
|
||||
|
||||
if ((var = switch_channel_get_variable(channel, "rtp_use_ssrc"))) {
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "src-id", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, var);
|
||||
}
|
||||
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "status", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, audio_flow(np->member));
|
||||
|
||||
|
||||
if (switch_channel_test_flag(channel, CF_VIDEO)) {
|
||||
off4 = 0;
|
||||
|
||||
if (!(x_tag3 = switch_xml_add_child_d(x_tag2, "media", off3++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%uv", np->member->id);
|
||||
switch_xml_set_attr_d(x_tag3, "id", tmpp);
|
||||
|
||||
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "type", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, "video");
|
||||
|
||||
if ((var = switch_channel_get_variable(channel, "rtp_use_video_ssrc"))) {
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "src-id", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, var);
|
||||
}
|
||||
|
||||
if (!(x_tag4 = switch_xml_add_child_d(x_tag3, "status", off4++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_tag4, switch_channel_test_flag(channel, CF_HOLD) ? "sendonly" : "sendrecv");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
switch_safe_free(user_uri);
|
||||
}
|
||||
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
|
||||
off1 = off2 = off3 = off4 = 0;
|
||||
|
||||
xml_text = switch_xml_toxml(xml, SWITCH_TRUE);
|
||||
switch_xml_free(xml);
|
||||
|
||||
switch_safe_free(dup_domain);
|
||||
switch_safe_free(uri);
|
||||
|
||||
return xml_text;
|
||||
}
|
||||
|
||||
|
||||
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", audio_flow(np->member));
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void conference_cdr_del(conference_member_t *member)
|
||||
{
|
||||
if (member->channel) {
|
||||
switch_channel_get_variables(member->channel, &member->cdr_node->var_event);
|
||||
}
|
||||
if (member->cdr_node) {
|
||||
member->cdr_node->leave_time = switch_epoch_time_now(NULL);
|
||||
memcpy(member->cdr_node->mflags, member->flags, sizeof(member->flags));
|
||||
member->cdr_node->member = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void conference_cdr_add(conference_member_t *member)
|
||||
{
|
||||
conference_cdr_node_t *np;
|
||||
switch_caller_profile_t *cp;
|
||||
switch_channel_t *channel;
|
||||
|
||||
np = switch_core_alloc(member->conference->pool, sizeof(*np));
|
||||
|
||||
np->next = member->conference->cdr_nodes;
|
||||
member->conference->cdr_nodes = member->cdr_node = np;
|
||||
member->cdr_node->join_time = switch_epoch_time_now(NULL);
|
||||
member->cdr_node->member = member;
|
||||
|
||||
if (!member->session) {
|
||||
member->cdr_node->record_path = switch_core_strdup(member->conference->pool, member->rec_path);
|
||||
return;
|
||||
}
|
||||
|
||||
channel = switch_core_session_get_channel(member->session);
|
||||
|
||||
if (!(cp = switch_channel_get_caller_profile(channel))) {
|
||||
return;
|
||||
}
|
||||
|
||||
member->cdr_node->cp = switch_caller_profile_dup(member->conference->pool, cp);
|
||||
|
||||
member->cdr_node->id = member->id;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void conference_cdr_rejected(conference_obj_t *conference, switch_channel_t *channel, cdr_reject_reason_t reason)
|
||||
{
|
||||
conference_cdr_reject_t *rp;
|
||||
switch_caller_profile_t *cp;
|
||||
|
||||
rp = switch_core_alloc(conference->pool, sizeof(*rp));
|
||||
|
||||
rp->next = conference->cdr_rejected;
|
||||
conference->cdr_rejected = rp;
|
||||
rp->reason = reason;
|
||||
rp->reject_time = switch_epoch_time_now(NULL);
|
||||
|
||||
if (!(cp = switch_channel_get_caller_profile(channel))) {
|
||||
return;
|
||||
}
|
||||
|
||||
rp->cp = switch_caller_profile_dup(conference->pool, cp);
|
||||
}
|
||||
|
||||
void conference_cdr_render(conference_obj_t *conference)
|
||||
{
|
||||
switch_xml_t cdr, x_ptr, x_member, x_members, x_conference, x_cp, x_flags, x_tag, x_rejected, x_attempt;
|
||||
conference_cdr_node_t *np;
|
||||
conference_cdr_reject_t *rp;
|
||||
int cdr_off = 0, conf_off = 0;
|
||||
char str[512];
|
||||
char *path = NULL, *xml_text;
|
||||
int fd;
|
||||
|
||||
if (zstr(conference->log_dir) && (conference->cdr_event_mode == CDRE_NONE)) return;
|
||||
|
||||
if (!conference->cdr_nodes && !conference->cdr_rejected) return;
|
||||
|
||||
if (!(cdr = switch_xml_new("cdr"))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_conference = switch_xml_add_child_d(cdr, "conference", cdr_off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "name", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_ptr, conference->name);
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "hostname", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_ptr, switch_core_get_hostname());
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "rate", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_snprintf(str, sizeof(str), "%d", conference->rate);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "interval", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_snprintf(str, sizeof(str), "%d", conference->interval);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "start_time", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_ptr, "type", "UNIX-epoch");
|
||||
switch_snprintf(str, sizeof(str), "%ld", (long)conference->start_time);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_conference, "end_time", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_ptr, "endconf_forced", conference_test_flag(conference, CFLAG_ENDCONF_FORCED) ? "true" : "false");
|
||||
switch_xml_set_attr_d(x_ptr, "type", "UNIX-epoch");
|
||||
switch_snprintf(str, sizeof(str), "%ld", (long)conference->end_time);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
|
||||
|
||||
if (!(x_members = switch_xml_add_child_d(x_conference, "members", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for (np = conference->cdr_nodes; np; np = np->next) {
|
||||
int member_off = 0;
|
||||
int flag_off = 0;
|
||||
|
||||
|
||||
if (!(x_member = switch_xml_add_child_d(x_members, "member", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
switch_xml_set_attr_d(x_member, "type", np->cp ? "caller" : "recording_node");
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_member, "join_time", member_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_ptr, "type", "UNIX-epoch");
|
||||
switch_snprintf(str, sizeof(str), "%ld", (long) np->join_time);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_member, "leave_time", member_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_ptr, "type", "UNIX-epoch");
|
||||
switch_snprintf(str, sizeof(str), "%ld", (long) np->leave_time);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
if (np->cp) {
|
||||
x_flags = switch_xml_add_child_d(x_member, "flags", member_off++);
|
||||
switch_assert(x_flags);
|
||||
|
||||
x_tag = switch_xml_add_child_d(x_flags, "is_moderator", flag_off++);
|
||||
switch_xml_set_txt_d(x_tag, cdr_test_mflag(np, MFLAG_MOD) ? "true" : "false");
|
||||
|
||||
x_tag = switch_xml_add_child_d(x_flags, "end_conference", flag_off++);
|
||||
switch_xml_set_txt_d(x_tag, cdr_test_mflag(np, MFLAG_ENDCONF) ? "true" : "false");
|
||||
|
||||
x_tag = switch_xml_add_child_d(x_flags, "was_kicked", flag_off++);
|
||||
switch_xml_set_txt_d(x_tag, cdr_test_mflag(np, MFLAG_KICKED) ? "true" : "false");
|
||||
|
||||
x_tag = switch_xml_add_child_d(x_flags, "is_ghost", flag_off++);
|
||||
switch_xml_set_txt_d(x_tag, cdr_test_mflag(np, MFLAG_GHOST) ? "true" : "false");
|
||||
|
||||
if (!(x_cp = switch_xml_add_child_d(x_member, "caller_profile", member_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_ivr_set_xml_profile_data(x_cp, np->cp, 0);
|
||||
}
|
||||
|
||||
if (!zstr(np->record_path)) {
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_member, "record_path", member_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_txt_d(x_ptr, np->record_path);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!(x_rejected = switch_xml_add_child_d(x_conference, "rejected", conf_off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for (rp = conference->cdr_rejected; rp; rp = rp->next) {
|
||||
int attempt_off = 0;
|
||||
int tag_off = 0;
|
||||
|
||||
if (!(x_attempt = switch_xml_add_child_d(x_rejected, "attempt", attempt_off++))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_attempt, "reason", tag_off++))) {
|
||||
abort();
|
||||
}
|
||||
if (rp->reason == CDRR_LOCKED) {
|
||||
switch_xml_set_txt_d(x_ptr, "conference_locked");
|
||||
} else if (rp->reason == CDRR_MAXMEMBERS) {
|
||||
switch_xml_set_txt_d(x_ptr, "max_members_reached");
|
||||
} else if (rp->reason == CDRR_PIN) {
|
||||
switch_xml_set_txt_d(x_ptr, "invalid_pin");
|
||||
}
|
||||
|
||||
if (!(x_ptr = switch_xml_add_child_d(x_attempt, "reject_time", tag_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_xml_set_attr_d(x_ptr, "type", "UNIX-epoch");
|
||||
switch_snprintf(str, sizeof(str), "%ld", (long) rp->reject_time);
|
||||
switch_xml_set_txt_d(x_ptr, str);
|
||||
|
||||
if (rp->cp) {
|
||||
if (!(x_cp = switch_xml_add_child_d(x_attempt, "caller_profile", attempt_off++))) {
|
||||
abort();
|
||||
}
|
||||
switch_ivr_set_xml_profile_data(x_cp, rp->cp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
xml_text = switch_xml_toxml(cdr, SWITCH_TRUE);
|
||||
|
||||
|
||||
if (!zstr(conference->log_dir)) {
|
||||
path = switch_mprintf("%s%s%s.cdr.xml", conference->log_dir, SWITCH_PATH_SEPARATOR, conference->uuid_str);
|
||||
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
|
||||
#else
|
||||
if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) > -1) {
|
||||
#endif
|
||||
int wrote;
|
||||
wrote = write(fd, xml_text, (unsigned) strlen(xml_text));
|
||||
wrote++;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
} else {
|
||||
char ebuf[512] = { 0 };
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error writing [%s][%s]\n",
|
||||
path, switch_strerror_r(errno, ebuf, sizeof(ebuf)));
|
||||
}
|
||||
|
||||
if (conference->cdr_event_mode != CDRE_NONE) {
|
||||
switch_event_t *event;
|
||||
|
||||
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_CDR) == SWITCH_STATUS_SUCCESS)
|
||||
// if (switch_event_create(&event, SWITCH_EVENT_CDR) == SWITCH_STATUS_SUCCESS)
|
||||
{
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CDR-Source", CONF_EVENT_CDR);
|
||||
if (conference->cdr_event_mode == CDRE_AS_CONTENT) {
|
||||
switch_event_set_body(event, xml_text);
|
||||
} else {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "CDR-Path", path);
|
||||
}
|
||||
switch_event_fire(&event);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not create CDR event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_safe_free(path);
|
||||
switch_safe_free(xml_text);
|
||||
switch_xml_free(cdr);
|
||||
}
|
|
@ -0,0 +1,828 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
|
||||
void conference_mod_event_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id)
|
||||
{
|
||||
cJSON *data, *addobj = NULL;
|
||||
const char *action = NULL;
|
||||
char *value = NULL;
|
||||
cJSON *jid = 0;
|
||||
char *conf_name = strdup(event_channel + 15);
|
||||
int cid = 0;
|
||||
char *p;
|
||||
switch_stream_handle_t stream = { 0 };
|
||||
char *exec = NULL;
|
||||
cJSON *msg, *jdata, *jvalue;
|
||||
char *argv[10] = {0};
|
||||
int argc = 0;
|
||||
|
||||
if (conf_name && (p = strchr(conf_name, '@'))) {
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
if ((data = cJSON_GetObjectItem(json, "data"))) {
|
||||
action = cJSON_GetObjectCstr(data, "command");
|
||||
if ((jid = cJSON_GetObjectItem(data, "id"))) {
|
||||
cid = jid->valueint;
|
||||
}
|
||||
|
||||
if ((jvalue = cJSON_GetObjectItem(data, "value"))) {
|
||||
|
||||
if (jvalue->type == cJSON_Array) {
|
||||
int i;
|
||||
argc = cJSON_GetArraySize(jvalue);
|
||||
if (argc > 10) argc = 10;
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
cJSON *str = cJSON_GetArrayItem(jvalue, i);
|
||||
if (str->type == cJSON_String) {
|
||||
argv[i] = str->valuestring;
|
||||
}
|
||||
}
|
||||
} else if (jvalue->type == cJSON_String) {
|
||||
value = jvalue->valuestring;
|
||||
argv[argc++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "conf %s CMD %s [%s] %d\n", conf_name, key, action, cid);
|
||||
|
||||
if (zstr(action)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
SWITCH_STANDARD_STREAM(stream);
|
||||
|
||||
if (!strcasecmp(action, "kick") ||
|
||||
!strcasecmp(action, "mute") ||
|
||||
!strcasecmp(action, "unmute") ||
|
||||
!strcasecmp(action, "tmute") ||
|
||||
!strcasecmp(action, "vmute") ||
|
||||
!strcasecmp(action, "unvmute") ||
|
||||
!strcasecmp(action, "tvmute")
|
||||
) {
|
||||
exec = switch_mprintf("%s %s %d", conf_name, action, cid);
|
||||
} else if (!strcasecmp(action, "volume_in") ||
|
||||
!strcasecmp(action, "volume_out") ||
|
||||
!strcasecmp(action, "vid-res-id") ||
|
||||
!strcasecmp(action, "vid-floor") ||
|
||||
!strcasecmp(action, "vid-layer") ||
|
||||
!strcasecmp(action, "vid-canvas") ||
|
||||
!strcasecmp(action, "vid-watching-canvas") ||
|
||||
!strcasecmp(action, "vid-banner")) {
|
||||
exec = switch_mprintf("%s %s %d %s", conf_name, action, cid, argv[0]);
|
||||
} else if (!strcasecmp(action, "play") || !strcasecmp(action, "stop")) {
|
||||
exec = switch_mprintf("%s %s %s", conf_name, action, argv[0]);
|
||||
} else if (!strcasecmp(action, "recording") || !strcasecmp(action, "vid-layout") || !strcasecmp(action, "vid-write-png")) {
|
||||
|
||||
if (!argv[1]) {
|
||||
argv[1] = "all";
|
||||
}
|
||||
|
||||
exec = switch_mprintf("%s %s %s %s", conf_name, action, argv[0], argv[1]);
|
||||
|
||||
} else if (!strcasecmp(action, "transfer") && cid) {
|
||||
conference_member_t *member;
|
||||
conference_obj_t *conference;
|
||||
|
||||
exec = switch_mprintf("%s %s %s", argv[0], switch_str_nil(argv[1]), switch_str_nil(argv[2]));
|
||||
stream.write_function(&stream, "+OK Call transferred to %s", argv[0]);
|
||||
|
||||
if ((conference = conference_find(conf_name, NULL))) {
|
||||
if ((member = conference_member_get(conference, cid))) {
|
||||
switch_ivr_session_transfer(member->session, argv[0], argv[1], argv[2]);
|
||||
switch_thread_rwlock_unlock(member->rwlock);
|
||||
}
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
}
|
||||
goto end;
|
||||
} else if (!strcasecmp(action, "list-videoLayouts")) {
|
||||
switch_hash_index_t *hi;
|
||||
void *val;
|
||||
const void *vvar;
|
||||
cJSON *array = cJSON_CreateArray();
|
||||
conference_obj_t *conference = NULL;
|
||||
if ((conference = conference_find(conf_name, NULL))) {
|
||||
switch_mutex_lock(mod_conference_globals.setup_mutex);
|
||||
if (conference->layout_hash) {
|
||||
for (hi = switch_core_hash_first(conference->layout_hash); hi; hi = switch_core_hash_next(&hi)) {
|
||||
switch_core_hash_this(hi, &vvar, NULL, &val);
|
||||
cJSON_AddItemToArray(array, cJSON_CreateString((char *)vvar));
|
||||
}
|
||||
}
|
||||
|
||||
if (conference->layout_group_hash) {
|
||||
for (hi = switch_core_hash_first(conference->layout_group_hash); hi; hi = switch_core_hash_next(&hi)) {
|
||||
char *name;
|
||||
switch_core_hash_this(hi, &vvar, NULL, &val);
|
||||
name = switch_mprintf("group:%s", (char *)vvar);
|
||||
cJSON_AddItemToArray(array, cJSON_CreateString(name));
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
|
||||
switch_mutex_unlock(mod_conference_globals.setup_mutex);
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
}
|
||||
addobj = array;
|
||||
}
|
||||
|
||||
if (exec) {
|
||||
conf_api_main_real(exec, NULL, &stream);
|
||||
}
|
||||
|
||||
end:
|
||||
|
||||
msg = cJSON_CreateObject();
|
||||
jdata = json_add_child_obj(msg, "data", NULL);
|
||||
|
||||
cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel));
|
||||
cJSON_AddItemToObject(jdata, "action", cJSON_CreateString("response"));
|
||||
|
||||
if (addobj) {
|
||||
cJSON_AddItemToObject(jdata, "conf-command", cJSON_CreateString(action));
|
||||
cJSON_AddItemToObject(jdata, "response", cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToObject(jdata, "responseData", addobj);
|
||||
} else if (exec) {
|
||||
cJSON_AddItemToObject(jdata, "conf-command", cJSON_CreateString(exec));
|
||||
cJSON_AddItemToObject(jdata, "response", cJSON_CreateString((char *)stream.data));
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT,"RES [%s][%s]\n", exec, (char *)stream.data);
|
||||
} else {
|
||||
cJSON_AddItemToObject(jdata, "error", cJSON_CreateString("Invalid Command"));
|
||||
}
|
||||
|
||||
switch_event_channel_broadcast(event_channel, &msg, __FILE__, mod_conference_globals.event_channel_id);
|
||||
|
||||
|
||||
switch_safe_free(stream.data);
|
||||
switch_safe_free(exec);
|
||||
|
||||
switch_safe_free(conf_name);
|
||||
|
||||
}
|
||||
|
||||
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, mod_conference_globals.event_channel_id);
|
||||
}
|
||||
|
||||
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;
|
||||
char *dup = 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, '.'))) {
|
||||
dup = strdup(name + 1);
|
||||
switch_assert(dup);
|
||||
name = dup;
|
||||
|
||||
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_safe_free(dup);
|
||||
|
||||
switch_event_channel_broadcast(event_channel, &reply, "mod_conference", mod_conference_globals.event_channel_id);
|
||||
}
|
||||
|
||||
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 (!conference_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, "mod_conference", mod_conference_globals.event_channel_id);
|
||||
|
||||
switch_safe_free(dup_domain);
|
||||
switch_safe_free(event_channel);
|
||||
}
|
||||
|
||||
void conference_command_handler(switch_live_array_t *la, const char *cmd, const char *sessid, cJSON *jla, void *user_data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void member_update_status_field(conference_member_t *member)
|
||||
{
|
||||
char *str, *vstr = "", display[128] = "", *json_display = NULL;
|
||||
cJSON *json, *audio, *video;
|
||||
|
||||
if (!member->conference->la || !member->json ||
|
||||
!member->status_field || switch_channel_test_flag(member->channel, CF_VIDEO_ONLY) || member_test_flag(member, MFLAG_SECOND_SCREEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch_live_array_lock(member->conference->la);
|
||||
|
||||
if (conference_test_flag(member->conference, CFLAG_JSON_STATUS)) {
|
||||
json = cJSON_CreateObject();
|
||||
audio = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(audio, "muted", cJSON_CreateBool(!member_test_flag(member, MFLAG_CAN_SPEAK)));
|
||||
cJSON_AddItemToObject(audio, "onHold", cJSON_CreateBool(switch_channel_test_flag(member->channel, CF_HOLD)));
|
||||
cJSON_AddItemToObject(audio, "talking", cJSON_CreateBool(member_test_flag(member, MFLAG_TALKING)));
|
||||
cJSON_AddItemToObject(audio, "floor", cJSON_CreateBool(member == member->conference->floor_holder));
|
||||
cJSON_AddItemToObject(audio, "energyScore", cJSON_CreateNumber(member->score));
|
||||
cJSON_AddItemToObject(json, "audio", audio);
|
||||
|
||||
if (switch_channel_test_flag(member->channel, CF_VIDEO) || member->avatar_png_img) {
|
||||
video = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(video, "avatarPresented", cJSON_CreateBool(!!member->avatar_png_img));
|
||||
cJSON_AddItemToObject(video, "mediaFlow", cJSON_CreateString(member->video_flow == SWITCH_MEDIA_FLOW_SENDONLY ? "sendOnly" : "sendRecv"));
|
||||
cJSON_AddItemToObject(video, "muted", cJSON_CreateBool(!member_test_flag(member, MFLAG_CAN_BE_SEEN)));
|
||||
cJSON_AddItemToObject(video, "floor", cJSON_CreateBool(member && member->id == member->conference->video_floor_holder));
|
||||
if (member && member->id == member->conference->video_floor_holder && conference_test_flag(member->conference, CFLAG_VID_FLOOR_LOCK)) {
|
||||
cJSON_AddItemToObject(video, "floorLocked", cJSON_CreateTrue());
|
||||
}
|
||||
cJSON_AddItemToObject(video, "reservationID", member->video_reservation_id ?
|
||||
cJSON_CreateString(member->video_reservation_id) : cJSON_CreateNull());
|
||||
|
||||
cJSON_AddItemToObject(video, "videoLayerID", cJSON_CreateNumber(member->video_layer_id));
|
||||
|
||||
cJSON_AddItemToObject(json, "video", video);
|
||||
} else {
|
||||
cJSON_AddItemToObject(json, "video", cJSON_CreateFalse());
|
||||
}
|
||||
|
||||
json_display = cJSON_PrintUnformatted(json);
|
||||
cJSON_Delete(json);
|
||||
} else {
|
||||
if (!member_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 (member_test_flag(member, MFLAG_TALKING)) {
|
||||
str = "TALKING (FLOOR)";
|
||||
} else {
|
||||
str = "FLOOR";
|
||||
}
|
||||
} else if (member_test_flag(member, MFLAG_TALKING)) {
|
||||
str = "TALKING";
|
||||
} else {
|
||||
str = "ACTIVE";
|
||||
}
|
||||
|
||||
if (switch_channel_test_flag(member->channel, CF_VIDEO)) {
|
||||
if (!member_test_flag(member, MFLAG_CAN_BE_SEEN)) {
|
||||
vstr = " VIDEO (BLIND)";
|
||||
} else {
|
||||
vstr = " VIDEO";
|
||||
if (member && member->id == member->conference->video_floor_holder) {
|
||||
vstr = " VIDEO (FLOOR)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_snprintf(display, sizeof(display), "%s%s", str, vstr);
|
||||
}
|
||||
|
||||
if (json_display) {
|
||||
member->status_field->valuestring = json_display;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
void adv_la(conference_obj_t *conference, conference_member_t *member, switch_bool_t join)
|
||||
{
|
||||
|
||||
//if (member->video_flow == SWITCH_MEDIA_FLOW_SENDONLY) {
|
||||
switch_channel_set_flag(member->channel, CF_VIDEO_REFRESH_REQ);
|
||||
switch_core_media_gen_key_frame(member->session);
|
||||
//}
|
||||
|
||||
if (conference && conference->la && member->session &&
|
||||
!switch_channel_test_flag(member->channel, CF_VIDEO_ONLY)) {
|
||||
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");
|
||||
const char *event_channel = cookie ? cookie : uuid;
|
||||
switch_event_t *variables;
|
||||
switch_event_header_t *hp;
|
||||
|
||||
msg = cJSON_CreateObject();
|
||||
data = json_add_child_obj(msg, "pvtData", NULL);
|
||||
|
||||
cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel));
|
||||
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));
|
||||
cJSON_AddItemToObject(data, "role", cJSON_CreateString(member_test_flag(member, MFLAG_MOD) ? "moderator" : "participant"));
|
||||
cJSON_AddItemToObject(data, "chatID", cJSON_CreateString(conference->chat_id));
|
||||
cJSON_AddItemToObject(data, "canvasCount", cJSON_CreateNumber(conference->canvas_count));
|
||||
|
||||
if (member_test_flag(member, MFLAG_SECOND_SCREEN)) {
|
||||
cJSON_AddItemToObject(data, "secondScreen", cJSON_CreateTrue());
|
||||
}
|
||||
|
||||
if (member_test_flag(member, MFLAG_MOD)) {
|
||||
cJSON_AddItemToObject(data, "modChannel", cJSON_CreateString(conference->mod_event_channel));
|
||||
}
|
||||
|
||||
switch_core_get_variables(&variables);
|
||||
for (hp = variables->headers; hp; hp = hp->next) {
|
||||
if (!strncasecmp(hp->name, "conf_verto_", 11)) {
|
||||
char *var = hp->name + 11;
|
||||
if (var) {
|
||||
cJSON_AddItemToObject(data, var, cJSON_CreateString(hp->value));
|
||||
}
|
||||
}
|
||||
}
|
||||
switch_event_destroy(&variables);
|
||||
|
||||
switch_event_channel_broadcast(event_channel, &msg, "mod_conference", mod_conference_globals.event_channel_id);
|
||||
|
||||
if (cookie) {
|
||||
switch_event_channel_permission_modify(cookie, conference->la_event_channel, join);
|
||||
switch_event_channel_permission_modify(cookie, conference->mod_event_channel, join);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_rfc_event(conference_obj_t *conference)
|
||||
{
|
||||
switch_event_t *event;
|
||||
char *body;
|
||||
char *name = NULL, *domain = NULL, *dup_domain = NULL;
|
||||
|
||||
if (!conference_test_flag(conference, CFLAG_RFC4579)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (switch_event_create(&event, SWITCH_EVENT_CONFERENCE_DATA) == SWITCH_STATUS_SUCCESS) {
|
||||
event->flags |= EF_UNIQ_HEADERS;
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-name", name);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-domain", domain);
|
||||
|
||||
body = conference_rfc4579_render(conference, NULL, event);
|
||||
switch_event_add_body(event, "%s", body);
|
||||
free(body);
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
switch_safe_free(dup_domain);
|
||||
|
||||
}
|
||||
|
||||
|
||||
switch_status_t conference_add_event_data(conference_obj_t *conference, switch_event_t *event)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Size", "%u", conference->count);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Ghosts", "%u", conference->count_ghosts);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Profile-Name", conference->profile_name);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Conference-Unique-ID", conference->uuid_str);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
switch_status_t conference_add_event_member_data(conference_member_t *member, switch_event_t *event)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
|
||||
if (!member)
|
||||
return status;
|
||||
|
||||
if (member->conference) {
|
||||
status = conference_add_event_data(member->conference, event);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Floor", "%s", (member == member->conference->floor_holder) ? "true" : "false" );
|
||||
}
|
||||
|
||||
if (member->session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(member->session);
|
||||
|
||||
if (member->verbose_events) {
|
||||
switch_channel_event_set_data(channel, event);
|
||||
} else {
|
||||
switch_channel_event_set_basic_data(channel, event);
|
||||
}
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Video", "%s",
|
||||
switch_channel_test_flag(switch_core_session_get_channel(member->session), CF_VIDEO) ? "true" : "false" );
|
||||
|
||||
}
|
||||
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Hear", "%s", member_test_flag(member, MFLAG_CAN_HEAR) ? "true" : "false" );
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "See", "%s", member_test_flag(member, MFLAG_CAN_BE_SEEN) ? "true" : "false" );
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Speak", "%s", member_test_flag(member, MFLAG_CAN_SPEAK) ? "true" : "false" );
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Talking", "%s", member_test_flag(member, MFLAG_TALKING) ? "true" : "false" );
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Mute-Detect", "%s", member_test_flag(member, MFLAG_MUTE_DETECT) ? "true" : "false" );
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", member->id);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-Type", "%s", member_test_flag(member, MFLAG_MOD) ? "moderator" : "member");
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-Ghost", "%s", member_test_flag(member, MFLAG_GHOST) ? "true" : "false");
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Energy-Level", "%d", member->energy_level);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Current-Energy", "%d", member->score);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* send a message to every member of the conference */
|
||||
void chat_message_broadcast(conference_obj_t *conference, switch_event_t *event)
|
||||
{
|
||||
conference_member_t *member = NULL;
|
||||
switch_event_t *processed;
|
||||
|
||||
switch_assert(conference != NULL);
|
||||
switch_event_create(&processed, SWITCH_EVENT_CHANNEL_DATA);
|
||||
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
for (member = conference->members; member; member = member->next) {
|
||||
if (member->session && !member_test_flag(member, MFLAG_NOCHANNEL)) {
|
||||
const char *presence_id = switch_channel_get_variable(member->channel, "presence_id");
|
||||
const char *chat_proto = switch_channel_get_variable(member->channel, "chat_proto");
|
||||
switch_event_t *reply = NULL;
|
||||
|
||||
if (presence_id && chat_proto) {
|
||||
if (switch_event_get_header(processed, presence_id)) {
|
||||
continue;
|
||||
}
|
||||
switch_event_dup(&reply, event);
|
||||
switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "to", presence_id);
|
||||
switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "conference_name", conference->name);
|
||||
switch_event_add_header_string(reply, SWITCH_STACK_BOTTOM, "conference_domain", conference->domain);
|
||||
|
||||
switch_event_set_body(reply, switch_event_get_body(event));
|
||||
|
||||
switch_core_chat_deliver(chat_proto, &reply);
|
||||
switch_event_add_header_string(processed, SWITCH_STACK_BOTTOM, presence_id, "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
switch_event_destroy(&processed);
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
}
|
||||
|
||||
void call_setup_event_handler(switch_event_t *event)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_FALSE;
|
||||
conference_obj_t *conference = NULL;
|
||||
char *conf = switch_event_get_header(event, "Target-Component");
|
||||
char *domain = switch_event_get_header(event, "Target-Domain");
|
||||
char *dial_str = switch_event_get_header(event, "Request-Target");
|
||||
char *dial_uri = switch_event_get_header(event, "Request-Target-URI");
|
||||
char *action = switch_event_get_header(event, "Request-Action");
|
||||
char *ext = switch_event_get_header(event, "Request-Target-Extension");
|
||||
char *ext_domain = switch_event_get_header(event, "Request-Target-Domain");
|
||||
char *full_url = switch_event_get_header(event, "full_url");
|
||||
char *call_id = switch_event_get_header(event, "Request-Call-ID");
|
||||
|
||||
if (!ext) ext = dial_str;
|
||||
|
||||
if (!zstr(conf) && !zstr(dial_str) && !zstr(action) && (conference = conference_find(conf, domain))) {
|
||||
switch_event_t *var_event;
|
||||
switch_event_header_t *hp;
|
||||
|
||||
if (conference_test_flag(conference, CFLAG_RFC4579)) {
|
||||
char *key = switch_mprintf("conf_%s_%s_%s_%s", conference->name, conference->domain, ext, ext_domain);
|
||||
char *expanded = NULL, *ostr = dial_str;;
|
||||
|
||||
if (!strcasecmp(action, "call")) {
|
||||
if((conference->max_members > 0) && (conference->count >= conference->max_members)) {
|
||||
// Conference member limit has been reached; do not proceed with setup request
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
} else {
|
||||
if (switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA) != SWITCH_STATUS_SUCCESS) {
|
||||
abort();
|
||||
}
|
||||
|
||||
for(hp = event->headers; hp; hp = hp->next) {
|
||||
if (!strncasecmp(hp->name, "var_", 4)) {
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, hp->name + 4, hp->value);
|
||||
}
|
||||
}
|
||||
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_call_key", key);
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_destination_number", ext);
|
||||
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_invite_uri", dial_uri);
|
||||
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_track_status", "true");
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "conference_track_call_id", call_id);
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "sip_invite_domain", domain);
|
||||
switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "sip_invite_contact_params", "~isfocus");
|
||||
|
||||
if (!strncasecmp(ostr, "url+", 4)) {
|
||||
ostr += 4;
|
||||
} else if (!switch_true(full_url) && conference->outcall_templ) {
|
||||
if ((expanded = switch_event_expand_headers(var_event, conference->outcall_templ))) {
|
||||
ostr = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
status = conference_outcall_bg(conference, NULL, NULL, ostr, 60, NULL, NULL, NULL, NULL, NULL, NULL, &var_event);
|
||||
|
||||
if (expanded && expanded != conference->outcall_templ) {
|
||||
switch_safe_free(expanded);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!strcasecmp(action, "end")) {
|
||||
if (switch_core_session_hupall_matching_var("conference_call_key", key, SWITCH_CAUSE_NORMAL_CLEARING)) {
|
||||
send_conference_notify(conference, "SIP/2.0 200 OK\r\n", call_id, SWITCH_TRUE);
|
||||
} else {
|
||||
send_conference_notify(conference, "SIP/2.0 481 Failure\r\n", call_id, SWITCH_TRUE);
|
||||
}
|
||||
status = SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_safe_free(key);
|
||||
} else { // Conference found but doesn't support referral.
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
} else { // Couldn't find associated conference. Indicate failure on refer subscription
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if(status != SWITCH_STATUS_SUCCESS) {
|
||||
// Unable to setup call, need to generate final NOTIFY
|
||||
if (switch_event_create(&event, SWITCH_EVENT_CONFERENCE_DATA) == SWITCH_STATUS_SUCCESS) {
|
||||
event->flags |= EF_UNIQ_HEADERS;
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-name", conf);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-domain", domain);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "conference-event", "refer");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call_id", call_id);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "final", "true");
|
||||
switch_event_add_body(event, "%s", "SIP/2.0 481 Failure\r\n");
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void conf_data_event_handler(switch_event_t *event)
|
||||
{
|
||||
switch_event_t *revent;
|
||||
char *name = switch_event_get_header(event, "conference-name");
|
||||
char *domain = switch_event_get_header(event, "conference-domain");
|
||||
conference_obj_t *conference = NULL;
|
||||
char *body = NULL;
|
||||
|
||||
if (!zstr(name) && (conference = conference_find(name, domain))) {
|
||||
if (conference_test_flag(conference, CFLAG_RFC4579)) {
|
||||
switch_event_dup(&revent, event);
|
||||
revent->event_id = SWITCH_EVENT_CONFERENCE_DATA;
|
||||
revent->flags |= EF_UNIQ_HEADERS;
|
||||
switch_event_add_header(revent, SWITCH_STACK_TOP, "Event-Name", "CONFERENCE_DATA");
|
||||
|
||||
body = conference_rfc4579_render(conference, event, revent);
|
||||
switch_event_add_body(revent, "%s", body);
|
||||
switch_event_fire(&revent);
|
||||
switch_safe_free(body);
|
||||
}
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pres_event_handler(switch_event_t *event)
|
||||
{
|
||||
char *to = switch_event_get_header(event, "to");
|
||||
char *domain_name = NULL;
|
||||
char *dup_to = NULL, *conf_name, *dup_conf_name = NULL;
|
||||
conference_obj_t *conference;
|
||||
|
||||
if (!to || strncasecmp(to, "conf+", 5) || !strchr(to, '@')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(dup_to = strdup(to))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
conf_name = dup_to + 5;
|
||||
|
||||
if ((domain_name = strchr(conf_name, '@'))) {
|
||||
*domain_name++ = '\0';
|
||||
}
|
||||
|
||||
dup_conf_name = switch_mprintf("%q@%q", conf_name, domain_name);
|
||||
|
||||
|
||||
if ((conference = conference_find(conf_name, NULL)) || (conference = conference_find(dup_conf_name, NULL))) {
|
||||
if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", CONF_CHAT_PROTO);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", conference->name);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "from", "%s@%s", conference->name, conference->domain);
|
||||
|
||||
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "force-status", "Active (%d caller%s)", conference->count, conference->count == 1 ? "" : "s");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog");
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", conf_name);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_ROUTING");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", conference->count == 1 ? "early" : "confirmed");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-direction", conference->count == 1 ? "outbound" : "inbound");
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
} else if (switch_event_create(&event, SWITCH_EVENT_PRESENCE_IN) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", CONF_CHAT_PROTO);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "login", conf_name);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", to);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "force-status", "Idle");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "rpid", "unknown");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "event_type", "presence");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "alt_event_type", "dialog");
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "event_count", "%d", EC++);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "unique-id", conf_name);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "channel-state", "CS_HANGUP");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "answer-state", "terminated");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-direction", "inbound");
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
switch_safe_free(dup_to);
|
||||
switch_safe_free(dup_conf_name);
|
||||
}
|
||||
|
||||
|
||||
switch_status_t chat_send(switch_event_t *message_event)
|
||||
{
|
||||
char name[512] = "", *p, *lbuf = NULL;
|
||||
conference_obj_t *conference = NULL;
|
||||
switch_stream_handle_t stream = { 0 };
|
||||
const char *proto;
|
||||
const char *from;
|
||||
const char *to;
|
||||
//const char *subject;
|
||||
const char *body;
|
||||
//const char *type;
|
||||
const char *hint;
|
||||
|
||||
proto = switch_event_get_header(message_event, "proto");
|
||||
from = switch_event_get_header(message_event, "from");
|
||||
to = switch_event_get_header(message_event, "to");
|
||||
body = switch_event_get_body(message_event);
|
||||
hint = switch_event_get_header(message_event, "hint");
|
||||
|
||||
|
||||
if ((p = strchr(to, '+'))) {
|
||||
to = ++p;
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if ((p = strchr(to, '@'))) {
|
||||
switch_copy_string(name, to, ++p - to);
|
||||
} else {
|
||||
switch_copy_string(name, to, sizeof(name));
|
||||
}
|
||||
|
||||
if (!(conference = conference_find(name, NULL))) {
|
||||
switch_core_chat_send_args(proto, CONF_CHAT_PROTO, to, hint && strchr(hint, '/') ? hint : from, "",
|
||||
"Conference not active.", NULL, NULL, SWITCH_FALSE);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
SWITCH_STANDARD_STREAM(stream);
|
||||
|
||||
if (body != NULL && (lbuf = strdup(body))) {
|
||||
/* special case list */
|
||||
if (conference->broadcast_chat_messages) {
|
||||
chat_message_broadcast(conference, message_event);
|
||||
} else if (switch_stristr("list", lbuf)) {
|
||||
conference_list_pretty(conference, &stream);
|
||||
/* provide help */
|
||||
} else {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
switch_safe_free(lbuf);
|
||||
|
||||
if (!conference->broadcast_chat_messages) {
|
||||
switch_core_chat_send_args(proto, CONF_CHAT_PROTO, to, hint && strchr(hint, '/') ? hint : from, "", stream.data, NULL, NULL, SWITCH_FALSE);
|
||||
}
|
||||
|
||||
switch_safe_free(stream.data);
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
|
||||
switch_status_t conference_file_close(conference_obj_t *conference, conference_file_node_t *node)
|
||||
{
|
||||
switch_event_t *event;
|
||||
conference_member_t *member = NULL;
|
||||
|
||||
if (test_eflag(conference, EFLAG_PLAY_FILE_DONE) &&
|
||||
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
|
||||
|
||||
conference_add_event_data(conference, event);
|
||||
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "seconds", "%ld", (long) node->fh.samples_in / node->fh.native_rate);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "milliseconds", "%ld", (long) node->fh.samples_in / (node->fh.native_rate / 1000));
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "samples", "%ld", (long) node->fh.samples_in);
|
||||
|
||||
if (node->fh.params) {
|
||||
switch_event_merge(event, node->fh.params);
|
||||
}
|
||||
|
||||
if (node->member_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "play-file-member-done");
|
||||
|
||||
if ((member = conference_member_get(conference, node->member_id))) {
|
||||
conference_add_event_member_data(member, event);
|
||||
switch_thread_rwlock_unlock(member->rwlock);
|
||||
}
|
||||
|
||||
} else {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "play-file-done");
|
||||
}
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "File", node->file);
|
||||
|
||||
if (node->async) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Async", "true");
|
||||
}
|
||||
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
#ifdef OPENAL_POSITIONING
|
||||
if (node->al && node->al->device) {
|
||||
close_al(node->al);
|
||||
}
|
||||
#endif
|
||||
if (switch_core_file_has_video(&node->fh) && conference->canvas) {
|
||||
conference->canvas->timer.interval = conference->video_fps.ms;
|
||||
conference->canvas->timer.samples = conference->video_fps.samples;
|
||||
switch_core_timer_sync(&conference->canvas->timer);
|
||||
conference->canvas->send_keyframe = 1;
|
||||
conference->playing_video_file = 0;
|
||||
}
|
||||
return switch_core_file_close(&node->fh);
|
||||
}
|
||||
|
||||
|
||||
/* Make files stop playing in a conference either the current one or all of them */
|
||||
uint32_t conference_stop_file(conference_obj_t *conference, file_stop_t stop)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
conference_file_node_t *nptr;
|
||||
|
||||
switch_assert(conference != NULL);
|
||||
|
||||
switch_mutex_lock(conference->mutex);
|
||||
|
||||
if (stop == FILE_STOP_ALL) {
|
||||
for (nptr = conference->fnode; nptr; nptr = nptr->next) {
|
||||
nptr->done++;
|
||||
count++;
|
||||
}
|
||||
if (conference->async_fnode) {
|
||||
conference->async_fnode->done++;
|
||||
count++;
|
||||
}
|
||||
} else if (stop == FILE_STOP_ASYNC) {
|
||||
if (conference->async_fnode) {
|
||||
conference->async_fnode->done++;
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
if (conference->fnode) {
|
||||
conference->fnode->done++;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Play a file in the conference room */
|
||||
switch_status_t conference_play_file(conference_obj_t *conference, char *file, uint32_t leadin, switch_channel_t *channel, uint8_t async)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
conference_file_node_t *fnode, *nptr = NULL;
|
||||
switch_memory_pool_t *pool;
|
||||
uint32_t count;
|
||||
char *dfile = NULL, *expanded = NULL;
|
||||
int say = 0;
|
||||
uint8_t channels = (uint8_t) conference->channels;
|
||||
int bad_params = 0;
|
||||
int flags = 0;
|
||||
|
||||
switch_assert(conference != NULL);
|
||||
|
||||
if (zstr(file)) {
|
||||
return SWITCH_STATUS_NOTFOUND;
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->mutex);
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
count = conference->count;
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
|
||||
if (!count) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (channel) {
|
||||
if ((expanded = switch_channel_expand_variables(channel, file)) != file) {
|
||||
file = expanded;
|
||||
} else {
|
||||
expanded = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strncasecmp(file, "say:", 4)) {
|
||||
say = 1;
|
||||
}
|
||||
|
||||
if (!async && say) {
|
||||
status = conference_say(conference, file + 4, leadin);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!switch_is_file_path(file)) {
|
||||
if (!say && conference->sound_prefix) {
|
||||
char *params_portion = NULL;
|
||||
char *file_portion = NULL;
|
||||
switch_separate_file_params(file, &file_portion, ¶ms_portion);
|
||||
|
||||
if (params_portion) {
|
||||
dfile = switch_mprintf("%s%s%s%s", params_portion, conference->sound_prefix, SWITCH_PATH_SEPARATOR, file_portion);
|
||||
} else {
|
||||
dfile = switch_mprintf("%s%s%s", conference->sound_prefix, SWITCH_PATH_SEPARATOR, file_portion);
|
||||
}
|
||||
|
||||
file = dfile;
|
||||
switch_safe_free(file_portion);
|
||||
switch_safe_free(params_portion);
|
||||
|
||||
} else if (!async) {
|
||||
status = conference_say(conference, file, leadin);
|
||||
goto done;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup a memory pool to use. */
|
||||
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
|
||||
status = SWITCH_STATUS_MEMERR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Create a node object */
|
||||
if (!(fnode = switch_core_alloc(pool, sizeof(*fnode)))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n");
|
||||
switch_core_destroy_memory_pool(&pool);
|
||||
status = SWITCH_STATUS_MEMERR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
fnode->conference = conference;
|
||||
fnode->layer_id = -1;
|
||||
fnode->type = NODE_TYPE_FILE;
|
||||
fnode->leadin = leadin;
|
||||
|
||||
if (switch_stristr("position=", file)) {
|
||||
/* positional requires mono input */
|
||||
fnode->fh.channels = channels = 1;
|
||||
}
|
||||
|
||||
retry:
|
||||
|
||||
flags = SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT;
|
||||
|
||||
if (conference->members_with_video && conference_test_flag(conference, CFLAG_TRANSCODE_VIDEO)) {
|
||||
flags |= SWITCH_FILE_FLAG_VIDEO;
|
||||
}
|
||||
|
||||
/* Open the file */
|
||||
fnode->fh.pre_buffer_datalen = SWITCH_DEFAULT_FILE_BUFFER_LEN;
|
||||
|
||||
if (switch_core_file_open(&fnode->fh, file, channels, conference->rate, flags, pool) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_event_t *event;
|
||||
|
||||
if (test_eflag(conference, EFLAG_PLAY_FILE) &&
|
||||
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
|
||||
conference_add_event_data(conference, event);
|
||||
|
||||
if (fnode->fh.params) {
|
||||
switch_event_merge(event, conference->fnode->fh.params);
|
||||
}
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "play-file");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "File", file);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Async", async ? "true" : "false");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Error", "File could not be played");
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
switch_core_destroy_memory_pool(&pool);
|
||||
status = SWITCH_STATUS_NOTFOUND;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (fnode->fh.params) {
|
||||
const char *vol = switch_event_get_header(fnode->fh.params, "vol");
|
||||
const char *position = switch_event_get_header(fnode->fh.params, "position");
|
||||
|
||||
if (!zstr(vol)) {
|
||||
fnode->fh.vol = atoi(vol);
|
||||
}
|
||||
|
||||
if (!bad_params && !zstr(position) && conference->channels == 2) {
|
||||
fnode->al = create_al(pool);
|
||||
if (parse_position(fnode->al, position) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_core_file_close(&fnode->fh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Position Data.\n");
|
||||
fnode->al = NULL;
|
||||
channels = (uint8_t)conference->channels;
|
||||
bad_params = 1;
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fnode->pool = pool;
|
||||
fnode->async = async;
|
||||
fnode->file = switch_core_strdup(fnode->pool, file);
|
||||
|
||||
if (!conference->fnode || (async && !conference->async_fnode)) {
|
||||
fnode_check_video(fnode);
|
||||
}
|
||||
|
||||
/* Queue the node */
|
||||
switch_mutex_lock(conference->mutex);
|
||||
|
||||
if (async) {
|
||||
if (conference->async_fnode) {
|
||||
nptr = conference->async_fnode;
|
||||
}
|
||||
conference->async_fnode = fnode;
|
||||
|
||||
if (nptr) {
|
||||
switch_memory_pool_t *tmppool;
|
||||
conference_file_close(conference, nptr);
|
||||
tmppool = nptr->pool;
|
||||
switch_core_destroy_memory_pool(&tmppool);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (nptr = conference->fnode; nptr && nptr->next; nptr = nptr->next);
|
||||
|
||||
if (nptr) {
|
||||
nptr->next = fnode;
|
||||
} else {
|
||||
conference->fnode = fnode;
|
||||
}
|
||||
}
|
||||
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
|
||||
done:
|
||||
|
||||
switch_safe_free(expanded);
|
||||
switch_safe_free(dfile);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Play a file */
|
||||
switch_status_t conference_local_play_file(conference_obj_t *conference, switch_core_session_t *session, char *path, uint32_t leadin, void *buf,
|
||||
uint32_t buflen)
|
||||
{
|
||||
uint32_t x = 0;
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
switch_channel_t *channel;
|
||||
char *expanded = NULL;
|
||||
switch_input_args_t args = { 0 }, *ap = NULL;
|
||||
|
||||
if (buf) {
|
||||
args.buf = buf;
|
||||
args.buflen = buflen;
|
||||
ap = &args;
|
||||
}
|
||||
|
||||
/* generate some space infront of the file to be played */
|
||||
for (x = 0; x < leadin; x++) {
|
||||
switch_frame_t *read_frame;
|
||||
status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
|
||||
|
||||
if (!SWITCH_READ_ACCEPTABLE(status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if all is well, really play the file */
|
||||
if (status == SWITCH_STATUS_SUCCESS) {
|
||||
char *dpath = NULL;
|
||||
|
||||
channel = switch_core_session_get_channel(session);
|
||||
if ((expanded = switch_channel_expand_variables(channel, path)) != path) {
|
||||
path = expanded;
|
||||
} else {
|
||||
expanded = NULL;
|
||||
}
|
||||
|
||||
if (!strncasecmp(path, "say:", 4)) {
|
||||
if (!(conference->tts_engine && conference->tts_voice)) {
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
} else {
|
||||
status = switch_ivr_speak_text(session, conference->tts_engine, conference->tts_voice, path + 4, ap);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!switch_is_file_path(path) && conference->sound_prefix) {
|
||||
if (!(dpath = switch_mprintf("%s%s%s", conference->sound_prefix, SWITCH_PATH_SEPARATOR, path))) {
|
||||
status = SWITCH_STATUS_MEMERR;
|
||||
goto done;
|
||||
}
|
||||
path = dpath;
|
||||
}
|
||||
|
||||
status = switch_ivr_play_file(session, NULL, path, ap);
|
||||
switch_safe_free(dpath);
|
||||
}
|
||||
|
||||
done:
|
||||
switch_safe_free(expanded);
|
||||
|
||||
return status;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
|
||||
/* stop the specified recording */
|
||||
switch_status_t conference_record_stop(conference_obj_t *conference, switch_stream_handle_t *stream, char *path)
|
||||
{
|
||||
conference_member_t *member = NULL;
|
||||
int count = 0;
|
||||
|
||||
switch_assert(conference != NULL);
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
for (member = conference->members; member; member = member->next) {
|
||||
if (member_test_flag(member, MFLAG_NOCHANNEL) && (!path || !strcmp(path, member->rec_path))) {
|
||||
if (!conference_test_flag(conference, CFLAG_CONF_RESTART_AUTO_RECORD) && member->rec && member->rec->autorec) {
|
||||
stream->write_function(stream, "Stopped AUTO recording file %s (Auto Recording Now Disabled)\n", member->rec_path);
|
||||
conference->auto_record = 0;
|
||||
} else {
|
||||
stream->write_function(stream, "Stopped recording file %s\n", member->rec_path);
|
||||
}
|
||||
|
||||
member_clear_flag_locked(member, MFLAG_RUNNING);
|
||||
count++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
conference->record_count -= count;
|
||||
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
return count;
|
||||
}
|
||||
/* stop/pause/resume the specified recording */
|
||||
switch_status_t conference_record_action(conference_obj_t *conference, char *path, recording_action_type_t action)
|
||||
{
|
||||
conference_member_t *member = NULL;
|
||||
int count = 0;
|
||||
//switch_file_handle_t *fh = NULL;
|
||||
|
||||
switch_assert(conference != NULL);
|
||||
switch_mutex_lock(conference->member_mutex);
|
||||
for (member = conference->members; member; member = member->next)
|
||||
{
|
||||
if (member_test_flag(member, MFLAG_NOCHANNEL) && (!path || !strcmp(path, member->rec_path)))
|
||||
{
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Action: %d\n", action);
|
||||
switch (action)
|
||||
{
|
||||
case REC_ACTION_STOP:
|
||||
member_clear_flag_locked(member, MFLAG_RUNNING);
|
||||
count++;
|
||||
break;
|
||||
case REC_ACTION_PAUSE:
|
||||
member_set_flag_locked(member, MFLAG_PAUSE_RECORDING);
|
||||
count = 1;
|
||||
break;
|
||||
case REC_ACTION_RESUME:
|
||||
member_clear_flag_locked(member, MFLAG_PAUSE_RECORDING);
|
||||
count = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch_mutex_unlock(conference->member_mutex);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/* Sub-Routine called by a record entity inside a conference */
|
||||
void *SWITCH_THREAD_FUNC conference_record_thread_run(switch_thread_t *thread, void *obj)
|
||||
{
|
||||
int16_t *data_buf;
|
||||
conference_member_t smember = { 0 }, *member;
|
||||
conference_record_t *rp, *last = NULL, *rec = (conference_record_t *) obj;
|
||||
conference_obj_t *conference = rec->conference;
|
||||
uint32_t samples = switch_samples_per_packet(conference->rate, conference->interval);
|
||||
uint32_t mux_used;
|
||||
char *vval;
|
||||
switch_timer_t timer = { 0 };
|
||||
uint32_t rlen;
|
||||
switch_size_t data_buf_len;
|
||||
switch_event_t *event;
|
||||
switch_size_t len = 0;
|
||||
int flags = 0;
|
||||
|
||||
data_buf_len = samples * sizeof(int16_t);
|
||||
|
||||
switch_zmalloc(data_buf, data_buf_len);
|
||||
|
||||
if (switch_thread_rwlock_tryrdlock(conference->rwlock) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Read Lock Fail\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data_buf_len = samples * sizeof(int16_t) * conference->channels;
|
||||
switch_zmalloc(data_buf, data_buf_len);
|
||||
|
||||
switch_mutex_lock(mod_conference_globals.hash_mutex);
|
||||
mod_conference_globals.threads++;
|
||||
switch_mutex_unlock(mod_conference_globals.hash_mutex);
|
||||
|
||||
member = &smember;
|
||||
|
||||
member->flags[MFLAG_CAN_HEAR] = member->flags[MFLAG_NOCHANNEL] = member->flags[MFLAG_RUNNING] = 1;
|
||||
|
||||
member->conference = conference;
|
||||
member->native_rate = conference->rate;
|
||||
member->rec = rec;
|
||||
member->rec_path = rec->path;
|
||||
member->rec_time = switch_epoch_time_now(NULL);
|
||||
member->rec->fh.channels = 1;
|
||||
member->rec->fh.samplerate = conference->rate;
|
||||
member->id = next_member_id();
|
||||
member->pool = rec->pool;
|
||||
|
||||
member->frame_size = SWITCH_RECOMMENDED_BUFFER_SIZE;
|
||||
member->frame = switch_core_alloc(member->pool, member->frame_size);
|
||||
member->mux_frame = switch_core_alloc(member->pool, member->frame_size);
|
||||
|
||||
|
||||
switch_mutex_init(&member->write_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_mutex_init(&member->flag_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_mutex_init(&member->fnode_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_mutex_init(&member->audio_in_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_mutex_init(&member->audio_out_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_mutex_init(&member->read_mutex, SWITCH_MUTEX_NESTED, rec->pool);
|
||||
switch_thread_rwlock_create(&member->rwlock, rec->pool);
|
||||
|
||||
/* Setup an audio buffer for the incoming audio */
|
||||
if (switch_buffer_create_dynamic(&member->audio_buffer, CONF_DBLOCK_SIZE, CONF_DBUFFER_SIZE, 0) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error Creating Audio Buffer!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup an audio buffer for the outgoing audio */
|
||||
if (switch_buffer_create_dynamic(&member->mux_buffer, CONF_DBLOCK_SIZE, CONF_DBUFFER_SIZE, 0) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error Creating Audio Buffer!\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (conference->canvas) {
|
||||
conference->canvas->send_keyframe = 1;
|
||||
}
|
||||
|
||||
member->rec->fh.pre_buffer_datalen = SWITCH_DEFAULT_FILE_BUFFER_LEN;
|
||||
|
||||
flags = SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT;
|
||||
|
||||
if (conference->members_with_video && conference_test_flag(conference, CFLAG_TRANSCODE_VIDEO)) {
|
||||
flags |= SWITCH_FILE_FLAG_VIDEO;
|
||||
if (conference->canvas) {
|
||||
char *orig_path = rec->path;
|
||||
rec->path = switch_core_sprintf(rec->pool, "{channels=%d,samplerate=%d,vw=%d,vh=%d,fps=%0.2f}%s",
|
||||
conference->channels,
|
||||
conference->rate,
|
||||
conference->canvas->width,
|
||||
conference->canvas->height,
|
||||
conference->video_fps.fps,
|
||||
orig_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (switch_core_file_open(&member->rec->fh, rec->path, (uint8_t) conference->channels, conference->rate, flags, rec->pool) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening File [%s]\n", rec->path);
|
||||
|
||||
if (test_eflag(conference, EFLAG_RECORD) &&
|
||||
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
|
||||
conference_add_event_data(conference, event);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "start-recording");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Path", rec->path);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Error", "File could not be opened for recording");
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->mutex);
|
||||
if (conference->video_floor_holder) {
|
||||
conference_member_t *member;
|
||||
if ((member = conference_member_get(conference, conference->video_floor_holder))) {
|
||||
if (member->session) {
|
||||
switch_core_session_video_reinit(member->session);
|
||||
}
|
||||
switch_thread_rwlock_unlock(member->rwlock);
|
||||
}
|
||||
}
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
|
||||
if (switch_core_timer_init(&timer, conference->timer_name, conference->interval, samples, rec->pool) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Setup timer success interval: %u samples: %u\n", conference->interval, samples);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Timer Setup Failed. Conference Cannot Start\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((vval = switch_mprintf("Conference %s", conference->name))) {
|
||||
switch_core_file_set_string(&member->rec->fh, SWITCH_AUDIO_COL_STR_TITLE, vval);
|
||||
switch_safe_free(vval);
|
||||
}
|
||||
|
||||
switch_core_file_set_string(&member->rec->fh, SWITCH_AUDIO_COL_STR_ARTIST, "FreeSWITCH mod_conference Software Conference Module");
|
||||
|
||||
if (test_eflag(conference, EFLAG_RECORD) &&
|
||||
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
|
||||
conference_add_event_data(conference, event);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "start-recording");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Path", rec->path);
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
if (conference_add_member(conference, member) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Joining Conference\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
while (member_test_flag(member, MFLAG_RUNNING) && conference_test_flag(conference, CFLAG_RUNNING) && (conference->count + conference->count_ghosts)) {
|
||||
|
||||
len = 0;
|
||||
|
||||
mux_used = (uint32_t) switch_buffer_inuse(member->mux_buffer);
|
||||
|
||||
if (member_test_flag(member, MFLAG_FLUSH_BUFFER)) {
|
||||
if (mux_used) {
|
||||
switch_mutex_lock(member->audio_out_mutex);
|
||||
switch_buffer_zero(member->mux_buffer);
|
||||
switch_mutex_unlock(member->audio_out_mutex);
|
||||
mux_used = 0;
|
||||
}
|
||||
member_clear_flag_locked(member, MFLAG_FLUSH_BUFFER);
|
||||
}
|
||||
|
||||
again:
|
||||
|
||||
if (switch_test_flag((&member->rec->fh), SWITCH_FILE_PAUSE)) {
|
||||
member_set_flag_locked(member, MFLAG_FLUSH_BUFFER);
|
||||
goto loop;
|
||||
}
|
||||
|
||||
if (mux_used >= data_buf_len) {
|
||||
/* Flush the output buffer and write all the data (presumably muxed) to the file */
|
||||
switch_mutex_lock(member->audio_out_mutex);
|
||||
//low_count = 0;
|
||||
|
||||
if ((rlen = (uint32_t) switch_buffer_read(member->mux_buffer, data_buf, data_buf_len))) {
|
||||
len = (switch_size_t) rlen / sizeof(int16_t) / conference->channels;
|
||||
}
|
||||
switch_mutex_unlock(member->audio_out_mutex);
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
mux_used = (uint32_t) switch_buffer_inuse(member->mux_buffer);
|
||||
|
||||
if (mux_used >= data_buf_len) {
|
||||
goto again;
|
||||
}
|
||||
|
||||
memset(data_buf, 255, (switch_size_t) data_buf_len);
|
||||
len = (switch_size_t) samples;
|
||||
}
|
||||
|
||||
if (!member_test_flag(member, MFLAG_PAUSE_RECORDING)) {
|
||||
if (!len || switch_core_file_write(&member->rec->fh, data_buf, &len) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Write Failed\n");
|
||||
member_clear_flag_locked(member, MFLAG_RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
loop:
|
||||
|
||||
switch_core_timer_next(&timer);
|
||||
} /* Rinse ... Repeat */
|
||||
|
||||
end:
|
||||
|
||||
for(;;) {
|
||||
switch_mutex_lock(member->audio_out_mutex);
|
||||
rlen = (uint32_t) switch_buffer_read(member->mux_buffer, data_buf, data_buf_len);
|
||||
switch_mutex_unlock(member->audio_out_mutex);
|
||||
|
||||
if (rlen > 0) {
|
||||
len = (switch_size_t) rlen / sizeof(int16_t)/ conference->channels;
|
||||
switch_core_file_write(&member->rec->fh, data_buf, &len);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch_safe_free(data_buf);
|
||||
switch_core_timer_destroy(&timer);
|
||||
conference_del_member(conference, member);
|
||||
|
||||
if (conference->canvas) {
|
||||
conference->canvas->send_keyframe = 1;
|
||||
}
|
||||
|
||||
switch_buffer_destroy(&member->audio_buffer);
|
||||
switch_buffer_destroy(&member->mux_buffer);
|
||||
member_clear_flag_locked(member, MFLAG_RUNNING);
|
||||
if (switch_test_flag((&member->rec->fh), SWITCH_FILE_OPEN)) {
|
||||
switch_mutex_lock(conference->mutex);
|
||||
switch_mutex_unlock(conference->mutex);
|
||||
switch_core_file_close(&member->rec->fh);
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Recording of %s Stopped\n", rec->path);
|
||||
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
|
||||
conference_add_event_data(conference, event);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "stop-recording");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Path", rec->path);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Samples-Out", "%ld", (long) member->rec->fh.samples_out);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Samplerate", "%ld", (long) member->rec->fh.samplerate);
|
||||
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Milliseconds-Elapsed", "%ld", (long) member->rec->fh.samples_out / (member->rec->fh.samplerate / 1000));
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
|
||||
if (rec->autorec && conference->auto_recording) {
|
||||
conference->auto_recording--;
|
||||
}
|
||||
|
||||
switch_mutex_lock(conference->flag_mutex);
|
||||
for (rp = conference->rec_node_head; rp; rp = rp->next) {
|
||||
if (rec == rp) {
|
||||
if (last) {
|
||||
last->next = rp->next;
|
||||
} else {
|
||||
conference->rec_node_head = rp->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch_mutex_unlock(conference->flag_mutex);
|
||||
|
||||
|
||||
if (rec->pool) {
|
||||
switch_memory_pool_t *pool = rec->pool;
|
||||
rec = NULL;
|
||||
switch_core_destroy_memory_pool(&pool);
|
||||
}
|
||||
|
||||
switch_mutex_lock(mod_conference_globals.hash_mutex);
|
||||
mod_conference_globals.threads--;
|
||||
switch_mutex_unlock(mod_conference_globals.hash_mutex);
|
||||
|
||||
switch_thread_rwlock_unlock(conference->rwlock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Neal Horman <neal at wanlink dot com>
|
||||
* Bret McDanel <trixter at 0xdecafbad dot com>
|
||||
* Dale Thatcher <freeswitch at dalethatcher dot com>
|
||||
* Chris Danielson <chris at maxpowersoft dot com>
|
||||
* Rupa Schomaker <rupa@rupa.com>
|
||||
* David Weekly <david@weekly.org>
|
||||
* Joao Mesquita <jmesquita@gmail.com>
|
||||
* Raymond Chandler <intralanman@freeswitch.org>
|
||||
* Seven Du <dujinfang@gmail.com>
|
||||
* Emmanuel Schmidbauer <e.schmidbauer@gmail.com>
|
||||
* William King <william.king@quentustech.com>
|
||||
*
|
||||
* mod_conference.c -- Software Conference Bridge
|
||||
*
|
||||
*/
|
||||
#include <mod_conference.h>
|
||||
|
||||
void set_mflags(const char *flags, member_flag_t *f)
|
||||
{
|
||||
if (flags) {
|
||||
char *dup = strdup(flags);
|
||||
char *p;
|
||||
char *argv[10] = { 0 };
|
||||
int i, argc = 0;
|
||||
|
||||
f[MFLAG_CAN_SPEAK] = f[MFLAG_CAN_HEAR] = f[MFLAG_CAN_BE_SEEN] = 1;
|
||||
|
||||
for (p = dup; p && *p; p++) {
|
||||
if (*p == ',') {
|
||||
*p = '|';
|
||||
}
|
||||
}
|
||||
|
||||
argc = switch_separate_string(dup, '|', argv, (sizeof(argv) / sizeof(argv[0])));
|
||||
|
||||
for (i = 0; i < argc && argv[i]; i++) {
|
||||
if (!strcasecmp(argv[i], "mute")) {
|
||||
f[MFLAG_CAN_SPEAK] = 0;
|
||||
f[MFLAG_TALKING] = 0;
|
||||
} else if (!strcasecmp(argv[i], "deaf")) {
|
||||
f[MFLAG_CAN_HEAR] = 0;
|
||||
} else if (!strcasecmp(argv[i], "mute-detect")) {
|
||||
f[MFLAG_MUTE_DETECT] = 1;
|
||||
} else if (!strcasecmp(argv[i], "dist-dtmf")) {
|
||||
f[MFLAG_DIST_DTMF] = 1;
|
||||
} else if (!strcasecmp(argv[i], "moderator")) {
|
||||
f[MFLAG_MOD] = 1;
|
||||
} else if (!strcasecmp(argv[i], "nomoh")) {
|
||||
f[MFLAG_NOMOH] = 1;
|
||||
} else if (!strcasecmp(argv[i], "endconf")) {
|
||||
f[MFLAG_ENDCONF] = 1;
|
||||
} else if (!strcasecmp(argv[i], "mintwo")) {
|
||||
f[MFLAG_MINTWO] = 1;
|
||||
} else if (!strcasecmp(argv[i], "video-bridge")) {
|
||||
f[MFLAG_VIDEO_BRIDGE] = 1;
|
||||
} else if (!strcasecmp(argv[i], "ghost")) {
|
||||
f[MFLAG_GHOST] = 1;
|
||||
} else if (!strcasecmp(argv[i], "join-only")) {
|
||||
f[MFLAG_JOIN_ONLY] = 1;
|
||||
} else if (!strcasecmp(argv[i], "positional")) {
|
||||
f[MFLAG_POSITIONAL] = 1;
|
||||
} else if (!strcasecmp(argv[i], "no-positional")) {
|
||||
f[MFLAG_NO_POSITIONAL] = 1;
|
||||
} else if (!strcasecmp(argv[i], "join-vid-floor")) {
|
||||
f[MFLAG_JOIN_VID_FLOOR] = 1;
|
||||
} else if (!strcasecmp(argv[i], "no-minimize-encoding")) {
|
||||
f[MFLAG_NO_MINIMIZE_ENCODING] = 1;
|
||||
} else if (!strcasecmp(argv[i], "second-screen")) {
|
||||
f[MFLAG_SECOND_SCREEN] = 1;
|
||||
f[MFLAG_CAN_SPEAK] = 0;
|
||||
f[MFLAG_TALKING] = 0;
|
||||
f[MFLAG_CAN_HEAR] = 0;
|
||||
f[MFLAG_SILENT] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
free(dup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void set_cflags(const char *flags, conference_flag_t *f)
|
||||
{
|
||||
if (flags) {
|
||||
char *dup = strdup(flags);
|
||||
char *p;
|
||||
char *argv[10] = { 0 };
|
||||
int i, argc = 0;
|
||||
|
||||
for (p = dup; p && *p; p++) {
|
||||
if (*p == ',') {
|
||||
*p = '|';
|
||||
}
|
||||
}
|
||||
|
||||
argc = switch_separate_string(dup, '|', argv, (sizeof(argv) / sizeof(argv[0])));
|
||||
|
||||
for (i = 0; i < argc && argv[i]; i++) {
|
||||
if (!strcasecmp(argv[i], "wait-mod")) {
|
||||
f[CFLAG_WAIT_MOD] = 1;
|
||||
} else if (!strcasecmp(argv[i], "video-floor-only")) {
|
||||
f[CFLAG_VID_FLOOR] = 1;
|
||||
} else if (!strcasecmp(argv[i], "audio-always")) {
|
||||
f[CFLAG_AUDIO_ALWAYS] = 1;
|
||||
} else if (!strcasecmp(argv[i], "restart-auto-record")) {
|
||||
f[CFLAG_CONF_RESTART_AUTO_RECORD] = 1;
|
||||
} else if (!strcasecmp(argv[i], "json-events")) {
|
||||
f[CFLAG_JSON_EVENTS] = 1;
|
||||
} else if (!strcasecmp(argv[i], "livearray-sync")) {
|
||||
f[CFLAG_LIVEARRAY_SYNC] = 1;
|
||||
} else if (!strcasecmp(argv[i], "livearray-json-status")) {
|
||||
f[CFLAG_JSON_STATUS] = 1;
|
||||
} else if (!strcasecmp(argv[i], "rfc-4579")) {
|
||||
f[CFLAG_RFC4579] = 1;
|
||||
} else if (!strcasecmp(argv[i], "auto-3d-position")) {
|
||||
f[CFLAG_POSITIONAL] = 1;
|
||||
} else if (!strcasecmp(argv[i], "minimize-video-encoding")) {
|
||||
f[CFLAG_MINIMIZE_VIDEO_ENCODING] = 1;
|
||||
} else if (!strcasecmp(argv[i], "video-bridge-first-two")) {
|
||||
f[CFLAG_VIDEO_BRIDGE_FIRST_TWO] = 1;
|
||||
} else if (!strcasecmp(argv[i], "video-required-for-canvas")) {
|
||||
f[CFLAG_VIDEO_REQUIRED_FOR_CANVAS] = 1;
|
||||
} else if (!strcasecmp(argv[i], "manage-inbound-video-bitrate")) {
|
||||
f[CFLAG_MANAGE_INBOUND_VIDEO_BITRATE] = 1;
|
||||
} else if (!strcasecmp(argv[i], "video-muxing-personal-canvas")) {
|
||||
f[CFLAG_PERSONAL_CANVAS] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
free(dup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void clear_eflags(char *events, uint32_t *f)
|
||||
{
|
||||
char buf[512] = "";
|
||||
char *next = NULL;
|
||||
char *event = buf;
|
||||
|
||||
if (events) {
|
||||
switch_copy_string(buf, events, sizeof(buf));
|
||||
|
||||
while (event) {
|
||||
next = strchr(event, ',');
|
||||
if (next) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
if (!strcmp(event, "add-member")) {
|
||||
*f &= ~EFLAG_ADD_MEMBER;
|
||||
} else if (!strcmp(event, "del-member")) {
|
||||
*f &= ~EFLAG_DEL_MEMBER;
|
||||
} else if (!strcmp(event, "energy-level")) {
|
||||
*f &= ~EFLAG_ENERGY_LEVEL;
|
||||
} else if (!strcmp(event, "volume-level")) {
|
||||
*f &= ~EFLAG_VOLUME_LEVEL;
|
||||
} else if (!strcmp(event, "gain-level")) {
|
||||
*f &= ~EFLAG_GAIN_LEVEL;
|
||||
} else if (!strcmp(event, "dtmf")) {
|
||||
*f &= ~EFLAG_DTMF;
|
||||
} else if (!strcmp(event, "stop-talking")) {
|
||||
*f &= ~EFLAG_STOP_TALKING;
|
||||
} else if (!strcmp(event, "start-talking")) {
|
||||
*f &= ~EFLAG_START_TALKING;
|
||||
} else if (!strcmp(event, "mute-detect")) {
|
||||
*f &= ~EFLAG_MUTE_DETECT;
|
||||
} else if (!strcmp(event, "mute-member")) {
|
||||
*f &= ~EFLAG_MUTE_MEMBER;
|
||||
} else if (!strcmp(event, "unmute-member")) {
|
||||
*f &= ~EFLAG_UNMUTE_MEMBER;
|
||||
} else if (!strcmp(event, "kick-member")) {
|
||||
*f &= ~EFLAG_KICK_MEMBER;
|
||||
} else if (!strcmp(event, "dtmf-member")) {
|
||||
*f &= ~EFLAG_DTMF_MEMBER;
|
||||
} else if (!strcmp(event, "energy-level-member")) {
|
||||
*f &= ~EFLAG_ENERGY_LEVEL_MEMBER;
|
||||
} else if (!strcmp(event, "volume-in-member")) {
|
||||
*f &= ~EFLAG_VOLUME_IN_MEMBER;
|
||||
} else if (!strcmp(event, "volume-out-member")) {
|
||||
*f &= ~EFLAG_VOLUME_OUT_MEMBER;
|
||||
} else if (!strcmp(event, "play-file")) {
|
||||
*f &= ~EFLAG_PLAY_FILE;
|
||||
} else if (!strcmp(event, "play-file-done")) {
|
||||
*f &= ~EFLAG_PLAY_FILE_DONE;
|
||||
} else if (!strcmp(event, "play-file-member")) {
|
||||
*f &= ~EFLAG_PLAY_FILE_MEMBER;
|
||||
} else if (!strcmp(event, "speak-text")) {
|
||||
*f &= ~EFLAG_SPEAK_TEXT;
|
||||
} else if (!strcmp(event, "speak-text-member")) {
|
||||
*f &= ~EFLAG_SPEAK_TEXT_MEMBER;
|
||||
} else if (!strcmp(event, "lock")) {
|
||||
*f &= ~EFLAG_LOCK;
|
||||
} else if (!strcmp(event, "unlock")) {
|
||||
*f &= ~EFLAG_UNLOCK;
|
||||
} else if (!strcmp(event, "transfer")) {
|
||||
*f &= ~EFLAG_TRANSFER;
|
||||
} else if (!strcmp(event, "bgdial-result")) {
|
||||
*f &= ~EFLAG_BGDIAL_RESULT;
|
||||
} else if (!strcmp(event, "floor-change")) {
|
||||
*f &= ~EFLAG_FLOOR_CHANGE;
|
||||
} else if (!strcmp(event, "record")) {
|
||||
*f &= ~EFLAG_RECORD;
|
||||
}
|
||||
|
||||
event = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void merge_mflags(member_flag_t *a, member_flag_t *b)
|
||||
{
|
||||
int x;
|
||||
|
||||
for (x = 0; x < MFLAG_MAX; x++) {
|
||||
if (b[x]) a[x] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void conference_set_flag(conference_obj_t *conference, conference_flag_t flag)
|
||||
{
|
||||
conference->flags[flag] = 1;
|
||||
}
|
||||
void conference_set_flag_locked(conference_obj_t *conference, conference_flag_t flag)
|
||||
{
|
||||
switch_mutex_lock(conference->flag_mutex);
|
||||
conference->flags[flag] = 1;
|
||||
switch_mutex_unlock(conference->flag_mutex);
|
||||
}
|
||||
void conference_clear_flag(conference_obj_t *conference, conference_flag_t flag)
|
||||
{
|
||||
conference->flags[flag] = 0;
|
||||
}
|
||||
void conference_clear_flag_locked(conference_obj_t *conference, conference_flag_t flag)
|
||||
{
|
||||
switch_mutex_lock(conference->flag_mutex);
|
||||
conference->flags[flag] = 0;
|
||||
switch_mutex_unlock(conference->flag_mutex);
|
||||
}
|
||||
switch_bool_t conference_test_flag(conference_obj_t *conference, conference_flag_t flag)
|
||||
{
|
||||
return !!conference->flags[flag];
|
||||
}
|
||||
|
||||
#if 0
|
||||
void conference_set_mflag(conference_obj_t *conference, member_flag_t mflag)
|
||||
{
|
||||
conference->mflags[mflag] = 1;
|
||||
}
|
||||
|
||||
void conference_clear_mflag(conference_obj_t *conference, member_flag_t mflag)
|
||||
{
|
||||
conference->mflags[mflag] = 0;
|
||||
}
|
||||
|
||||
switch_bool_t conference_test_mflag(conference_obj_t *conference, member_flag_t mflag)
|
||||
{
|
||||
return !!conference->mflags[mflag];
|
||||
}
|
||||
#endif
|
||||
|
||||
void member_set_flag(conference_member_t *member, member_flag_t flag)
|
||||
{
|
||||
member->flags[flag] = 1;
|
||||
}
|
||||
|
||||
void member_set_flag_locked(conference_member_t *member, member_flag_t flag)
|
||||
{
|
||||
switch_mutex_lock(member->flag_mutex);
|
||||
member->flags[flag] = 1;
|
||||
switch_mutex_unlock(member->flag_mutex);
|
||||
}
|
||||
|
||||
void member_clear_flag(conference_member_t *member, member_flag_t flag)
|
||||
{
|
||||
member->flags[flag] = 0;
|
||||
}
|
||||
void member_clear_flag_locked(conference_member_t *member, member_flag_t flag)
|
||||
{
|
||||
switch_mutex_lock(member->flag_mutex);
|
||||
member->flags[flag] = 0;
|
||||
switch_mutex_unlock(member->flag_mutex);
|
||||
}
|
||||
switch_bool_t member_test_flag(conference_member_t *member, member_flag_t flag)
|
||||
{
|
||||
return !!member->flags[flag];
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue