enum webchan_control_msg_format control_msg_format;
};
+/* This is from the perspective of the app, NOT Asterisk */
+enum webchan_media_direction {
+ WEBCHAN_MEDIA_DIRECTION_BOTH,
+ WEBCHAN_MEDIA_DIRECTION_OUT,
+ WEBCHAN_MEDIA_DIRECTION_IN,
+};
+
+static const char *websocket_media_direction_map[] = {
+ [WEBCHAN_MEDIA_DIRECTION_BOTH] = "both",
+ [WEBCHAN_MEDIA_DIRECTION_OUT] = "out",
+ [WEBCHAN_MEDIA_DIRECTION_IN] = "in",
+};
+
static struct ast_websocket_server *ast_ws_server;
static struct ao2_container *instances = NULL;
int frame_queue_length;
int queue_full;
int queue_paused;
+ int media_direction;
char connection_id[0];
};
#define REPORT_QUEUE_DRAINED "REPORT_QUEUE_DRAINED"
#define PAUSE_MEDIA "PAUSE_MEDIA"
#define CONTINUE_MEDIA "CONTINUE_MEDIA"
+#define SET_MEDIA_DIRECTION "SET_MEDIA_DIRECTION"
#define QUEUE_LENGTH_MAX 1000
#define QUEUE_LENGTH_XOFF_LEVEL 900
static int webchan_write(struct ast_channel *ast, struct ast_frame *f);
static int webchan_hangup(struct ast_channel *ast);
static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration);
+static int set_channel_timer(struct websocket_pvt *instance);
#define websocket_request_hangup(_instance, _cause, _tech) \
_websocket_request_hangup(_instance, _cause, _tech, __LINE__, __FUNCTION__)
return 0;
}
+#define ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command) \
+({ \
+ if (instance->passthrough) { \
+ send_event(instance, ERROR, "%s not supported in passthrough mode", command); \
+ ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n", \
+ ast_channel_name(instance->channel), command); \
+ return 0; \
+ } \
+})
+
+#define ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, direction) \
+({ \
+ if (instance->media_direction == direction) { \
+ send_event(instance, ERROR, "%s not supported while media direction " \
+ "is '%s'", command, websocket_media_direction_map[direction]); \
+ ast_debug(4, "%s: WebSocket media direction is '%s'. Ignoring %s command.\n", \
+ ast_channel_name(instance->channel), websocket_media_direction_map[direction], command); \
+ return 0; \
+ } \
+})
+
/*!
* \internal
* \brief Handle commands from the websocket
websocket_request_hangup(instance, AST_CAUSE_NORMAL, AST_WEBSOCKET_STATUS_NORMAL);
} else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, WEBCHAN_MEDIA_DIRECTION_IN);
AST_LIST_LOCK(&instance->frame_queue);
instance->bulk_media_in_progress = 1;
AST_LIST_UNLOCK(&instance->frame_queue);
id = data;
}
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, WEBCHAN_MEDIA_DIRECTION_IN);
ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
ast_channel_name(instance->channel), STOP_MEDIA_BUFFERING, id,
SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
AST_LIST_UNLOCK);
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, WEBCHAN_MEDIA_DIRECTION_IN);
if (instance->control_msg_format == WEBCHAN_CONTROL_MSG_FORMAT_JSON) {
id = ast_json_object_string_get(json, "correlation_id");
} else if (ast_strings_equal(command, FLUSH_MEDIA)) {
struct ast_frame *frame = NULL;
- if (instance->passthrough) {
- send_event(instance, ERROR, "FLUSH_MEDIA not supported in passthrough mode");
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
AST_LIST_LOCK(&instance->frame_queue);
while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
AST_LIST_UNLOCK(&instance->frame_queue);
} else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
AST_LIST_LOCK(&instance->frame_queue);
instance->report_queue_drained = 1;
return send_event(instance, STATUS);
} else if (ast_strings_equal(command, PAUSE_MEDIA)) {
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, WEBCHAN_MEDIA_DIRECTION_IN);
AST_LIST_LOCK(&instance->frame_queue);
instance->queue_paused = 1;
AST_LIST_UNLOCK(&instance->frame_queue);
} else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
- if (instance->passthrough) {
- send_event(instance, ERROR, "%s not supported in passthrough mode", command);
- ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
- ast_channel_name(instance->channel), command);
- return 0;
- }
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, WEBCHAN_MEDIA_DIRECTION_IN);
AST_LIST_LOCK(&instance->frame_queue);
instance->queue_paused = 0;
AST_LIST_UNLOCK(&instance->frame_queue);
+ } else if (ast_strings_equal(command, SET_MEDIA_DIRECTION)) {
+ const char *direction;
+
+ ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
+
+ if (instance->control_msg_format != WEBCHAN_CONTROL_MSG_FORMAT_JSON) {
+ send_event(instance, ERROR, "%s only supports JSON format.\n", command);
+ return 0;
+ }
+
+ direction = ast_json_object_string_get(json, "direction");
+ if (!direction) {
+ send_event(instance, ERROR, "%s requires a 'direction' parameter.\n", command);
+ return 0;
+ }
+
+ if (!strcmp("both", direction)) {
+ if (instance->media_direction == WEBCHAN_MEDIA_DIRECTION_BOTH) {
+ return 0;
+ }
+
+ if (!instance->timer) {
+ set_channel_timer(instance);
+ ast_queue_frame(instance->channel, &ast_null_frame);
+ }
+
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_BOTH;
+
+ } else if (!strcmp("out", direction)) {
+ if (instance->media_direction == WEBCHAN_MEDIA_DIRECTION_OUT) {
+ return 0;
+ }
+
+ if (!instance->timer) {
+ set_channel_timer(instance);
+ ast_queue_frame(instance->channel, &ast_null_frame);
+ }
+
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_OUT;
+
+ } else if (!strcmp("in", direction)) {
+ if (instance->media_direction == WEBCHAN_MEDIA_DIRECTION_IN) {
+ return 0;
+ }
+
+ if (instance->timer) {
+ ast_channel_internal_fd_clear(instance->channel, WS_TIMER_FDNO);
+ ast_timer_close(instance->timer);
+ instance->timer = NULL;
+ ast_queue_frame(instance->channel, &ast_null_frame);
+ }
+
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_IN;
+
+ } else {
+ send_event(instance, ERROR, "'%s' is not a valid direction for %s.\n",
+ direction, command);
+ return 0;
+ }
+
} else {
ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
ast_channel_name(instance->channel), command);
return -1;
}
- if (opcode != AST_WEBSOCKET_OPCODE_BINARY) {
+ if (opcode == AST_WEBSOCKET_OPCODE_BINARY) {
+ /* If the application's media direction is 'in', drop any media we receive from it */
+ if (instance->media_direction == WEBCHAN_MEDIA_DIRECTION_IN) {
+ ast_debug(5, "%s: WebSocket dropped frame (application media direction is 'in')\n",
+ ast_channel_name(instance->channel));
+ return 0;
+ }
+ } else {
ast_log(LOG_WARNING, "%s: WebSocket frame type %d not supported\n",
ast_channel_name(instance->channel), (int)opcode);
websocket_request_hangup(instance, AST_CAUSE_FAILURE, AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA);
return -1;
}
+ /* The app doesn't want media right now */
+ if (instance->media_direction == WEBCHAN_MEDIA_DIRECTION_OUT) {
+ return 0;
+ }
+
if (f->frametype == AST_FRAME_CNG) {
return 0;
}
OPT_WS_URI_PARAM = (1 << 2),
OPT_WS_PASSTHROUGH = (1 << 3),
OPT_WS_MSG_FORMAT = (1 << 4),
+ OPT_WS_MEDIA_DIRECTION = (1 << 5),
};
enum {
OPT_ARG_WS_URI_PARAM,
OPT_ARG_WS_PASSTHROUGH,
OPT_ARG_WS_MSG_FORMAT,
+ OPT_ARG_WS_MEDIA_DIRECTION,
OPT_ARG_ARRAY_SIZE
};
AST_APP_OPTION_ARG('v', OPT_WS_URI_PARAM, OPT_ARG_WS_URI_PARAM),
AST_APP_OPTION('p', OPT_WS_PASSTHROUGH),
AST_APP_OPTION_ARG('f', OPT_WS_MSG_FORMAT, OPT_ARG_WS_MSG_FORMAT),
+ AST_APP_OPTION_ARG('d', OPT_WS_MEDIA_DIRECTION, OPT_ARG_WS_MEDIA_DIRECTION),
END_OPTIONS );
static struct ast_channel *webchan_request(const char *type,
goto failure;
}
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_BOTH;
+ if (ast_test_flag(&opts, OPT_WS_MEDIA_DIRECTION)) {
+ if (!strcmp("both", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
+ /* The default. Don't need to do anything here other than
+ * ensure it is an allowed value. */
+ } else if (!strcmp("out", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_OUT;
+ } else if (!strcmp("in", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
+ instance->media_direction = WEBCHAN_MEDIA_DIRECTION_IN;
+ } else {
+ ast_log(LOG_ERROR, "Unrecognized option for media direction: '%s'.\n",
+ opt_args[OPT_ARG_WS_MEDIA_DIRECTION]);
+ goto failure;
+ }
+ }
+
instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
if (!instance->passthrough) {
instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
goto failure;
}
- if (set_channel_timer(instance) != 0) {
+ /* If the application's media direction is 'both' or 'out', we need the channel timer. */
+ if (instance->media_direction != WEBCHAN_MEDIA_DIRECTION_IN
+ && set_channel_timer(instance) != 0) {
goto failure;
}