res_stasis_playback.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  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", ast_json_deep_copy(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. ao2_cleanup(playback->control);
  106. ast_string_field_free_memory(playback);
  107. }
  108. static struct stasis_app_playback *playback_create(
  109. struct stasis_app_control *control, const char *id)
  110. {
  111. RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
  112. char uuid[AST_UUID_STR_LEN];
  113. if (!control) {
  114. return NULL;
  115. }
  116. playback = ao2_alloc(sizeof(*playback), playback_dtor);
  117. if (!playback || ast_string_field_init(playback, 128)) {
  118. return NULL;
  119. }
  120. if (!ast_strlen_zero(id)) {
  121. ast_string_field_set(playback, id, id);
  122. } else {
  123. ast_uuid_generate_str(uuid, sizeof(uuid));
  124. ast_string_field_set(playback, id, uuid);
  125. }
  126. ao2_ref(control, +1);
  127. playback->control = control;
  128. ao2_ref(playback, +1);
  129. return playback;
  130. }
  131. static int playback_hash(const void *obj, int flags)
  132. {
  133. const struct stasis_app_playback *playback = obj;
  134. const char *id = flags & OBJ_KEY ? obj : playback->id;
  135. return ast_str_hash(id);
  136. }
  137. static int playback_cmp(void *obj, void *arg, int flags)
  138. {
  139. struct stasis_app_playback *lhs = obj;
  140. struct stasis_app_playback *rhs = arg;
  141. const char *rhs_id = flags & OBJ_KEY ? arg : rhs->id;
  142. if (strcmp(lhs->id, rhs_id) == 0) {
  143. return CMP_MATCH | CMP_STOP;
  144. } else {
  145. return 0;
  146. }
  147. }
  148. static const char *state_to_string(enum stasis_app_playback_state state)
  149. {
  150. switch (state) {
  151. case STASIS_PLAYBACK_STATE_QUEUED:
  152. return "queued";
  153. case STASIS_PLAYBACK_STATE_PLAYING:
  154. return "playing";
  155. case STASIS_PLAYBACK_STATE_PAUSED:
  156. return "paused";
  157. case STASIS_PLAYBACK_STATE_STOPPED:
  158. case STASIS_PLAYBACK_STATE_COMPLETE:
  159. case STASIS_PLAYBACK_STATE_CANCELED:
  160. /* It doesn't really matter how we got here, but all of these
  161. * states really just mean 'done' */
  162. return "done";
  163. case STASIS_PLAYBACK_STATE_MAX:
  164. break;
  165. }
  166. return "?";
  167. }
  168. static void playback_publish(struct stasis_app_playback *playback)
  169. {
  170. RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
  171. RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
  172. ast_assert(playback != NULL);
  173. json = stasis_app_playback_to_json(playback);
  174. if (json == NULL) {
  175. return;
  176. }
  177. message = ast_channel_blob_create_from_cache(
  178. stasis_app_control_get_channel_id(playback->control),
  179. stasis_app_playback_snapshot_type(), json);
  180. if (message == NULL) {
  181. return;
  182. }
  183. stasis_app_control_publish(playback->control, message);
  184. }
  185. static int playback_first_update(struct stasis_app_playback *playback,
  186. const char *uniqueid)
  187. {
  188. int res;
  189. SCOPED_AO2LOCK(lock, playback);
  190. if (playback->state == STASIS_PLAYBACK_STATE_CANCELED) {
  191. ast_log(LOG_NOTICE, "%s: Playback canceled for %s\n",
  192. uniqueid, playback->media);
  193. res = -1;
  194. } else {
  195. res = 0;
  196. playback->state = STASIS_PLAYBACK_STATE_PLAYING;
  197. }
  198. playback_publish(playback);
  199. return res;
  200. }
  201. static void playback_final_update(struct stasis_app_playback *playback,
  202. long playedms, int res, const char *uniqueid)
  203. {
  204. SCOPED_AO2LOCK(lock, playback);
  205. playback->playedms = playedms;
  206. if (res == 0) {
  207. playback->state = STASIS_PLAYBACK_STATE_COMPLETE;
  208. } else {
  209. if (playback->state == STASIS_PLAYBACK_STATE_STOPPED) {
  210. ast_log(LOG_NOTICE, "%s: Playback stopped for %s\n",
  211. uniqueid, playback->media);
  212. } else {
  213. ast_log(LOG_WARNING, "%s: Playback failed for %s\n",
  214. uniqueid, playback->media);
  215. playback->state = STASIS_PLAYBACK_STATE_STOPPED;
  216. }
  217. }
  218. playback_publish(playback);
  219. }
  220. static void play_on_channel(struct stasis_app_playback *playback,
  221. struct ast_channel *chan)
  222. {
  223. int res;
  224. long offsetms;
  225. /* Even though these local variables look fairly pointless, the avoid
  226. * having a bunch of NULL's passed directly into
  227. * ast_control_streamfile() */
  228. const char *fwd = NULL;
  229. const char *rev = NULL;
  230. const char *stop = NULL;
  231. const char *pause = NULL;
  232. const char *restart = NULL;
  233. ast_assert(playback != NULL);
  234. offsetms = playback->offsetms;
  235. res = playback_first_update(playback, ast_channel_uniqueid(chan));
  236. if (res != 0) {
  237. return;
  238. }
  239. if (ast_channel_state(chan) != AST_STATE_UP) {
  240. ast_indicate(chan, AST_CONTROL_PROGRESS);
  241. }
  242. if (ast_begins_with(playback->media, SOUND_URI_SCHEME)) {
  243. playback->controllable = 1;
  244. /* Play sound */
  245. res = ast_control_streamfile_lang(chan, playback->media + strlen(SOUND_URI_SCHEME),
  246. fwd, rev, stop, pause, restart, playback->skipms, playback->language,
  247. &offsetms);
  248. } else if (ast_begins_with(playback->media, RECORDING_URI_SCHEME)) {
  249. /* Play recording */
  250. RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
  251. ao2_cleanup);
  252. const char *relname =
  253. playback->media + strlen(RECORDING_URI_SCHEME);
  254. recording = stasis_app_stored_recording_find_by_name(relname);
  255. if (!recording) {
  256. ast_log(LOG_ERROR, "Attempted to play recording '%s' on channel '%s' but recording does not exist",
  257. relname, ast_channel_name(chan));
  258. return;
  259. }
  260. playback->controllable = 1;
  261. res = ast_control_streamfile_lang(chan,
  262. stasis_app_stored_recording_get_file(recording), fwd, rev, stop, pause,
  263. restart, playback->skipms, playback->language, &offsetms);
  264. } else if (ast_begins_with(playback->media, NUMBER_URI_SCHEME)) {
  265. int number;
  266. if (sscanf(playback->media + strlen(NUMBER_URI_SCHEME), "%30d", &number) != 1) {
  267. ast_log(LOG_ERROR, "Attempted to play number '%s' on channel '%s' but number is invalid",
  268. playback->media + strlen(NUMBER_URI_SCHEME), ast_channel_name(chan));
  269. return;
  270. }
  271. res = ast_say_number(chan, number, stop, playback->language, NULL);
  272. } else if (ast_begins_with(playback->media, DIGITS_URI_SCHEME)) {
  273. res = ast_say_digit_str(chan, playback->media + strlen(DIGITS_URI_SCHEME),
  274. stop, playback->language);
  275. } else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
  276. res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
  277. stop, playback->language, AST_SAY_CASE_NONE);
  278. } else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
  279. playback->controllable = 1;
  280. res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
  281. } else {
  282. /* Play URL */
  283. ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported\n",
  284. playback->media, ast_channel_name(chan));
  285. return;
  286. }
  287. playback_final_update(playback, offsetms, res,
  288. ast_channel_uniqueid(chan));
  289. return;
  290. }
  291. /*!
  292. * \brief Special case code to play while a channel is in a bridge.
  293. *
  294. * \param bridge_channel The channel's bridge_channel.
  295. * \param playback_id Id of the playback to start.
  296. */
  297. static void play_on_channel_in_bridge(struct ast_bridge_channel *bridge_channel,
  298. const char *playback_id)
  299. {
  300. RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
  301. playback = stasis_app_playback_find_by_id(playback_id);
  302. if (!playback) {
  303. ast_log(LOG_ERROR, "Couldn't find playback %s\n",
  304. playback_id);
  305. return;
  306. }
  307. play_on_channel(playback, bridge_channel->chan);
  308. }
  309. /*!
  310. * \brief \ref RAII_VAR function to remove a playback from the global list when
  311. * leaving scope.
  312. */
  313. static void remove_from_playbacks(void *data)
  314. {
  315. struct stasis_app_playback *playback = data;
  316. ao2_unlink_flags(playbacks, playback,
  317. OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
  318. ao2_ref(playback, -1);
  319. }
  320. static int play_uri(struct stasis_app_control *control,
  321. struct ast_channel *chan, void *data)
  322. {
  323. struct stasis_app_playback *playback = data;
  324. struct ast_bridge *bridge;
  325. if (!control) {
  326. return -1;
  327. }
  328. bridge = stasis_app_get_bridge(control);
  329. if (bridge) {
  330. struct ast_bridge_channel *bridge_chan;
  331. /* Queue up playback on the bridge */
  332. ast_bridge_lock(bridge);
  333. bridge_chan = ao2_bump(bridge_find_channel(bridge, chan));
  334. ast_bridge_unlock(bridge);
  335. if (bridge_chan) {
  336. ast_bridge_channel_queue_playfile_sync(
  337. bridge_chan,
  338. play_on_channel_in_bridge,
  339. playback->id,
  340. NULL); /* moh_class */
  341. }
  342. ao2_cleanup(bridge_chan);
  343. } else {
  344. play_on_channel(playback, chan);
  345. }
  346. return 0;
  347. }
  348. static void set_target_uri(
  349. struct stasis_app_playback *playback,
  350. enum stasis_app_playback_target_type target_type,
  351. const char *target_id)
  352. {
  353. const char *type = NULL;
  354. switch (target_type) {
  355. case STASIS_PLAYBACK_TARGET_CHANNEL:
  356. type = "channel";
  357. break;
  358. case STASIS_PLAYBACK_TARGET_BRIDGE:
  359. type = "bridge";
  360. break;
  361. }
  362. ast_assert(type != NULL);
  363. ast_string_field_build(playback, target, "%s:%s", type, target_id);
  364. }
  365. struct stasis_app_playback *stasis_app_control_play_uri(
  366. struct stasis_app_control *control, const char *uri,
  367. const char *language, const char *target_id,
  368. enum stasis_app_playback_target_type target_type,
  369. int skipms, long offsetms, const char *id)
  370. {
  371. struct stasis_app_playback *playback;
  372. if (skipms < 0 || offsetms < 0) {
  373. return NULL;
  374. }
  375. ast_debug(3, "%s: Sending play(%s) command\n",
  376. stasis_app_control_get_channel_id(control), uri);
  377. playback = playback_create(control, id);
  378. if (!playback) {
  379. return NULL;
  380. }
  381. if (skipms == 0) {
  382. skipms = PLAYBACK_DEFAULT_SKIPMS;
  383. }
  384. ast_string_field_set(playback, media, uri);
  385. ast_string_field_set(playback, language, language);
  386. set_target_uri(playback, target_type, target_id);
  387. playback->skipms = skipms;
  388. playback->offsetms = offsetms;
  389. ao2_link(playbacks, playback);
  390. playback->state = STASIS_PLAYBACK_STATE_QUEUED;
  391. playback_publish(playback);
  392. stasis_app_send_command_async(control, play_uri, ao2_bump(playback), remove_from_playbacks);
  393. return playback;
  394. }
  395. enum stasis_app_playback_state stasis_app_playback_get_state(
  396. struct stasis_app_playback *control)
  397. {
  398. SCOPED_AO2LOCK(lock, control);
  399. return control->state;
  400. }
  401. const char *stasis_app_playback_get_id(
  402. struct stasis_app_playback *control)
  403. {
  404. /* id is immutable; no lock needed */
  405. return control->id;
  406. }
  407. struct stasis_app_playback *stasis_app_playback_find_by_id(const char *id)
  408. {
  409. return ao2_find(playbacks, id, OBJ_KEY);
  410. }
  411. struct ast_json *stasis_app_playback_to_json(
  412. const struct stasis_app_playback *playback)
  413. {
  414. RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
  415. if (playback == NULL) {
  416. return NULL;
  417. }
  418. json = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
  419. "id", playback->id,
  420. "media_uri", playback->media,
  421. "target_uri", playback->target,
  422. "language", playback->language,
  423. "state", state_to_string(playback->state));
  424. return ast_json_ref(json);
  425. }
  426. typedef int (*playback_opreation_cb)(struct stasis_app_playback *playback);
  427. static int playback_noop(struct stasis_app_playback *playback)
  428. {
  429. return 0;
  430. }
  431. static int playback_cancel(struct stasis_app_playback *playback)
  432. {
  433. SCOPED_AO2LOCK(lock, playback);
  434. playback->state = STASIS_PLAYBACK_STATE_CANCELED;
  435. return 0;
  436. }
  437. static int playback_stop(struct stasis_app_playback *playback)
  438. {
  439. SCOPED_AO2LOCK(lock, playback);
  440. if (!playback->controllable) {
  441. return -1;
  442. }
  443. playback->state = STASIS_PLAYBACK_STATE_STOPPED;
  444. return stasis_app_control_queue_control(playback->control,
  445. AST_CONTROL_STREAM_STOP);
  446. }
  447. static int playback_restart(struct stasis_app_playback *playback)
  448. {
  449. SCOPED_AO2LOCK(lock, playback);
  450. if (!playback->controllable) {
  451. return -1;
  452. }
  453. return stasis_app_control_queue_control(playback->control,
  454. AST_CONTROL_STREAM_RESTART);
  455. }
  456. static int playback_pause(struct stasis_app_playback *playback)
  457. {
  458. SCOPED_AO2LOCK(lock, playback);
  459. if (!playback->controllable) {
  460. return -1;
  461. }
  462. playback->state = STASIS_PLAYBACK_STATE_PAUSED;
  463. playback_publish(playback);
  464. return stasis_app_control_queue_control(playback->control,
  465. AST_CONTROL_STREAM_SUSPEND);
  466. }
  467. static int playback_unpause(struct stasis_app_playback *playback)
  468. {
  469. SCOPED_AO2LOCK(lock, playback);
  470. if (!playback->controllable) {
  471. return -1;
  472. }
  473. playback->state = STASIS_PLAYBACK_STATE_PLAYING;
  474. playback_publish(playback);
  475. return stasis_app_control_queue_control(playback->control,
  476. AST_CONTROL_STREAM_SUSPEND);
  477. }
  478. static int playback_reverse(struct stasis_app_playback *playback)
  479. {
  480. SCOPED_AO2LOCK(lock, playback);
  481. if (!playback->controllable) {
  482. return -1;
  483. }
  484. return stasis_app_control_queue_control(playback->control,
  485. AST_CONTROL_STREAM_REVERSE);
  486. }
  487. static int playback_forward(struct stasis_app_playback *playback)
  488. {
  489. SCOPED_AO2LOCK(lock, playback);
  490. if (!playback->controllable) {
  491. return -1;
  492. }
  493. return stasis_app_control_queue_control(playback->control,
  494. AST_CONTROL_STREAM_FORWARD);
  495. }
  496. /*!
  497. * \brief A sparse array detailing how commands should be handled in the
  498. * various playback states. Unset entries imply invalid operations.
  499. */
  500. playback_opreation_cb operations[STASIS_PLAYBACK_STATE_MAX][STASIS_PLAYBACK_MEDIA_OP_MAX] = {
  501. [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_STOP] = playback_cancel,
  502. [STASIS_PLAYBACK_STATE_QUEUED][STASIS_PLAYBACK_RESTART] = playback_noop,
  503. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_STOP] = playback_stop,
  504. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_RESTART] = playback_restart,
  505. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_PAUSE] = playback_pause,
  506. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_UNPAUSE] = playback_noop,
  507. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_REVERSE] = playback_reverse,
  508. [STASIS_PLAYBACK_STATE_PLAYING][STASIS_PLAYBACK_FORWARD] = playback_forward,
  509. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_STOP] = playback_stop,
  510. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_PAUSE] = playback_noop,
  511. [STASIS_PLAYBACK_STATE_PAUSED][STASIS_PLAYBACK_UNPAUSE] = playback_unpause,
  512. [STASIS_PLAYBACK_STATE_COMPLETE][STASIS_PLAYBACK_STOP] = playback_noop,
  513. [STASIS_PLAYBACK_STATE_CANCELED][STASIS_PLAYBACK_STOP] = playback_noop,
  514. [STASIS_PLAYBACK_STATE_STOPPED][STASIS_PLAYBACK_STOP] = playback_noop,
  515. };
  516. enum stasis_playback_oper_results stasis_app_playback_operation(
  517. struct stasis_app_playback *playback,
  518. enum stasis_app_playback_media_operation operation)
  519. {
  520. playback_opreation_cb cb;
  521. SCOPED_AO2LOCK(lock, playback);
  522. ast_assert((unsigned int)playback->state < STASIS_PLAYBACK_STATE_MAX);
  523. if (operation >= STASIS_PLAYBACK_MEDIA_OP_MAX) {
  524. ast_log(LOG_ERROR, "Invalid playback operation %u\n", operation);
  525. return -1;
  526. }
  527. cb = operations[playback->state][operation];
  528. if (!cb) {
  529. if (playback->state != STASIS_PLAYBACK_STATE_PLAYING) {
  530. /* So we can be specific in our error message. */
  531. return STASIS_PLAYBACK_OPER_NOT_PLAYING;
  532. } else {
  533. /* And, really, all operations should be valid during
  534. * playback */
  535. ast_log(LOG_ERROR,
  536. "Unhandled operation during playback: %u\n",
  537. operation);
  538. return STASIS_PLAYBACK_OPER_FAILED;
  539. }
  540. }
  541. return cb(playback) ?
  542. STASIS_PLAYBACK_OPER_FAILED : STASIS_PLAYBACK_OPER_OK;
  543. }
  544. static int load_module(void)
  545. {
  546. int r;
  547. r = STASIS_MESSAGE_TYPE_INIT(stasis_app_playback_snapshot_type);
  548. if (r != 0) {
  549. return AST_MODULE_LOAD_DECLINE;
  550. }
  551. playbacks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, PLAYBACK_BUCKETS,
  552. playback_hash, NULL, playback_cmp);
  553. if (!playbacks) {
  554. STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
  555. return AST_MODULE_LOAD_DECLINE;
  556. }
  557. return AST_MODULE_LOAD_SUCCESS;
  558. }
  559. static int unload_module(void)
  560. {
  561. ao2_cleanup(playbacks);
  562. playbacks = NULL;
  563. STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_playback_snapshot_type);
  564. return 0;
  565. }
  566. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application playback support",
  567. .support_level = AST_MODULE_SUPPORT_CORE,
  568. .load = load_module,
  569. .unload = unload_module,
  570. );