/*
 * 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>


api_command_t conference_api_sub_commands[] = {
	{"count", (void_fn_t) & conference_api_sub_count, CONF_API_SUB_ARGS_SPLIT, "count", ""},
	{"list", (void_fn_t) & conference_api_sub_list, CONF_API_SUB_ARGS_SPLIT, "list", "[delim <string>]|[count]"},
	{"xml_list", (void_fn_t) & conference_api_sub_xml_list, CONF_API_SUB_ARGS_SPLIT, "xml_list", ""},
	{"json_list", (void_fn_t) & conference_api_sub_json_list, CONF_API_SUB_ARGS_SPLIT, "json_list", "[compact]"},
	{"energy", (void_fn_t) & conference_api_sub_energy, CONF_API_SUB_MEMBER_TARGET, "energy", "<member_id|all|last|non_moderator> [<newval>]"},
	{"auto-energy", (void_fn_t) & conference_api_sub_auto_energy, CONF_API_SUB_MEMBER_TARGET, "auto-energy", "<member_id|all|last|non_moderator> [<newval>]"},
	{"max-energy", (void_fn_t) & conference_api_sub_max_energy, CONF_API_SUB_MEMBER_TARGET, "max-energy", "<member_id|all|last|non_moderator> [<newval>]"},
	{"agc", (void_fn_t) & conference_api_sub_agc, CONF_API_SUB_MEMBER_TARGET, "agc", "<member_id|all|last|non_moderator> [<newval>]"},
	{"vid-canvas", (void_fn_t) & conference_api_sub_canvas, CONF_API_SUB_MEMBER_TARGET, "vid-canvas", "<member_id|all|last|non_moderator> [<newval>]"},
	{"vid-watching-canvas", (void_fn_t) & conference_api_sub_watching_canvas, CONF_API_SUB_MEMBER_TARGET, "vid-watching-canvas", "<member_id|all|last|non_moderator> [<newval>]"},
	{"vid-layer", (void_fn_t) & conference_api_sub_layer, CONF_API_SUB_MEMBER_TARGET, "vid-layer", "<member_id|all|last|non_moderator> [<newval>]"},
	{"volume_in", (void_fn_t) & conference_api_sub_volume_in, CONF_API_SUB_MEMBER_TARGET, "volume_in", "<member_id|all|last|non_moderator> [<newval>]"},
	{"volume_out", (void_fn_t) & conference_api_sub_volume_out, CONF_API_SUB_MEMBER_TARGET, "volume_out", "<member_id|all|last|non_moderator> [<newval>]"},
	{"position", (void_fn_t) & conference_api_sub_position, CONF_API_SUB_MEMBER_TARGET, "position", "<member_id> <x>:<y>:<z>"},
	{"auto-3d-position", (void_fn_t) & conference_api_sub_auto_position, CONF_API_SUB_ARGS_SPLIT, "auto-3d-position", "[on|off]"},
	{"play", (void_fn_t) & conference_api_sub_play, CONF_API_SUB_ARGS_SPLIT, "play", "<file_path> [async|<member_id> [nomux]]"},
	{"pause_play", (void_fn_t) & conference_api_sub_pause_play, CONF_API_SUB_ARGS_SPLIT, "pause", "[<member_id>]"},
	{"play_status", (void_fn_t) & conference_api_sub_play_status, CONF_API_SUB_ARGS_SPLIT, "play_status", "[<member_id>]"},
	{"file_seek", (void_fn_t) & conference_api_sub_file_seek, CONF_API_SUB_ARGS_SPLIT, "file_seek", "[+-]<val> [<member_id>]"},
	{"say", (void_fn_t) & conference_api_sub_say, CONF_API_SUB_ARGS_AS_ONE, "say", "<text>"},
	{"saymember", (void_fn_t) & conference_api_sub_saymember, CONF_API_SUB_ARGS_AS_ONE, "saymember", "<member_id> <text>"},
	{"cam", (void_fn_t) & conference_api_sub_cam, CONF_API_SUB_ARGS_SPLIT, "cam", ""},
	{"stop", (void_fn_t) & conference_api_sub_stop, CONF_API_SUB_ARGS_SPLIT, "stop", "<[current|all|async|last]> [<member_id>]"},
	{"dtmf", (void_fn_t) & conference_api_sub_dtmf, CONF_API_SUB_MEMBER_TARGET, "dtmf", "<[member_id|all|last|non_moderator]> <digits>"},
	{"kick", (void_fn_t) & conference_api_sub_kick, CONF_API_SUB_MEMBER_TARGET, "kick", "<[member_id|all|last|non_moderator]> [<optional sound file>]"},
	{"vid-flip", (void_fn_t) & conference_api_sub_vid_flip, CONF_API_SUB_MEMBER_TARGET, "vid-flip", "<[member_id|all|last|non_moderator]>"},
	{"hup", (void_fn_t) & conference_api_sub_hup, CONF_API_SUB_MEMBER_TARGET, "hup", "<[member_id|all|last|non_moderator]>"},
	{"mute", (void_fn_t) & conference_api_sub_mute, CONF_API_SUB_MEMBER_TARGET, "mute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"tmute", (void_fn_t) & conference_api_sub_tmute, CONF_API_SUB_MEMBER_TARGET, "tmute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"unmute", (void_fn_t) & conference_api_sub_unmute, CONF_API_SUB_MEMBER_TARGET, "unmute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"vmute", (void_fn_t) & conference_api_sub_vmute, CONF_API_SUB_MEMBER_TARGET, "vmute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"tvmute", (void_fn_t) & conference_api_sub_tvmute, CONF_API_SUB_MEMBER_TARGET, "tvmute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"vmute-snap", (void_fn_t) & conference_api_sub_conference_video_vmute_snap, CONF_API_SUB_MEMBER_TARGET, "vmute-snap", "<[member_id|all]|last|non_moderator>"},
	{"unvmute", (void_fn_t) & conference_api_sub_unvmute, CONF_API_SUB_MEMBER_TARGET, "unvmute", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"vblind", (void_fn_t) & conference_api_sub_vblind, CONF_API_SUB_MEMBER_TARGET, "vblind", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"tvblind", (void_fn_t) & conference_api_sub_tvblind, CONF_API_SUB_MEMBER_TARGET, "tvblind", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"unvblind", (void_fn_t) & conference_api_sub_unvblind, CONF_API_SUB_MEMBER_TARGET, "unvblind", "<[member_id|all]|last|non_moderator> [<quiet>]"},
	{"deaf", (void_fn_t) & conference_api_sub_deaf, CONF_API_SUB_MEMBER_TARGET, "deaf", "<[member_id|all]|last|non_moderator>"},
	{"undeaf", (void_fn_t) & conference_api_sub_undeaf, CONF_API_SUB_MEMBER_TARGET, "undeaf", "<[member_id|all]|last|non_moderator>"},
	{"vid-filter", (void_fn_t) & conference_api_sub_video_filter, CONF_API_SUB_MEMBER_TARGET, "vid-filter", "<[member_id|all]|last|non_moderator> <string>"},
	{"relate", (void_fn_t) & conference_api_sub_relate, CONF_API_SUB_ARGS_SPLIT, "relate", "<member_id>[,<member_id>] <other_member_id>[,<other_member_id>] [nospeak|nohear|clear]"},
	{"lock", (void_fn_t) & conference_api_sub_lock, CONF_API_SUB_ARGS_SPLIT, "lock", ""},
	{"unlock", (void_fn_t) & conference_api_sub_unlock, CONF_API_SUB_ARGS_SPLIT, "unlock", ""},
	{"dial", (void_fn_t) & conference_api_sub_dial, CONF_API_SUB_ARGS_SPLIT, "dial", "<endpoint_module_name>/<destination> <callerid number> <callerid name>"},
	{"bgdial", (void_fn_t) & conference_api_sub_bgdial, CONF_API_SUB_ARGS_SPLIT, "bgdial", "<endpoint_module_name>/<destination> <callerid number> <callerid name>"},
	{"transfer", (void_fn_t) & conference_api_sub_transfer, CONF_API_SUB_ARGS_SPLIT, "transfer", "<conference_name> <member id> [...<member id>]"},
	{"record", (void_fn_t) & conference_api_sub_record, CONF_API_SUB_ARGS_SPLIT, "record", "<filename>"},
	{"chkrecord", (void_fn_t) & conference_api_sub_check_record, CONF_API_SUB_ARGS_SPLIT, "chkrecord", "<confname>"},
	{"norecord", (void_fn_t) & conference_api_sub_norecord, CONF_API_SUB_ARGS_SPLIT, "norecord", "<[filename|all]>"},
	{"pause", (void_fn_t) & conference_api_sub_pauserec, CONF_API_SUB_ARGS_SPLIT, "pause", "<filename>"},
	{"resume", (void_fn_t) & conference_api_sub_pauserec, CONF_API_SUB_ARGS_SPLIT, "resume", "<filename>"},
	{"recording", (void_fn_t) & conference_api_sub_recording, CONF_API_SUB_ARGS_SPLIT, "recording", "[start|stop|check|pause|resume] [<filename>|all]"},
	{"exit_sound", (void_fn_t) & conference_api_sub_exit_sound, CONF_API_SUB_ARGS_SPLIT, "exit_sound", "on|off|none|file <filename>"},
	{"enter_sound", (void_fn_t) & conference_api_sub_enter_sound, CONF_API_SUB_ARGS_SPLIT, "enter_sound", "on|off|none|file <filename>"},
	{"pin", (void_fn_t) & conference_api_sub_pin, CONF_API_SUB_ARGS_SPLIT, "pin", "<pin#>"},
	{"nopin", (void_fn_t) & conference_api_sub_pin, CONF_API_SUB_ARGS_SPLIT, "nopin", ""},
	{"get", (void_fn_t) & conference_api_sub_get, CONF_API_SUB_ARGS_SPLIT, "get", "<parameter-name>"},
	{"set", (void_fn_t) & conference_api_sub_set, CONF_API_SUB_ARGS_SPLIT, "set", "<max_members|sound_prefix|caller_id_name|caller_id_number|endconference_grace_time> <value>"},
	{"file-vol", (void_fn_t) & conference_api_sub_file_vol, CONF_API_SUB_ARGS_SPLIT, "file-vol", "<vol#>"},
	{"floor", (void_fn_t) & conference_api_sub_floor, CONF_API_SUB_MEMBER_TARGET, "floor", "<member_id|last>"},
	{"vid-floor", (void_fn_t) & conference_api_sub_vid_floor, CONF_API_SUB_MEMBER_TARGET, "vid-floor", "<member_id|last> [force]"},
	{"vid-banner", (void_fn_t) & conference_api_sub_vid_banner, CONF_API_SUB_MEMBER_TARGET, "vid-banner", "<member_id|last> <text>"},
	{"vid-mute-img", (void_fn_t) & conference_api_sub_vid_mute_img, CONF_API_SUB_MEMBER_TARGET, "vid-mute-img", "<member_id|last> [<path>|clear]"},
	{"vid-logo-img", (void_fn_t) & conference_api_sub_vid_logo_img, CONF_API_SUB_MEMBER_TARGET, "vid-logo-img", "<member_id|last> [<path>|clear]"},
	{"vid-codec-group", (void_fn_t) & conference_api_sub_vid_codec_group, CONF_API_SUB_MEMBER_TARGET, "vid-codec-group", "<member_id|last> [<group>|clear]"},
	{"vid-res-id", (void_fn_t) & conference_api_sub_vid_res_id, CONF_API_SUB_MEMBER_TARGET, "vid-res-id", "<member_id|last> <val>|clear"},
	{"vid-role-id", (void_fn_t) & conference_api_sub_vid_role_id, CONF_API_SUB_MEMBER_TARGET, "vid-role-id", "<member_id|last> <val>|clear"},
	{"get-uuid", (void_fn_t) & conference_api_sub_get_uuid, CONF_API_SUB_MEMBER_TARGET, "get-uuid", "<member_id|last>"},
	{"clear-vid-floor", (void_fn_t) & conference_api_sub_clear_vid_floor, CONF_API_SUB_ARGS_AS_ONE, "clear-vid-floor", ""},
	{"vid-layout", (void_fn_t) & conference_api_sub_vid_layout, CONF_API_SUB_ARGS_SPLIT, "vid-layout", "<layout name>|group <group name> [<canvas id>]"},
	{"vid-write-png", (void_fn_t) & conference_api_sub_write_png, CONF_API_SUB_ARGS_SPLIT, "vid-write-png", "<path>"},
	{"vid-fps", (void_fn_t) & conference_api_sub_vid_fps, CONF_API_SUB_ARGS_SPLIT, "vid-fps", "<fps>"},
	{"vid-res", (void_fn_t) & conference_api_sub_vid_res, CONF_API_SUB_ARGS_SPLIT, "vid-res", "<WxH>"},
	{"vid-fgimg", (void_fn_t) & conference_api_sub_canvas_fgimg, CONF_API_SUB_ARGS_SPLIT, "vid-fgimg", "<file> | clear [<canvas-id>]"},
	{"vid-bgimg", (void_fn_t) & conference_api_sub_canvas_bgimg, CONF_API_SUB_ARGS_SPLIT, "vid-bgimg", "<file> | clear [<canvas-id>]"},
	{"vid-bandwidth", (void_fn_t) & conference_api_sub_vid_bandwidth, CONF_API_SUB_ARGS_SPLIT, "vid-bandwidth", "<BW>"},
	{"vid-personal", (void_fn_t) & conference_api_sub_vid_personal, CONF_API_SUB_ARGS_SPLIT, "vid-personal", "[on|off]"}
};

switch_status_t conference_api_sub_pause_play(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	if (argc == 2) {
		switch_mutex_lock(conference->mutex);
		conference_fnode_toggle_pause(conference->fnode, stream);
		switch_mutex_unlock(conference->mutex);

		return SWITCH_STATUS_SUCCESS;
	}

	if (argc == 3) {
		uint32_t id = atoi(argv[2]);
		conference_member_t *member;

		if ((member = conference_member_get(conference, id))) {
			switch_mutex_lock(member->fnode_mutex);
			conference_fnode_toggle_pause(member->fnode, stream);
			switch_mutex_unlock(member->fnode_mutex);
			switch_thread_rwlock_unlock(member->rwlock);
			return SWITCH_STATUS_SUCCESS;
		} else {
			stream->write_function(stream, "-ERR Member: %u not found.\n", id);
		}
	}

	return SWITCH_STATUS_GENERR;
}

switch_status_t conference_api_sub_play_status(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	if (argc == 2) {
		switch_mutex_lock(conference->mutex);
		conference_fnode_check_status(conference->fnode, stream);
		switch_mutex_unlock(conference->mutex);

		return SWITCH_STATUS_SUCCESS;
	}

	if (argc == 3) {
		uint32_t id = atoi(argv[2]);
		conference_member_t *member;

		if ((member = conference_member_get(conference, id))) {
			switch_mutex_lock(member->fnode_mutex);
			conference_fnode_check_status(member->fnode, stream);
			switch_mutex_unlock(member->fnode_mutex);
			switch_thread_rwlock_unlock(member->rwlock);
			return SWITCH_STATUS_SUCCESS;
		} else {
			stream->write_function(stream, "-ERR Member: %u not found.\n", id);
		}
	}

	return SWITCH_STATUS_GENERR;
}

/* _In_opt_z_ const char *cmd, _In_opt_ switch_core_session_t *session, _In_ switch_stream_handle_t *stream */
switch_status_t conference_api_main_real(const char *cmd, switch_core_session_t *session, switch_stream_handle_t *stream)
{
	char *lbuf = NULL;
	switch_status_t status = SWITCH_STATUS_SUCCESS;
	char *http = NULL, *type = NULL;
	int argc;
	char *argv[25] = { 0 };

	if (!cmd) {
		cmd = "help";
	}

	if (stream->param_event) {
		http = switch_event_get_header(stream->param_event, "http-host");
		type = switch_event_get_header(stream->param_event, "content-type");
	}

	if (http) {
		/* Output must be to a web browser */
		if (type && !strcasecmp(type, "text/html")) {
			stream->write_function(stream, "<pre>\n");
		}
	}

	if (!(lbuf = strdup(cmd))) {
		return status;
	}

	argc = switch_separate_string(lbuf, ' ', argv, (sizeof(argv) / sizeof(argv[0])));

	/* try to find a command to execute */
	if (argc && argv[0]) {
		conference_obj_t *conference = NULL;

		if ((conference = conference_find(argv[0], NULL))) {
			if (argc >= 2) {
				conference_api_dispatch(conference, stream, argc, argv, cmd, 1);
			} else {
				stream->write_function(stream, "Conference command, not specified.\nTry 'help'\n");
			}
			switch_thread_rwlock_unlock(conference->rwlock);

		} else if (argv[0]) {
			/* special case the list command, because it doesn't require a conference argument */
			if (strcasecmp(argv[0], "list") == 0) {
				conference_api_sub_list(NULL, stream, argc, argv);
			} else if (strcasecmp(argv[0], "count") == 0) {
				conference_api_sub_count(NULL, stream, argc, argv);
			} else if (strcasecmp(argv[0], "xml_list") == 0) {
				conference_api_sub_xml_list(NULL, stream, argc, argv);
			} else if (strcasecmp(argv[0], "json_list") == 0) {
				conference_api_sub_json_list(NULL, stream, argc, argv);
			} else if (strcasecmp(argv[0], "help") == 0 || strcasecmp(argv[0], "commands") == 0) {
				stream->write_function(stream, "%s\n", api_syntax);
			} else if (argv[1] && strcasecmp(argv[1], "dial") == 0) {
				if (conference_api_sub_dial(NULL, stream, argc, argv) != SWITCH_STATUS_SUCCESS) {
					/* command returned error, so show syntax usage */
					stream->write_function(stream, "%s %s", conference_api_sub_commands[CONF_API_COMMAND_DIAL].pcommand,
										   conference_api_sub_commands[CONF_API_COMMAND_DIAL].psyntax);
				}
			} else if (argv[1] && strcasecmp(argv[1], "bgdial") == 0) {
				if (conference_api_sub_bgdial(NULL, stream, argc, argv) != SWITCH_STATUS_SUCCESS) {
					/* command returned error, so show syntax usage */
					stream->write_function(stream, "%s %s", conference_api_sub_commands[CONF_API_COMMAND_BGDIAL].pcommand,
										   conference_api_sub_commands[CONF_API_COMMAND_BGDIAL].psyntax);
				}
			} else {
				stream->write_function(stream, "-ERR Conference %s not found\n", argv[0]);
			}
		}

	} else {
		int i;

		for (i = 0; i < CONFFUNCAPISIZE; i++) {
			stream->write_function(stream, "<conf name> %s %s\n", conference_api_sub_commands[i].pcommand, conference_api_sub_commands[i].psyntax);
		}
	}


	switch_safe_free(lbuf);

	return status;
}

