/* * mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2011-2012, Barracuda Networks Inc. * * 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 mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is Barracuda Networks Inc. * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Mathieu Rene * Seven Du * * rtmp.c -- RTMP Signalling functions * */ #include "mod_rtmp.h" /* AMF */ #include "amf0.h" #include "io.h" #include "types.h" /* RTMP_INVOKE_FUNCTION is a macro that expands to: switch_status_t function(rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[]) */ RTMP_INVOKE_FUNCTION(rtmp_i_connect) { amf0_data *object1 = amf0_object_new(), *object2 = amf0_object_new(), *params = argv[0], *d; const char *s; if ((d = amf0_object_get(params, "app")) && (s = amf0_get_string(d))) { rsession->app = switch_core_strdup(rsession->pool, s); } if ((d = amf0_object_get(params, "flashVer")) && (s = amf0_get_string(d))) { rsession->flashVer = switch_core_strdup(rsession->pool, s); } if ((d = amf0_object_get(params, "swfUrl")) && (s = amf0_get_string(d))) { rsession->swfUrl = switch_core_strdup(rsession->pool, s); } if ((d = amf0_object_get(params, "tcUrl")) && (s = amf0_get_string(d))) { rsession->tcUrl = switch_core_strdup(rsession->pool, s); } if ((d = amf0_object_get(params, "pageUrl")) && (s = amf0_get_string(d))) { rsession->pageUrl = switch_core_strdup(rsession->pool, s); } if ((d = amf0_object_get(params, "capabilities"))) { rsession->capabilities = amf0_get_number(d); } if ((d = amf0_object_get(params, "audioCodecs"))) { rsession->audioCodecs = amf0_get_number(d); } if ((d = amf0_object_get(params, "videoCodecs"))) { rsession->videoCodecs = amf0_get_number(d); } if ((d = amf0_object_get(params, "videoFunction"))) { rsession->videoFunction = amf0_get_number(d); } amf0_object_add(object1, "fmsVer", amf0_number_new(1)); amf0_object_add(object1, "capabilities", amf0_number_new(31)); amf0_object_add(object2, "level", amf0_str("status")); amf0_object_add(object2, "code", amf0_str("NetConnection.Connect.Success")); amf0_object_add(object2, "description", amf0_str("Connection succeeded")); amf0_object_add(object2, "clientId", amf0_number_new(217834719)); amf0_object_add(object2, "objectEncoding", amf0_number_new(0)); rtmp_set_chunksize(rsession, rsession->profile->chunksize); { unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW) }; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_WINDOW_ACK_SIZE, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER); } { unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW), 0x1 /* Soft limit */}; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_SET_PEER_BW, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER); } { unsigned char buf[] = { INT16(RTMP_CTRL_STREAM_BEGIN), INT32(0) }; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); } /* respond with a success message */ rtmp_send_invoke_free(rsession, amfnumber, 0, 0, amf0_str("_result"), amf0_number_new(1), object1, object2, NULL); rtmp_send_invoke_free(rsession, 3, 0, 0, amf0_str("connected"), amf0_number_new(0), amf0_null_new(), amf0_str(rsession->uuid), NULL); switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_NOTICE, "Sent connect reply\n"); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_createStream) { rtmp_send_invoke_free(rsession, amfnumber, 0, 0, amf0_str("_result"), amf0_number_new(transaction_id), amf0_null_new(), amf0_number_new(rsession->next_streamid), NULL); switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Replied to createStream (%u)\n", amf0_get_number(argv[1])); rsession->next_streamid++; return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_initStream) { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Received initStream (%u)\n", rsession->next_streamid); rsession->next_streamid++; return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_noop) { return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio) { switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE; if (enabled) { switch_set_flag(rsession, SFLAG_AUDIO); } else { switch_clear_flag(rsession, SFLAG_AUDIO); } switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "%sending audio\n", enabled ? "S" : "Not s"); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo) { switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE; if (enabled) { switch_set_flag(rsession, SFLAG_VIDEO); if (rsession->tech_pvt) { switch_set_flag(rsession->tech_pvt, TFLAG_VID_WAIT_KEYFRAME); } } else { switch_clear_flag(rsession, SFLAG_VIDEO); } switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "%sending video\n", enabled ? "S" : "Not s"); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_play) { amf0_data *obj = amf0_object_new(); amf0_data *object = amf0_object_new(); switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Got play for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])), state->stream_id); /* Set outgoing chunk size to 1024 bytes */ rtmp_set_chunksize(rsession, 1024); rsession->media_streamid = state->stream_id; /* Send StreamBegin on the current stream */ { unsigned char buf[] = { INT16(RTMP_CTRL_STREAM_BEGIN), INT32(rsession->media_streamid) }; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); } { unsigned char buf[] = { INT16(RTMP_CTRL_SET_BUFFER_LENGTH), INT32(rsession->media_streamid), INT32(rsession->profile->buffer_len) }; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); } /* Send onStatus */ amf0_object_add(object, "level", amf0_str("status")); amf0_object_add(object, "code", amf0_str("NetStream.Play.Reset")); amf0_object_add(object, "description", amf0_str("description")); amf0_object_add(object, "details", amf0_str("details")); amf0_object_add(object, "clientid", amf0_number_new(217834719)); rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, amf0_str("onStatus"), amf0_number_new(0), amf0_null_new(), object, NULL); object = amf0_object_new(); amf0_object_add(object, "level", amf0_str("status")); amf0_object_add(object, "code", amf0_str("NetStream.Play.Start")); amf0_object_add(object, "description", amf0_str("description")); amf0_object_add(object, "details", amf0_str("details")); amf0_object_add(object, "clientid", amf0_number_new(217834719)); rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, amf0_str("onStatus"), amf0_number_new(0), amf0_null_new(), object, NULL); amf0_object_add(obj, "code", amf0_str("NetStream.Data.Start")); rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, amf0_str("onStatus"), obj, NULL); rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid, amf0_str("|RtmpSampleAccess"), amf0_boolean_new(1), amf0_boolean_new(1), NULL); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_publish) { amf0_data *object = amf0_object_new(); unsigned char buf[] = { INT16(RTMP_CTRL_STREAM_BEGIN), INT32(state->stream_id) }; rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0); amf0_object_add(object, "level", amf0_str("status")); amf0_object_add(object, "code", amf0_str("NetStream.Publish.Start")); amf0_object_add(object, "description", amf0_str("description")); amf0_object_add(object, "details", amf0_str("details")); amf0_object_add(object, "clientid", amf0_number_new(217834719)); rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, state->stream_id, amf0_str("onStatus"), amf0_number_new(0), amf0_null_new(), object, NULL); switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Got publish on stream %u.\n", state->stream_id); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_makeCall) { switch_core_session_t *newsession = NULL; char *number = NULL; if ((number = amf0_get_string(argv[1]))) { switch_event_t *event = NULL; char *auth, *user = NULL, *domain = NULL; if (argc >= 3 && (auth = amf0_get_string(argv[2])) && !zstr(auth)) { switch_split_user_domain(auth, &user, &domain); if (rtmp_session_check_user(rsession, user, domain) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in account [%s@%s]\n", number, switch_str_nil(user), switch_str_nil(domain)); return SWITCH_STATUS_FALSE; } } else if (rsession->profile->auth_calls && !rsession->account) { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in\n", number); return SWITCH_STATUS_FALSE; } if (amf0_is_object(argv[3])) { amf_object_to_event(argv[3], &event); } if (rtmp_session_create_call(rsession, &newsession, 0, RTMP_DEFAULT_STREAM_AUDIO, number, user, domain, event) != SWITCH_CAUSE_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create call.\n"); } if (event) { switch_event_destroy(&event); } } if (newsession) { rtmp_private_t *new_pvt = switch_core_session_get_private(newsession); rtmp_send_invoke_free(rsession, 3, 0, 0, amf0_str("onMakeCall"), amf0_number_new(transaction_id), amf0_null_new(), amf0_str(switch_core_session_get_uuid(newsession)), amf0_str(switch_str_nil(number)), amf0_str(switch_str_nil(new_pvt->auth)), NULL); rtmp_attach_private(rsession, switch_core_session_get_private(newsession)); } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_fcSubscribe) { switch_status_t status; int ac; amf0_data *av[3] = { 0 }; switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Got FCSubscribe for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])), state->stream_id); ac = 3; av[0] = argv[0]; av[1] = argv[1]; av[2] = amf0_boolean_new(1); switch_assert(av[2]); status = rtmp_i_receiveaudio(rsession, state, amfnumber, transaction_id, ac, av); if (status != SWITCH_STATUS_SUCCESS) return status; status = rtmp_i_receivevideo(rsession, state, amfnumber, transaction_id, ac, av); if (status != SWITCH_STATUS_SUCCESS) return status; amf0_data_free(av[2]); rtmp_i_makeCall(rsession, state, amfnumber, transaction_id, argc, argv); return status; } RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF) { /* Send DTMFs on the active channel */ switch_dtmf_t dtmf = { 0 }; switch_channel_t *channel; char *digits; if (!rsession->tech_pvt) { return SWITCH_STATUS_FALSE; } channel = switch_core_session_get_channel(rsession->tech_pvt->session); if (amf0_is_number(argv[2])) { dtmf.duration = amf0_get_number(argv[2]); } else if (!zstr(amf0_get_string(argv[2]))) { dtmf.duration = atoi(amf0_get_string(argv[2])); } if ((digits = amf0_get_string(argv[1]))) { size_t len = strlen(digits); size_t j; for (j = 0; j < len; j++) { dtmf.digit = digits[j]; switch_channel_queue_dtmf(channel, &dtmf); } } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_login) { char *user, *auth, *domain, *ddomain = NULL; user = amf0_get_string(argv[1]); auth = amf0_get_string(argv[2]); if (zstr(user) || zstr(auth)) { return SWITCH_STATUS_FALSE; } if ((domain = strchr(user, '@'))) { *domain++ = '\0'; } if (zstr(domain)) { ddomain = switch_core_get_domain(SWITCH_TRUE); domain = ddomain; } if (rtmp_check_auth(rsession, user, domain, auth) == SWITCH_STATUS_SUCCESS) { rtmp_session_login(rsession, user, domain); } else { rtmp_send_invoke_free(rsession, 3, 0, 0, amf0_str("onLogin"), amf0_number_new(0), amf0_null_new(), amf0_str("failure"), amf0_null_new(), amf0_null_new(), NULL); } switch_safe_free(ddomain); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_logout) { char *auth = amf0_get_string(argv[1]); char *user = NULL, *domain = NULL; /* Unregister from that user */ rtmp_clear_registration(rsession, auth, NULL); switch_split_user_domain(auth, &user, &domain); if (!zstr(user) && !zstr(domain)) { rtmp_session_logout(rsession, user, domain); } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_register) { char *auth = amf0_get_string(argv[1]); const char *user = NULL, *domain = NULL; char *dup = NULL; switch_status_t status; if (!rsession->account) { return SWITCH_STATUS_FALSE; } if (!zstr(auth)) { dup = strdup(auth); switch_split_user_domain(dup, (char**)&user, (char**)&domain); } else { dup = auth = switch_mprintf("%s@%s", rsession->account->user, rsession->account->domain); user = rsession->account->user; domain = rsession->account->domain; } if (rtmp_session_check_user(rsession, user, domain) == SWITCH_STATUS_SUCCESS) { rtmp_add_registration(rsession, auth, amf0_get_string(argv[2])); status = SWITCH_STATUS_SUCCESS; } else { status = SWITCH_STATUS_FALSE; } switch_safe_free(dup); return status; } RTMP_INVOKE_FUNCTION(rtmp_i_unregister) { rtmp_clear_registration(rsession, amf0_get_string(argv[1]), amf0_get_string(argv[2])); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_answer) { switch_channel_t *channel = NULL; char *uuid = amf0_get_string(argv[1]); if (!zstr(uuid)) { rtmp_private_t *new_tech_pvt = rtmp_locate_private(rsession, uuid); if (new_tech_pvt) { switch_channel_mark_answered(switch_core_session_get_channel(new_tech_pvt->session)); rtmp_attach_private(rsession, new_tech_pvt); } return SWITCH_STATUS_FALSE; } if (!rsession->tech_pvt) { return SWITCH_STATUS_FALSE; } /* No UUID specified but we're attached to a channel, mark it as answered */ channel = switch_core_session_get_channel(rsession->tech_pvt->session); switch_channel_mark_answered(channel); rtmp_attach_private(rsession, rsession->tech_pvt); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_attach) { rtmp_private_t *tech_pvt = NULL; char *uuid = amf0_get_string(argv[1]); if (!zstr(uuid)) { tech_pvt = rtmp_locate_private(rsession, uuid); } /* Will detach if an empty (or invalid) uuid is received */ rtmp_attach_private(rsession, tech_pvt); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_hangup) { /* CallID (or null/nothing to hangup the current call) */ char *uuid = amf0_get_string(argv[1]); char *scause; switch_channel_t *channel = NULL; switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING; if (!zstr(uuid)) { rtmp_private_t *tech_pvt = rtmp_locate_private(rsession, uuid); if (tech_pvt) { channel = switch_core_session_get_channel(tech_pvt->session); } } if (!channel) { if (!rsession->tech_pvt) { return SWITCH_STATUS_FALSE; } channel = switch_core_session_get_channel(rsession->tech_pvt->session); } if (amf0_is_number(argv[2])) { cause = amf0_get_number(argv[2]); } else if ((scause = amf0_get_string(argv[2])) && !zstr(scause)) { cause = switch_channel_str2cause(scause); } switch_channel_hangup(channel, cause); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_transfer) { char *uuid = amf0_get_string(argv[1]); char *dest = amf0_get_string(argv[2]); rtmp_private_t *tech_pvt; if (zstr(uuid) || zstr(dest)) { return SWITCH_STATUS_FALSE; } if ((tech_pvt = rtmp_locate_private(rsession, uuid))) { const char *other_uuid = switch_channel_get_partner_uuid(tech_pvt->channel); switch_core_session_t *session; if (!zstr(other_uuid) && (session = switch_core_session_locate(other_uuid))) { switch_ivr_session_transfer(session, dest, NULL, NULL); switch_core_session_rwunlock(session); } } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_join) { char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) }; const char *other_uuid[2]; rtmp_private_t *tech_pvt[2]; if (zstr(uuid[0]) || zstr(uuid[1])) { return SWITCH_STATUS_SUCCESS; } if (!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) || !(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) { return SWITCH_STATUS_FALSE; } if (tech_pvt[0] == tech_pvt[1]) { return SWITCH_STATUS_FALSE; } if ((other_uuid[0] = switch_channel_get_partner_uuid(tech_pvt[0]->channel)) && (other_uuid[1] = switch_channel_get_partner_uuid(tech_pvt[1]->channel))) { #ifndef RTMP_DONT_HOLD if (switch_test_flag(tech_pvt[0], TFLAG_DETACHED)) { switch_ivr_unhold(tech_pvt[0]->session); } if (switch_test_flag(tech_pvt[1], TFLAG_DETACHED)) { switch_ivr_unhold(tech_pvt[1]->session); } #endif switch_ivr_uuid_bridge(other_uuid[0], other_uuid[1]); } return SWITCH_STATUS_SUCCESS; } /* 3-way: [0] is always the current active call [1] is the call to be brought into the call, and that will be running the three_way application - Set the current app of other[1] to three_way with other_uuid[0] - Put tech_pvt[0] to sleep: set state to CS_HIBERNATE - set CF_TRANSFER, set state CS_EXECUTE (do we need CF_TRANSFER here?) - setup a state handler in other[1] to detect when it hangs up Check list: tech_pvt[0] or other[0] hangs up If we were attached to the call, switch the active call to tech_pvt[1] tech_pvt[1] or other[1] hangs up Clear up any 3-way indications on the tech_pvt[0] */ static switch_status_t three_way_on_soft_execute(switch_core_session_t *session); #if 0 static switch_status_t three_way_on_hangup(switch_core_session_t *session); #endif static const switch_state_handler_table_t three_way_state_handlers_remote = { /*.on_init */ NULL, /*.on_routing */ NULL, /*.on_execute */ NULL, /*.on_hangup */ NULL, /*.on_exchange_media */ NULL, /*.on_soft_execute */ three_way_on_soft_execute, /*.on_consume_media */ NULL, /*.on_hibernate */ NULL }; /* runs on other_session[1] */ static switch_status_t three_way_on_soft_execute(switch_core_session_t *other_session) { switch_channel_t *other_channel = switch_core_session_get_channel(other_session); const char *uuid = switch_channel_get_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE); const char *my_uuid = switch_channel_get_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE); switch_core_session_t *my_session; switch_channel_t *my_channel; rtmp_private_t *tech_pvt; if (zstr(uuid) || zstr(my_uuid)) { return SWITCH_STATUS_SUCCESS; } if (zstr(my_uuid) || !(my_session = switch_core_session_locate(my_uuid))) { return SWITCH_STATUS_SUCCESS; } if (!switch_core_session_check_interface(my_session, rtmp_globals.rtmp_endpoint_interface)) { /* In case someone tempers with my variables, since we get tech_pvt from there */ switch_core_session_rwunlock(my_session); return SWITCH_STATUS_SUCCESS; } my_channel = switch_core_session_get_channel(my_session); tech_pvt = switch_core_session_get_private(my_session); switch_ivr_eavesdrop_session(other_session, uuid, NULL, ED_MUX_READ | ED_MUX_WRITE); /* 3-way call ended, whatever the reason * We need to go back to our original state. */ if (!switch_channel_up(other_channel)) { /* channel[1] hung up, check if we have special post-bridge actions, and hangup otherwise */ /* if my_channel isn't ready, it means something else has control of it, leave it alone */ if (switch_channel_ready(my_channel)) { const char *s; if ((s = switch_channel_get_variable(my_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE)) && switch_true(s)) { switch_ivr_park_session(my_session); } else if ((s = switch_channel_get_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE)) && !zstr(s)) { int argc; char *argv[4] = { 0 }; char *mydata = switch_core_session_strdup(my_session, s); switch_channel_set_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE, NULL); if ((argc = switch_split(mydata, ':', argv)) >= 1) { switch_ivr_session_transfer(my_session, argv[0], argv[1], argv[2]); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(my_session), SWITCH_LOG_ERROR, "No extension specified.\n"); } } else { switch_channel_hangup(my_channel, SWITCH_CAUSE_NORMAL_CLEARING); } } } else if (switch_channel_ready(other_channel)) { /* channel[1] didn't hangup, must be channel[0] then, rebridge this one with its original partner */ switch_ivr_uuid_bridge(switch_core_session_get_uuid(other_session), my_uuid); } else { /* channel[1] being taken out of our control, take the other leg out of CS_HIBERNATE if its ready, or else leave it alone */ if (switch_channel_ready(my_channel)) { switch_channel_set_state(my_channel, CS_EXECUTE); } } switch_channel_clear_state_handler(other_channel, &three_way_state_handlers_remote); switch_channel_set_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL); switch_channel_set_variable(my_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL); switch_channel_set_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE, NULL); switch_clear_flag(tech_pvt, TFLAG_THREE_WAY); if (my_session) { switch_core_session_rwunlock(my_session); } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_three_way) { /* The first uuid is the (local) uuid of the current call, the 2nd one should be already detached */ char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) }; rtmp_private_t *tech_pvt[2]; const char *other_uuid[2]; switch_core_session_t *other_session[2] = { 0 }; switch_channel_t *other_channel[2] = { 0 }; if (zstr(uuid[0]) || zstr(uuid[1]) || !(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) || !(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) { return SWITCH_STATUS_FALSE; } /* Make sure we don't 3-way with the same call, and that it doesnt turn into a 4-way, we aren't that permissive */ if (tech_pvt[0] == tech_pvt[1] || switch_test_flag(tech_pvt[0], TFLAG_THREE_WAY) || switch_test_flag(tech_pvt[1], TFLAG_THREE_WAY)) { return SWITCH_STATUS_FALSE; } if (!(other_uuid[0] = switch_channel_get_partner_uuid(tech_pvt[0]->channel)) || !(other_uuid[1] = switch_channel_get_partner_uuid(tech_pvt[1]->channel))) { return SWITCH_STATUS_FALSE; /* Both calls aren't bridged */ } if (!(other_session[0] = switch_core_session_locate(other_uuid[0])) || !(other_session[1] = switch_core_session_locate(other_uuid[1]))) { goto done; } other_channel[0] = switch_core_session_get_channel(other_session[0]); other_channel[1] = switch_core_session_get_channel(other_session[1]); /* Save which uuid is the 3-way target */ switch_channel_set_variable(other_channel[1], RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]); switch_channel_set_variable(tech_pvt[1]->channel, RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]); /* Attach redirect */ switch_set_flag(tech_pvt[1], TFLAG_THREE_WAY); /* Set soft_holding_uuid to the uuid of the other matching channel, so they can can be bridged back when the 3-way is over */ switch_channel_set_variable(tech_pvt[1]->channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, other_uuid[1]); switch_channel_set_variable(other_channel[1], SWITCH_SOFT_HOLDING_UUID_VARIABLE, uuid[1]); /* Start the 3-way on the 2nd channel using a media bug */ switch_channel_add_state_handler(other_channel[1], &three_way_state_handlers_remote); switch_channel_set_flag(tech_pvt[1]->channel, CF_TRANSFER); switch_channel_set_state(tech_pvt[1]->channel, CS_HIBERNATE); switch_channel_set_flag(other_channel[1], CF_TRANSFER); switch_channel_set_state(other_channel[1], CS_SOFT_EXECUTE); done: if (other_session[0]) { switch_core_session_rwunlock(other_session[0]); } if (other_session[1]) { switch_core_session_rwunlock(other_session[1]); } return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_sendevent) { amf0_data *obj = NULL; switch_event_t *event = NULL; switch_status_t status = SWITCH_STATUS_SUCCESS; const char *uuid = NULL; if (argv[1] && argv[1]->type == AMF0_TYPE_OBJECT) { obj = argv[1]; } else if (argv[2] && argv[2]->type == AMF0_TYPE_OBJECT) { uuid = amf0_get_string(argv[1]); obj = argv[2]; } else { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Bad argument for sendevent"); return SWITCH_STATUS_FALSE; } if (switch_event_create_subclass(&event, zstr(uuid) ? SWITCH_EVENT_CUSTOM : SWITCH_EVENT_MESSAGE, zstr(uuid) ? RTMP_EVENT_CLIENTCUSTOM : NULL) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create event\n"); return SWITCH_STATUS_FALSE; } rtmp_event_fill(rsession, event); /* Build event using amf array */ if ((status = amf_object_to_event(obj, &event)) != SWITCH_STATUS_SUCCESS) { switch_event_destroy(&event); return SWITCH_STATUS_FALSE; } if (!zstr(uuid)) { rtmp_private_t *session_pvt = rtmp_locate_private(rsession, uuid); if (session_pvt) { if (switch_core_session_queue_event(session_pvt->session, &event) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_pvt->session), SWITCH_LOG_ERROR, "Couldn't queue event to session\n"); switch_event_destroy(&event); status = SWITCH_STATUS_FALSE; } else { status = SWITCH_STATUS_SUCCESS; } } } switch_event_fire(&event); return SWITCH_STATUS_SUCCESS; } RTMP_INVOKE_FUNCTION(rtmp_i_log) { const char *data = amf0_get_string(argv[1]); switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Log: %s\n", data); return SWITCH_STATUS_SUCCESS; } /* 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: */