Beispiel #1
0
/*! Forward a bridge's topics to an app */
static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
	struct ast_bridge *bridge)
{
	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);

	if (!app || !bridge) {
		return NULL;
	}

	forwards = forwards_create(app, bridge->uniqueid);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_BRIDGE;
	forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge),
		app->topic);
	if (!forwards->topic_forward) {
		return NULL;
	}

	forwards->topic_cached_forward = stasis_forward_all(
		ast_bridge_topic_cached(bridge), app->topic);
	if (!forwards->topic_cached_forward) {
		/* Half-subscribed is a bad thing */
		stasis_forward_cancel(forwards->topic_forward);
		forwards->topic_forward = NULL;
		return NULL;
	}

	ao2_ref(forwards, +1);
	return forwards;
}
Beispiel #2
0
/*! Forward a endpoint's topics to an app */
static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
	struct ast_endpoint *endpoint)
{
	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);

	if (!app || !endpoint) {
		return NULL;
	}

	forwards = forwards_create(app, ast_endpoint_get_id(endpoint));
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_ENDPOINT;
	forwards->topic_forward = stasis_forward_all(ast_endpoint_topic(endpoint),
		app->topic);
	if (!forwards->topic_forward) {
		return NULL;
	}

	forwards->topic_cached_forward = stasis_forward_all(
		ast_endpoint_topic_cached(endpoint), app->topic);
	if (!forwards->topic_cached_forward) {
		/* Half-subscribed is a bad thing */
		stasis_forward_cancel(forwards->topic_forward);
		forwards->topic_forward = NULL;
		return NULL;
	}

	ao2_ref(forwards, +1);
	return forwards;
}
Beispiel #3
0
/*! Forward a bridge's topics to an app */
static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
	struct ast_bridge *bridge)
{
	struct app_forwards *forwards;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, bridge ? bridge->uniqueid : BRIDGE_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_BRIDGE;
	if (bridge) {
		forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge),
			app->topic);
	}
	forwards->topic_cached_forward = stasis_forward_all(
		bridge ? ast_bridge_topic_cached(bridge) : ast_bridge_topic_all_cached(),
		app->topic);

	if ((!forwards->topic_forward && bridge) || !forwards->topic_cached_forward) {
		/* Half-subscribed is a bad thing */
		forwards_unsubscribe(forwards);
		ao2_ref(forwards, -1);
		return NULL;
	}

	return forwards;
}
Beispiel #4
0
/*! Forward a channel's topics to an app */
static struct app_forwards *forwards_create_channel(struct stasis_app *app,
	struct ast_channel *chan)
{
	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);

	if (!app || !chan) {
		return NULL;
	}

	forwards = forwards_create(app, ast_channel_uniqueid(chan));
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_CHANNEL;
	forwards->topic_forward = stasis_forward_all(ast_channel_topic(chan),
		app->topic);
	if (!forwards->topic_forward) {
		return NULL;
	}

	forwards->topic_cached_forward = stasis_forward_all(
		ast_channel_topic_cached(chan), app->topic);
	if (!forwards->topic_cached_forward) {
		/* Half-subscribed is a bad thing */
		stasis_forward_cancel(forwards->topic_forward);
		forwards->topic_forward = NULL;
		return NULL;
	}

	ao2_ref(forwards, +1);
	return forwards;
}
Beispiel #5
0
/*! Forward a channel's topics to an app */
static struct app_forwards *forwards_create_channel(struct stasis_app *app,
	struct ast_channel *chan)
{
	struct app_forwards *forwards;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, chan ? ast_channel_uniqueid(chan) : CHANNEL_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_CHANNEL;
	if (chan) {
		forwards->topic_forward = stasis_forward_all(ast_channel_topic(chan),
			app->topic);
	}
	forwards->topic_cached_forward = stasis_forward_all(
		chan ? ast_channel_topic_cached(chan) : ast_channel_topic_all_cached(),
		app->topic);

	if ((!forwards->topic_forward && chan) || !forwards->topic_cached_forward) {
		/* Half-subscribed is a bad thing */
		forwards_unsubscribe(forwards);
		ao2_ref(forwards, -1);
		return NULL;
	}