switch_status_t conference_api_sub_syntax(char **syntax)
{
	/* build api interface help ".syntax" field string */
	uint32_t i;
	size_t nl = 0, ol = 0;
	char cmd_str[256];
	char *tmp = NULL, *p = strdup("");

	for (i = 0; i < CONFFUNCAPISIZE; i++) {
		nl = strlen(conference_api_sub_commands[i].pcommand) + strlen(conference_api_sub_commands[i].psyntax) + 5;

		switch_snprintf(cmd_str, sizeof(cmd_str), "add conference ::conference::conference_list_conferences %s", conference_api_sub_commands[i].pcommand);
		switch_console_set_complete(cmd_str);

		if (p != NULL) {
			ol = strlen(p);
		}
		tmp = realloc(p, ol + nl);
		if (tmp != NULL) {
			p = tmp;
			strcat(p, "\t\t");
			strcat(p, conference_api_sub_commands[i].pcommand);
			if (!zstr(conference_api_sub_commands[i].psyntax)) {
				strcat(p, " ");
				strcat(p, conference_api_sub_commands[i].psyntax);
			}
			if (i < CONFFUNCAPISIZE - 1) {
				strcat(p, "\n");
			}
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't realloc\n");
			return SWITCH_STATUS_TERM;
		}
	}

	*syntax = p;

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_mute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	conference_utils_member_clear_flag_locked(member, MFLAG_CAN_SPEAK);
	conference_utils_member_clear_flag_locked(member, MFLAG_TALKING);

	if (member->session && !conference_utils_member_test_flag(member, MFLAG_MUTE_DETECT)) {
		switch_core_media_hard_mute(member->session, SWITCH_TRUE);
	}

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_MUTE);
	}
	member->score_iir = 0;

	if (stream != NULL) {
		stream->write_function(stream, "+OK mute %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_MUTE_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "mute-member");
		switch_event_fire(&event);
	}

	if (conference_utils_test_flag(member->conference, CFLAG_POSITIONAL)) {
		conference_al_gen_arc(member->conference, NULL);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_tmute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (conference_utils_member_test_flag(member, MFLAG_CAN_SPEAK)) {
		return conference_api_sub_mute(member, stream, data);
	}

	return conference_api_sub_unmute(member, stream, data);
}


switch_status_t conference_api_sub_unmute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	conference_utils_member_set_flag_locked(member, MFLAG_CAN_SPEAK);

	if (member->session && !conference_utils_member_test_flag(member, MFLAG_MUTE_DETECT)) {
		switch_core_media_hard_mute(member->session, SWITCH_FALSE);
	}

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_UNMUTE);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK unmute %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_MUTE_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "unmute-member");
		switch_event_fire(&event);
	}

	if (conference_utils_test_flag(member->conference, CFLAG_POSITIONAL)) {
		conference_al_gen_arc(member->conference, NULL);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_conference_video_vmute_snap(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_bool_t clear = SWITCH_FALSE;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (switch_core_session_media_flow(member->session, SWITCH_MEDIA_TYPE_VIDEO) == SWITCH_MEDIA_FLOW_SENDONLY) {
		return SWITCH_STATUS_SUCCESS;
	}

	if (!member->conference->canvases[0]) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK vmute image snapped %u\n", member->id);
	}

	if (data && !strcasecmp((char *)data, "clear")) {
		clear = SWITCH_TRUE;
	}

	conference_video_vmute_snap(member, clear);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vmute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (switch_core_session_media_flow(member->session, SWITCH_MEDIA_TYPE_VIDEO) == SWITCH_MEDIA_FLOW_SENDONLY) {
		return SWITCH_STATUS_SUCCESS;
	}

	conference_utils_member_clear_flag_locked(member, MFLAG_CAN_BE_SEEN);
	conference_video_reset_video_bitrate_counters(member);

	if (member->channel) {
		switch_channel_set_flag(member->channel, CF_VIDEO_PAUSE_READ);
		switch_core_session_request_video_refresh(member->session);
		switch_channel_video_sync(member->channel);
	}

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_MUTE);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK vmute %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_MUTE_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "vmute-member");
		switch_event_fire(&event);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_tvmute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (conference_utils_member_test_flag(member, MFLAG_CAN_BE_SEEN)) {
		return conference_api_sub_vmute(member, stream, data);
	}

	return conference_api_sub_unvmute(member, stream, data);
}


switch_status_t conference_api_sub_unvmute(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;
	mcu_layer_t *layer = NULL;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (switch_core_session_media_flow(member->session, SWITCH_MEDIA_TYPE_VIDEO) == SWITCH_MEDIA_FLOW_SENDONLY) {
		return SWITCH_STATUS_SUCCESS;
	}

	if ((layer = conference_video_get_layer_locked(member))) {
		layer->clear = 1;
		conference_video_release_layer(&layer);
	}

	conference_utils_member_set_flag_locked(member, MFLAG_CAN_BE_SEEN);
	conference_video_reset_video_bitrate_counters(member);

	if (member->channel) {
		switch_channel_clear_flag(member->channel, CF_VIDEO_PAUSE_READ);
		switch_channel_video_sync(member->channel);
	}

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_UNMUTE);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK unvmute %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_MUTE_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "unvmute-member");
		switch_event_fire(&event);
	}


	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vblind(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (switch_core_session_media_flow(member->session, SWITCH_MEDIA_TYPE_VIDEO) == SWITCH_MEDIA_FLOW_SENDONLY) {
		return SWITCH_STATUS_SUCCESS;
	}

	switch_core_session_write_blank_video(member->session, 50);
	conference_utils_member_clear_flag_locked(member, MFLAG_CAN_SEE);
	conference_video_reset_video_bitrate_counters(member);

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_BLIND);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK vblind %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_BLIND_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "vblind-member");
		switch_event_fire(&event);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_tvblind(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (conference_utils_member_test_flag(member, MFLAG_CAN_SEE)) {
		return conference_api_sub_vblind(member, stream, data);
	}

	return conference_api_sub_unvblind(member, stream, data);
}


