diff --git a/apps/app_minivm.c b/apps/app_minivm.c index 06ac02a5e06918cb70c8710b2db8bb2b914abba8..cdcf8b9331340b3a924ba630217c347449469de9 100644 --- a/apps/app_minivm.c +++ b/apps/app_minivm.c @@ -1674,7 +1674,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0); if (ast_test_flag(vmu, MVM_OPERATOR)) canceldtmf = "0"; - cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE); + cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE); if (record_gain) ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0); if (cmd == -1) /* User has hung up, no options to give */ diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 3696c740c6d5ea0cbe251209ab768086668d71f2..a258e6a0a082d3ebace0afb277c031156f8ebcd1 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -14682,7 +14682,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0); if (ast_test_flag(vmu, VM_OPERATOR)) canceldtmf = "0"; - cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE); + cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE); if (strchr(canceldtmf, cmd)) { /* need this flag here to distinguish between pressing '0' during message recording or after */ canceleddtmf = 1; diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c index 94a2c13d0829d64005c86de3157abfd3f17e1945..17f7c896589672d57c5bf394e13d6690f20f7ba1 100644 --- a/funcs/func_frame_trace.c +++ b/funcs/func_frame_trace.c @@ -343,6 +343,18 @@ static void print_frame(struct ast_frame *frame) case AST_CONTROL_STREAM_FORWARD: ast_verbose("SubClass: STREAM_FORWARD\n"); break; + case AST_CONTROL_RECORD_CANCEL: + ast_verbose("SubClass: RECORD_CANCEL\n"); + break; + case AST_CONTROL_RECORD_STOP: + ast_verbose("SubClass: RECORD_STOP\n"); + break; + case AST_CONTROL_RECORD_SUSPEND: + ast_verbose("SubClass: RECORD_SUSPEND\n"); + break; + case AST_CONTROL_RECORD_MUTE: + ast_verbose("SubClass: RECORD_MUTE\n"); + break; } if (frame->subclass.integer == -1) { diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 251288546ef0e84f55b81136e3e3d4dd7c972878..06b903e2f352332eae99131508bbbc891aca4bb3 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -709,11 +709,12 @@ enum ast_record_if_exists { * skip_confirmation_sound is false. * * \param chan the channel being recorded - * \param playfile Filename of sound to play before recording begins + * \param playfile Filename of sound to play before recording begins. A beep is also played when playfile completes, before the recording begins. * \param recordfile Filename to save the recording * \param maxtime_sec Longest possible message length in seconds * \param fmt string containing all formats to be recorded delimited by '|' * \param duration pointer to integer for storing length of the recording + * \param beep If true, play a beep before recording begins (and doesn't play \a playfile) * \param sound_duration pointer to integer for storing length of the recording minus all silence * \param silencethreshold tolerance of noise levels that can be considered silence for the purpose of silence timeout, -1 for default * \param maxsilence_ms Length of time in milliseconds which will trigger a timeout from silence, -1 for default @@ -728,7 +729,7 @@ enum ast_record_if_exists { * \retval 't' Recording ended from the message exceeding the maximum duration * \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept. */ -int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists); +int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists); /*! * \brief Record a file based on input from a channel. Use default accept and cancel DTMF. diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index bedc3a25d9c022135c5954aca0b6a79e5a4560b8..1cb7d591fa5a0f47daade4e65bd95eb620ff5b8d 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -278,7 +278,11 @@ enum ast_control_frame_type { AST_CONTROL_STREAM_RESTART = 1002, /*!< Indicate to a channel in playback to restart the stream */ AST_CONTROL_STREAM_REVERSE = 1003, /*!< Indicate to a channel in playback to rewind */ AST_CONTROL_STREAM_FORWARD = 1004, /*!< Indicate to a channel in playback to fast forward */ - + /* Control frames to manipulate recording on a channel. */ + AST_CONTROL_RECORD_CANCEL = 1100, /*!< Indicated to a channel in record to stop recording and discard the file */ + AST_CONTROL_RECORD_STOP = 1101, /*!< Indicated to a channel in record to stop recording */ + AST_CONTROL_RECORD_SUSPEND = 1102, /*!< Indicated to a channel in record to suspend/unsuspend recording */ + AST_CONTROL_RECORD_MUTE = 1103, /*!< Indicated to a channel in record to mute/unmute (i.e. write silence) recording */ }; enum ast_frame_read_action { diff --git a/include/asterisk/stasis_app_recording.h b/include/asterisk/stasis_app_recording.h index 9c9930406b9653e61e08e1714fc6dd5dbee5a7e6..e8b4558ab03cd11befce40fbce75bf8770b0bfc4 100644 --- a/include/asterisk/stasis_app_recording.h +++ b/include/asterisk/stasis_app_recording.h @@ -44,14 +44,30 @@ enum stasis_app_recording_state { STASIS_APP_RECORDING_STATE_PAUSED, /*! The media has stopped recording */ STASIS_APP_RECORDING_STATE_COMPLETE, - /*! The media has stopped playing */ + /*! The media has stopped recording, with error */ STASIS_APP_RECORDING_STATE_FAILED, + /*! The media has stopped recording, discard the recording file */ + STASIS_APP_RECORDING_STATE_CANCELED, + /*! Sentinel */ + STASIS_APP_RECORDING_STATE_MAX, }; /*! Valid operation for controlling a recording. */ enum stasis_app_recording_media_operation { - /*! Stop the recording operation. */ + /*! Stop the recording, deleting the media file(s) */ + STASIS_APP_RECORDING_CANCEL, + /*! Stop the recording. */ STASIS_APP_RECORDING_STOP, + /*! Pause the recording */ + STASIS_APP_RECORDING_PAUSE, + /*! Unpause the recording */ + STASIS_APP_RECORDING_UNPAUSE, + /*! Mute the recording (record silence) */ + STASIS_APP_RECORDING_MUTE, + /*! Unmute the recording */ + STASIS_APP_RECORDING_UNMUTE, + /*! Sentinel */ + STASIS_APP_RECORDING_OPER_MAX, }; #define STASIS_APP_RECORDING_TERMINATE_INVALID 0 diff --git a/main/app.c b/main/app.c index 8d081fe8c7be957cfebfb688affceb64b4786b21..ee2bbf467e27908e30dcc7feb74c880dee2b7579 100644 --- a/main/app.c +++ b/main/app.c @@ -1145,6 +1145,78 @@ int ast_play_and_wait(struct ast_channel *chan, const char *fn) return d; } +/*! + * \brief Construct a silence frame of the same duration as \a orig. + * + * The \a orig frame must be \ref AST_FORMAT_SLINEAR. + * + * \param orig Frame as basis for silence to generate. + * \return New frame of silence; free with ast_frfree(). + * \return \c NULL on error. + */ +static struct ast_frame *make_silence(const struct ast_frame *orig) +{ + struct ast_frame *silence; + size_t size; + size_t datalen; + size_t samples = 0; + struct ast_frame *next; + + if (!orig) { + return NULL; + } + + if (orig->subclass.format.id != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Attempting to silence non-slin frame\n"); + return NULL; + } + + for (next = AST_LIST_NEXT(orig, frame_list); + orig; + orig = next, next = orig ? AST_LIST_NEXT(orig, frame_list) : NULL) { + samples += orig->samples; + } + + ast_verb(4, "Silencing %zd samples\n", samples); + + + datalen = sizeof(short) * samples; + size = sizeof(*silence) + datalen; + silence = ast_calloc(1, size); + if (!silence) { + return NULL; + } + + silence->mallocd = AST_MALLOCD_HDR; + silence->frametype = AST_FRAME_VOICE; + silence->data.ptr = (void *)(silence + 1); + silence->samples = samples; + silence->datalen = datalen; + + ast_format_set(&silence->subclass.format, AST_FORMAT_SLINEAR, 0); + + return silence; +} + +/*! + * \brief Sets a channel's read format to \ref AST_FORMAT_SLINEAR, recording + * its original format. + * + * \param chan Channel to modify. + * \param[out] orig_format Output variable to store channel's original read + * format. + * \return 0 on success. + * \return -1 on error. + */ +static int set_read_to_slin(struct ast_channel *chan, struct ast_format *orig_format) +{ + if (!chan || !orig_format) { + return -1; + } + ast_format_copy(orig_format, ast_channel_readformat(chan)); + return ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR); +} + static int global_silence_threshold = 128; static int global_maxsilence = 0; @@ -1274,8 +1346,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, return -1; } ast_dsp_set_threshold(sildet, silencethreshold); - ast_format_copy(&rfmt, ast_channel_readformat(chan)); - res = ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR); + res = set_read_to_slin(chan, &rfmt); if (res < 0) { ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); ast_dsp_free(sildet); @@ -1293,9 +1364,15 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, } if (x == fmtcnt) { - /* Loop forever, writing the packets we read to the writer(s), until - we read a digit or get a hangup */ + /* Loop, writing the packets we read to the writer(s), until + * we have reason to stop. */ struct ast_frame *f; + int paused = 0; + int muted = 0; + time_t pause_start = 0; + int paused_secs = 0; + int pausedsilence = 0; + for (;;) { if (!(res = ast_waitfor(chan, 2000))) { ast_debug(1, "One waitfor failed, trying another\n"); @@ -1315,11 +1392,29 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, } if (f->frametype == AST_FRAME_VOICE) { /* write each format */ - for (x = 0; x < fmtcnt; x++) { - if (prepend && !others[x]) { - break; + if (paused) { + /* It's all good */ + res = 0; + } else { + RAII_VAR(struct ast_frame *, silence, NULL, ast_frame_dtor); + struct ast_frame *orig = f; + + if (muted) { + silence = make_silence(orig); + if (!silence) { + ast_log(LOG_WARNING, + "Error creating silence\n"); + break; + } + f = silence; } - res = ast_writestream(others[x], f); + for (x = 0; x < fmtcnt; x++) { + if (prepend && !others[x]) { + break; + } + res = ast_writestream(others[x], f); + } + f = orig; } /* Silence Detection */ @@ -1331,6 +1426,17 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, } olddspsilence = dspsilence; + if (paused) { + /* record how much silence there was while we are paused */ + pausedsilence = dspsilence; + } else if (dspsilence > pausedsilence) { + /* ignore the paused silence */ + dspsilence -= pausedsilence; + } else { + /* dspsilence has reset, reset pausedsilence */ + pausedsilence = 0; + } + if (dspsilence > maxsilence) { /* Ended happily with silence */ ast_verb(3, "Recording automatically stopped after a silence of %d seconds\n", dspsilence/1000); @@ -1362,15 +1468,51 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, break; } if (strchr(canceldtmf, f->subclass.integer)) { - ast_verb(3, "User cancelled message by pressing %c\n", f->subclass.integer); + ast_verb(3, "User canceled message by pressing %c\n", f->subclass.integer); res = f->subclass.integer; outmsg = 0; break; } + } else if (f->frametype == AST_FRAME_CONTROL) { + if (f->subclass.integer == AST_CONTROL_RECORD_CANCEL) { + ast_verb(3, "Message canceled by control\n"); + outmsg = 0; /* cancels the recording */ + res = 0; + break; + } else if (f->subclass.integer == AST_CONTROL_RECORD_STOP) { + ast_verb(3, "Message ended by control\n"); + res = 0; + break; + } else if (f->subclass.integer == AST_CONTROL_RECORD_SUSPEND) { + paused = !paused; + ast_verb(3, "Message %spaused by control\n", + paused ? "" : "un"); + if (paused) { + pause_start = time(NULL); + } else { + paused_secs += time(NULL) - pause_start; + } + } else if (f->subclass.integer == AST_CONTROL_RECORD_MUTE) { + muted = !muted; + ast_verb(3, "Message %smuted by control\n", + muted ? "" : "un"); + /* We can only silence slin frames, so + * set the mode, if we haven't already + * for sildet + */ + if (muted && !rfmt.id) { + ast_verb(3, "Setting read format to linear mode\n"); + res = set_read_to_slin(chan, &rfmt); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); + break; + } + } + } } - if (maxtime) { + if (maxtime && !paused) { end = time(NULL); - if (maxtime < (end - start)) { + if (maxtime < (end - start - paused_secs)) { ast_verb(3, "Took too long, cutting it short...\n"); res = 't'; outmsg = 2; @@ -1493,9 +1635,9 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, static const char default_acceptdtmf[] = "#"; static const char default_canceldtmf[] = ""; -int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists) +int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists) { - return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists); + return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists); } int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path) diff --git a/main/channel.c b/main/channel.c index f1d98de126853fa2f8ddbefa1ec0d4b67f5b1a76..8e6a4c945c8c17161133ef287f1cf2b096930ea2 100644 --- a/main/channel.c +++ b/main/channel.c @@ -4303,6 +4303,10 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con case AST_CONTROL_STREAM_REVERSE: case AST_CONTROL_STREAM_FORWARD: case AST_CONTROL_STREAM_RESTART: + case AST_CONTROL_RECORD_CANCEL: + case AST_CONTROL_RECORD_STOP: + case AST_CONTROL_RECORD_SUSPEND: + case AST_CONTROL_RECORD_MUTE: break; case AST_CONTROL_INCOMPLETE: @@ -4561,6 +4565,10 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, case AST_CONTROL_STREAM_REVERSE: case AST_CONTROL_STREAM_FORWARD: case AST_CONTROL_STREAM_RESTART: + case AST_CONTROL_RECORD_CANCEL: + case AST_CONTROL_RECORD_STOP: + case AST_CONTROL_RECORD_SUSPEND: + case AST_CONTROL_RECORD_MUTE: /* Nothing left to do for these. */ res = 0; break; diff --git a/res/ari/resource_recordings.c b/res/ari/resource_recordings.c index 46439ff0b8a13078d9560fce9781443496d871e5..f87ff0a0ae89f9380e09b4086bccf0d35d15dd93 100644 --- a/res/ari/resource_recordings.c +++ b/res/ari/resource_recordings.c @@ -71,27 +71,81 @@ void ast_ari_get_live_recording(struct ast_variable *headers, ast_ari_response_ok(response, ast_json_ref(json)); } -void ast_ari_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct ast_ari_response *response) +static void control_recording(const char *name, + enum stasis_app_recording_media_operation operation, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_cancel_recording\n"); + RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + enum stasis_app_recording_oper_results res; + + recording = stasis_app_recording_find_by_name(name); + if (recording == NULL) { + ast_ari_response_error(response, 404, "Not Found", + "Recording not found"); + return; + } + + res = stasis_app_recording_operation(recording, operation); + + switch (res) { + case STASIS_APP_RECORDING_OPER_OK: + ast_ari_response_no_content(response); + return; + case STASIS_APP_RECORDING_OPER_FAILED: + ast_ari_response_error(response, 500, + "Internal Server Error", "Recording operation failed"); + return; + case STASIS_APP_RECORDING_OPER_NOT_RECORDING: + ast_ari_response_error(response, 409, + "Conflict", "Recording not in session"); + } } -void ast_ari_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct ast_ari_response *response) + +void ast_ari_cancel_recording(struct ast_variable *headers, + struct ast_cancel_recording_args *args, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_stop_recording\n"); + control_recording(args->recording_name, STASIS_APP_RECORDING_CANCEL, + response); } -void ast_ari_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct ast_ari_response *response) + +void ast_ari_stop_recording(struct ast_variable *headers, + struct ast_stop_recording_args *args, + struct ast_ari_response *response) +{ + control_recording(args->recording_name, STASIS_APP_RECORDING_STOP, + response); +} + +void ast_ari_pause_recording(struct ast_variable *headers, + struct ast_pause_recording_args *args, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_pause_recording\n"); + control_recording(args->recording_name, STASIS_APP_RECORDING_PAUSE, + response); } -void ast_ari_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct ast_ari_response *response) + +void ast_ari_unpause_recording(struct ast_variable *headers, + struct ast_unpause_recording_args *args, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_unpause_recording\n"); + control_recording(args->recording_name, STASIS_APP_RECORDING_UNPAUSE, + response); } -void ast_ari_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct ast_ari_response *response) + +void ast_ari_mute_recording(struct ast_variable *headers, + struct ast_mute_recording_args *args, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_mute_recording\n"); + control_recording(args->recording_name, STASIS_APP_RECORDING_MUTE, + response); } -void ast_ari_unmute_recording(struct ast_variable *headers, struct ast_unmute_recording_args *args, struct ast_ari_response *response) + +void ast_ari_unmute_recording(struct ast_variable *headers, + struct ast_unmute_recording_args *args, + struct ast_ari_response *response) { - ast_log(LOG_ERROR, "TODO: ast_ari_unmute_recording\n"); + control_recording(args->recording_name, STASIS_APP_RECORDING_UNMUTE, + response); } diff --git a/res/ari/resource_recordings.h b/res/ari/resource_recordings.h index e3ee88be88144a460a29055c7d1572dbe937336d..2529766e79c24354da3342fb074b3716197de748 100644 --- a/res/ari/resource_recordings.h +++ b/res/ari/resource_recordings.h @@ -134,7 +134,7 @@ struct ast_pause_recording_args { /*! * \brief Pause a live recording. * - * Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused. + * Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused. Paused time is not included in the accounting for maxDurationSeconds. * * \param headers HTTP headers * \param args Swagger parameters diff --git a/res/res_ari_recordings.c b/res/res_ari_recordings.c index 01ad49a653f5dcc4de1b690fe3d7d55da65ff11c..77fc830cffc5b97f8c97916423dc0efd80daf9c6 100644 --- a/res/res_ari_recordings.c +++ b/res/res_ari_recordings.c @@ -295,6 +295,7 @@ static void ast_ari_get_live_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ is_valid = 1; break; default: @@ -351,6 +352,7 @@ static void ast_ari_cancel_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ is_valid = 1; break; default: @@ -407,6 +409,7 @@ static void ast_ari_stop_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ is_valid = 1; break; default: @@ -463,6 +466,8 @@ static void ast_ari_pause_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ + case 409: /* Recording not in session */ is_valid = 1; break; default: @@ -519,6 +524,8 @@ static void ast_ari_unpause_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ + case 409: /* Recording not in session */ is_valid = 1; break; default: @@ -575,6 +582,8 @@ static void ast_ari_mute_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ + case 409: /* Recording not in session */ is_valid = 1; break; default: @@ -631,6 +640,8 @@ static void ast_ari_unmute_recording_cb( break; case 500: /* Internal Server Error */ case 501: /* Not Implemented */ + case 404: /* Recording not found */ + case 409: /* Recording not in session */ is_valid = 1; break; default: diff --git a/res/res_stasis_recording.c b/res/res_stasis_recording.c index f94f428954207ec2188bf9e98c5bc54819943bfe..575ccae1ea9ac695bce055a7ed8fd285cf5068e6 100644 --- a/res/res_stasis_recording.c +++ b/res/res_stasis_recording.c @@ -59,11 +59,13 @@ struct stasis_app_recording { struct stasis_app_recording_options *options; /*! Absolute path (minus extension) of the recording */ char *absolute_name; - /*! Control object for the channel we're playing back to */ + /*! Control object for the channel we're recording */ struct stasis_app_control *control; /*! Current state of the recording. */ enum stasis_app_recording_state state; + /*! Indicates whether the recording is currently muted */ + int muted:1; }; static int recording_hash(const void *obj, int flags) @@ -99,6 +101,10 @@ static const char *state_to_string(enum stasis_app_recording_state state) return "done"; case STASIS_APP_RECORDING_STATE_FAILED: return "failed"; + case STASIS_APP_RECORDING_STATE_CANCELED: + return "canceled"; + case STASIS_APP_RECORDING_STATE_MAX: + return "?"; } return "?"; @@ -253,12 +259,13 @@ static void *record_file(struct stasis_app_control *control, } ast_play_and_record_full(chan, - recording->options->beep ? "beep" : NULL, + NULL, /* playfile */ recording->absolute_name, recording->options->max_duration_seconds, recording->options->format, &duration, NULL, /* sound_duration */ + recording->options->beep, -1, /* silencethreshold */ recording->options->max_silence_seconds * 1000, NULL, /* path */ @@ -403,12 +410,127 @@ struct ast_json *stasis_app_recording_to_json( return ast_json_ref(json); } +typedef int (*recording_operation_cb)(struct stasis_app_recording *recording); + +static int recording_noop(struct stasis_app_recording *recording) +{ + return 0; +} + +static int recording_disregard(struct stasis_app_recording *recording) +{ + recording->state = STASIS_APP_RECORDING_STATE_CANCELED; + return 0; +} + +static int recording_cancel(struct stasis_app_recording *recording) +{ + int res = 0; + recording->state = STASIS_APP_RECORDING_STATE_CANCELED; + res |= stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_CANCEL); + res |= ast_filedelete(recording->absolute_name, NULL); + return res; +} + +static int recording_stop(struct stasis_app_recording *recording) +{ + recording->state = STASIS_APP_RECORDING_STATE_COMPLETE; + return stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_STOP); +} + +static int recording_pause(struct stasis_app_recording *recording) +{ + recording->state = STASIS_APP_RECORDING_STATE_PAUSED; + return stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_SUSPEND); +} + +static int recording_unpause(struct stasis_app_recording *recording) +{ + recording->state = STASIS_APP_RECORDING_STATE_RECORDING; + return stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_SUSPEND); +} + +static int recording_mute(struct stasis_app_recording *recording) +{ + if (recording->muted) { + /* already muted */ + return 0; + } + + recording->muted = 1; + return stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_MUTE); +} + +static int recording_unmute(struct stasis_app_recording *recording) +{ + if (!recording->muted) { + /* already unmuted */ + return 0; + } + + return stasis_app_control_queue_control(recording->control, + AST_CONTROL_RECORD_MUTE); +} + +recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = { + [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard, + [STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute, + [STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute, + [STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute, +}; + enum stasis_app_recording_oper_results stasis_app_recording_operation( struct stasis_app_recording *recording, enum stasis_app_recording_media_operation operation) { - ast_assert(0); // TODO - return STASIS_APP_RECORDING_OPER_FAILED; + recording_operation_cb cb; + SCOPED_AO2LOCK(lock, recording); + + if (recording->state < 0 || recording->state >= STASIS_APP_RECORDING_STATE_MAX) { + ast_log(LOG_WARNING, "Invalid recording state %d\n", + recording->state); + return -1; + } + + if (operation < 0 || operation >= STASIS_APP_RECORDING_OPER_MAX) { + ast_log(LOG_WARNING, "Invalid recording operation %d\n", + operation); + return -1; + } + + cb = operations[recording->state][operation]; + + if (!cb) { + if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) { + /* So we can be specific in our error message. */ + return STASIS_APP_RECORDING_OPER_NOT_RECORDING; + } else { + /* And, really, all operations should be valid during + * recording */ + ast_log(LOG_ERROR, + "Unhandled operation during recording: %d\n", + operation); + return STASIS_APP_RECORDING_OPER_FAILED; + } + } + + return cb(recording) ? + STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK; } static int load_module(void) diff --git a/rest-api/api-docs/recordings.json b/rest-api/api-docs/recordings.json index b564edee0a9afd5fcc5af5231430aee629385c64..b4dd6d090ec36ce5132c4acaaa9be4530a788456 100644 --- a/rest-api/api-docs/recordings.json +++ b/rest-api/api-docs/recordings.json @@ -87,6 +87,12 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + } ] }, { @@ -103,6 +109,12 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + } ] } ] @@ -124,6 +136,12 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + } ] } ] @@ -134,7 +152,7 @@ { "httpMethod": "POST", "summary": "Pause a live recording.", - "notes": "Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused.", + "notes": "Pausing a recording suspends silence detection, which will be restarted when the recording is unpaused. Paused time is not included in the accounting for maxDurationSeconds.", "nickname": "pauseRecording", "responseClass": "void", "parameters": [ @@ -146,6 +164,16 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + }, + { + "code": 409, + "reason": "Recording not in session" + } ] } ] @@ -167,6 +195,16 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + }, + { + "code": 409, + "reason": "Recording not in session" + } ] } ] @@ -189,6 +227,16 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + }, + { + "code": 409, + "reason": "Recording not in session" + } ] } ] @@ -210,6 +258,16 @@ "allowMultiple": false, "dataType": "string" } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Recording not found" + }, + { + "code": 409, + "reason": "Recording not in session" + } ] } ]