	return forwards;
}
struct stasis_cp_single *stasis_cp_single_create(struct stasis_cp_all *all,
	const char *name)
{
	RAII_VAR(struct stasis_cp_single *, one, NULL, ao2_cleanup);

	one = ao2_alloc(sizeof(*one), one_dtor);
	if (!one) {
		return NULL;
	}

	one->topic = stasis_topic_create(name);
	if (!one->topic) {
		return NULL;
	}
	one->topic_cached = stasis_caching_topic_create(one->topic, all->cache);
	if (!one->topic_cached) {
		return NULL;
	}

	one->forward_topic_to_all = stasis_forward_all(one->topic, all->topic);
	if (!one->forward_topic_to_all) {
		return NULL;
	}
	one->forward_cached_to_all = stasis_forward_all(
		stasis_caching_get_topic(one->topic_cached), all->topic_cached);
	if (!one->forward_cached_to_all) {
		return NULL;
	}

	ao2_ref(one, +1);
	return one;
}
Beispiel #7
0
/*! Forward a endpoint's topics to an app */
static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
	struct ast_endpoint *endpoint)
{
	struct app_forwards *forwards;
	int ret = 0;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, endpoint ? ast_endpoint_get_id(endpoint) : ENDPOINT_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_ENDPOINT;
	if (endpoint) {
		forwards->topic_forward = stasis_forward_all(ast_endpoint_topic(endpoint),
			app->topic);
		forwards->topic_cached_forward = stasis_forward_all(
			ast_endpoint_topic_cached(endpoint), app->topic);

		if (!forwards->topic_forward || !forwards->topic_cached_forward) {
			/* Half-subscribed is a bad thing */
			forwards_unsubscribe(forwards);
			ao2_ref(forwards, -1);
			return NULL;
		}
	} else {
		/* Since endpoint subscriptions also subscribe to channels, in the case
		 * of all endpoint subscriptions, we only want messages for the endpoints.
		 * As such, we route those particular messages and then re-publish them
		 * on the app's topic.
		 */
		ast_assert(app->endpoint_router == NULL);
		app->endpoint_router = stasis_message_router_create(ast_endpoint_topic_all_cached());
		if (!app->endpoint_router) {
			forwards_unsubscribe(forwards);
			ao2_ref(forwards, -1);
			return NULL;
		}

		ret |= stasis_message_router_add(app->endpoint_router,
			ast_endpoint_state_type(), endpoint_state_cb, app);
		ret |= stasis_message_router_add(app->endpoint_router,
			ast_endpoint_contact_state_type(), endpoint_state_cb, app);

		if (ret) {
			ao2_ref(app->endpoint_router, -1);
			app->endpoint_router = NULL;
			ao2_ref(forwards, -1);
			return NULL;
		}
	}

	return forwards;
}
Beispiel #8
0
struct stasis_topic *stasis_topic_pool_get_topic(struct stasis_topic_pool *pool, const char *topic_name)
{
	RAII_VAR(struct topic_pool_entry *, topic_pool_entry, NULL, ao2_cleanup);
	SCOPED_AO2LOCK(topic_container_lock, pool->pool_container);

	topic_pool_entry = ao2_find(pool->pool_container, topic_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
	if (topic_pool_entry) {
		return topic_pool_entry->topic;
	}

	topic_pool_entry = topic_pool_entry_alloc();
	if (!topic_pool_entry) {
		return NULL;
	}

	topic_pool_entry->topic = stasis_topic_create(topic_name);
	if (!topic_pool_entry->topic) {
		return NULL;
	}

	topic_pool_entry->forward = stasis_forward_all(topic_pool_entry->topic, pool->pool_topic);
	if (!topic_pool_entry->forward) {
		return NULL;
	}

	if (!ao2_link_flags(pool->pool_container, topic_pool_entry, OBJ_NOLOCK)) {
		return NULL;
	}

	return topic_pool_entry->topic;
}
struct stasis_cp_all *stasis_cp_all_create(const char *name,
	snapshot_get_id id_fn)
{
	char *cached_name = NULL;
	struct stasis_cp_all *all;
	static int cache_id;

	all = ao2_t_alloc(sizeof(*all), all_dtor, name);
	if (!all) {
		return NULL;
	}

	ast_asprintf(&cached_name, "cache_pattern:%d/%s", ast_atomic_fetchadd_int(&cache_id, +1), name);
	if (!cached_name) {
		ao2_ref(all, -1);

		return NULL;
	}

	all->topic = stasis_topic_create(name);
	all->topic_cached = stasis_topic_create(cached_name);
	ast_free(cached_name);
	all->cache = stasis_cache_create(id_fn);
	all->forward_all_to_cached =
		stasis_forward_all(all->topic, all->topic_cached);

	if (!all->topic || !all->topic_cached || !all->cache ||
		!all->forward_all_to_cached) {
		ao2_ref(all, -1);

		return NULL;
	}

	return all;
}
Beispiel #10
0
int manager_bridging_init(void)
{
	int ret = 0;
	struct stasis_topic *manager_topic;
	struct stasis_topic *bridge_topic;

	if (bridge_state_router) {
		/* Already initialized */
		return 0;
	}

	ast_register_cleanup(manager_bridging_cleanup);

	manager_topic = ast_manager_get_topic();
	if (!manager_topic) {
		return -1;
	}

	bridge_topic = ast_bridge_topic_all_cached();
	if (!bridge_topic) {
		return -1;
	}

	topic_forwarder = stasis_forward_all(bridge_topic, manager_topic);
	if (!topic_forwarder) {
		return -1;
	}

	bridge_state_router = ast_manager_get_message_router();
	if (!bridge_state_router) {
		return -1;
	}

	ret |= stasis_message_router_add_cache_update(bridge_state_router,
		ast_bridge_snapshot_type(), bridge_snapshot_update, NULL);

	ret |= stasis_message_router_add(bridge_state_router,
		ast_bridge_merge_message_type(), bridge_merge_cb, NULL);

	ret |= stasis_message_router_add(bridge_state_router,
		ast_channel_entered_bridge_type(), channel_enter_cb, NULL);

	ret |= stasis_message_router_add(bridge_state_router,
		ast_channel_left_bridge_type(), channel_leave_cb, NULL);

	ret |= ast_manager_register_xml_core("BridgeList", 0, manager_bridges_list);
	ret |= ast_manager_register_xml_core("BridgeInfo", 0, manager_bridge_info);
	ret |= ast_manager_register_xml_core("BridgeDestroy", 0, manager_bridge_destroy);
	ret |= ast_manager_register_xml_core("BridgeKick", 0, manager_bridge_kick);

	/* If somehow we failed to add any routes, just shut down the whole
	 * thing and fail it.
	 */
	if (ret) {
		manager_bridging_cleanup();
		return -1;
	}

	return 0;
}
Beispiel #11
0
/*! Forward a channel's topics to an app */
static struct app_forwards *forwards_create_channel(struct stasis_app *app,
	struct ast_channel *chan)
{
	struct app_forwards *forwards;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, chan ? ast_channel_uniqueid(chan) : CHANNEL_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_CHANNEL;
	forwards->topic_forward = stasis_forward_all(
		chan ? ast_channel_topic(chan) : ast_channel_topic_all(),
		app->topic);