switch_status_t conference_api_sub_unvblind(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (switch_core_session_media_flow(member->session, SWITCH_MEDIA_TYPE_VIDEO) == SWITCH_MEDIA_FLOW_SENDONLY) {
		return SWITCH_STATUS_SUCCESS;
	}

	conference_utils_member_set_flag_locked(member, MFLAG_CAN_SEE);
	conference_video_reset_video_bitrate_counters(member);

	switch_channel_set_flag(member->channel, CF_VIDEO_REFRESH_REQ);

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_UNBLIND);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK unvblind %u\n", member->id);
	}

	if (test_eflag(member->conference, EFLAG_BLIND_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "unvblind-member");
		switch_event_fire(&event);
	}


	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_deaf(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	conference_utils_member_clear_flag_locked(member, MFLAG_CAN_HEAR);

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_DEAF);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK deaf %u\n", member->id);
	}
	if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "deaf-member");
		switch_event_fire(&event);
	}

	if (conference_utils_test_flag(member->conference, CFLAG_POSITIONAL)) {
		conference_al_gen_arc(member->conference, NULL);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_video_filter(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *filter_str = (char *) data;

	switch_core_video_parse_filter_string(&member->video_filters, filter_str);

	stream->write_function(stream, "+OK\n");

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_undeaf(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	conference_utils_member_set_flag_locked(member, MFLAG_CAN_HEAR);

	if (!(data) || !strstr((char *) data, "quiet")) {
		conference_utils_member_set_flag(member, MFLAG_INDICATE_UNDEAF);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK undeaf %u\n", member->id);
	}
	if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "undeaf-member");
		switch_event_fire(&event);
	}

	if (conference_utils_test_flag(member->conference, CFLAG_POSITIONAL)) {
		conference_al_gen_arc(member->conference, NULL);
	}

	conference_member_update_status_field(member);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_hup(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	conference_utils_member_clear_flag(member, MFLAG_RUNNING);

	if (member->conference && test_eflag(member->conference, EFLAG_HUP_MEMBER)) {
		if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_member_add_event_data(member, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "hup-member");
			switch_event_fire(&event);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_kick(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	conference_utils_member_clear_flag(member, MFLAG_RUNNING);
	conference_utils_member_set_flag_locked(member, MFLAG_KICKED);
	switch_core_session_kill_channel(member->session, SWITCH_SIG_BREAK);

	if (data && member->session) {
		member->kicked_sound = switch_core_session_strdup(member->session, (char *) data);
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK kicked %u\n", member->id);
	}

	if (member->conference && test_eflag(member->conference, EFLAG_KICK_MEMBER)) {
		if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_member_add_event_data(member, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "kick-member");
			switch_event_fire(&event);
		}
	}

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_vid_flip(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *arg = (char *) data;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (conference_utils_member_test_flag(member, MFLAG_FLIP_VIDEO) && !arg) {
		conference_utils_member_clear_flag_locked(member, MFLAG_FLIP_VIDEO);
		conference_utils_member_clear_flag_locked(member, MFLAG_ROTATE_VIDEO);
	} else {
		conference_utils_member_set_flag_locked(member, MFLAG_FLIP_VIDEO);

		if (arg) {
			if (!strcasecmp(arg, "rotate")) {
				conference_utils_member_set_flag_locked(member, MFLAG_ROTATE_VIDEO);
			} else if (switch_is_number(arg)) {
				int num = atoi(arg);

				if (num == 0 || num == 90 || num == 180 || num == 270) {
					member->flip = num;
				}
			}
		} else {
			member->flip = 180;
		}
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK flipped %u\n", member->id);
	}


	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_dtmf(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;
	char *dtmf = (char *) data;

	if (member == NULL) {
		stream->write_function(stream, "-ERR Invalid member!\n");
		return SWITCH_STATUS_GENERR;
	}

	if (zstr(dtmf)) {
		stream->write_function(stream, "-ERR Invalid input!\n");
		return SWITCH_STATUS_GENERR;
	} else {
		char *p;

		for(p = dtmf; p && *p; p++) {
			switch_dtmf_t *dt, digit = { *p, SWITCH_DEFAULT_DTMF_DURATION };

			switch_zmalloc(dt, sizeof(*dt));
			*dt = digit;

			switch_queue_push(member->dtmf_queue, dt);
			switch_core_session_kill_channel(member->session, SWITCH_SIG_BREAK);
		}
	}

	if (stream != NULL) {
		stream->write_function(stream, "+OK sent %s to %u\n", (char *) data, member->id);
	}

	if (test_eflag(member->conference, EFLAG_DTMF_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "dtmf-member");
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Digits", dtmf);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_watching_canvas(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	int index;
	char *val = (char *) data;

	if (member->conference->canvas_count == 1) {
		stream->write_function(stream, "-ERR Only 1 Canvas\n");
		return SWITCH_STATUS_SUCCESS;
	}

	index = conference_member_get_canvas_id(member, val, SWITCH_TRUE);

	if (index < 0) {
		stream->write_function(stream, "-ERR Invalid DATA\n");
		return SWITCH_STATUS_SUCCESS;
	}

	member->watching_canvas_id = index;
	conference_video_reset_member_codec_index(member);
	switch_core_session_request_video_refresh(member->session);
	switch_core_media_gen_key_frame(member->session);
	member->conference->canvases[index]->send_keyframe = 10;
	member->conference->canvases[index]->refresh = 1;
	stream->write_function(stream, "+OK watching canvas %d\n", index + 1);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_canvas(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	int index;
	char *val = (char *) data;
	//mcu_canvas_t *canvas = NULL;

	if (member->conference->canvas_count == 1) {
		stream->write_function(stream, "-ERR Only 1 Canvas\n");
		return SWITCH_STATUS_SUCCESS;
	}

	switch_mutex_lock(member->conference->canvas_mutex);

	index = conference_member_get_canvas_id(member, val, SWITCH_FALSE);

	if (index < 0) {
		stream->write_function(stream, "-ERR Invalid DATA\n");
		switch_mutex_unlock(member->conference->canvas_mutex);
		return SWITCH_STATUS_SUCCESS;
	}

	conference_video_detach_video_layer(member);
	member->canvas_id = index;
	member->layer_timeout = DEFAULT_LAYER_TIMEOUT;

	//canvas = member->conference->canvases[member->canvas_id];
	//conference_video_attach_video_layer(member, canvas, index);
	conference_video_reset_member_codec_index(member);
	switch_mutex_unlock(member->conference->canvas_mutex);

	switch_core_session_request_video_refresh(member->session);
	switch_core_media_gen_key_frame(member->session);
	member->conference->canvases[index]->send_keyframe = 10;
	member->conference->canvases[index]->refresh = 1;
	stream->write_function(stream, "+OK canvas %d\n", member->canvas_id + 1);

	return SWITCH_STATUS_SUCCESS;
}



switch_status_t conference_api_sub_layer(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	int index = -1;
	mcu_canvas_t *canvas = NULL;
	char *val = (char *) data;

	if (!val) {
		stream->write_function(stream, "-ERR Invalid DATA\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (member->canvas_id < 0) {
		stream->write_function(stream, "-ERR Invalid Canvas\n");
		return SWITCH_STATUS_FALSE;
	}


	switch_mutex_lock(member->conference->canvas_mutex);

	if (switch_is_number(val)) {
		index = atoi(val) - 1;

		if (index < 0) {
			index = 0;
		}
	} else {
		index = member->video_layer_id;

		if (index < 0) index = 0;

		if (!strcasecmp(val, "next")) {
			index++;
		} else if (!strcasecmp(val, "prev")) {
			index--;
		}
	}

	canvas = member->conference->canvases[member->canvas_id];

	if (index >= canvas->total_layers) {
		index = 0;
	}

	if (index < 0) {
		index = canvas->total_layers - 1;
	}

	conference_video_attach_video_layer(member, canvas, index);
	switch_mutex_unlock(member->conference->canvas_mutex);

	switch_core_session_request_video_refresh(member->session);
	switch_core_media_gen_key_frame(member->session);
	canvas->send_keyframe = 10;
	canvas->refresh = 1;
	stream->write_function(stream, "+OK layer %d\n", member->video_layer_id + 1);

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_energy(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->energy_level += 200;
			if (member->energy_level > 1800) {
				member->energy_level = 1800;
			}
		} else if (!strcasecmp(data, "down")) {
			member->energy_level -= 200;
			if (member->energy_level < 0) {
				member->energy_level = 0;
			}
		} else {
			member->energy_level = atoi((char *) data);
		}
		unlock_member(member);
	}
	if (stream != NULL) {
		stream->write_function(stream, "Energy %u = %d\n", member->id, member->energy_level);
	}

	if (member->auto_energy_level && member->energy_level > member->auto_energy_level) {
		member->auto_energy_level  = 0;
		stream->write_function(stream, "Auto-Energy level exceeded, Auto-Energy mode disabled\n", SWITCH_VA_NONE);
	}


	if (test_eflag(member->conference, EFLAG_ENERGY_LEVEL_MEMBER) &&
		data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "energy-level-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Energy-Level", "%d", member->energy_level);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

void conference_api_set_agc(conference_member_t *member, const char *data)
{
	int tmp = 0;
	char *argv[4] = { 0 };
	char *conf;

	if (data) {
		conf = switch_core_strdup(member->pool, data);
		switch_split(conf, ':', argv);
	} else {
		member->agc_level = member->conference->agc_level;
		member->agc_low_energy_level = member->conference->agc_low_energy_level;
		member->agc_change_factor = member->conference->agc_change_factor;
		member->agc_margin = member->conference->agc_margin;
		member->agc_period_len = member->conference->agc_period_len;
	}

	if (argv[0]) {
		tmp = atoi(argv[0]);

		if (tmp > 0) {
			member->agc_level = tmp;
		}
	}

	if (argv[1]) {
		tmp = atoi(argv[1]);

		if (tmp > 0) {
			member->agc_low_energy_level = tmp;
		}
	}


	if (argv[2]) {
		tmp = atoi(argv[2]);

		if (tmp > 0) {
			member->agc_change_factor = tmp;
		}
	}


	if (argv[2]) {
		tmp = atoi(argv[0]);

		if (tmp > 0) {
			member->agc_period_len = (1000 / member->conference->interval) * tmp;
		}
	}


	if (!member->agc) {
		switch_agc_create(&member->agc, member->agc_level, member->agc_low_energy_level, member->agc_margin,
						  member->agc_change_factor, member->agc_period_len);
		switch_agc_set_token(member->agc, switch_channel_get_name(member->channel));
	} else {
		switch_agc_set(member->agc, member->agc_level, member->agc_low_energy_level, member->agc_margin,
					   member->agc_change_factor, member->agc_period_len);
	}

}


switch_status_t conference_api_sub_agc(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->agc_level += 200;
			if (member->agc_level > 1800) {
				member->agc_level = 1800;
			}
		} else if (!strcasecmp(data, "down")) {
			member->agc_level -= 200;
			if (member->agc_level < 0) {
				member->agc_level = 0;
			}
		} else {
			conference_api_set_agc(member, (char *)data);
		}
		unlock_member(member);
	}
	if (stream != NULL) {
		stream->write_function(stream, "Agc %u = %d\n", member->id, member->agc_level);
	}


	//	if (test_eflag(member->conference, EFLAG_AGC_LEVEL_MEMBER) &&
	if (data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "agc-level-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Agc-Level", "%d", member->agc_level);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_auto_energy(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->auto_energy_level += 200;
			if (member->auto_energy_level > 1800) {
				member->auto_energy_level = 1800;
			}
		} else if (!strcasecmp(data, "down")) {
			member->auto_energy_level -= 200;
			if (member->auto_energy_level < 0) {
				member->auto_energy_level = 0;
			}
		} else {
			member->auto_energy_level = atoi((char *) data);
		}
		unlock_member(member);
	}
	if (stream != NULL) {
		stream->write_function(stream, "%u = Auto-Energy: %d Energy: %d\n", member->id, member->auto_energy_level, member->energy_level);
	}

	if (!member->energy_level) {
		member->energy_level = member->auto_energy_level / 2;
	}


	if (test_eflag(member->conference, EFLAG_ENERGY_LEVEL_MEMBER) &&
		data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "auto-energy-level-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Auto-Energy-Level", "%d", member->auto_energy_level);
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Energy-Level", "%d", member->energy_level);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_max_energy(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->max_energy_level += 200;
			if (member->max_energy_level > 1800) {
				member->max_energy_level = 1800;
			}
		} else if (!strcasecmp(data, "down")) {
			member->max_energy_level -= 200;
			if (member->max_energy_level < 0) {
				member->max_energy_level = 0;
			}
		} else {
			member->max_energy_level = atoi((char *) data);
		}
		unlock_member(member);
	}

	if (member->max_energy_level && member->max_energy_level < member->energy_level) {
		member->max_energy_level = 0;
		stream->write_function(stream, "-ERR %u Max-Energy cannot exceed energy level.\n", member->id);
	} else if (data) {
		char *p, *q;
		if ((p = strchr(data, ':'))) {
			p++;
			if (*p) {
				int tmp = atoi(p);
				if (tmp >= 0) {
					member->burst_mute_count = tmp / member->conference->interval;
				}

				if ((q = strchr(p, ':'))) {
					q++;
					if (*q) {
						int tmp = atoi(q);
						
						if (tmp >= 0) {
							member->max_energy_hit_trigger = tmp;
						}
					}
				}
			}
		}
	}


	if (stream != NULL) {
		stream->write_function(stream, "%u = Max-Energy: %d Energy: %d Max-Energy-Mute: %dms Max-Energy-Hit-Trigger %d\n", 
							   member->id, member->energy_level, member->max_energy_level, member->burst_mute_count * member->conference->interval, member->max_energy_hit_trigger);
	}


	if (test_eflag(member->conference, EFLAG_ENERGY_LEVEL_MEMBER) &&
		data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "max-energy-level-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Max-Energy-Level", "%d", member->max_energy_level);
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Max-Energy-Mute", "%d", member->burst_mute_count * member->conference->interval);
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Max-Energy-Hit-Trigger", "%d", member->max_energy_hit_trigger);
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Energy-Level", "%d", member->max_energy_level);

		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_auto_position(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
#ifdef OPENAL_POSITIONING
	char *arg = NULL;
	int set = 0;

	if (argc > 2) {
		arg = argv[2];
	}


	if (!zstr(arg)) {
		if (!strcasecmp(arg, "on")) {
			conference_utils_set_flag(conference, CFLAG_POSITIONAL);
			set = 1;
		} else if (!strcasecmp(arg, "off")) {
			conference_utils_clear_flag(conference, CFLAG_POSITIONAL);
		}
	}

	if (set && conference_utils_test_flag(conference, CFLAG_POSITIONAL)) {
		conference_al_gen_arc(conference, stream);
	}

	stream->write_function(stream, "+OK positioning %s\n", conference_utils_test_flag(conference, CFLAG_POSITIONAL) ? "on" : "off");

#else
	stream->write_function(stream, "-ERR not supported\n");

#endif

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_position(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
#ifndef OPENAL_POSITIONING
	if (stream) stream->write_function(stream, "-ERR not supported\n");
#else
	switch_event_t *event;

	if (member == NULL) {
		return SWITCH_STATUS_GENERR;
	}

	if (conference_utils_member_test_flag(member, MFLAG_NO_POSITIONAL)) {
		if (stream) stream->write_function(stream, 
										   "-ERR %s has positional audio blocked.\n", switch_channel_get_name(member->channel));
		return SWITCH_STATUS_SUCCESS;
	}

	if (!member->al) {
		if (!conference_utils_member_test_flag(member, MFLAG_POSITIONAL) && member->conference->channels == 2) {
			conference_utils_member_set_flag(member, MFLAG_POSITIONAL);
			member->al = conference_al_create(member->pool);
		} else {

			if (stream) {
				stream->write_function(stream, "-ERR Positional audio not avalilable %d\n", member->conference->channels);
			}
			return SWITCH_STATUS_FALSE;
		}
	}


	if (data) {
		if (conference_member_parse_position(member, data) != SWITCH_STATUS_SUCCESS) {
			if (stream) {
				stream->write_function(stream, "-ERR invalid input!\n");
			}
			return SWITCH_STATUS_FALSE;
		}
	}


	if (stream != NULL) {
		stream->write_function(stream, "+OK Position %u = %0.2f:%0.2f:%0.2f\n", member->id, member->al->pos_x, member->al->pos_y, member->al->pos_z);
	}

	if (test_eflag(member->conference, EFLAG_SET_POSITION_MEMBER) &&
		data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "set-position-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Position", "%0.2f:%0.2f:%0.2f", member->al->pos_x, member->al->pos_y, member->al->pos_z);
		switch_event_fire(&event);
	}

#endif

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_volume_in(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->volume_in_level++;
			switch_normalize_volume(member->volume_in_level);
		} else if (!strcasecmp(data, "down")) {
			member->volume_in_level--;
			switch_normalize_volume(member->volume_in_level);
		} else {
			member->volume_in_level = atoi((char *) data);
			switch_normalize_volume(member->volume_in_level);
		}
		unlock_member(member);

	}
	if (stream != NULL) {
		stream->write_function(stream, "+OK Volume IN %u = %d\n", member->id, member->volume_in_level);
	}
	if (test_eflag(member->conference, EFLAG_VOLUME_IN_MEMBER) &&
		data && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "volume-in-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Volume-Level", "%d", member->volume_in_level);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_volume_out(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	switch_event_t *event;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (data) {
		lock_member(member);
		if (!strcasecmp(data, "up")) {
			member->volume_out_level++;
			switch_normalize_volume(member->volume_out_level);
		} else if (!strcasecmp(data, "down")) {
			member->volume_out_level--;
			switch_normalize_volume(member->volume_out_level);
		} else {
			member->volume_out_level = atoi((char *) data);
			switch_normalize_volume(member->volume_out_level);
		}
		unlock_member(member);
	}
	if (stream != NULL) {
		stream->write_function(stream, "+OK Volume OUT %u = %d\n", member->id, member->volume_out_level);
	}
	if (test_eflag(member->conference, EFLAG_VOLUME_OUT_MEMBER) && data &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "volume-out-member");
		switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Volume-Level", "%d", member->volume_out_level);
		switch_event_fire(&event);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_personal(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int on = 0;

	if (!conference->canvases[0]) {
		stream->write_function(stream, "-ERR conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (argv[2]) {
		on = switch_true(argv[2]);
		if (on) {
			conference_utils_set_flag(conference, CFLAG_PERSONAL_CANVAS);
			conference->video_layout_group = "grid";
			conference_utils_set_flag(conference, CFLAG_REFRESH_LAYOUT);
		} else {
			conference_utils_clear_flag(conference, CFLAG_PERSONAL_CANVAS);
		}
	}

	stream->write_function(stream, "+OK personal is %s\n", on ? "on" : "off");

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_bandwidth(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	uint32_t i;
	int32_t video_write_bandwidth;
	int x = 0, id = -1;
	char *group = NULL;
	char *array[4] = {0};
	int sdiv = 0, fdiv = 0;
	
	if (!conference_utils_test_flag(conference, CFLAG_MINIMIZE_VIDEO_ENCODING)) {
		stream->write_function(stream, "-ERR Bandwidth control not available.\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (!argv[2]) {
		stream->write_function(stream, "-ERR Invalid input\n");
		return SWITCH_STATUS_SUCCESS;
	}
	
	switch_split(argv[2], ':', array);

	if (array[1]) {
		sdiv = atoi(array[1]);
		if (sdiv < 2 || sdiv > 8) {
			sdiv = 0;
		}
	}
	
	if (array[2]) {
		fdiv = atoi(array[2]);
		if (fdiv < 2 || fdiv > 8) {
			fdiv = 0;
		}
	}

	video_write_bandwidth = switch_parse_bandwidth_string(array[0]);
	
	if (argv[3]) {
		group = argv[3];
	}

	if (argv[4]) {

		if (argv[4]) {
			id = atoi(argv[4]);
		}

		if (id < 1 || id > MAX_CANVASES+1) {
			id = -1;
		}

		if (id < 1 || id > conference->canvas_count) {
			stream->write_function(stream, "-ERR Invalid canvas\n");
			goto end;
		}
	}

	switch_mutex_lock(conference->member_mutex);
	for (i = 0; i <= conference->canvas_count; i++) {
		if (i > -1 && i != id - 1) {
			continue;
		}

		if (conference->canvases[i]) {
			mcu_canvas_t *canvas = conference->canvases[i];
			int j;

			for (j = 0; j < canvas->write_codecs_count; j++) {
				if ((zstr(group) || !strcmp(group, switch_str_nil(canvas->write_codecs[j]->video_codec_group)))) {
					switch_core_codec_control(&canvas->write_codecs[j]->codec, SCC_VIDEO_BANDWIDTH,
											  SCCT_INT, &video_write_bandwidth, SCCT_NONE, NULL, NULL, NULL);
					stream->write_function(stream, "+OK Set Bandwidth for canvas %d index %d group[%s] to %d\n", i + 1, j, 
										   switch_str_nil(canvas->write_codecs[j]->video_codec_group), video_write_bandwidth);
					
					if (fdiv) {
						canvas->write_codecs[j]->fps_divisor = fdiv;
					}
					
					if (sdiv) {
						int w = 0, h = 0;

						w = canvas->width;
						h = canvas->width;
						
						w /= sdiv;
						h /= sdiv;

						switch_img_free(&canvas->write_codecs[j]->scaled_img);
						canvas->write_codecs[j]->scaled_img = switch_img_alloc(NULL, SWITCH_IMG_FMT_I420, w, h, 16);
					}
					
					x++;
				}
			}
		}
	}
	switch_mutex_unlock(conference->member_mutex);

 end:

	if (!x) {
		stream->write_function(stream, "-ERR Bandwidth not set\n");
	}

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_canvas_bgimg(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	mcu_canvas_t *canvas = NULL;
	int idx = 0;
	char *file = NULL;
	switch_status_t status = SWITCH_STATUS_FALSE;

	if (!argv[2]) {
		stream->write_function(stream, "-ERR Invalid input\n");
		return SWITCH_STATUS_SUCCESS;
	}

	file = argv[2];

	if (argv[3]) {
		idx = atoi(argv[3]) - 1;
	}

	if (idx < 0 || idx > SUPER_CANVAS_ID || !conference->canvases[idx]) {
		stream->write_function(stream, "-ERR Invalid canvas\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if ((canvas = conference->canvases[idx])) {
		switch_mutex_lock(canvas->mutex);
		if (!strcasecmp(file, "clear")) {
			conference_video_reset_image(canvas->img, &canvas->bgcolor);
		} else {
			status = conference_video_set_canvas_bgimg(canvas, file);
		}
		switch_mutex_unlock(canvas->mutex);
	}

	if (status == SWITCH_STATUS_SUCCESS) {
		stream->write_function(stream, "+OK Set Bgimg %s\n", file);
	} else {
		stream->write_function(stream, "-ERR Error Setting Bgimg %s\n", file);
	}

	return SWITCH_STATUS_SUCCESS;
}


switch_status_t conference_api_sub_canvas_fgimg(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	mcu_canvas_t *canvas = NULL;
	int idx = 0;
	char *file = NULL;
	switch_status_t status = SWITCH_STATUS_FALSE;

	if (!argv[2]) {
		stream->write_function(stream, "-ERR Invalid input\n");
		return SWITCH_STATUS_SUCCESS;
	}

	file = argv[2];

	if (argv[3]) {
		idx = atoi(argv[3]) - 1;
	}

	if (idx < 0 || idx > SUPER_CANVAS_ID || !conference->canvases[idx]) {
		stream->write_function(stream, "-ERR Invalid canvas\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if ((canvas = conference->canvases[idx])) {
		switch_mutex_lock(canvas->mutex);
		if (!strcasecmp(file, "clear")) {
			conference_video_reset_image(canvas->img, &canvas->bgcolor);
		} else {
			status = conference_video_set_canvas_fgimg(canvas, file);
		}
		switch_mutex_unlock(canvas->mutex);
	}

	if (status == SWITCH_STATUS_SUCCESS) {
		stream->write_function(stream, "+OK Set FGimg %s\n", file);
	} else {
		stream->write_function(stream, "-ERR Error Setting FGimg %s\n", file);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_res(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int canvas_w = 0, canvas_h = 0, id = 0;
	char *video_canvas_size = argv[2];

	
	if (!conference->canvases[0]) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (zstr(video_canvas_size)) {
		stream->write_function(stream, "-ERR Invalid size\n");
		return SWITCH_STATUS_SUCCESS;
	} else {
		char *p;

		if ((canvas_w = atoi(video_canvas_size))) {
			if ((p = strchr(video_canvas_size, 'x'))) {
				p++;
				if (*p) {
					canvas_h = atoi(p);
				}
			}
		}
	}

	if (canvas_w < 320 || canvas_h < 180) {
		stream->write_function(stream, "-ERR Invalid size\n");
		return SWITCH_STATUS_SUCCESS;
	}


	if (argv[3]) {

		id = atoi(argv[3]);
		
		if (id < 1 || id > MAX_CANVASES+1) {
			id = -1;
		}

		if (id < 1) {
			stream->write_function(stream, "-ERR Invalid canvas\n");
		}

	}

	if (id == 0 && conference->canvases[0]) id = 1;

	if (id > conference->canvas_count + 1) {
		id = 1;
	}

	conference_video_change_res(conference, canvas_w, canvas_h, id - 1);
	
	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_fps(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	float fps = 0;

	if (!conference->canvases[0]) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (!argv[2]) {
		stream->write_function(stream, "+OK Current FPS [%0.2f]\n", conference->video_fps.fps);
		return SWITCH_STATUS_SUCCESS;
	}

	fps = (float)atof(argv[2]);

	if (conference_video_set_fps(conference, fps)) {
		stream->write_function(stream, "+OK FPS set to [%s]\n", argv[2]);
	} else {
		stream->write_function(stream, "-ERR Invalid FPS [%s]\n", argv[2]);
	}

	return SWITCH_STATUS_SUCCESS;

}

switch_status_t conference_api_sub_write_png(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_status_t status = SWITCH_STATUS_FALSE;
	mcu_canvas_t *canvas = NULL;

	if (!argv[2]) {
		stream->write_function(stream, "-ERR Invalid input\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (!conference->canvas_count) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (conference->canvas_count > 1) {
		/* pick super canvas */
		canvas = conference->canvases[conference->canvas_count];
	} else {
		canvas = conference->canvases[0];
	}

	switch_mutex_lock(canvas->mutex);
	status = switch_img_write_png(canvas->img, argv[2]);
	switch_mutex_unlock(canvas->mutex);

	stream->write_function(stream, "%s\n", status == SWITCH_STATUS_SUCCESS ? "+OK" : "-ERR");

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_layout(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	video_layout_t *vlayout = NULL;
	char *group_name = NULL;

	int idx = 0;

	if (!argv[2]) {
		stream->write_function(stream, "-ERR Invalid input\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (!conference->canvases[0]) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (!strcasecmp(argv[2], "list")) {
		switch_hash_index_t *hi;
		void *val;
		const void *vvar;
		for (hi = switch_core_hash_first(conference->layout_hash); hi; hi = switch_core_hash_next(&hi)) {
			switch_core_hash_this(hi, &vvar, NULL, &val);
			stream->write_function(stream, "%s\n", (char *)vvar);
		}
		return SWITCH_STATUS_SUCCESS;
	}

	if (!strncasecmp(argv[2], "group", 5)) {
		layout_group_t *lg = NULL;
		int xx = 4;

		if ((group_name = strchr(argv[2], ':'))) {
			group_name++;
			xx--;
		} else {
			group_name = argv[3];
		}

		if (!group_name) {
			stream->write_function(stream, "-ERR Group name not specified.\n");
			return SWITCH_STATUS_SUCCESS;
		} else {
			if (((lg = switch_core_hash_find(conference->layout_group_hash, group_name)))) {
				if (conference_utils_test_flag(conference, CFLAG_PERSONAL_CANVAS)) {
					stream->write_function(stream, "-ERR Change personal canvas to layout group [%s]\n", group_name);
					conference->video_layout_group = switch_core_strdup(conference->pool, group_name);
					conference_utils_set_flag(conference, CFLAG_REFRESH_LAYOUT);
					return SWITCH_STATUS_SUCCESS;
				}
			} else {
				group_name = NULL;
			}

			stream->write_function(stream, "+OK Change to layout group [%s]\n", group_name);

			if (argv[xx]) {
				if ((idx = atoi(argv[xx])) > 0) {
					idx--;
				}
			}
		}
	}

	if (!vlayout && (vlayout = switch_core_hash_find(conference->layout_hash, argv[2]))) {
		if (argv[3]) {
			if ((idx = atoi(argv[3]))) {
				idx--;
			}
		}
	}

	if (!vlayout && !group_name) {
		stream->write_function(stream, "-ERR Invalid layout [%s]\n", argv[2]);
		return SWITCH_STATUS_SUCCESS;
	}

	if (idx < 0 || idx > (int)(conference->canvas_count - 1)) idx = 0;


	if (conference_utils_test_flag(conference, CFLAG_PERSONAL_CANVAS)) {
		stream->write_function(stream, "+OK Change personal canvas set to layout [%s]\n", vlayout->name);
		switch_mutex_lock(conference->member_mutex);
		conference->new_personal_vlayout = vlayout;
		switch_mutex_unlock(conference->member_mutex);
	} else {

		switch_mutex_lock(conference->canvases[idx]->mutex);
		if (vlayout) {
			stream->write_function(stream, "+OK Change canvas %d to layout [%s]\n", idx + 1, vlayout->name);
			conference->canvases[idx]->new_vlayout = vlayout;
			conference->canvases[idx]->video_layout_group = NULL;
		} else if (group_name) {
			conference->canvases[idx]->video_layout_group = switch_core_strdup(conference->pool, group_name);
			conference_utils_set_flag(conference, CFLAG_REFRESH_LAYOUT);
		}
		switch_mutex_unlock(conference->canvases[idx]->mutex);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_count(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{

	if (conference) {
		conference_list_count_only(conference, stream);
	} else {
		int count = 0;
		switch_hash_index_t *hi;
		switch_mutex_lock(conference_globals.hash_mutex);
		for (hi = switch_core_hash_first(conference_globals.conference_hash); hi; hi = switch_core_hash_next(&hi)) {
			count++;
		}
		switch_mutex_unlock(conference_globals.hash_mutex);
		stream->write_function(stream, "%d", count);
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_list(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int ret_status = SWITCH_STATUS_GENERR;
	int count = 0;
	switch_hash_index_t *hi;
	void *val;
	char *d = ";";
	int pretty = 0;
	int summary = 0;
	int countonly = 0;
	int argofs = (argc >= 2 && strcasecmp(argv[1], "list") == 0);	/* detect being called from chat vs. api */

	if (argv[1 + argofs]) {
		if (argv[2 + argofs] && !strcasecmp(argv[1 + argofs], "delim")) {
			d = argv[2 + argofs];

			if (*d == '"') {
				if (++d) {
					char *p;
					if ((p = strchr(d, '"'))) {
						*p = '\0';
					}
				} else {
					d = ";";
				}
			}
		} else if (strcasecmp(argv[1 + argofs], "pretty") == 0) {
			pretty = 1;
		} else if (strcasecmp(argv[1 + argofs], "summary") == 0) {
			summary = 1;
		} else if (strcasecmp(argv[1 + argofs], "count") == 0) {
			countonly = 1;
		}
	}

	if (conference == NULL) {
		switch_mutex_lock(conference_globals.hash_mutex);
		for (hi = switch_core_hash_first(conference_globals.conference_hash); hi; hi = switch_core_hash_next(&hi)) {
			int fcount = 0;
			switch_core_hash_this(hi, NULL, NULL, &val);
			conference = (conference_obj_t *) val;

			stream->write_function(stream, "+OK Conference %s (%u member%s rate: %u%s flags: ",
								   conference->name,
								   conference->count,
								   conference->count == 1 ? "" : "s", conference->rate, conference_utils_test_flag(conference, CFLAG_LOCKED) ? " locked" : "");

			if (conference_utils_test_flag(conference, CFLAG_LOCKED)) {
				stream->write_function(stream, "%slocked", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_DESTRUCT)) {
				stream->write_function(stream, "%sdestruct", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_WAIT_MOD)) {
				stream->write_function(stream, "%swait_mod", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_AUDIO_ALWAYS)) {
				stream->write_function(stream, "%saudio_always", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_RUNNING)) {
				stream->write_function(stream, "%srunning", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_ANSWERED)) {
				stream->write_function(stream, "%sanswered", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_ENFORCE_MIN)) {
				stream->write_function(stream, "%senforce_min", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_BRIDGE_TO)) {
				stream->write_function(stream, "%sbridge_to", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_DYNAMIC)) {
				stream->write_function(stream, "%sdynamic", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_EXIT_SOUND)) {
				stream->write_function(stream, "%sexit_sound", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_ENTER_SOUND)) {
				stream->write_function(stream, "%senter_sound", fcount ? "|" : "");
				fcount++;
			}

			if (conference->record_count > 0) {
				stream->write_function(stream, "%srecording", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_VID_FLOOR)) {
				stream->write_function(stream, "%svideo_floor_only", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_RFC4579)) {
				stream->write_function(stream, "%svideo_rfc4579", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_LIVEARRAY_SYNC)) {
				stream->write_function(stream, "%slivearray_sync", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_VID_FLOOR_LOCK)) {
				stream->write_function(stream, "%svideo_floor_lock", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_TRANSCODE_VIDEO)) {
				stream->write_function(stream, "%stranscode_video", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_VIDEO_MUXING)) {
				stream->write_function(stream, "%svideo_muxing", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_MINIMIZE_VIDEO_ENCODING)) {
				stream->write_function(stream, "%sminimize_video_encoding", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_MANAGE_INBOUND_VIDEO_BITRATE)) {
				stream->write_function(stream, "%smanage_inbound_bitrate", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_JSON_STATUS)) {
				stream->write_function(stream, "%sjson_status", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_VIDEO_BRIDGE_FIRST_TWO)) {
				stream->write_function(stream, "%svideo_bridge_first_two", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_VIDEO_REQUIRED_FOR_CANVAS)) {
				stream->write_function(stream, "%svideo_required_for_canvas", fcount ? "|" : "");
				fcount++;
			}

			if (conference_utils_test_flag(conference, CFLAG_PERSONAL_CANVAS)) {
				stream->write_function(stream, "%spersonal_canvas", fcount ? "|" : "");
				fcount++;
			}

			if (!fcount) {
				stream->write_function(stream, "none");
			}

			stream->write_function(stream, ")\n");

			count++;
			if (!summary) {
				if (pretty) {
					conference_list_pretty(conference, stream);
				} else {
					conference_list(conference, stream, d);
				}
			}
		}
		switch_mutex_unlock(conference_globals.hash_mutex);
	} else {
		count++;
		if (countonly) {
			conference_list_count_only(conference, stream);
		} else if (pretty) {
			conference_list_pretty(conference, stream);
		} else {
			conference_list(conference, stream, d);
		}
	}

	if (!count) {
		stream->write_function(stream, "+OK No active conferences.\n");
	}

	ret_status = SWITCH_STATUS_SUCCESS;

	return ret_status;
}

switch_status_t conference_api_sub_floor(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	switch_mutex_lock(member->conference->mutex);

	if (member->conference->floor_holder == member) {
		conference_member_set_floor_holder(member->conference, NULL);
		if (stream != NULL) {
			stream->write_function(stream, "+OK floor none\n");
		}
	} else if (member->conference->floor_holder == NULL) {
		conference_member_set_floor_holder(member->conference, member);
		if (stream != NULL) {
			stream->write_function(stream, "+OK floor %u\n", member->id);
		}
	} else {
		if (stream != NULL) {
			stream->write_function(stream, "-ERR floor is held by %u\n", member->conference->floor_holder->id);
		}
	}

	switch_mutex_unlock(member->conference->mutex);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_clear_vid_floor(conference_obj_t *conference, switch_stream_handle_t *stream, void *data)
{

	switch_mutex_lock(conference->mutex);
	conference_utils_clear_flag(conference, CFLAG_VID_FLOOR_LOCK);
	//conference_video_set_floor_holder(conference, NULL);
	switch_mutex_unlock(conference->mutex);

	stream->write_function(stream, "+OK floor Cleared\n", SWITCH_VA_NONE);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_mute_img(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *text = (char *) data;
	mcu_layer_t *layer = NULL;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		return SWITCH_STATUS_FALSE;
	}

	layer = conference_video_get_layer_locked(member);

	if (!layer) {
		goto end;
	}

	member->video_mute_png = NULL;

	if (text) {
		switch_img_free(&layer->mute_img);
	}

	if (text && strcasecmp(text, "clear")) {
		member->video_mute_png = switch_core_strdup(member->pool, text);
	}

end:

	stream->write_function(stream, "%s\n", member->video_mute_png ? member->video_mute_png : "_undef_");

	conference_video_release_layer(&layer);

	return SWITCH_STATUS_SUCCESS;

}


switch_status_t conference_api_sub_vid_logo_img(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *text = (char *) data;
	mcu_layer_t *layer = NULL;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		return SWITCH_STATUS_FALSE;
	}

	conference_member_set_logo(member, text);

	layer = conference_video_get_layer_locked(member);

	if (!layer) {
		goto end;
	}

	conference_video_layer_set_logo(member, layer);

 end:

	stream->write_function(stream, "+OK Video logo %s\n", member->video_logo ? "set" : "cleared");

	conference_video_release_layer(&layer);

	return SWITCH_STATUS_SUCCESS;

}


switch_status_t conference_api_sub_vid_codec_group(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *text = (char *) data;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		return SWITCH_STATUS_FALSE;
	}

	if (text) {

		if (!strcmp(text, "clear")) {
			member->video_codec_group = NULL;
		} else {
			member->video_codec_group = switch_core_strdup(member->pool, text);
		}

		switch_mutex_lock(member->conference->member_mutex);
		member->video_codec_index = -1;
		switch_mutex_unlock(member->conference->member_mutex);
		stream->write_function(stream, "+OK Video codec group %s %s\n", member->video_codec_group ? "set" : "cleared", switch_str_nil(member->video_codec_group));
	} else {
		stream->write_function(stream, "+OK Video codec group is %s\n", member->video_codec_group);
	}
	

	return SWITCH_STATUS_SUCCESS;

}

switch_status_t conference_api_sub_get_uuid(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	if (member->session) {
		stream->write_function(stream, "%s", switch_core_session_get_uuid(member->session));
	} else {
		stream->write_function(stream, "_undef_");
	}

	return SWITCH_STATUS_SUCCESS;
}

static void clear_res_id(conference_obj_t *conference, conference_member_t *member, const char *id)
{
	conference_member_t *imember;

	switch_mutex_lock(conference->member_mutex);
	for (imember = conference->members; imember; imember = imember->next) {
		if (imember == member) {
			continue;
		}

		if (imember->video_reservation_id && !strcasecmp(imember->video_reservation_id, id)) {
			imember->video_reservation_id = NULL;
			conference_video_detach_video_layer(imember);
		}
	}
	switch_mutex_unlock(conference->member_mutex);
}

static void clear_role_id(conference_obj_t *conference, conference_member_t *member, const char *id)
{
	conference_member_t *imember;

	switch_mutex_lock(conference->member_mutex);
	for (imember = conference->members; imember; imember = imember->next) {
		if (imember == member) {
			continue;
		}

		if (imember->video_role_id && !strcasecmp(imember->video_role_id, id)) {
			imember->video_role_id = NULL;
			conference_video_detach_video_layer(imember);
		}
	}
	switch_mutex_unlock(conference->member_mutex);
}

switch_status_t conference_api_sub_vid_res_id(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *text = (char *) data;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		return SWITCH_STATUS_FALSE;
	}

	if (!member->conference->canvases[0]) {
		stream->write_function(stream, "-ERR conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (zstr(text) || !strcasecmp(text, "clear") || (member->video_reservation_id && !strcasecmp(text, member->video_reservation_id))) {
		member->video_reservation_id = NULL;
		stream->write_function(stream, "+OK reservation_id cleared\n");
		conference_video_detach_video_layer(member);
	} else {
		clear_res_id(member->conference, member, text);
		if (!member->video_reservation_id || strcmp(member->video_reservation_id, text)) {
			member->video_reservation_id = switch_core_strdup(member->pool, text);
		}
		stream->write_function(stream, "+OK reservation_id %s\n", text);
		conference_video_detach_video_layer(member);
		conference_video_find_floor(member, SWITCH_FALSE);
	}



	return SWITCH_STATUS_SUCCESS;

}


switch_status_t conference_api_sub_vid_role_id(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	char *text = (char *) data;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		return SWITCH_STATUS_FALSE;
	}

	if (!member->conference->canvases[0]) {
		stream->write_function(stream, "-ERR conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (zstr(text) || !strcasecmp(text, "clear") || (member->video_role_id && !strcasecmp(text, member->video_role_id))) {
		member->video_role_id = NULL;
		stream->write_function(stream, "+OK role_id cleared\n");
	} else {
		clear_role_id(member->conference, member, text);
		if (!member->video_role_id || strcmp(member->video_role_id, text)) {
			member->video_role_id = switch_core_strdup(member->pool, text);
		}
		stream->write_function(stream, "+OK role_id %s\n", text);
		conference_video_find_floor(member, SWITCH_FALSE);
	}



	return SWITCH_STATUS_SUCCESS;

}

switch_status_t conference_api_sub_vid_banner(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	mcu_layer_t *layer = NULL;
	char *text = (char *) data;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	switch_url_decode(text);

	if (!switch_channel_test_flag(member->channel, CF_VIDEO)) {
		stream->write_function(stream, "-ERR Channel %s does not have video capability!\n", switch_channel_get_name(member->channel));
		return SWITCH_STATUS_SUCCESS;
	}

	layer = conference_video_get_layer_locked(member);

	if (!layer) {
		stream->write_function(stream, "-ERR Channel %s is not in a video layer\n", switch_channel_get_name(member->channel));
		goto end;
	}

	if (zstr(text)) {
		stream->write_function(stream, "-ERR No text supplied\n", switch_channel_get_name(member->channel));
		goto end;
	}

	member->video_banner_text = switch_core_strdup(member->pool, text);

	conference_video_layer_set_banner(member, layer, NULL);

	stream->write_function(stream, "+OK\n");

 end:

	conference_video_release_layer(&layer);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_vid_floor(conference_member_t *member, switch_stream_handle_t *stream, void *data)
{
	int force = 0;

	if (member == NULL)
		return SWITCH_STATUS_GENERR;

	if (!switch_channel_test_flag(member->channel, CF_VIDEO) && !member->avatar_png_img) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Channel %s does not have video capability!\n", switch_channel_get_name(member->channel));
		return SWITCH_STATUS_FALSE;
	}

	switch_mutex_lock(member->conference->mutex);

	if (data && switch_stristr("force", (char *) data)) {
		force = 1;
	}

	if (member->conference->video_floor_holder == member->id && conference_utils_test_flag(member->conference, CFLAG_VID_FLOOR_LOCK)) {
		conference_utils_clear_flag(member->conference, CFLAG_VID_FLOOR_LOCK);

		conference_member_set_floor_holder(member->conference, member);
		if (stream == NULL) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "conference %s OK video floor auto\n", member->conference->name);
		} else {
			stream->write_function(stream, "+OK floor none\n");
		}

	} else if (force || member->conference->video_floor_holder == 0) {
		conference_utils_set_flag(member->conference, CFLAG_VID_FLOOR_LOCK);
		conference_video_set_floor_holder(member->conference, member, SWITCH_TRUE);
		if (test_eflag(member->conference, EFLAG_FLOOR_CHANGE)) {
			if (stream == NULL) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "conference %s OK video floor %d %s\n",
								  member->conference->name, member->id, switch_channel_get_name(member->channel));
			} else {
				stream->write_function(stream, "+OK floor %u\n", member->id);
			}
		}
	} else {
		if (stream == NULL) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "conference %s floor already held by %d %s\n",
							  member->conference->name, member->id, switch_channel_get_name(member->channel));
		} else {
			stream->write_function(stream, "-ERR floor is held by %u\n", member->conference->video_floor_holder);
		}
	}

	switch_mutex_unlock(member->conference->mutex);

	return SWITCH_STATUS_SUCCESS;
}



switch_status_t conference_api_sub_file_seek(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	if (argc == 3) {
		switch_mutex_lock(conference->mutex);
		conference_fnode_seek(conference->fnode, stream, argv[2]);
		switch_mutex_unlock(conference->mutex);

		return SWITCH_STATUS_SUCCESS;
	}

	if (argc == 4) {
		uint32_t id = atoi(argv[3]);
		conference_member_t *member = conference_member_get(conference, id);
		if (member == NULL) {
			stream->write_function(stream, "-ERR Member: %u not found.\n", id);
			return SWITCH_STATUS_GENERR;
		}

		switch_mutex_lock(member->fnode_mutex);
		conference_fnode_seek(member->fnode, stream, argv[2]);
		switch_mutex_unlock(member->fnode_mutex);
		switch_thread_rwlock_unlock(member->rwlock);
		return SWITCH_STATUS_SUCCESS;
	}

	return SWITCH_STATUS_GENERR;
}

switch_status_t conference_api_sub_play(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int ret_status = SWITCH_STATUS_GENERR;
	switch_event_t *event;
	uint8_t async = 0;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if ((argc == 4 && !strcasecmp(argv[3], "async")) || (argc == 5 && !strcasecmp(argv[4], "async"))) {
		argc--;
		async++;
	}

	if (argc == 3) {
		if (conference_file_play(conference, argv[2], 0, NULL, async) == SWITCH_STATUS_SUCCESS) {
			stream->write_function(stream, "+OK (play) Playing file %s\n", argv[2]);
			if (test_eflag(conference, EFLAG_PLAY_FILE) &&
				switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
				conference_event_add_data(conference, event);

				if (conference->fnode && conference->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", argv[2]);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Async", async ? "true" : "false");
				switch_event_fire(&event);
			}
		} else {
			stream->write_function(stream, "-ERR (play) File: %s not found.\n", argv[2] ? argv[2] : "(unspecified)");
		}
		ret_status = SWITCH_STATUS_SUCCESS;
	} else if (argc >= 4) {
		uint32_t id = atoi(argv[3]);
		conference_member_t *member;
		switch_bool_t mux = SWITCH_TRUE;

		if (argc > 4 && !strcasecmp(argv[4], "nomux")) {
			mux = SWITCH_FALSE;
		}

		if ((member = conference_member_get(conference, id))) {
			if (conference_member_play_file(member, argv[2], 0, mux) == SWITCH_STATUS_SUCCESS) {
				stream->write_function(stream, "+OK (play) Playing file %s to member %u\n", argv[2], id);
				if (test_eflag(conference, EFLAG_PLAY_FILE_MEMBER) &&
					switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
					conference_member_add_event_data(member, event);

					if (member->fnode->fh.params) {
						switch_event_merge(event, member->fnode->fh.params);
					}

					switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "play-file-member");
					switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "File", argv[2]);
					switch_event_fire(&event);
				}
			} else {
				stream->write_function(stream, "-ERR (play) File: %s not found.\n", argv[2] ? argv[2] : "(unspecified)");
			}
			switch_thread_rwlock_unlock(member->rwlock);
			ret_status = SWITCH_STATUS_SUCCESS;
		} else {
			stream->write_function(stream, "-ERR Member: %u not found.\n", id);
		}
	}

	return ret_status;
}

switch_status_t conference_api_sub_say(conference_obj_t *conference, switch_stream_handle_t *stream, const char *text)
{
	switch_event_t *event;

	if (zstr(text)) {
		stream->write_function(stream, "-ERR (say) Error! No text.\n");
		return SWITCH_STATUS_GENERR;
	}

	if (conference_say(conference, text, 0) != SWITCH_STATUS_SUCCESS) {
		stream->write_function(stream, "-ERR (say) Error!\n");
		return SWITCH_STATUS_GENERR;
	}

	stream->write_function(stream, "+OK (say) OK\n");
	if (test_eflag(conference, EFLAG_SPEAK_TEXT) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_event_add_data(conference, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "speak-text");
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Text", text);
		switch_event_fire(&event);
	}
	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_saymember(conference_obj_t *conference, switch_stream_handle_t *stream, const char *text)
{
	int ret_status = SWITCH_STATUS_GENERR;
	char *expanded = NULL;
	char *start_text = NULL;
	char *workspace = NULL;
	uint32_t id = 0;
	conference_member_t *member = NULL;
	switch_event_t *event;

	if (zstr(text)) {
		stream->write_function(stream, "-ERR (saymember) No Text!\n");
		goto done;
	}

	if (!(workspace = strdup(text))) {
		stream->write_function(stream, "-ERR (saymember) Memory Error!\n");
		goto done;
	}

	if ((start_text = strchr(workspace, ' '))) {
		*start_text++ = '\0';
		text = start_text;
	}

	id = atoi(workspace);

	if (!id || zstr(text)) {
		stream->write_function(stream, "-ERR (saymember) No Text!\n");
		goto done;
	}

	if (!(member = conference_member_get(conference, id))) {
		stream->write_function(stream, "-ERR (saymember) Unknown Member %u!\n", id);
		goto done;
	}

	if ((expanded = switch_channel_expand_variables(switch_core_session_get_channel(member->session), (char *) text)) != text) {
		text = expanded;
	} else {
		expanded = NULL;
	}

	if (!text || conference_member_say(member, (char *) text, 0) != SWITCH_STATUS_SUCCESS) {
		stream->write_function(stream, "-ERR (saymember) Error!\n");
		goto done;
	}

	stream->write_function(stream, "-ERR (saymember) OK\n");
	if (test_eflag(member->conference, EFLAG_SPEAK_TEXT_MEMBER) &&
		switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_member_add_event_data(member, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "speak-text-member");
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Text", text);
		switch_event_fire(&event);
	}
	ret_status = SWITCH_STATUS_SUCCESS;

 done:

	if (member) {
		switch_thread_rwlock_unlock(member->rwlock);
	}

	switch_safe_free(workspace);
	switch_safe_free(expanded);
	return ret_status;
}

switch_status_t conference_api_sub_cam(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int x;
	int canvas_id = -1;
	int layer_id = -1;
	int ok = 0;
	mcu_canvas_t *canvas = NULL;
	mcu_layer_t *layer = NULL;

	if (!conference->canvases[0]) {
		stream->write_function(stream, "-ERR Conference is not in mixing mode\n");
		return SWITCH_STATUS_SUCCESS;
	}

	if (argc > 4) {
		canvas_id = atoi(argv[2]);
		layer_id = atoi(argv[3]);

		if (canvas_id > -1 && layer_id > -1 && canvas_id < conference->canvas_count) {
			switch_mutex_lock(conference->canvas_mutex);
			canvas = conference->canvases[canvas_id];
			switch_mutex_lock(canvas->mutex);
			if (layer_id < canvas->total_layers) {
				layer = &canvas->layers[layer_id];
				ok = 1;
				for (x = 4; x < argc; x++) {
					char *p = strchr(argv[x], '=');
					int val = -1, isfalse = 0;
					char *str_arg = NULL;
					
					if (p) {
						*p++ = '\0';
						if (!p) p = "";
						
						if (!strcasecmp(argv[x], "zoom") || !strcasecmp(argv[x], "pan")) {
							str_arg = p;
							if (switch_false(p)) {
								isfalse = 1;
							}
						} else {
							if (switch_is_number(p)) {
								val = atoi(p);
							} else if (switch_true(p)) {
								val = 1;
							} else {
								val = 0;
								isfalse = 1;
							}
						}
					} else if (!strcasecmp(argv[x], "reset")) {
						str_arg = "true";
					}

					if (val < 0 && !str_arg) {
						stream->write_function(stream, "-ERR invalid val for option [%s]\n", argv[x]);
						continue;
					}

					if (!strcasecmp(argv[x], "autozoom")) {
						if ((layer->cam_opts.autozoom = val)) {
							layer->cam_opts.manual_zoom = 0;
						}
						
					} else if (!strcasecmp(argv[x], "autopan")) {
						if ((layer->cam_opts.autopan = val)) {
							layer->cam_opts.manual_pan = 0;
						}
					} else if (!strcasecmp(argv[x], "zoom_factor")) {
						if (val > 0 && val < 5) {
							layer->cam_opts.zoom_factor = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-4\n", argv[x]);
						}
					} else if (!strcasecmp(argv[x], "snap_factor")) {
						if (val > 0 && val < layer->screen_w / 2) {
							layer->cam_opts.snap_factor = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-%d\n", argv[x], layer->screen_w / 2);
						}
					} else if (!strcasecmp(argv[x], "zoom_move_factor")) {
						if (val > 0 && val < layer->screen_w / 2) {
							layer->cam_opts.zoom_move_factor = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-4\n", argv[x], layer->screen_w / 2);
						}
					} else if (!strcasecmp(argv[x], "pan_speed")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.pan_speed = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "pan_accel_speed")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.pan_accel_speed = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "pan_accel_min")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.pan_accel_min = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "zoom_speed")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.zoom_speed = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "zoom_accel_speed")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.zoom_accel_speed = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "zoom_accel_min")) {
						if (val > 0 && val < 100) {
							layer->cam_opts.zoom_accel_min = val;
						} else {
							stream->write_function(stream, "-ERR invalid val for option [%s] must be 1-100\n");
						}
					} else if (!strcasecmp(argv[x], "reset")) {
						conference_video_reset_layer_cam(layer);
					} else if (!strcasecmp(argv[x], "pan")) {
						char *x_val = NULL, *y_val = NULL;
						int x = -1, y = -1;
						int on = 0;

						if (isfalse) {
							layer->pan_geometry.x = 0;
							layer->pan_geometry.y = 0;
							layer->cam_opts.manual_pan = 0;
						} else {
							
							if (str_arg) {
								char *p = strchr(str_arg, ':');
								
								if (p) {
									*p++ = '\0';

									if (*str_arg == 'x') {
										x_val = p;
									} else if (*str_arg == 'y') {
										y_val = p;
									}
								}
							}

							if (!x_val && !y_val) {
								stream->write_function(stream, "-ERR invalid val for pan\n");
							}

							if (x_val) x = atoi(x_val);
							if (y_val) y = atoi(y_val);
							
							if (x_val && strrchr(x_val, 'i')) {
								int nx = (int)layer->pan_geometry.x + x;

								if (nx < 0) nx = 0;
								if (nx + layer->pan_geometry.w > layer->img->d_w) nx = layer->img->d_w - layer->pan_geometry.w;
								
								layer->pan_geometry.x = nx;
								on++;
							} else if (x > -1) {
								layer->pan_geometry.x = x;
								on++;
							}
							
							if (y_val && strrchr(y_val, 'i')) {
								int ny = (int)layer->pan_geometry.y + y;

								if (ny < 0) ny = 0;
								if (ny + layer->pan_geometry.h > layer->img->d_h) ny = layer->img->d_h - layer->pan_geometry.h;
								
								layer->pan_geometry.y = ny;
								on++;
							} else if (y > -1) {
								layer->pan_geometry.y = y;
								on++;
							}	
							

							if (on) {
								layer->cam_opts.manual_pan = 1;
								layer->cam_opts.autopan = 0;
								stream->write_function(stream, "+OK PAN %d,%d\n", layer->pan_geometry.x, layer->pan_geometry.y);
							}
						}

						
					} else if (!strcasecmp(argv[x], "zoom")) {
						if (str_arg && !isfalse) {
							char *array[4] = {0};
							int iray[4] = {0};
							int ac;
							
							if ((ac = switch_split(str_arg, ':', array)) >= 3) {
								int i;

								for (i = 0; i < ac; i++) {
									int tmp = atoi(array[i]);

									if (tmp < 0) break;
									iray[i] = tmp;
								}

								if (i == ac) {
									layer->cam_opts.manual_zoom = 1;
									layer->cam_opts.autozoom = 0;

									layer->zoom_geometry.x = iray[0];
									layer->zoom_geometry.y = iray[1];
									layer->zoom_geometry.w = iray[2];
									if (iray[3]) {
										layer->zoom_geometry.h = iray[3];
									} else {
										layer->zoom_geometry.h = iray[2];
									}
									
									layer->crop_x = iray[0];
									layer->crop_y = iray[1];
									layer->crop_w = iray[2];
									layer->crop_h = iray[2];
									layer->pan_geometry = layer->zoom_geometry;
								} else {
									ok = 0;
								}
							}
						} else {
							layer->zoom_geometry.x = 0;
							layer->zoom_geometry.y = 0;
							layer->zoom_geometry.w = 0;
							layer->zoom_geometry.h = 0;
							layer->cam_opts.manual_zoom = 0;
						}
					} else {
						stream->write_function(stream, "-ERR invalid option [%s]\n", argv[x]);
					}
				}
			}
			switch_mutex_unlock(canvas->mutex);
			switch_mutex_unlock(conference->canvas_mutex);

		}
	}

	if (ok) {
		stream->write_function(stream, "+OK\n");
	} else {
		stream->write_function(stream, "-ERR invalid args\n");
	}


	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_stop(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	uint8_t current = 0, all = 0, async = 0;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc > 2) {
		current = strcasecmp(argv[2], "current") ? 0 : 1;
		all = strcasecmp(argv[2], "all") ? 0 : 1;
		async = strcasecmp(argv[2], "async") ? 0 : 1;
	} else {
		all = 1;
	}

	if (!(current || all || async))
		return SWITCH_STATUS_GENERR;

	if (argc == 4) {
		uint32_t id = atoi(argv[3]);
		conference_member_t *member;

		if ((member = conference_member_get(conference, id))) {
			uint32_t stopped = conference_member_stop_file(member, async ? FILE_STOP_ASYNC : current ? FILE_STOP_CURRENT : FILE_STOP_ALL);
			stream->write_function(stream, "+OK Stopped %u files.\n", stopped);
			switch_thread_rwlock_unlock(member->rwlock);
		} else {
			stream->write_function(stream, "-ERR Member: %u not found.\n", id);
		}
	} else {
		uint32_t stopped = conference_file_stop(conference, async ? FILE_STOP_ASYNC : current ? FILE_STOP_CURRENT : FILE_STOP_ALL);
		stream->write_function(stream, "+OK Stopped %u files.\n", stopped);
	}
	return SWITCH_STATUS_SUCCESS;
}

void _conference_api_sub_relate_show_member_relationships(conference_obj_t *conference, switch_stream_handle_t *stream, uint32_t member_id)
{
	conference_member_t *member;
	for (member = conference->members; member; member = member->next) {
		conference_relationship_t *rel;

		if (member_id > 0 && member->id != member_id) continue;

		for (rel = member->relationships; rel; rel = rel->next) {
			stream->write_function(stream, "%d -> %d %s%s%s\n", member->id, rel->id,
								   (rel->flags & RFLAG_CAN_SPEAK) ? "SPEAK " : "NOSPEAK ",
								   (rel->flags & RFLAG_CAN_HEAR) ? "HEAR " : "NOHEAR ",
								   (rel->flags & RFLAG_CAN_SEND_VIDEO) ? "SENDVIDEO " : "NOSENDVIDEO ");
		}
	}
}

void _conference_api_sub_relate_clear_member_relationship(conference_obj_t *conference, switch_stream_handle_t *stream, uint32_t id, uint32_t oid)
{
	conference_member_t *member = NULL, *other_member = NULL;
	if ((member = conference_member_get(conference, id))) {
		conference_member_del_relationship(member, oid);
		other_member = conference_member_get(conference, oid);

		if (other_member) {
			if (conference_utils_member_test_flag(other_member, MFLAG_RECEIVING_VIDEO)) {
				conference_utils_member_clear_flag(other_member, MFLAG_RECEIVING_VIDEO);
				if (conference->floor_holder) {
					switch_core_session_request_video_refresh(conference->floor_holder->session);
				}
			}
			switch_thread_rwlock_unlock(other_member->rwlock);
		}

		stream->write_function(stream, "+OK relationship %u->%u cleared.\n", id, oid);
		switch_thread_rwlock_unlock(member->rwlock);
	} else {
		stream->write_function(stream, "-ERR relationship %u->%u not found.\n", id, oid);
	}
}

void _conference_api_sub_relate_set_member_relationship(conference_obj_t *conference, switch_stream_handle_t *stream, uint32_t id, uint32_t oid, uint8_t nospeak, uint8_t nohear, uint8_t sendvideo, char *action)
{

	conference_member_t *member = NULL, *other_member = NULL;

	if ((member = conference_member_get(conference, id))) {
		other_member = conference_member_get(conference, oid);
	}

	if (member && other_member) {
		conference_relationship_t *rel = NULL;

		if (sendvideo && conference_utils_member_test_flag(other_member, MFLAG_RECEIVING_VIDEO) && (! (nospeak || nohear))) {
			stream->write_function(stream, "-ERR member %d already receiving video", oid);
			goto skip;
		}

		if ((rel = conference_member_get_relationship(member, other_member))) {
			rel->flags = 0;
		} else {
			rel = conference_member_add_relationship(member, oid);
		}

		if (rel) {
			switch_set_flag(rel, RFLAG_CAN_SPEAK | RFLAG_CAN_HEAR);
			if (nospeak) {
				switch_clear_flag(rel, RFLAG_CAN_SPEAK);
				conference_utils_member_clear_flag_locked(member, MFLAG_TALKING);
			}
			if (nohear) {
				switch_clear_flag(rel, RFLAG_CAN_HEAR);
			}
			if (sendvideo) {
				switch_set_flag(rel, RFLAG_CAN_SEND_VIDEO);
				conference_utils_member_set_flag(other_member, MFLAG_RECEIVING_VIDEO);
				switch_core_session_request_video_refresh(member->session);
			}

			stream->write_function(stream, "+OK %u->%u %s set\n", id, oid, action);
		} else {
			stream->write_function(stream, "-ERR error!\n");
		}
	} else {
		stream->write_function(stream, "-ERR relationship %u->%u not found.\n", id, oid);
	}

skip:
	if (member) {
		switch_thread_rwlock_unlock(member->rwlock);
	}

	if (other_member) {
		switch_thread_rwlock_unlock(other_member->rwlock);
	}
}

switch_status_t conference_api_sub_relate(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	uint8_t nospeak = 0, nohear = 0, sendvideo = 0, clear = 0;
	int members = 0;
	int other_members = 0;
	char *members_array[100] = { 0 };
	char *other_members_array[100] = { 0 };
	char *lbuf_members = NULL, *lbuf_other_members = NULL, *action = NULL;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 3) {
		switch_mutex_lock(conference->mutex);

		if (conference->relationship_total) {
			if (argc == 3) {
				char *lbuf = NULL;
				lbuf = strdup(argv[2]);
				members = switch_separate_string(lbuf, ',', members_array, (sizeof(members_array) / sizeof(members_array[0])));
				if (members) {
					int i;
					uint32_t member_id;
					for (i = 0; i < members && members_array[i]; i++) {
						member_id = atoi(members_array[i]);
						_conference_api_sub_relate_show_member_relationships(conference, stream, member_id);
					}
				}
				switch_safe_free(lbuf);
			}

		} else {
			stream->write_function(stream, "+OK No relationships\n");
		}
		switch_mutex_unlock(conference->mutex);
		return SWITCH_STATUS_SUCCESS;
	}

	if (argc <= 4)
		return SWITCH_STATUS_GENERR;

	nospeak = strstr(argv[4], "nospeak") ? 1 : 0;
	nohear = strstr(argv[4], "nohear") ? 1 : 0;
	sendvideo = strstr(argv[4], "sendvideo") ? 1 : 0;

	if (!strcasecmp(argv[4], "clear")) {
		clear = 1;
	}

	if (!(clear || nospeak || nohear || sendvideo)) {
		return SWITCH_STATUS_GENERR;
	}

	lbuf_members = strdup(argv[2]);
	lbuf_other_members = strdup(argv[3]);
	action = strdup(argv[4]);
	members = switch_separate_string(lbuf_members, ',', members_array, (sizeof(members_array) / sizeof(members_array[0])));
	other_members = switch_separate_string(lbuf_other_members, ',', other_members_array, (sizeof(other_members_array) / sizeof(other_members_array[0])));
	if (members && other_members) {
		int i, i2;
		uint32_t member_id, other_member_id;
		for (i = 0; i < members && members_array[i]; i++) {
			member_id = atoi(members_array[i]);
			for (i2 = 0; i2 < other_members && other_members_array[i2]; i2++) {
				other_member_id = atoi(other_members_array[i2]);
				if (clear) {
					_conference_api_sub_relate_clear_member_relationship(conference, stream, member_id, other_member_id);
				}
				if (nospeak || nohear || sendvideo) {
					_conference_api_sub_relate_set_member_relationship(conference, stream, member_id, other_member_id, nospeak, nohear, sendvideo, action);
				}
			}
		}
	}
	switch_safe_free(lbuf_members);
	switch_safe_free(lbuf_other_members);
	switch_safe_free(action);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_lock(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_event_t *event;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (conference->is_locked_sound) {
		conference_file_play(conference, conference->is_locked_sound, CONF_DEFAULT_LEADIN, NULL, 0);
	}

	conference_utils_set_flag_locked(conference, CFLAG_LOCKED);
	stream->write_function(stream, "+OK %s locked\n", argv[0]);
	if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_event_add_data(conference, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "lock");
		switch_event_fire(&event);
	}

	return 0;
}

switch_status_t conference_api_sub_unlock(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_event_t *event;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (conference->is_unlocked_sound) {
		conference_file_play(conference, conference->is_unlocked_sound, CONF_DEFAULT_LEADIN, NULL, 0);
	}

	conference_utils_clear_flag_locked(conference, CFLAG_LOCKED);
	stream->write_function(stream, "+OK %s unlocked\n", argv[0]);
	if (test_eflag(conference, EFLAG_UNLOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
		conference_event_add_data(conference, event);
		switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "unlock");
		switch_event_fire(&event);
	}

	return 0;
}

switch_status_t conference_api_sub_exit_sound(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_event_t *event;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 2) {
		stream->write_function(stream, "-ERR Not enough args\n");
		return SWITCH_STATUS_GENERR;
	}

	if ( !strcasecmp(argv[2], "on") ) {
		conference_utils_set_flag_locked(conference, CFLAG_EXIT_SOUND);
		stream->write_function(stream, "+OK %s exit sounds on (%s)\n", argv[0], conference->exit_sound);
		if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_event_add_data(conference, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "exit-sounds-on");
			switch_event_fire(&event);
		}
	} else if ( !strcasecmp(argv[2], "off") || !strcasecmp(argv[2], "none") ) {
		conference_utils_clear_flag_locked(conference, CFLAG_EXIT_SOUND);
		stream->write_function(stream, "+OK %s exit sounds off (%s)\n", argv[0], conference->exit_sound);
		if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_event_add_data(conference, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "exit-sounds-off");
			switch_event_fire(&event);
		}
	} else if ( !strcasecmp(argv[2], "file") ) {
		if (! argv[3]) {
			stream->write_function(stream, "-ERR No filename specified\n");
		} else {
			/* TODO: if possible, verify file exists before setting it */
			stream->write_function(stream,"+OK Old exit sound: [%s]\n", conference->exit_sound);
			conference->exit_sound = switch_core_strdup(conference->pool, argv[3]);
			stream->write_function(stream, "+OK %s exit sound file set to %s\n", argv[0], conference->exit_sound);
			if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
				conference_event_add_data(conference, event);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "exit-sound-file-changed");
				switch_event_fire(&event);
			}
		}
	} else {
		stream->write_function(stream, "-ERR Bad args\n");
		return SWITCH_STATUS_GENERR;
	}

	return 0;
}


switch_status_t conference_api_sub_enter_sound(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_event_t *event;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 2) {
		stream->write_function(stream, "-ERR Not enough args\n");
		return SWITCH_STATUS_GENERR;
	}

	if ( !strcasecmp(argv[2], "on") ) {
		conference_utils_set_flag_locked(conference, CFLAG_ENTER_SOUND);
		stream->write_function(stream, "+OK %s enter sounds on (%s)\n", argv[0], conference->enter_sound);
		if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_event_add_data(conference, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "enter-sounds-on");
			switch_event_fire(&event);
		}
	} else if ( !strcasecmp(argv[2], "off") || !strcasecmp(argv[2], "none") ) {
		conference_utils_clear_flag_locked(conference, CFLAG_ENTER_SOUND);
		stream->write_function(stream, "+OK %s enter sounds off (%s)\n", argv[0], conference->enter_sound);
		if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
			conference_event_add_data(conference, event);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "enter-sounds-off");
			switch_event_fire(&event);
		}
	} else if ( !strcasecmp(argv[2], "file") ) {
		if (! argv[3]) {
			stream->write_function(stream, "-ERR No filename specified\n");
		} else {
			/* TODO: verify file exists before setting it */
			conference->enter_sound = switch_core_strdup(conference->pool, argv[3]);
			stream->write_function(stream, "+OK %s enter sound file set to %s\n", argv[0], conference->enter_sound);
			if (test_eflag(conference, EFLAG_LOCK) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
				conference_event_add_data(conference, event);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "enter-sound-file-changed");
				switch_event_fire(&event);
			}
		}
	} else {
		stream->write_function(stream, "-ERR Bad args\n");
		return SWITCH_STATUS_GENERR;
	}

	return 0;
}


