package org.restcomm.javax.media.mscontrol.mediagroup; import jain.protocol.ip.mgcp.JainMgcpCommandEvent; import jain.protocol.ip.mgcp.JainMgcpListener; import jain.protocol.ip.mgcp.JainMgcpResponseEvent; import jain.protocol.ip.mgcp.message.Constants; import jain.protocol.ip.mgcp.message.NotificationRequest; import jain.protocol.ip.mgcp.message.Notify; import jain.protocol.ip.mgcp.message.parms.EventName; import jain.protocol.ip.mgcp.message.parms.RequestIdentifier; import jain.protocol.ip.mgcp.message.parms.RequestedAction; import jain.protocol.ip.mgcp.message.parms.RequestedEvent; import jain.protocol.ip.mgcp.message.parms.ReturnCode; import jain.protocol.ip.mgcp.pkg.MgcpEvent; import java.net.URI; import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList; import javax.media.mscontrol.MediaErr; import javax.media.mscontrol.join.JoinException; import javax.media.mscontrol.MediaEvent; import javax.media.mscontrol.MediaEventListener; import javax.media.mscontrol.MediaSession; import javax.media.mscontrol.MsControlException; import javax.media.mscontrol.Parameters; import javax.media.mscontrol.UnsupportedException; import javax.media.mscontrol.join.Joinable.Direction; import javax.media.mscontrol.mediagroup.MediaGroup; import javax.media.mscontrol.mediagroup.Player; import javax.media.mscontrol.mediagroup.Recorder; import javax.media.mscontrol.mediagroup.RecorderEvent; import javax.media.mscontrol.mediagroup.SpeechDetectorConstants; import javax.media.mscontrol.mediagroup.signals.SignalDetector; import javax.media.mscontrol.resource.RTC; import javax.media.mscontrol.resource.Resource; import javax.media.mscontrol.resource.Trigger; import org.restcomm.fsm.FSM; import org.restcomm.fsm.State; import org.restcomm.fsm.StateEventHandler; import org.restcomm.fsm.TransitionHandler; import org.restcomm.fsm.UnknownTransitionException; import org.restcomm.javax.media.mscontrol.mediagroup.signals.Options; import org.restcomm.javax.media.mscontrol.spi.DriverImpl; import org.restcomm.jsr309.mgcp.PackageAU; /** * * @author amit bhayani * @author kulikov * @author Jose Antonio Santos Cadenas */ public class RecorderImpl implements Recorder, JainMgcpListener { public final static String STATE_IDLE = "IDLE"; public final static String STATE_ACTIVATING = "ACTIVATING"; public final static String STATE_ACTIVE = "ACTIVE"; public final static String STATE_PAUSED = "PAUSED"; public final static String STATE_STOPPING = "STOPPING"; public final static String SIGNAL_START = "start"; public final static String SIGNAL_START_PAUSED = "start_paused"; public final static String SIGNAL_STOP = "stop"; public final static String SIGNAL_PAUSE = "pause"; public final static String SIGNAL_RESUME = "resume"; public final static String SIGNAL_SUCCESS = "success"; public final static String SIGNAL_FAILURE = "failure"; public final static String SIGNAL_COMPLETE = "complete"; protected MediaGroupImpl parent = null; private FSM fsm; private String params; protected CopyOnWriteArrayList<MediaEventListener<? extends MediaEvent<?>>> listeners = new CopyOnWriteArrayList<MediaEventListener<? extends MediaEvent<?>>>(); private RecorderEventImpl recorderEvent; private boolean rtcTriggered = false; private ArrayList<Trigger> triggers = new ArrayList(); private MgcpSender mgcpSender; public RecorderImpl(MediaGroupImpl mediaGroup) { this.parent = mediaGroup; mgcpSender=new MgcpSender(); initFSM(); } private void initFSM() { fsm = new FSM(parent.getMediaSession().getDriver().getScheduler()); fsm.createState(STATE_IDLE); fsm.createState(STATE_ACTIVATING); fsm.createState(STATE_ACTIVE); fsm.createState(STATE_PAUSED); fsm.createState(STATE_STOPPING).setOnEnter(new StopRequest()); fsm.setStart(STATE_IDLE); fsm.setEnd(STATE_IDLE); //state IDLE fsm.createTransition(SIGNAL_START, STATE_IDLE, STATE_ACTIVATING).setHandler(new RecordRequest()); fsm.createTransition(SIGNAL_START_PAUSED, STATE_IDLE, STATE_PAUSED); fsm.createTransition(SIGNAL_SUCCESS, STATE_ACTIVATING, STATE_ACTIVE); //server said follow to the hand. fsm.createTransition(SIGNAL_FAILURE, STATE_ACTIVATING, STATE_IDLE).setHandler(new CompleteNotify()); //user has asked to stop player fsm.createTransition(SIGNAL_STOP, STATE_ACTIVATING, STATE_IDLE); //state ACTIVE fsm.createTransition(SIGNAL_STOP, STATE_ACTIVE, STATE_STOPPING); fsm.createTransition(SIGNAL_PAUSE, STATE_ACTIVE, STATE_PAUSED); fsm.createTransition(SIGNAL_COMPLETE, STATE_ACTIVE, STATE_IDLE).setHandler(new CompleteNotify()); fsm.createTransition(SIGNAL_FAILURE, STATE_ACTIVE, STATE_IDLE); //state PAUSED fsm.createTransition(SIGNAL_STOP, STATE_PAUSED, STATE_IDLE); fsm.createTransition(SIGNAL_RESUME, STATE_PAUSED, STATE_ACTIVE); //state STOPPING fsm.createTransition(SIGNAL_SUCCESS, STATE_STOPPING, STATE_IDLE).setHandler(new StoppedNotify()); } public void record(URI streamID, RTC[] rtc, Parameters optargs) throws MsControlException { if (rtc != null) { verifyRTC(rtc); } if (parent.getJoinees(Direction.RECV).length == 0) { throw new JoinException(this.parent.getURI() + " Container is not joined to any other container"); } String[] patterns = this.getPatterns(rtc, optargs); Options options = new Options(); options.setRecordID(streamID.toString()); //patterns if (patterns != null) { options.setDigitPattern(patterns); } //check for max duration if (optargs != null && optargs.containsKey(Recorder.MAX_DURATION)) { options.setRecordDuraion((Integer)optargs.get(Recorder.MAX_DURATION)); } //check for append if (optargs != null && optargs.containsKey(Recorder.APPEND)) { options.setOverride(!(Boolean)optargs.get(Recorder.APPEND)); } //post-speech time if (optargs != null && optargs.containsKey(Recorder.SILENCE_TERMINATION_ON)) { options.setSilenceTermination((Boolean)optargs.get(Recorder.SILENCE_TERMINATION_ON)); } //final timeout if (optargs != null && optargs.containsKey(SpeechDetectorConstants.FINAL_TIMEOUT)) { options.setSilenceTermination(false); if (optargs.get(SpeechDetectorConstants.FINAL_TIMEOUT) != Resource.FOR_EVER) { options.setPostSpeechTimer((Integer)optargs.get(SpeechDetectorConstants.FINAL_TIMEOUT)); } } Boolean hasPrompt=false; //initial prompt if (optargs != null && optargs.containsKey(Recorder.PROMPT)) { hasPrompt=true; options.setPrompt(((URI)optargs.get(Recorder.PROMPT)).toString()); } options.setNonInterruptiblePlay(true); if (rtc != null) { System.out.println("-------------------------------------"); System.out.println("triggers=" + rtc.length); for (int i = 0; i < rtc.length; i++) { System.out.println("Trigger = " + rtc[i].getTrigger()); if (rtc[i] == MediaGroup.SIGDET_STOPPLAY) { options.setNonInterruptiblePlay(false); } else if (rtc[i].getTrigger() == SignalDetector.DETECTION_OF_ONE_SIGNAL) { options.setDigitsNumber(1); if(rtc[i].getAction()==Recorder.STOP && hasPrompt) options.setDigitsNumber(2); } else if (rtc[i].getTrigger() == Player.PLAY_START && rtc[i].getAction() == SignalDetector.FLUSH_BUFFER) { options.setClearDigits(true); } } } params = options.toString(); try { fsm.signal(SIGNAL_START); } catch (UnknownTransitionException e) { throw new MsControlException(e.getMessage()); } } private void verifyRTC(RTC[] rtc) throws UnsupportedException { for (RTC r: rtc ) { if (r.getTrigger() == Player.PLAY_START && r.getAction() == Player.STOP) { throw new UnsupportedException("Invalid RTC"); } } } private String[] getPatterns(RTC[] rtc, Parameters options) { if (rtc == null || options == null) { return null; } ArrayList<String> list = new ArrayList(); for (RTC r : rtc) { for (int i = 0; i < SignalDetector.PATTERN_MATCH.length; i++) { if (r.getTrigger() == SignalDetector.PATTERN_MATCH[i]) { if (options.containsKey(SignalDetector.PATTERN[i])) { String s = (String) options.get(SignalDetector.PATTERN[i]); String pattern = ""; //now hack for TCK: test_2_1_9_7_MultipleReturnKeys for (int k = 0; k < s.length() - 1; k++) { pattern += (s.charAt(k) + "|"); } pattern += s.charAt(s.length() - 1); list.add(pattern); triggers.add(SignalDetector.PATTERN_MATCH[i]); } } } } if (list.isEmpty()) { return null; } String[] patterns = new String[list.size()]; list.toArray(patterns); this.rtcTriggered = true; return patterns; } public MediaGroup getContainer() { return this.parent; } public void stop() { try { fsm.signal(SIGNAL_STOP); } catch (UnknownTransitionException e) { } } public void addListener(MediaEventListener<RecorderEvent> listener) { this.listeners.add(listener); } public MediaSession getMediaSession() { return this.parent.getMediaSession(); } public void removeListener(MediaEventListener<RecorderEvent> listener) { this.listeners.remove(listener); } protected void update(RecorderEvent anEvent) { for (MediaEventListener m : listeners) { m.onEvent(anEvent); } } private void requestRecording() { // generate request identifier and transaction ID RequestIdentifier reqID = parent.nextRequestID(); int txID = parent.getMediaSession().getDriver().getNextTxID(); RequestedAction[] actions = new RequestedAction[] { RequestedAction.NotifyImmediately }; // constructs request NotificationRequest req = new NotificationRequest(this, parent.getEndpoint().getIdentifier(), reqID); ArrayList<EventName> signalList = new ArrayList<EventName>(); ArrayList<RequestedEvent> eventList = new ArrayList(); signalList.add(new EventName(PackageAU.Name, PackageAU.pr.withParm(params))); EventName[] signals = new EventName[signalList.size()]; signalList.toArray(signals); //player events eventList.add(new RequestedEvent(new EventName(PackageAU.Name, MgcpEvent.oc), actions)); eventList.add(new RequestedEvent(new EventName(PackageAU.Name, MgcpEvent.of), actions)); RequestedEvent[] events = new RequestedEvent[eventList.size()]; eventList.toArray(events); req.setRequestedEvents(events); req.setSignalRequests(signals); req.setTransactionHandle(txID); DriverImpl driver=parent.getMediaSession().getDriver(); req.setNotifiedEntity(driver.getCallAgent()); driver.attach(txID, this); driver.attach(reqID, this); if(this.parent.isStopping()) mgcpSender.init(driver, req); else driver.send(req); } private void stopRecording() { // generate request identifier and transaction ID RequestIdentifier reqID = parent.nextRequestID(); int txID = parent.getMediaSession().getDriver().getNextTxID(); // constructs request NotificationRequest req = new NotificationRequest(this, parent.getEndpoint().getIdentifier(), reqID); req.setTransactionHandle(txID); DriverImpl driver=parent.getMediaSession().getDriver(); req.setNotifiedEntity(driver.getCallAgent()); driver.attach(txID, this); driver.attach(reqID, this); driver.send(req); } private void signal(String signal) { try { fsm.signal(signal); } catch (UnknownTransitionException e) { } } private void fireEvent(RecorderEventImpl evt) { new Thread(new EventSender(evt)).start(); } /** * Fires event. * * @param evt the event to be fired */ private void fireEvent(EventName eventName) { switch (eventName.getEventIdentifier().intValue()) { case MgcpEvent.REPORT_ON_COMPLETION : //parse options Options options = new Options(eventName.getEventIdentifier().getParms()); switch (options.getReturnCode()) { case 100 : if (options.getPatternIndex() >= 0) { recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.RTC_TRIGGERED, triggers.get(options.getPatternIndex()), 0); } else if (options.getDigitsCollected() != null) { recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.RTC_TRIGGERED, SignalDetector.DETECTION_OF_ONE_SIGNAL, 0); } else { recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.NO_QUALIFIER, null, 0); } signal(SIGNAL_COMPLETE); break; case 328 : recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.DURATION_EXCEEDED, null, 0); signal(SIGNAL_COMPLETE); break; case 327 : recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.SILENCE, null, 0); signal(SIGNAL_COMPLETE); break; } break; case MgcpEvent.REPORT_FAILURE : recorderEvent = new RecorderEventImpl(this, RecorderEvent.RECORD_COMPLETED, true, MediaErr.NOT_FOUND, "Not found"); signal(SIGNAL_FAILURE); break; } } public void processMgcpCommandEvent(JainMgcpCommandEvent event) { switch (event.getObjectIdentifier()) { case Constants.CMD_NOTIFY : Notify notify = (Notify) event; EventName[] events = notify.getObservedEvents(); for (EventName evt: events) { fireEvent(evt); } break; default : return; } } public void processMgcpResponseEvent(JainMgcpResponseEvent event) { switch (event.getReturnCode().getValue()) { case ReturnCode.TRANSACTION_BEING_EXECUTED : break; case ReturnCode.TRANSACTION_EXECUTED_NORMALLY : try { fsm.signal(SIGNAL_SUCCESS); } catch (UnknownTransitionException e) { } break; default : try { fsm.signal(SIGNAL_FAILURE); } catch (UnknownTransitionException e) { } } } private void delay(long amount) { try { Thread.sleep(amount); } catch (InterruptedException e) { } } public void stopCompleted() { if(mgcpSender.waiting) mgcpSender.run(); } private class RecordRequest implements TransitionHandler { public void process(State state) { requestRecording(); } } private class StopRequest implements StateEventHandler { public void onEvent(State state) { stopRecording(); } } private class CompleteNotify implements TransitionHandler { public void process(State state) { fireEvent(recorderEvent); } } private class StoppedNotify implements TransitionHandler { public void process(State state) { fireEvent(new RecorderEventImpl(null, RecorderEvent.RECORD_COMPLETED, true, RecorderEvent.NO_QUALIFIER, null, 0)); } } private class EventSender implements Runnable { protected RecorderEvent event; public EventSender(RecorderEvent event) { this.event = event; } public void run() { for (MediaEventListener l : listeners) { l.onEvent(event); } } } private class MgcpSender { private DriverImpl driver; private NotificationRequest req; private Boolean waiting=false; public MgcpSender() { } public void init(DriverImpl driver,NotificationRequest req) { this.driver=driver; this.req=req; waiting=true; } public void run() { driver.send(req); waiting=false; } } }