PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/servers/media/controls/mgcp/src/main/java/org/mobicents/media/server/mgcp/pkg/au/PlayRecord.java

http://mobicents.googlecode.com/
Java | 507 lines | 291 code | 85 blank | 131 comment | 40 complexity | 153dc58aa704ce9e45fc67ab5f6d87a6 MD5 | raw file
Possible License(s): LGPL-3.0, GPL-3.0, LGPL-2.1, GPL-2.0, CC-BY-SA-3.0, CC0-1.0, Apache-2.0, BSD-3-Clause
  1. /*
  2. * JBoss, Home of Professional Open Source
  3. * Copyright 2011, Red Hat, Inc. and individual contributors
  4. * by the @authors tag. See the copyright.txt in the distribution for a
  5. * full listing of individual contributors.
  6. *
  7. * This is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU Lesser General Public License as
  9. * published by the Free Software Foundation; either version 2.1 of
  10. * the License, or (at your option) any later version.
  11. *
  12. * This software is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this software; if not, write to the Free
  19. * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20. * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  21. */
  22. package org.mobicents.media.server.mgcp.pkg.au;
  23. import java.util.Iterator;
  24. import org.apache.log4j.Logger;
  25. import org.mobicents.media.server.mgcp.controller.signal.Event;
  26. import org.mobicents.media.server.mgcp.controller.signal.NotifyImmediately;
  27. import org.mobicents.media.server.mgcp.controller.signal.Signal;
  28. import org.mobicents.media.server.spi.Connection;
  29. import org.mobicents.media.server.spi.Endpoint;
  30. import org.mobicents.media.server.spi.MediaType;
  31. import org.mobicents.media.server.spi.dtmf.DtmfDetector;
  32. import org.mobicents.media.server.spi.listener.TooManyListenersException;
  33. import org.mobicents.media.server.spi.player.Player;
  34. import org.mobicents.media.server.spi.player.PlayerEvent;
  35. import org.mobicents.media.server.spi.player.PlayerListener;
  36. import org.mobicents.media.server.spi.recorder.Recorder;
  37. import org.mobicents.media.server.spi.recorder.RecorderEvent;
  38. import org.mobicents.media.server.spi.recorder.RecorderListener;
  39. import org.mobicents.media.server.utils.Text;
  40. /**
  41. * Implements play announcement signal.
  42. *
  43. * Plays a prompt and collects DTMF digits entered by a user. If no
  44. * digits are entered or an invalid digit pattern is entered, the
  45. * user may be reprompted and given another chance to enter a correct
  46. * pattern of digits. The following digits are supported: 0-9, *,
  47. * #, A, B, C, D. By default PlayCollect does not play an initial
  48. * prompt, makes only one attempt to collect digits, and therefore
  49. * functions as a simple Collect operation. Various special purpose
  50. * keys, key sequences, and key sets can be defined for use during
  51. * the PlayCollect operation.
  52. *
  53. *
  54. * @author kulikov
  55. * @author Laurent Schweizer
  56. */
  57. public class PlayRecord extends Signal {
  58. private Event oc = new Event(new Text("oc"));
  59. private Event of = new Event(new Text("of"));
  60. private volatile boolean isActive;
  61. private Player player;
  62. private Recorder recorder;
  63. private DtmfDetector dtmfDetector;
  64. private Options options;
  65. private EventBuffer buffer = new EventBuffer();
  66. private RecordingHandler recordingHandler;
  67. private DtmfHandler dtmfHandler;
  68. private PromptHandler promptHandler;
  69. private volatile boolean isPromptActive;
  70. private Iterator<Text> prompt;
  71. private int segCount;
  72. private final static Logger logger = Logger.getLogger(PlayRecord.class);
  73. public PlayRecord(String name) {
  74. super(name);
  75. oc.add(new NotifyImmediately("N"));
  76. of.add(new NotifyImmediately("N"));
  77. //create recorder listener
  78. recordingHandler = new RecordingHandler(this);
  79. dtmfHandler = new DtmfHandler(this);
  80. promptHandler = new PromptHandler(this);
  81. }
  82. @Override
  83. public void execute() {
  84. segCount = 0;
  85. //set flag active signal
  86. this.isActive = true;
  87. //get options of the request
  88. options = new Options(getTrigger().getParams());
  89. //start digits collect phase
  90. logger.info(String.format("(%s) Prepare digit collect phase", getEndpoint().getLocalName()));
  91. //Initializes resources for DTMF detection
  92. //at this stage DTMF detector started but local buffer is not assigned
  93. //yet as listener
  94. prepareCollectPhase(options);
  95. //if initial prompt has been specified then start with prompt phase
  96. if (options.hasPrompt()) {
  97. logger.info(String.format("(%s) Start prompt phase", getEndpoint().getLocalName()));
  98. this.isPromptActive = true;
  99. startPromptPhase(options);
  100. return;
  101. }
  102. //flush DTMF detector buffer and start collect phase
  103. logger.info(String.format("(%s) Start collect phase", getEndpoint().getLocalName()));
  104. flushBuffer();
  105. //now all buffered digits must be inside local buffer
  106. startCollectPhase();
  107. logger.info(String.format("(%s) Start record phase", getEndpoint().getLocalName()));
  108. startRecordPhase(options);
  109. }
  110. @Override
  111. public boolean doAccept(Text event) {
  112. if (!oc.isActive() && oc.matches(event)) {
  113. return true;
  114. }
  115. if (!of.isActive() && of.matches(event)) {
  116. return true;
  117. }
  118. return false;
  119. }
  120. @Override
  121. public void cancel() {
  122. //disable signal activity and terminate
  123. this.isActive = false;
  124. this.terminate();
  125. }
  126. /**
  127. * Starts the prompt phase.
  128. *
  129. * @param options requested options.
  130. */
  131. private void startPromptPhase(Options options) {
  132. player = this.getPlayer();
  133. try {
  134. //assign listener
  135. player.addListener(promptHandler);
  136. //specify URL to play
  137. prompt = options.getPrompt().iterator();
  138. player.setURL(prompt.next().toString());
  139. //start playback
  140. player.start();
  141. } catch (TooManyListenersException e) {
  142. of.fire(this, new Text("Too many listeners"));
  143. } catch (Exception e) {
  144. of.fire(this, new Text(e.getMessage()));
  145. }
  146. }
  147. /**
  148. * Terminates prompt phase if it was started or do nothing otherwise.
  149. */
  150. private void terminatePrompt() {
  151. //jump to end of segments
  152. if (prompt != null) {
  153. while (prompt.hasNext()) {
  154. prompt.next();
  155. }
  156. }
  157. //stop current segment
  158. if (player != null) {
  159. player.stop();
  160. player.removeListener(promptHandler);
  161. }
  162. }
  163. /**
  164. * Starts record phase.
  165. *
  166. * @param options requested options
  167. */
  168. private void startRecordPhase(Options options) {
  169. recorder = getRecorder();
  170. //assign record duration
  171. recorder.setMaxRecordTime(options.getRecordDuration());
  172. //post speech timer
  173. recorder.setPostSpeechTimer(options.getPostSpeechTimer());
  174. try {
  175. recorder.addListener(recordingHandler);
  176. recorder.setRecordFile(options.getRecordID().toString(), !options.isOverride());
  177. recorder.start();
  178. } catch (Exception e) {
  179. of.fire(this, new Text(e.getMessage()));
  180. }
  181. }
  182. /**
  183. * Terminates prompt phase if it was started or do nothing otherwise.
  184. */
  185. private void terminateRecordPhase() {
  186. if (recorder != null) {
  187. recorder.stop();
  188. recorder.removeListener(recordingHandler);
  189. }
  190. }
  191. /**
  192. * Prepares resources for DTMF collection phase.
  193. *
  194. * @param options
  195. */
  196. private void prepareCollectPhase(Options options) {
  197. //obtain detector instance
  198. dtmfDetector = this.getDetector();
  199. //DTMF detector was buffering digits and now it can contain
  200. //digits in the buffer
  201. //Clean detector's buffer if requested
  202. if (options.isClearDigits()) {
  203. dtmfDetector.clearDigits();
  204. }
  205. //clear local buffer
  206. buffer.clear();
  207. buffer.setListener(dtmfHandler);
  208. //assign requested parameters
  209. buffer.setPatterns(options.getDigitPattern());
  210. buffer.setCount(options.getDigitsNumber());
  211. }
  212. /**
  213. * Terminates digit collect phase.
  214. */
  215. private void terminateCollectPhase() {
  216. if (dtmfDetector != null) {
  217. dtmfDetector.removeListener(buffer);
  218. // dtmfDetector.clearDigits();
  219. buffer.passivate();
  220. buffer.clear();
  221. }
  222. }
  223. /**
  224. * Flushes DTMF buffer content to local buffer
  225. */
  226. private void flushBuffer() {
  227. try {
  228. //attach local buffer to DTMF detector
  229. //but do not flush
  230. dtmfDetector.addListener(buffer);
  231. dtmfDetector.flushBuffer();
  232. } catch (TooManyListenersException e) {
  233. of.fire(this, new Text("Too many listeners for DTMF detector"));
  234. }
  235. }
  236. private void startCollectPhase() {
  237. buffer.activate();
  238. buffer.flush();
  239. }
  240. /**
  241. * Terminates any activity.
  242. */
  243. private void terminate() {
  244. this.terminatePrompt();
  245. this.terminateRecordPhase();
  246. this.terminateCollectPhase();
  247. }
  248. private Player getPlayer() {
  249. if (getTrigger().getConnectionID() == null) {
  250. Endpoint endpoint = getEndpoint();
  251. return (Player) getEndpoint().getResource(MediaType.AUDIO, Player.class);
  252. }
  253. String connectionID = getTrigger().getConnectionID().toString();
  254. Connection connection = getConnection(connectionID);
  255. if (connection == null) {
  256. return null;
  257. }
  258. return null;
  259. }
  260. private Recorder getRecorder() {
  261. if (getTrigger().getConnectionID() == null) {
  262. Endpoint endpoint = getEndpoint();
  263. return (Recorder) getEndpoint().getResource(MediaType.AUDIO, Recorder.class);
  264. }
  265. String connectionID = getTrigger().getConnectionID().toString();
  266. Connection connection = getConnection(connectionID);
  267. if (connection == null) {
  268. return null;
  269. }
  270. return null;
  271. }
  272. private DtmfDetector getDetector() {
  273. if (getTrigger().getConnectionID() == null) {
  274. Endpoint endpoint = getEndpoint();
  275. return (DtmfDetector) getEndpoint().getResource(MediaType.AUDIO, DtmfDetector.class);
  276. }
  277. String connectionID = getTrigger().getConnectionID().toString();
  278. Connection connection = getConnection(connectionID);
  279. if (connection == null) {
  280. return null;
  281. }
  282. return null;
  283. }
  284. @Override
  285. public void reset() {
  286. super.reset();
  287. this.terminate();
  288. oc.reset();
  289. of.reset();
  290. }
  291. private void next(long delay) {
  292. try {
  293. segCount++;
  294. player.setURL(prompt.next().toString());
  295. player.setInitialDelay(delay * 1000000L);
  296. //start playback
  297. player.start();
  298. } catch (Exception e) {
  299. of.fire(this, new Text(e.getMessage()));
  300. }
  301. }
  302. /**
  303. * Handler for prompt phase.
  304. */
  305. private class PromptHandler implements PlayerListener {
  306. private PlayRecord signal;
  307. /**
  308. * Creates new handler instance.
  309. *
  310. * @param signal the play record signal instance
  311. */
  312. protected PromptHandler(PlayRecord signal) {
  313. this.signal = signal;
  314. }
  315. public void process(PlayerEvent event) {
  316. switch (event.getID()) {
  317. case PlayerEvent.START :
  318. if (segCount == 0) {
  319. flushBuffer();
  320. }
  321. break;
  322. case PlayerEvent.STOP :
  323. if (prompt.hasNext()) {
  324. next(options.getInterval());
  325. return;
  326. }
  327. //start collect phase when prompted has finished
  328. if (isPromptActive) {
  329. isPromptActive = false;
  330. logger.info(String.format("(%s) Prompt phase terminated, start collect/record phase", getEndpoint().getLocalName()));
  331. startCollectPhase();
  332. logger.info(String.format("(%s) Start record phase", getEndpoint().getLocalName()));
  333. startRecordPhase(options);
  334. }
  335. break;
  336. case PlayerEvent.FAILED :
  337. of.fire(signal, null);
  338. complete();
  339. break;
  340. }
  341. }
  342. }
  343. /**
  344. * Handler for recorder events
  345. */
  346. private class RecordingHandler implements RecorderListener {
  347. private PlayRecord signal;
  348. protected RecordingHandler(PlayRecord signal) {
  349. this.signal = signal;
  350. }
  351. public void process(RecorderEvent event) {
  352. switch (event.getID()) {
  353. case RecorderEvent.STOP :
  354. switch (event.getQualifier()) {
  355. case RecorderEvent.MAX_DURATION_EXCEEDED :
  356. oc.fire(signal, new Text("rc=328"));
  357. break;
  358. case RecorderEvent.NO_SPEECH :
  359. oc.fire(signal, new Text("rc=327"));
  360. break;
  361. }
  362. break;
  363. }
  364. }
  365. }
  366. /**
  367. * Handler for digit collect phase.
  368. *
  369. */
  370. private class DtmfHandler implements BufferListener {
  371. private PlayRecord signal;
  372. /**
  373. * Constructor for this handler.
  374. * @param signal
  375. */
  376. public DtmfHandler(PlayRecord signal) {
  377. this.signal = signal;
  378. }
  379. /**
  380. * (Non Java-doc.)
  381. *
  382. * @see BufferListener#patternMatches(int, java.lang.String)
  383. */
  384. public void patternMatches(int index, String s) {
  385. oc.fire(signal, new Text("rc=100 dc=" + s + " pi=" + index));
  386. reset();
  387. complete();
  388. }
  389. /**
  390. * (Non Java-doc.)
  391. *
  392. * @see BufferListener#countMatches(java.lang.String)
  393. */
  394. public void countMatches(String s) {
  395. oc.fire(signal, new Text("rc=100 dc=" + s));
  396. reset();
  397. complete();
  398. }
  399. /**
  400. * (Non Java-doc.)
  401. *
  402. * @see BufferListener#tone(java.lang.String)
  403. */
  404. public void tone(String s) {
  405. logger.info(String.format("(%s) Tone '%s' has been detected", getEndpoint().getLocalName(), s));
  406. if (!options.isNonInterruptable()) {
  407. if (isPromptActive) {
  408. logger.info(String.format("(%s) Tone '%s' has been detected: prompt phase interrupted", getEndpoint().getLocalName(), s));
  409. terminatePrompt();
  410. } else {
  411. logger.info(String.format("(%s) Tone '%s' has been detected: collected", getEndpoint().getLocalName(), s));
  412. }
  413. } else {
  414. if (isPromptActive) {
  415. logger.info(String.format("(%s) Tone '%s' has been detected, waiting for prompt phase termination", getEndpoint().getLocalName(), s));
  416. } else {
  417. logger.info(String.format("(%s) Tone '%s' has been detected: collected", getEndpoint().getLocalName(), s));
  418. }
  419. }
  420. }
  421. }
  422. }