switch_status_t conference_api_sub_dial(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_call_cause_t cause;
	char *tmp;

	switch_assert(stream != NULL);

	if (argc <= 2) {
		stream->write_function(stream, "-ERR Bad Args\n");
		return SWITCH_STATUS_GENERR;
	}

	if (conference && argv[2] && strstr(argv[2], "vlc/")) {
		tmp = switch_core_sprintf(conference->pool, "{vlc_rate=%d,vlc_channels=%d,vlc_interval=%d}%s",
								  conference->rate, conference->channels, conference->interval, argv[2]);
		argv[2] = tmp;
	}

	if (conference) {
		conference_outcall(conference, NULL, NULL, argv[2], 60, NULL, argv[4], argv[3], NULL, &cause, NULL, NULL);
	} else {
		conference_outcall(NULL, argv[0], NULL, argv[2], 60, NULL, argv[4], argv[3], NULL, &cause, NULL, NULL);
	}
	stream->write_function(stream, "+OK Call Requested: result: [%s]\n", switch_channel_cause2str(cause));

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_bgdial(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_uuid_t uuid;
	char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];

	switch_assert(stream != NULL);

	if (argc <= 2) {
		stream->write_function(stream, "-ERR Bad Args\n");
		return SWITCH_STATUS_GENERR;
	}

	switch_uuid_get(&uuid);
	switch_uuid_format(uuid_str, &uuid);

	if (conference) {
		conference_outcall_bg(conference, NULL, NULL, argv[2], 60, NULL, argv[4], argv[3], uuid_str, NULL, NULL, NULL);
	} else {
		conference_outcall_bg(NULL, argv[0], NULL, argv[2], 60, NULL, argv[4], argv[3], uuid_str, NULL, NULL, NULL);
	}

	stream->write_function(stream, "+OK Job-UUID: %s\n", uuid_str);

	return SWITCH_STATUS_SUCCESS;
}



