/*! * \brief Performs common setup for a bridge playback operation * with both new controls and when existing controls are found. * * \param args_media media string split from arguments * \param args_lang language string split from arguments * \param args_offset_ms milliseconds offset split from arguments * \param args_playback_id string to use for playback split from * arguments (null valid) * \param response ARI response being built * \param bridge Bridge the playback is being peformed on * \param found_channel The channel that was found controlling playback * * \retval PLAY_FOUND_SUCCESS The operation was successful * \retval PLAY_FOUND_FAILURE The operation failed (terminal failure) * \retval PLAY_FOUND_CHANNEL_UNAVAILABLE The operation failed because * the channel requested to playback with is breaking down. */ static enum play_found_result ari_bridges_play_found(const char *args_media, const char *args_lang, int args_offset_ms, int args_skipms, const char *args_playback_id, struct ast_ari_response *response, struct ast_bridge *bridge, struct ast_channel *found_channel) { RAII_VAR(struct ast_channel *, play_channel, found_channel, ao2_cleanup); RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); RAII_VAR(char *, playback_url, NULL, ast_free); RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); control = stasis_app_control_find_by_channel(play_channel); if (!control) { ast_ari_response_error( response, 500, "Internal Error", "Failed to get control snapshot"); return PLAY_FOUND_FAILURE; } ao2_lock(control); if (stasis_app_control_is_done(control)) { /* We failed to queue the action. Bailout and return that we aren't terminal. */ ao2_unlock(control); return PLAY_FOUND_CHANNEL_UNAVAILABLE; } if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms, args_skipms, args_playback_id, response, bridge, control, &json, &playback_url)) { ao2_unlock(control); return PLAY_FOUND_FAILURE; } ao2_unlock(control); ast_ari_response_created(response, playback_url, ast_json_ref(json)); return PLAY_FOUND_SUCCESS; }
void ast_ari_bridges_record(struct ast_variable *headers, struct ast_ari_bridges_record_args *args, struct ast_ari_response *response) { RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup); RAII_VAR(struct ast_channel *, record_channel, NULL, ast_hangup); RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup); RAII_VAR(char *, recording_url, NULL, ast_free); RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); RAII_VAR(struct stasis_app_recording_options *, options, NULL, ao2_cleanup); RAII_VAR(char *, uri_encoded_name, NULL, ast_free); RAII_VAR(struct stasis_forward *, channel_forward, NULL, stasis_forward_cancel); struct stasis_topic *channel_topic; struct stasis_topic *bridge_topic; size_t uri_name_maxlen; struct bridge_channel_control_thread_data *thread_data; pthread_t threadid; ast_assert(response != NULL); if (bridge == NULL) { return; } if (!(record_channel = prepare_bridge_media_channel("Recorder"))) { ast_ari_response_error( response, 500, "Internal Server Error", "Failed to create recording channel"); return; } bridge_topic = ast_bridge_topic(bridge); channel_topic = ast_channel_topic(record_channel); /* Forward messages from the recording channel topic to the bridge topic so that anything listening for * messages on the bridge topic will receive the recording start/stop messages. Other messages that would * go to this channel will be suppressed since the channel is marked as internal. */ if (!bridge_topic || !channel_topic || !(channel_forward = stasis_forward_all(channel_topic, bridge_topic))) { ast_ari_response_error( response, 500, "Internal Error", "Could not forward record channel stasis messages to bridge topic"); return; } if (ast_unreal_channel_push_to_bridge(record_channel, bridge, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) { ast_ari_response_error( response, 500, "Internal Error", "Failed to put recording channel into the bridge"); return; } control = stasis_app_control_create(record_channel); if (control == NULL) { ast_ari_response_alloc_failed(response); return; } options = stasis_app_recording_options_create(args->name, args->format); if (options == NULL) { ast_ari_response_alloc_failed(response); return; } ast_string_field_build(options, target, "bridge:%s", args->bridge_id); options->max_silence_seconds = args->max_silence_seconds; options->max_duration_seconds = args->max_duration_seconds; options->terminate_on = stasis_app_recording_termination_parse(args->terminate_on); options->if_exists = stasis_app_recording_if_exists_parse(args->if_exists); options->beep = args->beep; if (options->terminate_on == STASIS_APP_RECORDING_TERMINATE_INVALID) { ast_ari_response_error( response, 400, "Bad Request", "terminateOn invalid"); return; } if (options->if_exists == AST_RECORD_IF_EXISTS_ERROR) { ast_ari_response_error( response, 400, "Bad Request", "ifExists invalid"); return; } if (!ast_get_format_for_file_ext(options->format)) { ast_ari_response_error( response, 422, "Unprocessable Entity", "specified format is unknown on this system"); return; } recording = stasis_app_control_record(control, options); if (recording == NULL) { switch(errno) { case EINVAL: /* While the arguments are invalid, we should have * caught them prior to calling record. */ ast_ari_response_error( response, 500, "Internal Server Error", "Error parsing request"); break; case EEXIST: ast_ari_response_error(response, 409, "Conflict", "Recording '%s' already exists and can not be overwritten", args->name); break; case ENOMEM: ast_ari_response_alloc_failed(response); break; case EPERM: ast_ari_response_error( response, 400, "Bad Request", "Recording name invalid"); break; default: ast_log(LOG_WARNING, "Unrecognized recording error: %s\n", strerror(errno)); ast_ari_response_error( response, 500, "Internal Server Error", "Internal Server Error"); break; } return; } uri_name_maxlen = strlen(args->name) * 3; uri_encoded_name = ast_malloc(uri_name_maxlen); if (!uri_encoded_name) { ast_ari_response_alloc_failed(response); return; } ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen, ast_uri_http); if (ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name) == -1) { recording_url = NULL; ast_ari_response_alloc_failed(response); return; } json = stasis_app_recording_to_json(recording); if (!json) { ast_ari_response_alloc_failed(response); return; } thread_data = ast_calloc(1, sizeof(*thread_data)); if (!thread_data) { ast_ari_response_alloc_failed(response); return; } thread_data->bridge_channel = record_channel; thread_data->control = control; thread_data->forward = channel_forward; if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) { ast_ari_response_alloc_failed(response); ast_free(thread_data); return; } /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */ record_channel = NULL; control = NULL; channel_forward = NULL; ast_ari_response_created(response, recording_url, ast_json_ref(json)); }
static void ari_bridges_play_new(const char *args_media, const char *args_lang, int args_offset_ms, int args_skipms, const char *args_playback_id, struct ast_ari_response *response, struct ast_bridge *bridge) { RAII_VAR(struct ast_channel *, play_channel, NULL, ast_hangup); RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); RAII_VAR(struct stasis_forward *, channel_forward, NULL, stasis_forward_cancel); RAII_VAR(char *, playback_url, NULL, ast_free); struct stasis_topic *channel_topic; struct stasis_topic *bridge_topic; struct bridge_channel_control_thread_data *thread_data; pthread_t threadid; if (!(play_channel = prepare_bridge_media_channel("Announcer"))) { ast_ari_response_error( response, 500, "Internal Error", "Could not create playback channel"); return; } ast_debug(1, "Created announcer channel '%s'\n", ast_channel_name(play_channel)); bridge_topic = ast_bridge_topic(bridge); channel_topic = ast_channel_topic(play_channel); /* Forward messages from the playback channel topic to the bridge topic so that anything listening for * messages on the bridge topic will receive the playback start/stop messages. Other messages that would * go to this channel will be suppressed since the channel is marked as internal. */ if (!bridge_topic || !channel_topic || !(channel_forward = stasis_forward_all(channel_topic, bridge_topic))) { ast_ari_response_error( response, 500, "Internal Error", "Could not forward playback channel stasis messages to bridge topic"); return; } if (ast_unreal_channel_push_to_bridge(play_channel, bridge, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) { ast_ari_response_error( response, 500, "Internal Error", "Failed to put playback channel into the bridge"); return; } control = stasis_app_control_create(play_channel); if (control == NULL) { ast_ari_response_alloc_failed(response); return; } ao2_lock(control); if (ari_bridges_play_helper(args_media, args_lang, args_offset_ms, args_skipms, args_playback_id, response, bridge, control, &json, &playback_url)) { ao2_unlock(control); return; } ao2_unlock(control); if (stasis_app_bridge_playback_channel_add(bridge, play_channel, control)) { ast_ari_response_alloc_failed(response); return; } /* Give play_channel and control reference to the thread data */ thread_data = ast_calloc(1, sizeof(*thread_data)); if (!thread_data) { ast_ari_response_alloc_failed(response); return; } thread_data->bridge_channel = play_channel; thread_data->control = control; thread_data->forward = channel_forward; if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) { ast_ari_response_alloc_failed(response); ast_free(thread_data); return; } /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */ play_channel = NULL; control = NULL; channel_forward = NULL; ast_ari_response_created(response, playback_url, ast_json_ref(json)); }
void ast_ari_channels_record(struct ast_variable *headers, struct ast_ari_channels_record_args *args, struct ast_ari_response *response) { RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup); RAII_VAR(char *, recording_url, NULL, ast_free); struct ast_json *json; RAII_VAR(struct stasis_app_recording_options *, options, NULL, ao2_cleanup); RAII_VAR(char *, uri_encoded_name, NULL, ast_free); size_t uri_name_maxlen; ast_assert(response != NULL); if (args->max_duration_seconds < 0) { ast_ari_response_error( response, 400, "Bad Request", "max_duration_seconds cannot be negative"); return; } if (args->max_silence_seconds < 0) { ast_ari_response_error( response, 400, "Bad Request", "max_silence_seconds cannot be negative"); return; } control = find_control(response, args->channel_id); if (control == NULL) { /* Response filled in by find_control */ return; } options = stasis_app_recording_options_create(args->name, args->format); if (options == NULL) { ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); } ast_string_field_build(options, target, "channel:%s", args->channel_id); options->max_silence_seconds = args->max_silence_seconds; options->max_duration_seconds = args->max_duration_seconds; options->terminate_on = stasis_app_recording_termination_parse(args->terminate_on); options->if_exists = stasis_app_recording_if_exists_parse(args->if_exists); options->beep = args->beep; if (options->terminate_on == STASIS_APP_RECORDING_TERMINATE_INVALID) { ast_ari_response_error( response, 400, "Bad Request", "terminateOn invalid"); return; } if (options->if_exists == -1) { ast_ari_response_error( response, 400, "Bad Request", "ifExists invalid"); return; } if (!ast_get_format_for_file_ext(options->format)) { ast_ari_response_error( response, 422, "Unprocessable Entity", "specified format is unknown on this system"); return; } recording = stasis_app_control_record(control, options); if (recording == NULL) { switch(errno) { case EINVAL: /* While the arguments are invalid, we should have * caught them prior to calling record. */ ast_ari_response_error( response, 500, "Internal Server Error", "Error parsing request"); break; case EEXIST: ast_ari_response_error(response, 409, "Conflict", "Recording '%s' already exists and can not be overwritten", args->name); break; case ENOMEM: ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); break; case EPERM: ast_ari_response_error( response, 400, "Bad Request", "Recording name invalid"); break; default: ast_log(LOG_WARNING, "Unrecognized recording error: %s\n", strerror(errno)); ast_ari_response_error( response, 500, "Internal Server Error", "Internal Server Error"); break; } return; } uri_name_maxlen = strlen(args->name) * 3; uri_encoded_name = ast_malloc(uri_name_maxlen); if (!uri_encoded_name) { ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); return; } ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen, ast_uri_http); if (ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name) == -1) { recording_url = NULL; ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); return; } json = stasis_app_recording_to_json(recording); if (!json) { ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); return; } ast_ari_response_created(response, recording_url, json); }
static void ari_channels_handle_play( const char *args_channel_id, const char *args_media, const char *args_lang, int args_offsetms, int args_skipms, const char *args_playback_id, struct ast_ari_response *response) { RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup); RAII_VAR(char *, playback_url, NULL, ast_free); struct ast_json *json; const char *language; ast_assert(response != NULL); control = find_control(response, args_channel_id); if (control == NULL) { /* Response filled in by find_control */ return; } snapshot = stasis_app_control_get_snapshot(control); if (!snapshot) { ast_ari_response_error( response, 404, "Not Found", "Channel not found"); return; } if (args_skipms < 0) { ast_ari_response_error( response, 400, "Bad Request", "skipms cannot be negative"); return; } if (args_offsetms < 0) { ast_ari_response_error( response, 400, "Bad Request", "offsetms cannot be negative"); return; } language = S_OR(args_lang, snapshot->language); playback = stasis_app_control_play_uri(control, args_media, language, args_channel_id, STASIS_PLAYBACK_TARGET_CHANNEL, args_skipms, args_offsetms, args_playback_id); if (!playback) { ast_ari_response_error( response, 500, "Internal Server Error", "Failed to queue media for playback"); return; } if (ast_asprintf(&playback_url, "/playback/%s", stasis_app_playback_get_id(playback)) == -1) { playback_url = NULL; ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); return; } json = stasis_app_playback_to_json(playback); if (!json) { ast_ari_response_error( response, 500, "Internal Server Error", "Out of memory"); return; } ast_ari_response_created(response, playback_url, json); }