	if (!forwards->topic_forward) {
		ao2_ref(forwards, -1);
		return NULL;
	}

	return forwards;
}
Beispiel #12
0
/*! Forward a bridge's topics to an app */
static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
	struct ast_bridge *bridge)
{
	struct app_forwards *forwards;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, bridge ? bridge->uniqueid : BRIDGE_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_BRIDGE;
	forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge), app->topic);

	if (!forwards->topic_forward && bridge) {
		forwards_unsubscribe(forwards);
		ao2_ref(forwards, -1);
		return NULL;
	}

	return forwards;
}
Beispiel #13
0
struct stasis_cp_all *stasis_cp_all_create(const char *name,
	snapshot_get_id id_fn)
{
	RAII_VAR(char *, cached_name, NULL, ast_free);
	RAII_VAR(struct stasis_cp_all *, all, NULL, ao2_cleanup);

	all = ao2_alloc(sizeof(*all), all_dtor);
	if (!all) {
		return NULL;
	}

	ast_asprintf(&cached_name, "%s-cached", name);
	if (!cached_name) {
		return NULL;
	}

	all->topic = stasis_topic_create(name);
	all->topic_cached = stasis_topic_create(cached_name);
	all->cache = stasis_cache_create(id_fn);
	all->forward_all_to_cached =
		stasis_forward_all(all->topic, all->topic_cached);

	if (!all->topic || !all->topic_cached || !all->cache ||
		!all->forward_all_to_cached) {
		return NULL;
	}

	ao2_ref(all, +1);
	return all;
}
struct stasis_cp_single *stasis_cp_single_create(struct stasis_cp_all *all,
	const char *name)
{
	struct stasis_cp_single *one;

	one = stasis_cp_sink_create(all, name);
	if (!one) {
		return NULL;
	}

	one->forward_topic_to_all = stasis_forward_all(one->topic, all->topic);
	one->forward_cached_to_all = stasis_forward_all(
		stasis_caching_get_topic(one->topic_cached), all->topic_cached);

	if (!one->forward_topic_to_all || !one->forward_cached_to_all) {
		ao2_ref(one, -1);

		return NULL;
	}

	return one;
}
Beispiel #15
0
int ast_channel_forward_endpoint(struct ast_channel *chan,
	struct ast_endpoint *endpoint)
{
	ast_assert(chan != NULL);
	ast_assert(endpoint != NULL);

	chan->endpoint_forward =
		stasis_forward_all(ast_channel_topic(chan),
			ast_endpoint_topic(endpoint));

	if (chan->endpoint_forward == NULL) {
		return -1;
	}

	return 0;
}
Beispiel #16
0
int manager_mwi_init(void)
{
	int ret = 0;
	struct stasis_topic *manager_topic;
	struct stasis_topic *mwi_topic;
	struct stasis_message_router *message_router;

	manager_topic = ast_manager_get_topic();
	if (!manager_topic) {
		return -1;
	}
	message_router = ast_manager_get_message_router();
	if (!message_router) {
		return -1;
	}
	mwi_topic = ast_mwi_topic_all();
	if (!mwi_topic) {
		return -1;
	}

	topic_forwarder = stasis_forward_all(mwi_topic, manager_topic);
	if (!topic_forwarder) {
		return -1;
	}

	ast_register_cleanup(manager_mwi_shutdown);

	ret |= stasis_message_router_add(message_router,
					 ast_mwi_state_type(),
					 mwi_update_cb,
					 NULL);

	ret |= stasis_message_router_add(message_router,
					 ast_mwi_vm_app_type(),
					 mwi_app_event_cb,
					 NULL);

	/* If somehow we failed to add any routes, just shut down the whole
	 * thing and fail it.
	 */
	if (ret) {
		manager_mwi_shutdown();
		return -1;
	}

	return 0;
}
Beispiel #17
0
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));
}
Beispiel #18
0
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));
}