switch_status_t conference_api_sub_transfer(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_status_t ret_status = SWITCH_STATUS_SUCCESS;
	char *conference_name = NULL, *profile_name;
	switch_event_t *params = NULL;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc > 3 && !zstr(argv[2])) {
		int x;

		conference_name = strdup(argv[2]);

		if ((profile_name = strchr(conference_name, '@'))) {
			*profile_name++ = '\0';
		} else {
			profile_name = "default";
		}

		for (x = 3; x < argc; x++) {
			conference_member_t *member = NULL;
			uint32_t id = atoi(argv[x]);
			switch_channel_t *channel;
			switch_event_t *event;
			char *xdest = NULL;

			if (!id || !(member = conference_member_get(conference, id))) {
				stream->write_function(stream, "-ERR No Member %u in conference %s.\n", id, conference->name);
				continue;
			}

			channel = switch_core_session_get_channel(member->session);
			xdest = switch_core_session_sprintf(member->session, "conference:%s@%s", conference_name, profile_name);
			switch_ivr_session_transfer(member->session, xdest, "inline", NULL);

			switch_channel_set_variable(channel, "last_transfered_conference", conference_name);

			stream->write_function(stream, "+OK Member '%d' sent to conference %s.\n", member->id, argv[2]);

			/* tell them what happened */
			if (test_eflag(conference, EFLAG_TRANSFER) &&
				switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
				conference_member_add_event_data(member, event);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Old-Conference-Name", conference->name);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "New-Conference-Name", argv[3]);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "transfer");
				switch_event_fire(&event);
			}

			switch_thread_rwlock_unlock(member->rwlock);
		}
	} else {
		ret_status = SWITCH_STATUS_GENERR;
	}

	if (params) {
		switch_event_destroy(&params);
	}

	switch_safe_free(conference_name);

	return ret_status;
}

