res_stasis_playback.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2013, Digium, Inc.
  5. *
  6. * David M. Lee, II <dlee@digium.com>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*! \file
  19. *
  20. * \brief res_stasis playback support.
  21. *
  22. * \author David M. Lee, II <dlee@digium.com>
  23. */
  24. /*** MODULEINFO
  25. <depend type="module">res_stasis</depend>
  26. <depend type="module">res_stasis_recording</depend>
  27. <support_level>core</support_level>
  28. ***/
  29. #include "asterisk.h"
  30. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  31. #include "asterisk/app.h"
  32. #include "asterisk/astobj2.h"
  33. #include "asterisk/bridge.h"
  34. #include "asterisk/bridge_internal.h"
  35. #include "asterisk/file.h"
  36. #include "asterisk/logger.h"
  37. #include "asterisk/module.h"
  38. #include "asterisk/paths.h"
  39. #include "asterisk/stasis_app_impl.h"
  40. #include "asterisk/stasis_app_playback.h"
  41. #include "asterisk/stasis_app_recording.h"
  42. #include "asterisk/stasis_channels.h"
  43. #include "asterisk/stringfields.h"
  44. #include "asterisk/uuid.h"
  45. #include "asterisk/say.h"
  46. #include "asterisk/indications.h"
  47. /*! Number of hash buckets for playback container. Keep it prime! */
  48. #define PLAYBACK_BUCKETS 127
  49. /*! Default number of milliseconds of media to skip */
  50. #define PLAYBACK_DEFAULT_SKIPMS 3000
  51. #define SOUND_URI_SCHEME "sound:"
  52. #define RECORDING_URI_SCHEME "recording:"
  53. #define NUMBER_URI_SCHEME "number:"
  54. #define DIGITS_URI_SCHEME "digits:"
  55. #define CHARACTERS_URI_SCHEME "characters:"
  56. #define TONE_URI_SCHEME "tone:"
  57. /*! Container of all current playbacks */
  58. static struct ao2_container *playbacks;
  59. /*! Playback control object for res_stasis */
  60. struct stasis_app_playback {
  61. AST_DECLARE_STRING_FIELDS(
  62. AST_STRING_FIELD(id); /*!< Playback unique id */
  63. AST_STRING_FIELD(media); /*!< Playback media uri */
  64. AST_STRING_FIELD(language); /*!< Preferred language */
  65. AST_STRING_FIELD(target); /*!< Playback device uri */
  66. );
  67. /*! Control object for the channel we're playing back to */
  68. struct stasis_app_control *control;
  69. /*! Number of milliseconds to skip before playing */
  70. long offsetms;
  71. /*! Number of milliseconds to skip for forward/reverse operations */
  72. int skipms;
  73. /*! Number of milliseconds of media that has been played */
  74. long playedms;
  75. /*! Current playback state */
  76. enum stasis_app_playback_state state;
  77. /*! Set when the playback can be controlled */
  78. unsigned int controllable:1;
  79. };
  80. static struct ast_json *playback_to_json(struct stasis_message *message,
  81. const struct stasis_message_sanitizer *sanitize)
  82. {
  83. struct ast_channel_blob *channel_blob = stasis_message_data(message);
  84. struct ast_json *blob = channel_blob->blob;
  85. const char *state =
  86. ast_json_string_get(ast_json_object_get(blob, "state"));
  87. const char *type;
  88. if (!strcmp(state, "playing")) {
  89. type = "PlaybackStarted";
  90. } else if (!strcmp(state, "done")) {
  91. type = "PlaybackFinished";
  92. } else {
  93. return NULL;
  94. }
  95. return ast_json_pack("{s: s, s: O}",
  96. "type", type,
  97. "playback", blob);
  98. }
  99. STASIS_MESSAGE_TYPE_DEFN(stasis_app_playback_snapshot_type,
  100. .to_json = playback_to_json,
  101. );
  102. static void playback_dtor(void *obj)
  103. {
  104. struct stasis_app_playback *playback = obj;
  105. ast_string_field_free_memory(playback);
  106. }
  107. static struct stasis_app_playback *playback_create(
  108. struct stasis_app_control *control, const char *id)
  109. {
  110. RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
  111. char uuid[AST_UUID_STR_LEN];
  112. if (!control) {
  113. return NULL;
  114. }
  115. playback = ao2_alloc(sizeof(*playback), playback_dtor);
  116. if (!playback || ast_string_field_init(playback, 128)) {
  117. return NULL;
  118. }
  119. if (!ast_strlen_zero(id)) {
  120. ast_string_field_set(playback, id, id);
  121. } else {
  122. ast_uuid_generate_str(uuid, sizeof(uuid));
  123. ast_string_field_set(playback, id, uuid);
  124. }
  125. playback->control = control;
  126. ao2_ref(playback, +1);
  127. return playback;
  128. }
  129. static int playback_hash(const void *obj, int flags)
  130. {
  131. const struct stasis_app_playback *playback = obj;
  132. const char *id = flags & OBJ_KEY ? obj : playback->id;
  133. return ast_str_hash(id);
  134. }
  135. static int playback_cmp(void *obj, void *arg, int flags)
  136. {
  137. struct stasis_app_playback *lhs = obj;
  138. struct stasis_app_playback *rhs = arg;
  139. const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
  140. if (strcmp(lhs->id, rhs_id) == 0) {
  141. return CMP_MATCH | CMP_STOP;
  142. } else {
  143. return 0;
  144. }
  145. }
  146. static const char *state_to_string(enum stasis_app_playback_state state)
  147. {
  148. switch (state) {
  149. case STASIS_PLAYBACK_STATE_QUEUED:
  150. return "queued";
  151. case STASIS_PLAYBACK_STATE_PLAYING:
  152. return "playing";
  153. case STASIS_PLAYBACK_STATE_PAUSED:
  154. return "paused";
  155. case STASIS_PLAYBACK_STATE_STOPPED:
  156. case STASIS_PLAYBACK_STATE_COMPLETE:
  157. case STASIS_PLAYBACK_STATE_CANCELED:
  158. /* It doesn't really matter how we got here, but all of these
  159. * states really just mean 'done' */
  160. return "done";
  161. case STASIS_PLAYBACK_STATE_MAX:
  162. break;
  163. }
  164. return "?";
  165. }
  166. static void playback_publish(struct stasis_app_playback *playback)
  167. {
  168. RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
  169. RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
  170. ast_assert(playback != NULL);
  171. json = stasis_app_playback_to_json(playback);
  172. if (json == NULL) {
  173. return;
  174. }
  175. message = ast_channel_blob_create_from_cache(
  176. stasis_app_control_get_channel_id(playback->control),
  177. stasis_app_playback_snapshot_type(), json);
  178. if (message == NULL) {
  179. return;
  180. }
  181. stasis_app_control_publish(playback->control, message);
  182. }
  183. static int playback_first_update(struct stasis_app_playback *playback,
  184. const char *uniqueid)
  185. {
  186. int res;
  187. SCOPED_AO2LOCK(lock, playback);
  188. if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
  189. ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
  190. uniqueid, playback->media);
  191. res = -1;
  192. } else {
  193. res = 0;
  194. playback->state = STASIS_PLAYBACK_STATE_PLAYING;
  195. }
  196. playback_publish(playback);
  197. return res;
  198. }
  199. static void playback_final_update(struct stasis_app_playback *playback,
  200. long playedms, int res, const char *uniqueid)
  201. {
  202. SCOPED_AO2LOCK(lock, playback);
  203. playback->playedms = playedms;
  204. if (res == 0) {
  205. playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
  206. } else {
  207. if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
  208. ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
  209. uniqueid, playback->media);
  210. } else {
  211. ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
  212. uniqueid, playback->media);
  213. playback->state = STASIS_PLAYBACK_STATE_STOPPED;
  214. }
  215. }
  216. playback_publish(playback);
  217. }
  218. static void play_on_channel(struct stasis_app_playback *playback,
  219. struct ast_channel *chan)
  220. {
  221. int res;
  222. long offsetms;
  223. /* Even though these local variables look fairly pointless, the avoid
  224. * having a bunch of NULL's passed directly into
  225. * ast_control_streamfile() */
  226. const char *fwd = NULL;
  227. const char *rev = NULL;
  228. const char *stop = NULL;
  229. const char *pause = NULL;
  230. const char *restart = NULL;
  231. ast_assert(playback != NULL);
  232. offsetms = playback->offsetms;
  233. res = playback_first_update(playback, ast_channel_uniqueid(chan));
  234. if (res != 0) {
  235. return;
  236. }
  237. if (ast_channel_state(chan) != AST_STATE_UP) {
  238. ast_indicate(chan, AST_CONTROL_PROGRESS);
  239. }
  240. if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
  241. playback->controllable = 1;
  242. /* Play sound */
  243. res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
  244. fwd, rev, stop, pause, restart, playback->skipms, playback->language,
  245. &offsetms);
  246. } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
  247. /* Play recording */
  248. RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
  249. ao2_cleanup);
  250. const char *relname =
  251. playback->media + strlen(RECORDING_URI_SCHEME);
  252. recording = stasis_app_stored_recording_find_by_name(relname);
  253. if (!recording) {
  254. ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
  255. relname, ast_channel_name(chan));
  256. return;
  257. }
  258. playback->controllable = 1;
  259. res = ast_control_streamfile_lang(chan,
  260. stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
  261. restart, playback->skipms, playback->language, &offsetms);
  262. } else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
  263. int number;
  264. if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
  265. ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
  266. playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
  267. return;
  268. }
  269. res = ast_say_number(chan, number, stop, playback->language, NULL);
  270. } else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
  271. res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
  272. stop, playback->language);
  273. } else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
  274. res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
  275. stop, playback->language, AST_SAY_CASE_NONE);
  276. } else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
  277. playback->controllable = 1;
  278. res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
  279. } else {
  280. /* Play URL */
  281. ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
  282. playback->media, ast_channel_name(chan));
  283. return;
  284. }
  285. playback_final_update(playback, offsetms, res,
  286. ast_channel_uniqueid(chan));
  287. return;
  288. }
  289. /*!
  290. * \brief Special case code to play while a channel is in a bridge.
  291. *
  292. * \param bridge_channel The channel's bridge_channel.
  293. * \param playback_id Id of the playback to start.
  294. */
  295. static void play_on_channel_in_bridge(struct ast_bridge_channel *bridge_channel,
  296. const char *playback_id)
  297. {
  298. RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
  299. playback = stasis_app_playback_find_by_id(playback_id);
  300. if (!playback) {
  301. ast_log(LOG_ERROR, "Couldn't find playback %s\n",
  302. playback_id);
  303. return;
  304. }
  305. play_on_channel(playback, bridge_channel->chan);
  306. }
  307. /*!
  308. * \brief \ref RAII_VAR function to remove a playback from the global list when
  309. * leaving scope.
  310. */
  311. static void remove_from_playbacks(void *data)
  312. {
  313. struct stasis_app_playback *playback = data;
  314. ao2_unlink_flags(playbacks, playback,
  315. OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
  316. ao2_ref(playback, -1);
  317. }
  318. static int play_uri(struct stasis_app_control *control,
  319. struct ast_channel *chan, void *data)
  320. {
  321. struct stasis_app_playback *playback = data;
  322. struct ast_bridge *bridge;
  323. if (!control) {
  324. return -1;
  325. }
  326. bridge = stasis_app_get_bridge(control);
  327. if (bridge) {
  328. struct ast_bridge_channel *bridge_chan;
  329. /* Queue up playback on the bridge */
  330. ast_bridge_lock(bridge);
  331. bridge_chan = ao2_bump(bridge_find_channel(bridge, chan));
  332. ast_bridge_unlock(bridge);
  333. if (bridge_chan) {
  334. ast_bridge_channel_queue_playfile_sync(
  335. bridge_chan,
  336. play_on_channel_in_bridge,
  337. playback->id,
  338. NULL); /* moh_class */
  339. }
  340. ao2_cleanup(bridge_chan);
  341. } else {
  342. play_on_channel(playback, chan);
  343. }
  344. return 0;
  345. }
  346. static void set_target_uri(
  347. struct stasis_app_playback *playback,
  348. enum stasis_app_playback_target_type target_type,
  349. const char *target_id)
  350. {
  351. const char *type = NULL;
  352. switch (target_type) {
  353. case STASIS_PLAYBACK_TARGET_CHANNEL:
  354. type = "channel";
  355. break;
  356. case STASIS_PLAYBACK_TARGET_BRIDGE:
  357. type = "bridge";
  358. break;
  359. }
  360. ast_assert(type != NULL);
  361. ast_string_field_build(playback, target, "%s:%s", type, target_id);
  362. }
  363. struct stasis_app_playback *stasis_app_control_play_uri(
  364. struct stasis_app_control *control, const char *uri,
  365. const char *language, const char *target_id,
  366. enum stasis_app_playback_target_type target_type,
  367. int skipms, long offsetms, const char *id)
  368. {
  369. struct stasis_app_playback *playback;
  370. if (skipms < 0 || offsetms < 0) {
  371. return NULL;
  372. }
  373. ast_debug(3, "%s: Sending play(%s) command\n",
  374. stasis_app_control_get_channel_id(control), uri);
  375. playback = playback_create(control, id);
  376. if (!playback) {
  377. return NULL;
  378. }
  379. if (skipms == 0) {
  380. skipms = PLAYBACK_DEFAULT_SKIPMS;
  381. }
  382. ast_string_field_set(playback, media, uri);
  383. ast_string_field_set(playback, language, language);
  384. set_target_uri(playback, target_type, target_id);
  385. playback->skipms = skipms;
  386. playback->offsetms = offsetms;
  387. ao2_link(playbacks, playback);
  388. playback->state = STASIS_PLAYBACK_STATE_QUEUED;
  389. playback_publish(playback);
  390. stasis_app_send_command_async(control, play_uri, ao2_bump(playback), remove_from_playbacks);
  391. return playback;
  392. }
  393. enum stasis_app_playback_state stasis_app_playback_get_state(
  394. struct stasis_app_playback *control)
  395. {
  396. SCOPED_AO2LOCK(lock, control);
  397. return control->state;
  398. }
  399. const char *stasis_app_playback_get_id(
  400. struct stasis_app_playback *control)
  401. {
  402. /* id is immutable; no lock needed */
  403. return control->id;
  404. }
  405. struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
  406. {
  407. return ao2_find(playbacks, id, OBJ_KEY);
  408. }
  409. struct ast_json *stasis_app_playback_to_json(
  410. const struct stasis_app_playback *playback)
  411. {
  412. RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
  413. if (playback == NULL) {
  414. return NULL;
  415. }
  416. json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
  417. "id", playback->id,
  418. "media_uri", playback->media,
  419. "target_uri", playback->target,
  420. "language", playback->language,
  421. "state", state_to_string(playback->state));
  422. return ast_json_ref(json);
  423. }
  424. typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
  425. static int playback_noop(struct stasis_app_playback *playback)
  426. {
  427. return 0;
  428. }
  429. static int playback_cancel(struct stasis_app_playback *playback)
  430. {
  431. SCOPED_AO2LOCK(lock, playback);
  432. playback->state = STASIS_PLAYBACK_STATE_CANCELED;
  433. return 0;
  434. }
  435. static int playback_stop(struct stasis_app_playback *playback)
  436. {
  437. SCOPED_AO2LOCK(lock, playback);
  438. if (!playback->controllable) {
  439. return -1;
  440. }
  441. playback->state = STASIS_PLAYBACK_STATE_STOPPED;
  442. return stasis_app_control_queue_control(playback->control,
  443. AST_CONTROL_STREAM_STOP);
  444. }
  445. static int playback_restart(struct stasis_app_playback *playback)
  446. {
  447. SCOPED_AO2LOCK(lock, playback);
  448. if (!playback->controllable) {
  449. return -1;
  450. }
  451. return stasis_app_control_queue_control(playback->control,
  452. AST_CONTROL_STREAM_RESTART);
  453. }
  454. static int playback_pause(struct stasis_app_playback *playback)
  455. {
  456. SCOPED_AO2LOCK(lock, playback);
  457. if (!playback->controllable) {
  458. return -1;
  459. }
  460. playback->state = STASIS_PLAYBACK_STATE_PAUSED;
  461. playback_publish(playback);
  462. return stasis_app_control_queue_control(playback->control,
  463. AST_CONTROL_STREAM_SUSPEND);
  464. }
  465. static int playback_unpause(struct stasis_app_playback *playback)
  466. {
  467. SCOPED_AO2LOCK(lock, playback);
  468. if (!playback->controllable) {
  469. return -1;
  470. }
  471. playback->state = STASIS_PLAYBACK_STATE_PLAYING;
  472. playback_publish(playback);
  473. return stasis_app_control_queue_control(playback->control,
  474. AST_CONTROL_STREAM_SUSPEND);
  475. }
  476. static int playback_reverse(struct stasis_app_playback *playback)
  477. {
  478. SCOPED_AO2LOCK(lock, playback);
  479. if (!playback->controllable) {
  480. return -1;
  481. }
  482. return stasis_app_control_queue_control(playback->control,
  483. AST_CONTROL_STREAM_REVERSE);
  484. }
  485. static int playback_forward(struct stasis_app_playback *playback)
  486. {
  487. SCOPED_AO2LOCK(lock, playback);
  488. if (!playback->controllable) {
  489. return -1;
  490. }
  491. return stasis_app_control_queue_control(playback->control,
  492. AST_CONTROL_STREAM_FORWARD);
  493. }
  494. /*!
  495. * \brief A sparse array detailing how commands should be handled in the
  496. * various playback states. Unset entries imply invalid operations.
  497. */
  498. playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
  499. [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
  500. [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
  501. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
  502. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
  503. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
  504. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
  505. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
  506. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
  507. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
  508. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
  509. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
  510. [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
  511. [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
  512. [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
  513. };
  514. enum stasis_playback_oper_results stasis_app_playback_operation(
  515. struct stasis_app_playback *playback,
  516. enum stasis_app_playback_media_operation operation)
  517. {
  518. playback_opreation_cb cb;
  519. SCOPED_AO2LOCK(lock, playback);
  520. ast_assert(playback->state >= 0 && playback->state < STASIS_PLAYBACK_STATE_MAX);
  521. if (operation < 0 || operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
  522. ast_log(LOG_ERROR, "Invalid playback operation %u\n", operation);
  523. return -1;
  524. }
  525. cb = operations[playback->state][operation];
  526. if (!cb) {
  527. if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
  528. /* So we can be specific in our error message. */
  529. return STASIS_PLAYBACK_OPER_NOT_PLAYING;
  530. } else {
  531. /* And, really, all operations should be valid during
  532. * playback */
  533. ast_log(LOG_ERROR,
  534. "Unhandled operation during playback: %u\n",
  535. operation);
  536. return STASIS_PLAYBACK_OPER_FAILED;
  537. }
  538. }
  539. return cb(playback) ?
  540. STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
  541. }
  542. static int load_module(void)
  543. {
  544. int r;
  545. r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
  546. if (r != 0) {
  547. return AST_MODULE_LOAD_FAILURE;
  548. }
  549. playbacks = ao2_container_alloc(PLAYBACK_BUCKETS, playback_hash,
  550. playback_cmp);
  551. if (!playbacks) {
  552. return AST_MODULE_LOAD_FAILURE;
  553. }
  554. return AST_MODULE_LOAD_SUCCESS;
  555. }
  556. static int unload_module(void)
  557. {
  558. ao2_cleanup(playbacks);
  559. playbacks = NULL;
  560. STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
  561. return 0;
  562. }
  563. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
  564. .support_level = AST_MODULE_SUPPORT_CORE,
  565. .load = load_module,
  566. .unload = unload_module,
  567. .nonoptreq = "res_stasis,res_stasis_recording");