switch_status_t conference_api_sub_check_record(conference_obj_t *conference, switch_stream_handle_t *stream, int arc, char **argv)
{
	conference_record_t *rec;
	int x = 0;

	switch_mutex_lock(conference->flag_mutex);
	for (rec = conference->rec_node_head; rec; rec = rec->next) {
		stream->write_function(stream, "+OK Record file %s%s%s\n", rec->path, rec->autorec ? " " : "", rec->autorec ? "(Auto)" : "");
		x++;
	}

	if (!x) {
		stream->write_function(stream, "-ERR Conference is not being recorded.\n");
	}
	switch_mutex_unlock(conference->flag_mutex);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_record(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int id = 0;
	conference_record_t *rec;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 2) {
		return SWITCH_STATUS_GENERR;
	}

	if (argv[3]) {

		id = atoi(argv[3]);

		if (id < 1 || id > MAX_CANVASES+1) {
			id = -1;
		}

		if (id < 1) {
			stream->write_function(stream, "-ERR Invalid canvas\n");
		}

	}

	if (id == 0 && conference->canvases[0]) id = 1;

	if (id > conference->canvas_count + 1) {
		id = 1;
	}


	switch_mutex_lock(conference->flag_mutex);
	for (rec = conference->rec_node_head; rec; rec = rec->next) {
		char *path_a, *path_b;
		
		if ((path_a = strrchr(rec->path, '}'))) {
			while(*path_a == ' ' || *path_a == '}') path_a++;
		} else {
			path_a = rec->path;
		}

		if ((path_b = strrchr(argv[2], '}'))) {
			while(*path_b == ' ' || *path_b == '}') path_b++;
		} else {
			path_b = argv[2];
		}
		
		if (!strcmp(path_a, path_b)) {
			stream->write_function(stream, "-ERR file [%s] is already being used for recording.\n", rec->path);
			switch_mutex_unlock(conference->flag_mutex);
			return SWITCH_STATUS_SUCCESS;
		}
	}
	switch_mutex_unlock(conference->flag_mutex);


	if (id > 0) {
		stream->write_function(stream, "+OK Record file %s canvas %d\n", argv[2], id);
	} else {
		stream->write_function(stream, "+OK Record file %s\n", argv[2]);
	}

	conference->record_filename = switch_core_strdup(conference->pool, argv[2]);
	conference->record_count++;
	conference_record_launch_thread(conference, argv[2], id - 1, SWITCH_FALSE);
	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_norecord(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int all, before = conference->record_count, ttl = 0;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 2)
		return SWITCH_STATUS_GENERR;

	all = (strcasecmp(argv[2], "all") == 0);

	if (!conference_record_stop(conference, stream, all ? NULL : argv[2]) && !all) {
		stream->write_function(stream, "-ERR non-existant recording '%s'\n", argv[2]);
	}

	ttl = before - conference->record_count;
	stream->write_function(stream, "+OK Stopped recording %d file%s\n", ttl, ttl == 1 ? "" : "s");

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_pauserec(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_event_t *event;
	recording_action_type_t action;

	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc <= 2)
		return SWITCH_STATUS_GENERR;

	if (strcasecmp(argv[1], "pause") == 0) {
		action = REC_ACTION_PAUSE;
	} else if (strcasecmp(argv[1], "resume") == 0) {
		action = REC_ACTION_RESUME;
	} else {
		return SWITCH_STATUS_GENERR;
	}
	stream->write_function(stream, "+OK %s recording file %s\n",
						   action == REC_ACTION_PAUSE ? "Pause" : "Resume", argv[2]);
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,	"%s recording file %s\n",
					  action == REC_ACTION_PAUSE ? "Pause" : "Resume", argv[2]);

	if (!conference_record_action(conference, argv[2], action)) {
		stream->write_function(stream, "-ERR non-existant recording '%s'\n", argv[2]);
	} else {
		if (test_eflag(conference, EFLAG_RECORD) && switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS)
			{
				conference_event_add_data(conference, event);
				if (action == REC_ACTION_PAUSE) {
					switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "pause-recording");
				} else {
					switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Action", "resume-recording");
				}
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Path", argv[2]);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Other-Recordings", conference->record_count ? "true" : "false");
				switch_event_fire(&event);
			}
	}

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_recording(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if (argc > 2 && argc <= 3) {
		if (strcasecmp(argv[2], "stop") == 0 || strcasecmp(argv[2], "check") == 0) {
			argv[3] = "all";
			argc++;
		}
	}

	if (argc <= 3) {
		/* It means that old syntax is used */
		return conference_api_sub_record(conference,stream,argc,argv);
	} else {
		/* for new syntax call existing functions with fixed parameter list */
		if (strcasecmp(argv[2], "start") == 0) {
			argv[1] = argv[2];
			argv[2] = argv[3];
			argv[3] = argv[4];
			return conference_api_sub_record(conference,stream,4,argv);
		} else if (strcasecmp(argv[2], "stop") == 0) {
			argv[1] = argv[2];
			argv[2] = argv[3];
			return conference_api_sub_norecord(conference,stream,4,argv);
		} else if (strcasecmp(argv[2], "check") == 0) {
			argv[1] = argv[2];
			argv[2] = argv[3];
			return conference_api_sub_check_record(conference,stream,4,argv);
		} else if (strcasecmp(argv[2], "pause") == 0) {
			argv[1] = argv[2];
			argv[2] = argv[3];
			return conference_api_sub_pauserec(conference,stream,4,argv);
		} else if (strcasecmp(argv[2], "resume") == 0) {
			argv[1] = argv[2];
			argv[2] = argv[3];
			return conference_api_sub_pauserec(conference,stream,4,argv);
		} else {
			return SWITCH_STATUS_GENERR;
		}
	}
}

switch_status_t conference_api_sub_file_vol(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	if (argc >= 1) {
		conference_file_node_t *fnode;
		int vol = 0;
		int ok = 0;

		if (argc < 2) {
			stream->write_function(stream, "-ERR missing args\n");
			return SWITCH_STATUS_GENERR;
		}

		switch_mutex_lock(conference->mutex);

		fnode = conference->fnode;

		vol = atoi(argv[2]);

		if (argc > 3) {
			if (strcasecmp(argv[3], "async")) {
				fnode = conference->async_fnode;
			}
		}

		if (fnode && fnode->type == NODE_TYPE_FILE) {
			fnode->fh.vol = vol;
			ok = 1;
		}
		switch_mutex_unlock(conference->mutex);


		if (ok) {
			stream->write_function(stream, "+OK volume changed\n");
			return SWITCH_STATUS_SUCCESS;
		} else {
			stream->write_function(stream, "-ERR File not playing\n");
			return SWITCH_STATUS_GENERR;
		}


	} else {
		stream->write_function(stream, "-ERR Invalid parameters:\n");
		return SWITCH_STATUS_GENERR;
	}
}

switch_status_t conference_api_sub_pin(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	if ((argc == 4) && (!strcmp(argv[2], "mod"))) {
		conference->mpin = switch_core_strdup(conference->pool, argv[3]);
		stream->write_function(stream, "+OK Moderator Pin for conference %s set: %s\n", argv[0], conference->mpin);
		return SWITCH_STATUS_SUCCESS;
	} else if ((argc == 3) && (!strcmp(argv[1], "pin"))) {
		conference->pin = switch_core_strdup(conference->pool, argv[2]);
		stream->write_function(stream, "+OK Pin for conference %s set: %s\n", argv[0], conference->pin);
		return SWITCH_STATUS_SUCCESS;
	} else if (argc == 2 && (!strcmp(argv[1], "nopin"))) {
		conference->pin = NULL;
		stream->write_function(stream, "+OK Pin for conference %s deleted\n", argv[0]);
		return SWITCH_STATUS_SUCCESS;
	} else {
		stream->write_function(stream, "-ERR Invalid parameters:\n");
		return SWITCH_STATUS_GENERR;
	}
}

switch_status_t conference_api_sub_get(conference_obj_t *conference,
									   switch_stream_handle_t *stream, int argc, char **argv) {
	int ret_status = SWITCH_STATUS_GENERR;

	if (argc != 3) {
		ret_status = SWITCH_STATUS_FALSE;
	} else {
		ret_status = SWITCH_STATUS_SUCCESS;
		if (strcasecmp(argv[2], "run_time") == 0) {
			stream->write_function(stream, "%ld",
								   switch_epoch_time_now(NULL) - conference->run_time);
		} else if (strcasecmp(argv[2], "count") == 0) {
			stream->write_function(stream, "%d",
								   conference->count);
		} else if (strcasecmp(argv[2], "count_ghosts") == 0) {
			stream->write_function(stream, "%d",
								   conference->count_ghosts);
		} else if (strcasecmp(argv[2], "max_members") == 0) {
			stream->write_function(stream, "%d",
								   conference->max_members);
		} else if (strcasecmp(argv[2], "rate") == 0) {
			stream->write_function(stream, "%d",
								   conference->rate);
		} else if (strcasecmp(argv[2], "profile_name") == 0) {
			stream->write_function(stream, "%s",
								   conference->profile_name);
		} else if (strcasecmp(argv[2], "sound_prefix") == 0) {
			stream->write_function(stream, "%s",
								   conference->sound_prefix);
		} else if (strcasecmp(argv[2], "caller_id_name") == 0) {
			stream->write_function(stream, "%s",
								   conference->caller_id_name);
		} else if (strcasecmp(argv[2], "caller_id_number") == 0) {
			stream->write_function(stream, "%s",
								   conference->caller_id_number);
		} else if (strcasecmp(argv[2], "is_locked") == 0) {
			stream->write_function(stream, "%s",
								   conference_utils_test_flag(conference, CFLAG_LOCKED) ? "locked" : "");
		} else if (strcasecmp(argv[2], "endconference_grace_time") == 0) {
			stream->write_function(stream, "%d",
								   conference->endconference_grace_time);
		} else if (strcasecmp(argv[2], "uuid") == 0) {
			stream->write_function(stream, "%s",
								   conference->uuid_str);
		} else if (strcasecmp(argv[2], "wait_mod") == 0) {
			stream->write_function(stream, "%s",
								   conference_utils_test_flag(conference, CFLAG_WAIT_MOD) ? "true" : "");
		} else {
			ret_status = SWITCH_STATUS_FALSE;
		}
	}

	return ret_status;
}

switch_status_t conference_api_sub_set(conference_obj_t *conference,
									   switch_stream_handle_t *stream, int argc, char **argv) {
	int ret_status = SWITCH_STATUS_GENERR;

	if (argc != 4 || zstr(argv[3])) {
		ret_status = SWITCH_STATUS_FALSE;
	} else {
		ret_status = SWITCH_STATUS_SUCCESS;
		if (strcasecmp(argv[2], "max_members") == 0) {
			int new_max = atoi(argv[3]);
			if (new_max >= 0) {
				stream->write_function(stream, "%d", conference->max_members);
				conference->max_members = new_max;
			} else {
				ret_status = SWITCH_STATUS_FALSE;
			}
		} else	if (strcasecmp(argv[2], "sound_prefix") == 0) {
			stream->write_function(stream, "%s",conference->sound_prefix);
			conference->sound_prefix = switch_core_strdup(conference->pool, argv[3]);
		} else	if (strcasecmp(argv[2], "caller_id_name") == 0) {
			stream->write_function(stream, "%s",conference->caller_id_name);
			conference->caller_id_name = switch_core_strdup(conference->pool, argv[3]);
		} else	if (strcasecmp(argv[2], "caller_id_number") == 0) {
			stream->write_function(stream, "%s",conference->caller_id_number);
			conference->caller_id_number = switch_core_strdup(conference->pool, argv[3]);
		} else if (strcasecmp(argv[2], "endconference_grace_time") == 0) {
			int new_gt = atoi(argv[3]);
			if (new_gt >= 0) {
				stream->write_function(stream, "%d", conference->endconference_grace_time);
				conference->endconference_grace_time = new_gt;
			} else {
				ret_status = SWITCH_STATUS_FALSE;
			}
		} else {
			ret_status = SWITCH_STATUS_FALSE;
		}
	}

	return ret_status;
}



switch_status_t conference_api_sub_xml_list(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	int count = 0;
	switch_hash_index_t *hi;
	void *val;
	switch_xml_t x_conference, x_conferences;
	int off = 0;
	char *ebuf;

	x_conferences = switch_xml_new("conferences");
	switch_assert(x_conferences);

	if (conference == NULL) {
		switch_mutex_lock(conference_globals.hash_mutex);
		for (hi = switch_core_hash_first(conference_globals.conference_hash); hi; hi = switch_core_hash_next(&hi)) {
			switch_core_hash_this(hi, NULL, NULL, &val);
			conference = (conference_obj_t *) val;

			x_conference = switch_xml_add_child_d(x_conferences, "conference", off++);
			switch_assert(conference);

			count++;
			conference_xlist(conference, x_conference, off);

		}
		switch_mutex_unlock(conference_globals.hash_mutex);
	} else {
		x_conference = switch_xml_add_child_d(x_conferences, "conference", off++);
		switch_assert(conference);
		count++;
		conference_xlist(conference, x_conference, off);
	}


	ebuf = switch_xml_toxml(x_conferences, SWITCH_TRUE);

	stream->write_function(stream, "%s", ebuf);

	switch_xml_free(x_conferences);
	free(ebuf);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_sub_json_list(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv)
{
	switch_hash_index_t *hi;
	void *val;
	char *ebuf;
	cJSON *conferences = cJSON_CreateArray();
	switch_bool_t compact = SWITCH_FALSE;

	switch_assert(conferences);

	if (conference == NULL) {
		switch_mutex_lock(conference_globals.hash_mutex);
		for (hi = switch_core_hash_first(conference_globals.conference_hash); hi; hi = switch_core_hash_next(&hi)) {
			switch_core_hash_this(hi, NULL, NULL, &val);
			conference = (conference_obj_t *) val;
			conference_jlist(conference, conferences);
		}
		switch_mutex_unlock(conference_globals.hash_mutex);
		compact = (argc == 2 && !strcmp(argv[1], "compact"));
	} else {
		conference_jlist(conference, conferences);
		compact = (argc == 3 && !strcmp(argv[2], "compact"));
	}

	ebuf = compact ? cJSON_PrintUnformatted(conferences) : cJSON_Print(conferences);
	switch_assert(ebuf);
	stream->write_function(stream, "%s", ebuf);
	free(ebuf);

	return SWITCH_STATUS_SUCCESS;
}

switch_status_t conference_api_dispatch(conference_obj_t *conference, switch_stream_handle_t *stream, int argc, char **argv, const char *cmdline, int argn)
{
	switch_status_t status = SWITCH_STATUS_FALSE;
	uint32_t i, found = 0;
	switch_assert(conference != NULL);
	switch_assert(stream != NULL);

	/* loop through the command table to find a match */
	for (i = 0; i < CONFFUNCAPISIZE && !found; i++) {
		if (strcasecmp(argv[argn], conference_api_sub_commands[i].pname) == 0) {
			found = 1;
			switch (conference_api_sub_commands[i].fntype) {

				/* commands that we've broken the command line into arguments for */
			case CONF_API_SUB_ARGS_SPLIT:
				{
					conference_api_args_cmd_t pfn = (conference_api_args_cmd_t) conference_api_sub_commands[i].pfnapicmd;

					if (pfn(conference, stream, argc, argv) != SWITCH_STATUS_SUCCESS) {
						/* command returned error, so show syntax usage */
						stream->write_function(stream, "%s %s", conference_api_sub_commands[i].pcommand, conference_api_sub_commands[i].psyntax);
					}
				}
				break;

				/* member specific command that can be iterated */
			case CONF_API_SUB_MEMBER_TARGET:
				{
					uint32_t id = 0;
					uint8_t all = 0;
					uint8_t last = 0;
					uint8_t non_mod = 0;

					if (argv[argn + 1]) {
						if (!(id = atoi(argv[argn + 1]))) {
							all = strcasecmp(argv[argn + 1], "all") ? 0 : 1;
							non_mod = strcasecmp(argv[argn + 1], "non_moderator") ? 0 : 1;
							last = strcasecmp(argv[argn + 1], "last") ? 0 : 1;
						}
					}

					if (all || non_mod) {
						conference_member_itterator(conference, stream, non_mod, (conference_api_member_cmd_t) conference_api_sub_commands[i].pfnapicmd, argv[argn + 2]);
					} else if (last) {
						conference_member_t *member = NULL;
						conference_member_t *last_member = NULL;

						switch_mutex_lock(conference->member_mutex);

						/* find last (oldest) member */
						member = conference->members;
						while (member != NULL) {
							if (last_member == NULL || member->id > last_member->id) {
								last_member = member;
							}
							member = member->next;
						}

						/* exec functio on last (oldest) member */
						if (last_member != NULL && last_member->session && !conference_utils_member_test_flag(last_member, MFLAG_NOCHANNEL)) {
							conference_api_member_cmd_t pfn = (conference_api_member_cmd_t) conference_api_sub_commands[i].pfnapicmd;
							pfn(last_member, stream, argv[argn + 2]);
						}

						switch_mutex_unlock(conference->member_mutex);
					} else if (id) {
						conference_api_member_cmd_t pfn = (conference_api_member_cmd_t) conference_api_sub_commands[i].pfnapicmd;
						conference_member_t *member = conference_member_get(conference, id);

						if (member != NULL) {
							pfn(member, stream, argv[argn + 2]);
							switch_thread_rwlock_unlock(member->rwlock);
						} else {
							stream->write_function(stream, "-ERR Non-Existant ID %u\n", id);
						}
					} else if (!zstr(argv[argn + 1]) && strchr(argv[argn + 1], '=')) {
						conference_api_member_cmd_t pfn = (conference_api_member_cmd_t) conference_api_sub_commands[i].pfnapicmd;
						conference_member_t *member;
						char *var, *val;

						var = strdup(argv[argn + 1]);

						if ((val = strchr(var, '='))) {
							*val++ = '\0';
						}

						member = conference_member_get_by_var(conference, var, val);

						if (member != NULL) {
							pfn(member, stream, argv[argn + 2]);
							switch_thread_rwlock_unlock(member->rwlock);
						} else {
							stream->write_function(stream, "-ERR Non-Existant member\n");
						}

						switch_safe_free(var);
					} else {
						stream->write_function(stream, "%s %s", conference_api_sub_commands[i].pcommand, conference_api_sub_commands[i].psyntax);
					}
				}
				break;

				/* commands that deals with all text after command */
			case CONF_API_SUB_ARGS_AS_ONE:
				{
					conference_api_text_cmd_t pfn = (conference_api_text_cmd_t) conference_api_sub_commands[i].pfnapicmd;
					char *start_text;
					const char *modified_cmdline = cmdline;
					const char *cmd = conference_api_sub_commands[i].pname;

					if (!zstr(modified_cmdline) && (start_text = strstr(modified_cmdline, cmd))) {
						modified_cmdline = start_text + strlen(cmd);
						while (modified_cmdline && (*modified_cmdline == ' ' || *modified_cmdline == '\t')) {
							modified_cmdline++;
						}
					}

					/* call the command handler */
					if (pfn(conference, stream, modified_cmdline) != SWITCH_STATUS_SUCCESS) {
						/* command returned error, so show syntax usage */
						stream->write_function(stream, "%s %s", conference_api_sub_commands[i].pcommand, conference_api_sub_commands[i].psyntax);
					}
				}
				break;
			}
		}
	}

	if (!found) {
		stream->write_function(stream, "-ERR Conference command '%s' not found.\n", argv[argn]);
	} else {
		status = SWITCH_STATUS_SUCCESS;
	}

	return status;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
 */
