/**
* Copyright 2010 Voxeo Corporation Licensed under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.voxeo.moho.media;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.media.mscontrol.EventType;
import javax.media.mscontrol.MediaErr;
import javax.media.mscontrol.MediaEventListener;
import javax.media.mscontrol.MediaSession;
import javax.media.mscontrol.MsControlException;
import javax.media.mscontrol.Parameter;
import javax.media.mscontrol.Parameters;
import javax.media.mscontrol.Qualifier;
import javax.media.mscontrol.UnsupportedException;
import javax.media.mscontrol.mediagroup.MediaGroup;
import javax.media.mscontrol.mediagroup.Player;
import javax.media.mscontrol.mediagroup.PlayerEvent;
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.mediagroup.signals.SignalDetectorEvent;
import javax.media.mscontrol.mediagroup.signals.SignalGenerator;
import javax.media.mscontrol.mediagroup.signals.SpeechRecognitionEvent;
import javax.media.mscontrol.networkconnection.NetworkConnection;
import javax.media.mscontrol.resource.RTC;
import javax.media.mscontrol.resource.ResourceEvent;
import org.apache.log4j.Logger;
import com.voxeo.moho.ApplicationContextImpl;
import com.voxeo.moho.MediaException;
import com.voxeo.moho.MediaService;
import com.voxeo.moho.Mixer;
import com.voxeo.moho.common.event.MohoInputCompleteEvent;
import com.voxeo.moho.common.event.MohoInputDetectedEvent;
import com.voxeo.moho.common.event.MohoOutputCompleteEvent;
import com.voxeo.moho.common.event.MohoOutputPausedEvent;
import com.voxeo.moho.common.event.MohoOutputResumedEvent;
import com.voxeo.moho.common.event.MohoRecordCompleteEvent;
import com.voxeo.moho.common.event.MohoRecordPausedEvent;
import com.voxeo.moho.common.event.MohoRecordResumedEvent;
import com.voxeo.moho.common.event.MohoRecordStartedEvent;
import com.voxeo.moho.common.util.InheritLogContextRunnable;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.event.InputCompleteEvent;
import com.voxeo.moho.event.MediaCompleteEvent;
import com.voxeo.moho.event.OutputCompleteEvent;
import com.voxeo.moho.event.OutputCompleteEvent.Cause;
import com.voxeo.moho.event.RecordCompleteEvent;
import com.voxeo.moho.media.dialect.CallRecordListener;
import com.voxeo.moho.media.dialect.MediaDialect;
import com.voxeo.moho.media.input.EnergyGrammar;
import com.voxeo.moho.media.input.Grammar;
import com.voxeo.moho.media.input.InputCommand;
import com.voxeo.moho.media.input.SignalGrammar;
import com.voxeo.moho.media.input.SignalGrammar.Signal;
import com.voxeo.moho.media.input.SimpleGrammar;
import com.voxeo.moho.media.output.AudibleResource;
import com.voxeo.moho.media.output.AudioURIResource;
import com.voxeo.moho.media.output.OutputCommand;
import com.voxeo.moho.media.output.OutputCommand.BargeinType;
import com.voxeo.moho.media.output.TextToSpeechResource;
import com.voxeo.moho.media.record.RecordCommand;
import com.voxeo.moho.sip.JoinDelegate;
import com.voxeo.moho.sip.SIPCallImpl;
import com.voxeo.moho.spi.ExecutionContext;
import com.voxeo.moho.util.NLSMLParser;
public class GenericMediaService<T extends EventSource> implements MediaService<T> {
private static final Logger LOG = Logger.getLogger(GenericMediaService.class);
protected T _parent;
protected MediaSession _session;
protected MediaGroup _group;
protected MediaGroup _group2;
protected Player _player = null;
protected Recorder _recorder = null;
protected SignalDetector _detector = null;
protected SignalGenerator _generator = null;
protected ExecutionContext _context;
protected MediaDialect _dialect;
protected List<MediaOperation<?, ? extends MediaCompleteEvent<?>>> _futures = new CopyOnWriteArrayList<MediaOperation<?, ? extends MediaCompleteEvent<?>>>();
protected PlayerListener playerListener = new PlayerListener();
protected GenericMediaService(final T parent, final MediaGroup group) {
_parent = parent;
_group = group;
_session = group.getMediaSession();
_context = (ExecutionContext) ((EventSource) _parent).getApplicationContext();
_dialect = ((ApplicationContextImpl) _context).getDialect();
}
protected synchronized Player getPlayer() {
if (_player == null) {
try {
_player = _group.getPlayer();
_player.addListener(playerListener);
}
catch (UnsupportedException ex) {
LOG.debug("", ex);
throw new UnsupportedOperationException("player is not supported by " + _group);
}
catch (MsControlException e) {
LOG.error("", e);
throw new MediaException(e);
}
if (_player == null) {
throw new UnsupportedOperationException("Can't get Player.");
}
}
return _player;
}
protected synchronized Recorder getRecorder() {
if (_recorder == null) {
// For mixer, create another media group for recording, so the recorder
// can record the output from the media service itself.
if (_group2 == null && _parent instanceof Mixer) {
try {
_group2 = _session.createMediaGroup(MediaGroup.PLAYER_RECORDER_SIGNALDETECTOR_SIGNALGENERATOR, null);
}
catch (final MsControlException e1) {
try {
_group2 = _session.createMediaGroup(MediaGroup.PLAYER_RECORDER_SIGNALDETECTOR, null);
}
catch (final MsControlException e2) {
try {
_group2 = _session.createMediaGroup(MediaGroup.PLAYER, null);
}
catch (final MsControlException e3) {
throw new MediaException(e3);
}
}
}
if (_group2 != null) {
try {
JoinDelegate.bridgeJoin((Mixer) _parent, _group2);
}
catch (Exception ex) {
LOG.error("Exception when joining recorder media group", ex);
}
}
}
try {
if (_group2 != null) {
_recorder = _group2.getRecorder();
}
else {
_recorder = _group.getRecorder();
}
}
catch (UnsupportedException ex) {
LOG.debug("", ex);
throw new UnsupportedOperationException("Recorder is not supported by " + _group);
}
catch (MsControlException e) {
LOG.error("", e);
throw new MediaException(e);
}
if (_recorder == null) {
throw new UnsupportedOperationException("Can't get Recorder.");
}
}
return _recorder;
}
protected synchronized SignalDetector getSignalDetector() {
if (_detector == null) {
try {
_detector = _group.getSignalDetector();
}
catch (UnsupportedException ex) {
LOG.debug("", ex);
throw new UnsupportedOperationException("SignalDetector is not supported by " + _group);
}
catch (MsControlException e) {
LOG.error("", e);
throw new MediaException(e);
}
if (_detector == null) {
throw new UnsupportedOperationException("Can't get SignalDetector.");
}
}
return _detector;
}
protected synchronized SignalGenerator getSignalGenerator() {
if (_generator == null) {
try {
_generator = _group.getSignalGenerator();
}
catch (UnsupportedException ex) {
LOG.debug("", ex);
throw new UnsupportedOperationException("SignalGenerator is not supported by " + _group);
}
catch (MsControlException e) {
LOG.error("", e);
throw new MediaException(e);
}
if (_generator == null) {
throw new UnsupportedOperationException("Can't get SignalGenerator.");
}
}
return _generator;
}
@Override
public MediaGroup getMediaGroup(boolean create) {
return _group;
}
@Override
public MediaGroup getMediaGroup() {
return _group;
}
@Override
public Input<T> input(final String grammar) throws MediaException {
return prompt((String) null, grammar, 0).getInput();
}
@Override
public Input<T> input(final InputCommand input) throws MediaException {
return prompt(null, input, 0).getInput();
}
@Override
public Output<T> output(final String text) throws MediaException {
return prompt(text, null, 0).getOutput();
}
@Override
public Output<T> output(final URI media) throws MediaException {
return prompt(media, null, 0).getOutput();
}
@Override
public Output<T> output(final OutputCommand output) throws MediaException {
return prompt(output, null, 0).getOutput();
}
@Override
public Prompt<T> prompt(final String text, final String grammar, final int repeat) throws MediaException {
final OutputCommand output = text == null ? null : new OutputCommand(new TextToSpeechResource(text));
final InputCommand input = grammar == null ? null : new InputCommand(new SimpleGrammar(grammar));
return prompt(output, input, repeat);
}
@Override
public Prompt<T> prompt(final URI media, final String grammar, final int repeat) throws MediaException {
final OutputCommand output = media == null ? null : new OutputCommand(new AudioURIResource(media));
final InputCommand input = grammar == null ? null : new InputCommand(new SimpleGrammar(grammar));
return prompt(output, input, repeat);
}
private boolean haveOutput(OutputCommand output) {
return output != null && output.getAudibleResources() != null && output.getAudibleResources().length > 0;
}
private boolean haveInput(InputCommand input) {
return input != null && input.getGrammars() != null && input.getGrammars().length > 0;
}
private Prompt<T> internaOutput(OutputCommandInputCommandPair outinputPair) {
OutputCommand output = outinputPair.getOutputCommand();
int repeat = outinputPair.getRepeat();
Prompt<T> retval = outinputPair.getPrompt();
if (haveOutput(outinputPair.getOutputCommand())) {
// _currentOutput = outinputPair;
final Parameters params = _group.createParameters();
final List<RTC> rtcs = new ArrayList<RTC>();
if (output.getParameters() != null) {
params.putAll(output.getParameters());
}
if (output.size() > 0) {
params.putAll(output);
}
if (output.getRtcs() != null) {
for (final RTC rtc : output.getRtcs()) {
rtcs.add(rtc);
}
}
if (output.getAllRTC() != null && output.getAllRTC().size() > 0) {
rtcs.addAll(output.getAllRTC());
}
switch (output.getBehavior()) {
case QUEUE:
params.put(Player.BEHAVIOUR_IF_BUSY, Player.QUEUE_IF_BUSY);
break;
case STOP:
params.put(Player.BEHAVIOUR_IF_BUSY, Player.STOP_IF_BUSY);
break;
case ERROR:
params.put(Player.BEHAVIOUR_IF_BUSY, Player.FAIL_IF_BUSY);
break;
}
switch (output.getBargeinType()) {
case ANY:
rtcs.add(new RTC(SignalDetector.DETECTION_OF_ONE_SIGNAL, Player.STOP_ALL));
rtcs.add(new RTC(SpeechDetectorConstants.START_OF_SPEECH, Player.STOP_ALL));
params.put(SpeechDetectorConstants.BARGE_IN_ENABLED, Boolean.TRUE);
break;
case DTMF:
rtcs.add(new RTC(SignalDetector.DETECTION_OF_ONE_SIGNAL, Player.STOP_ALL));
params.put(SpeechDetectorConstants.BARGE_IN_ENABLED, Boolean.TRUE);
break;
case SPEECH:
rtcs.add(new RTC(SpeechDetectorConstants.START_OF_SPEECH, Player.STOP_ALL));
params.put(SpeechDetectorConstants.BARGE_IN_ENABLED, Boolean.TRUE);
break;
case NONE:
params.put(SpeechDetectorConstants.BARGE_IN_ENABLED, Boolean.FALSE);
break;
}
params.put(Player.MAX_DURATION, output.getMaxtime());
params.put(Player.START_OFFSET, output.getStartingOffset());
params.put(Player.VOLUME_CHANGE, output.getVolumeUnit());
params.put(Player.AUDIO_CODEC, output.getCodec());
params.put(Player.FILE_FORMAT, output.getFormat());
params.put(Player.JUMP_PLAYLIST_INCREMENT, output.getJumpPlaylistIncrement());
params.put(Player.JUMP_TIME, output.getMoveTime());
params.put(Player.START_IN_PAUSED_MODE, output.isStartInPausedMode());
params.put(Player.ENABLED_EVENTS, new EventType[] {PlayerEvent.SPEED_CHANGED, PlayerEvent.VOLUME_CHANGED,
PlayerEvent.RESUMED, PlayerEvent.PAUSED});
_dialect.setTextToSpeechVoice(params, output.getVoiceName());
_dialect.setTextToSpeechLanguage(params, output.getLanguage());
if (output.getRepeatTimes() > 1) {
params.put(Player.REPEAT_COUNT, output.getRepeatTimes());
params.put(Player.INTERVAL, output.getRepeatInterval());
}
if (repeat > 1) {
params.put(Player.REPEAT_COUNT, repeat);
params.put(Player.INTERVAL, output.getRepeatInterval());
}
final List<URI> uris = new ArrayList<URI>();
final MediaResource[] reses = output.getAudibleResources();
for (final MediaResource r : reses) {
uris.add(r.toURI());
}
if (haveInput(outinputPair.getInputCommand())) {
params.put(SignalDetector.PROMPT, uris.toArray(new URI[] {}));
detectSignal(outinputPair.getInputCommand(), (InputImpl<T>) retval.getInput(), params, rtcs);
}
else {
try {
playerListener.addOutput(outinputPair.getOutput());
getPlayer().play(uris.toArray(new URI[] {}), rtcs.toArray(new RTC[] {}), params);
_futures.add(outinputPair.getOutput());
}
catch (final MsControlException e) {
playerListener.removeOutput(outinputPair.getOutput());
throw new MediaException(e);
}
}
}
else {
detectSignal(outinputPair.getInputCommand(), (InputImpl<T>) retval.getInput(), null, null);
}
return retval;
}
@SuppressWarnings("deprecation")
@Override
public synchronized Prompt<T> prompt(final OutputCommand output, final InputCommand input, final int repeat)
throws MediaException {
if (!haveInput(input) && !haveOutput(output)) {
throw new IllegalArgumentException("No output or input.");
}
final PromptImpl<T> retval = new PromptImpl<T>();
OutputImpl<T> outFuture = null;
InputImpl<T> inFuture = null;
if (haveInput(input)) {
inFuture = new InputImpl<T>(_group);
retval.setInput(inFuture);
}
else if (output != null) {
outFuture = new OutputImpl<T>(_group);
retval.setOutput(outFuture);
}
OutputCommandInputCommandPair outinputPair = new OutputCommandInputCommandPair(output, input, inFuture, outFuture,
repeat, retval);
return internaOutput(outinputPair);
}
@Override
public Recording<T> record(final URI recording) throws MediaException {
Recorder recorder = getRecorder();
final RecordingImpl<T> retval = new RecordingImpl<T>(_group2 != null? _group2 : _group);
try {
recorder.addListener(new RecorderListener(retval));
recorder.record(recording, RTC.NO_RTC, Parameters.NO_PARAMETER);
_futures.add(retval);
return retval;
}
catch (final Exception e) {
throw new MediaException(e);
}
}
@Override
public Recording<T> record(final RecordCommand command) throws MediaException {
if (command.isDuplex() && _parent instanceof SIPCallImpl) {
NetworkConnection nc = (NetworkConnection) ((SIPCallImpl) _parent).getMediaObject();
final CallRecordingImpl<T> retValue = new CallRecordingImpl<T>(nc, _dialect);
try {
final Parameters params = _group.createParameters();
if (command.getAudioCODEC() != null) {
_dialect.setCallRecordAudioCodec(params, command.getAudioCODEC());
}
if (command.getFileFormat() != null) {
_dialect.setCallRecordFileFormat(params, command.getFileFormat());
}
_dialect.startCallRecord(nc, command.getRecordURI(), RTC.NO_RTC, params, new CallRecordListenerImpl(retValue, command));
if(command.getMaxDuration() > 0) {
MaxCallRecordDurationTask timerTask = new MaxCallRecordDurationTask(retValue);
ScheduledFuture future = ((ApplicationContextImpl) _context).getScheduledEcutor().schedule(timerTask, command.getMaxDuration(), TimeUnit.MILLISECONDS);
retValue.setMaxDurationTimerFuture(future);
retValue.setMaxDurationTask(timerTask);
}
_futures.add(retValue);
}
catch (Exception ex) {
throw new MediaException(ex);
}
return retValue;
}
Recorder recorder = getRecorder();
final RecordingImpl<T> retval = new RecordingImpl<T>(_group2 != null? _group2 : _group);
try {
final List<RTC> rtcs = new ArrayList<RTC>();
final Parameters params = _group.createParameters();
if (!command.isSignalTruncationOn()) {
params.put(Recorder.SIGNAL_TRUNCATION_ON, Boolean.FALSE);
}
if (command.isAppend()) {
params.put(Recorder.APPEND, Boolean.TRUE);
}
if (command.getAudioClockRate() > 0) {
params.put(Recorder.AUDIO_CLOCKRATE, command.getAudioClockRate());
}
if (command.getAudioCODEC() != null) {
params.put(Recorder.AUDIO_CODEC, command.getAudioCODEC());
}
if (command.getAudioFMTP() != null) {
params.put(Recorder.AUDIO_FMTP, command.getAudioFMTP());
}
if (command.getAudioMaxBitRate() > 0) {
params.put(Recorder.AUDIO_MAX_BITRATE, command.getAudioMaxBitRate());
}
if (command.isStartBeep()) {
params.put(Recorder.START_BEEP, Boolean.TRUE);
if (command.getBeepFrequency() > 0 || command.getBeepFrequency() == -1) {
params.put(Recorder.BEEP_FREQUENCY, command.getBeepFrequency());
}
if (command.getBeepLength() > 0 || command.getBeepLength() == -1) {
params.put(Recorder.BEEP_LENGTH, command.getBeepLength());
}
}
else {
params.put(Recorder.START_BEEP, Boolean.FALSE);
}
if (command.isStartInPausedMode()) {
params.put(Recorder.START_IN_PAUSED_MODE, Boolean.TRUE);
}
if (command.getFileFormat() != null) {
params.put(Recorder.FILE_FORMAT, command.getFileFormat());
}
if (command.getMaxDuration() > 0) {
params.put(Recorder.MAX_DURATION, command.getMaxDuration());
}
if (command.getMinDuration() > 0) {
params.put(Recorder.MIN_DURATION, command.getMinDuration());
}
if (command.getPrompt() != null) {
final AudibleResource[] resources = command.getPrompt().getAudibleResources();
if (resources.length > 0) {
final URI[] uris = new URI[resources.length];
for (int i = 0; i < resources.length; i++) {
uris[i] = resources[i].toURI();
}
params.put(Recorder.PROMPT, uris);
}
if (command.getPrompt().getBargeinType() != BargeinType.NONE) {
params.put(SpeechDetectorConstants.BARGE_IN_ENABLED, Boolean.TRUE);
params.put(Recorder.SPEECH_DETECTION_MODE, Recorder.DETECT_FIRST_OCCURRENCE);
}
if (command.getPrompt().getVoiceName() != null) {
_dialect.setTextToSpeechVoice(params, command.getPrompt().getVoiceName());
}
if (command.getPrompt().getLanguage() != null) {
_dialect.setTextToSpeechLanguage(params, command.getPrompt().getLanguage());
}
}
if (command.isSilenceTerminationOn()) {
params.put(Recorder.SILENCE_TERMINATION_ON, Boolean.TRUE);
}
if (command.getSpeechDetectionMode() != null) {
switch (command.getSpeechDetectionMode()) {
case DETECTOR_INACTIVE:
params.put(Recorder.SPEECH_DETECTION_MODE, Recorder.DETECTOR_INACTIVE);
break;
case DETECT_FIRST_OCCURRENCE:
params.put(Recorder.SPEECH_DETECTION_MODE, Recorder.DETECT_FIRST_OCCURRENCE);
break;
case DETECT_ALL_OCCURRENCES:
params.put(Recorder.SPEECH_DETECTION_MODE, Recorder.DETECT_ALL_OCCURRENCES);
}
}
params.put(SpeechDetectorConstants.INITIAL_TIMEOUT, command.getInitialTimeout());
if (command.getFinalTimeout() > 0) {
params.put(Recorder.SILENCE_TERMINATION_ON, Boolean.TRUE);
params.put(SpeechDetectorConstants.FINAL_TIMEOUT, command.getFinalTimeout());
}
if (command.getVideoCODEC() != null) {
params.put(Recorder.VIDEO_CODEC, command.getVideoCODEC());
}
if (command.getVideoFMTP() != null) {
params.put(Recorder.VIDEO_FMTP, command.getVideoFMTP());
}
if (command.getVideoMaxBitRate() > 0) {
params.put(Recorder.VIDEO_MAX_BITRATE, command.getVideoMaxBitRate());
}
if (command.getFinishOnKey() != null) {
params.put(SignalDetector.PATTERN[0], command.getFinishOnKey());
rtcs.add(new RTC(SignalDetector.PATTERN_MATCH[0], Recorder.STOP));
}
_dialect.enableRecorderPromptCompleteEvent(params, true);
if (command.isIgnorePromptFailure()) {
_dialect.setIgnorePromptFailure(params, true);
}
recorder.addListener(new RecorderListener(retval));
recorder.record(command.getRecordURI(), rtcs.toArray(new RTC[] {}), params);
_futures.add(retval);
return retval;
}
catch (final Exception e) {
throw new MediaException(e);
}
}
@SuppressWarnings("deprecation")
protected Input<T> detectSignal(final InputCommand cmd, final InputImpl<T> input, Parameters internalParams,
List<RTC> internalRtcs) throws MediaException {
if (cmd == null) {
throw new MediaException("InputCommand not initialized");
}
if (cmd.isRecord()) {
try {
getRecorder().record(cmd.getRecordURI(), cmd.getRtcs() != null ? cmd.getRtcs() : RTC.NO_RTC,
cmd.getParameters() != null ? cmd.getParameters() : Parameters.NO_PARAMETER);
}
catch (final Exception e) {
throw new MediaException(e);
}
}
final Parameters params = _group.createParameters();
if (cmd.getParameters() != null) {
params.putAll(cmd.getParameters());
}
if (internalParams != null) {
params.putAll(internalParams);
}
final List<RTC> rtcs = new ArrayList<RTC>();
if (cmd.getRtcs() != null) {
for (final RTC rtc : cmd.getRtcs()) {
rtcs.add(rtc);
}
}
if (internalRtcs != null) {
rtcs.addAll(internalRtcs);
}
if (cmd.size() > 0) {
params.putAll(cmd);
}
if (cmd.getAllRTC() != null && cmd.getAllRTC().size() > 0) {
rtcs.addAll(cmd.getAllRTC());
}
params.put(SignalDetector.BUFFERING, cmd.isBuffering());
params.put(SignalDetector.MAX_DURATION, cmd.getMaxTimeout());
params.put(SignalDetector.INITIAL_TIMEOUT, cmd.getInitialTimeout());
params.put(SignalDetector.INTER_SIG_TIMEOUT, cmd.getInterDigitsTimeout());
params.put(SpeechDetectorConstants.SENSITIVITY, cmd.getSensitivity());
if (cmd.isSupervised()) {
_dialect.enableDetectorPromptCompleteEvent(params, true);
final EventType[] enabledEvent = (EventType[]) params.get(SignalDetector.ENABLED_EVENTS);
if (enabledEvent != null && enabledEvent.length > 0) {
EventType[] newEabledEvents = Arrays.copyOf(enabledEvent, enabledEvent.length + 1);
newEabledEvents[newEabledEvents.length - 1] = SignalDetectorEvent.SIGNAL_DETECTED;
params.put(SignalDetector.ENABLED_EVENTS, newEabledEvents);
}
else {
params.put(SignalDetector.ENABLED_EVENTS, new EventType[] {SignalDetectorEvent.SIGNAL_DETECTED});
}
}
if (cmd.getSpeechCompleteTimeout() > 0) {
_dialect.setSpeechCompleteTimeout(params, cmd.getSpeechCompleteTimeout());
}
if (cmd.getSpeechIncompleteTimeout() > 0) {
_dialect.setSpeechIncompleteTimeout(params, cmd.getSpeechIncompleteTimeout());
}
if (cmd.isIgnorePromptFailure()) {
_dialect.setIgnorePromptFailure(params, true);
}
_dialect.setSpeechLanguage(params, cmd.getRecognizer());
_dialect.setSpeechTermChar(params, cmd.getTerminator());
_dialect.setSpeechInputMode(params, cmd.getInputMode());
_dialect.setDtmfHotwordEnabled(params, cmd.isDtmfHotword());
_dialect.setDtmfTypeaheadEnabled(params, cmd.isDtmfTypeahead());
_dialect.setConfidence(params, cmd.getMinConfidence());
_dialect.setAutoReset(params, cmd.getAutoReset());
_dialect.setEnergyParameters(params, cmd.getEnergyParameters());
_dialect.setBeepParameters(params, cmd.getBeepParameters());
final Grammar[] grammars = cmd.getGrammars();
List<InputPattern> patterns = null;
List<Parameter> patternKeys = null;
List<EventType> patternMatchedEvts = null;
if (grammars.length > 0) {
patterns = new ArrayList<InputPattern>(grammars.length);
patternKeys = new ArrayList<Parameter>(grammars.length);
patternMatchedEvts = new ArrayList<EventType>(grammars.length);
int i = 0;
for (final Grammar grammar : grammars) {
if (grammar == null) {
continue;
}
Object pattern = null;
if (grammar instanceof EnergyGrammar) {
final EnergyGrammar eg = (EnergyGrammar) grammar;
if (eg.isStartOfSpeech()) {
pattern = SpeechRecognitionEvent.START_OF_SPEECH;
}
else if (eg.isEndOfSpeech()) {
pattern = SpeechRecognitionEvent.END_OF_SPEECH;
}
}
else if (grammar instanceof SignalGrammar) {
final SignalGrammar sg = (SignalGrammar) grammar;
pattern = _dialect.getSignalConstants(sg.getSignal());
}
else {
final URI uri = grammar.toURI();
if ("data".equals(uri.getScheme())) {
pattern = uri;
}
else if ("digits".equals(uri.getScheme())) {
try {
pattern = URLDecoder.decode(uri.getSchemeSpecificPart(), "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
else {
try {
pattern = uri.toURL();
}
catch (MalformedURLException e) {
LOG.warn("Skipped Grammar! Only 'data' URIs and http/https/ftp/file URLs are permitted [uri="
+ uri.toString() + "]");
}
}
}
if (pattern == null) {
continue;
}
patterns.add(new InputPattern(i, pattern, grammar.isTerminatingCondition()));
if (grammar.isTerminatingCondition()) {
patternKeys.add(SignalDetector.PATTERN[i]);
}
else {
patternMatchedEvts.add(SignalDetectorEvent.PATTERN_MATCHED[i]);
}
i++;
}
if (patterns.size() > 0) {
final Parameters patternParams = _group.createParameters();
for (InputPattern p : patterns) {
if (LOG.isDebugEnabled()) {
LOG.debug(p);
}
patternParams.put(SignalDetector.PATTERN[p.getIndex()], p.getValue());
}
_group.setParameters(patternParams);
}
if (patternMatchedEvts.size() > 0) {
final EventType[] enabledEvent = (EventType[]) params.get(SignalDetector.ENABLED_EVENTS);
if (enabledEvent != null && enabledEvent.length > 0) {
EventType[] newEabledEvents = Arrays.copyOf(enabledEvent, enabledEvent.length + patternMatchedEvts.size());
System.arraycopy(patternMatchedEvts.toArray(new EventType[patternMatchedEvts.size()]), 0, newEabledEvents,
enabledEvent.length, patternMatchedEvts.size());
params.put(SignalDetector.ENABLED_EVENTS, newEabledEvents);
}
else {
params.put(SignalDetector.ENABLED_EVENTS,
patternMatchedEvts.toArray(new EventType[patternMatchedEvts.size()]));
}
}
}
if (patterns == null && cmd.getNumberOfDigits() == -1) {
throw new MediaException("No pattern");
}
getSignalDetector().addListener(new DetectorListener(input, cmd, patterns));
try {
if (cmd.isFlushBuffer()) {
getSignalDetector().flushBuffer();
}
getSignalDetector().receiveSignals(
cmd.getNumberOfDigits(),
(patternKeys == null || patternKeys.size() == 0) ? SignalDetector.NO_PATTERN : patternKeys
.toArray(new Parameter[patternKeys.size()]), rtcs.toArray(new RTC[] {}), params);
_futures.add(input);
}
catch (final MsControlException e) {
// if (params.get(SignalDetector.PROMPT) != null) {
// _currentOutput = null;
// }
throw new MediaException(e);
}
return input;
}
protected class CallRecordListenerImpl implements CallRecordListener {
private RecordCommand command;
private CallRecordingImpl<T> callRecording;
private long duration = 0;
public CallRecordListenerImpl(CallRecordingImpl<T> callRecording, RecordCommand command) {
super();
this.callRecording = callRecording;
this.command = command;
}
@Override
public void callRecordComplete(ResourceEvent event) {
Qualifier q = event.getQualifier();
RecordCompleteEvent.Cause cause = RecordCompleteEvent.Cause.UNKNOWN;
String errorText = null;
if (q == SpeechDetectorConstants.INITIAL_TIMEOUT_EXPIRED) {
cause = RecordCompleteEvent.Cause.INI_TIMEOUT;
}
else if (q == ResourceEvent.STOPPED) {
if (callRecording.isMaxDurationStop()) {
cause = RecordCompleteEvent.Cause.TIMEOUT;
}
else if (callRecording.isNormalDisconnect()) {
cause = RecordCompleteEvent.Cause.DISCONNECT;
}
else {
cause = RecordCompleteEvent.Cause.CANCEL;
}
}
else if (q == ResourceEvent.RTC_TRIGGERED) {
cause = RecordCompleteEvent.Cause.CANCEL;
}
else if (q == ResourceEvent.NO_QUALIFIER) {
if (event.getError() != ResourceEvent.NO_ERROR) {
cause = RecordCompleteEvent.Cause.ERROR;
}
errorText = event.getError() + ": " + event.getErrorText();
}
duration = _dialect.getCallRecordDuration(event);
final RecordCompleteEvent<T> recordCompleteEvent = new MohoRecordCompleteEvent<T>(_parent, cause, duration,
errorText, callRecording);
_parent.dispatch(recordCompleteEvent);
callRecording.done(recordCompleteEvent);
_futures.remove(callRecording);
if (callRecording.getMaxDurationTimerFuture() != null) {
callRecording.getMaxDurationTimerFuture().cancel(true);
((ApplicationContextImpl) _context).getScheduledEcutor().remove(callRecording.getMaxDurationTask());
((ApplicationContextImpl) _context).getScheduledEcutor().purge();
}
}
@Override
public void callRecordPause(ResourceEvent event) {
duration=_dialect.getCallRecordDuration(event);
_parent.dispatch(new MohoRecordPausedEvent<T>(_parent));
callRecording.pauseActionDone();
if (callRecording.getMaxDurationTimerFuture() != null) {
// cancel max-duration task
callRecording.getMaxDurationTimerFuture().cancel(true);
((ApplicationContextImpl) _context).getScheduledEcutor().remove(callRecording.getMaxDurationTask());
((ApplicationContextImpl) _context).getScheduledEcutor().purge();
callRecording.setMaxDurationTimerFuture(null);
callRecording.setMaxDurationTask(null);
}
}
@Override
public void callRecordResume(ResourceEvent event) {
if (!event.isSuccessful()) {
final RecordCompleteEvent<T> recordCompleteEvent = new MohoRecordCompleteEvent<T>(_parent,
RecordCompleteEvent.Cause.ERROR, _dialect.getCallRecordDuration(event), callRecording);
_parent.dispatch(recordCompleteEvent);
callRecording.done(new MediaException(event.getErrorText()));
_futures.remove(callRecording);
}
else {
_parent.dispatch(new MohoRecordResumedEvent<T>(_parent));
callRecording.resumeActionDone();
if (command.getMaxDuration() > 0) {
// restart max-duration task
long delay = command.getMaxDuration() - duration;
MaxCallRecordDurationTask timerTask = new MaxCallRecordDurationTask(callRecording);
ScheduledFuture future = ((ApplicationContextImpl) _context).getScheduledEcutor().schedule(timerTask, delay,
TimeUnit.MILLISECONDS);
callRecording.setMaxDurationTimerFuture(future);
callRecording.setMaxDurationTask(timerTask);
}
}
}
}
@SuppressWarnings("rawtypes")
protected class PlayerListener implements MediaEventListener<PlayerEvent> {
Queue<OutputImpl> _outputQueue = new LinkedList<OutputImpl>();
public void addOutput(OutputImpl outputImpl) {
_outputQueue.offer(outputImpl);
}
public void removeOutput(OutputImpl outputImpl) {
_outputQueue.remove(outputImpl);
}
@Override
public void onEvent(final PlayerEvent e) {
Runnable runable = new InheritLogContextRunnable() {
@Override
public void run() {
final EventType t = e.getEventType();
synchronized (GenericMediaService.this) {
OutputImpl _output = _outputQueue.peek();
if (_output == null) {
LOG.error("Received PlayerEvent, but didn't find corresponding output future. " + e);
throw new RuntimeException("Received PlayerEvent, but didn't find corresponding output future.");
}
if (t == PlayerEvent.PLAY_COMPLETED) {
_outputQueue.remove(_output);
OutputCompleteEvent.Cause cause = Cause.UNKNOWN;
String errorText = null;
final Qualifier q = e.getQualifier();
if (q == PlayerEvent.END_OF_PLAY_LIST) {
cause = Cause.END;
}
else if (q == PlayerEvent.DURATION_EXCEEDED) {
cause = Cause.TIMEOUT;
}
else if (q == ResourceEvent.RTC_TRIGGERED) {
if (e.getRTCTrigger() == MediaGroup.SIGDET_STOPPLAY.getTrigger()) {
cause = Cause.BARGEIN;
}
// for _group.triggerAction(Player.STOP);
else if (e.getRTCTrigger() == ResourceEvent.MANUAL_TRIGGER) {
if (_output.isNormalDisconnect()) {
cause = Cause.DISCONNECT;
}
else {
cause = Cause.CANCEL;
}
}
}
else if (q == ResourceEvent.STOPPED) {
if (_output.isNormalDisconnect()) {
cause = Cause.DISCONNECT;
}
else {
cause = Cause.CANCEL;
}
}
else if (q == ResourceEvent.NO_QUALIFIER) {
if (e.getError() != ResourceEvent.NO_ERROR) {
cause = Cause.ERROR;
}
errorText = e.getError() + ": " + e.getErrorText();
}
final OutputCompleteEvent<T> outputCompleteEvent = new MohoOutputCompleteEvent<T>(_parent, cause,
errorText, _output);
_output.done(outputCompleteEvent);
_parent.dispatch(outputCompleteEvent);
_futures.remove(_output);
}
else if (t == PlayerEvent.PAUSED) {
_output.pauseActionDone();
_parent.dispatch(new MohoOutputPausedEvent<T>(_parent));
}
else if (t == PlayerEvent.RESUMED) {
_output.resumeActionDone();
_parent.dispatch(new MohoOutputResumedEvent<T>(_parent));
}
else if (t == PlayerEvent.SPEED_CHANGED) {
_output.speedActionDone();
}
else if (t == PlayerEvent.VOLUME_CHANGED) {
_output.volumeActionDone();
}
}
}
};
// avoid deadlock with 309 trigger
if (_context != null) {
try {
_context.getExecutor().execute(runable);
}
catch(Exception ex) {
LOG.warn("Exception when processing PlayerEvent using executor, processing it in current thread. " + e, ex);
runable.run();
}
}
else {
runable.run();
}
}
}
protected class DetectorListener implements MediaEventListener<SignalDetectorEvent> {
private InputImpl<T> _input = null;
private InputCommand _cmd = null;
private List<InputPattern> _patterns = null;
public DetectorListener(final InputImpl<T> input, final InputCommand inputCmd, final List<InputPattern> patterns) {
_input = input;
_cmd = inputCmd;
_patterns = patterns;
}
@Override
public void onEvent(final SignalDetectorEvent e) {
if (LOG.isDebugEnabled()) {
LOG.debug("DetectorListener[" + _parent + "] received: " + e);
}
final EventType t = e.getEventType();
if (t == SignalDetectorEvent.RECEIVE_SIGNALS_COMPLETED) {
getSignalDetector().removeListener(this);
if (_cmd.isRecord()) {
getRecorder().stop();
}
InputCompleteEvent.Cause cause = InputCompleteEvent.Cause.UNKNOWN;
final Qualifier q = e.getQualifier();
String errorText = null;
final InputPattern pattern = patternMatched(q);
if (q == SignalDetectorEvent.DURATION_EXCEEDED) {
cause = InputCompleteEvent.Cause.MAX_TIMEOUT;
}
else if (q == SignalDetectorEvent.INITIAL_TIMEOUT_EXCEEDED) {
cause = InputCompleteEvent.Cause.INI_TIMEOUT;
}
else if (q == SignalDetectorEvent.INTER_SIG_TIMEOUT_EXCEEDED) {
cause = InputCompleteEvent.Cause.IS_TIMEOUT;
}
else if (q == SpeechRecognitionEvent.NO_GRAMMAR_MATCH) {
cause = InputCompleteEvent.Cause.NO_MATCH;
}
else if (q == ResourceEvent.STOPPED || q == ResourceEvent.RTC_TRIGGERED) {
if (_input.isNormalDisconnect()) {
cause = InputCompleteEvent.Cause.DISCONNECT;
}
else {
cause = InputCompleteEvent.Cause.CANCEL;
}
}
else if (q == SignalDetectorEvent.NUM_SIGNALS_DETECTED) {
cause = InputCompleteEvent.Cause.MATCH;
}
else if (pattern != null) {
if (pattern.getValue() == SpeechRecognitionEvent.START_OF_SPEECH) {
cause = InputCompleteEvent.Cause.START_OF_SPEECH;
}
else if (pattern.getValue() == SpeechRecognitionEvent.END_OF_SPEECH) {
cause = InputCompleteEvent.Cause.END_OF_SPEECH;
}
else {
cause = InputCompleteEvent.Cause.MATCH;
}
}
else if (q == ResourceEvent.NO_QUALIFIER) {
if (e.getError() != ResourceEvent.NO_ERROR) {
cause = InputCompleteEvent.Cause.ERROR;
}
errorText = e.getError() + ": " + e.getErrorText();
}
else {
cause = _dialect.getInputCompleteEventCause(q);
}
final MohoInputCompleteEvent<T> inputCompleteEvent = new MohoInputCompleteEvent<T>(_parent,
cause == null ? InputCompleteEvent.Cause.UNKNOWN : cause, errorText, _input);
if (e instanceof SpeechRecognitionEvent) {
final SpeechRecognitionEvent se = (SpeechRecognitionEvent) e;
String signalString = e.getSignalString();
if (signalString != null) {
final Signal signal = Signal.parse(signalString);
if (signal == null) {
inputCompleteEvent.setConcept(se.getTag());
inputCompleteEvent.setTag(se.getTag());
inputCompleteEvent.setConfidence(1.0F);
inputCompleteEvent.setInterpretation(signalString);
inputCompleteEvent.setUtterance(signalString);
inputCompleteEvent.setInputMode(InputMode.DTMF);
}
else {
inputCompleteEvent.setSignal(signal);
}
}
else {
inputCompleteEvent.setUtterance(se.getUserInput());
inputCompleteEvent.setTag(se.getTag());
inputCompleteEvent.setConcept(se.getTag());
}
final URL semanticResult = se.getSemanticResult();
if (semanticResult != null && "application/x-nlsml".equalsIgnoreCase(semanticResult.getHost())) {
try {
inputCompleteEvent.setNlsml(semanticResult.getPath());
final List<Map<String, String>> nlsml = NLSMLParser.parse(inputCompleteEvent.getNlsml());
for (final Map<String, String> reco : nlsml) {
final String conf = reco.get("_confidence");
if (conf != null && signalString == null) {
inputCompleteEvent.setConfidence(Float.parseFloat(conf));
}
final String interpretation = reco.get("_interpretation");
if (interpretation != null) {
inputCompleteEvent.setInterpretation(interpretation);
}
final String inputmode = reco.get("_inputmode");
if (inputmode != null && signalString == null) {
if (inputmode.equalsIgnoreCase("speech") || inputmode.equalsIgnoreCase("voice")) {
inputCompleteEvent.setInputMode(InputMode.SPEECH);
}
else {
inputCompleteEvent.setInputMode(InputMode.DTMF);
}
}
}
}
catch (final Exception e1) {
LOG.warn("No NLSML", e1);
}
}
}
else {
String signalString = e.getSignalString();
final Signal signal = Signal.parse(signalString);
if (signal == null) {
inputCompleteEvent.setConcept(signalString);
inputCompleteEvent.setConfidence(1.0F);
inputCompleteEvent.setInterpretation(signalString);
inputCompleteEvent.setUtterance(signalString);
inputCompleteEvent.setInputMode(InputMode.DTMF);
}
else {
inputCompleteEvent.setSignal(signal);
}
}
inputCompleteEvent.setSISlots(_dialect.getSISlots(e));
_parent.dispatch(inputCompleteEvent);
_input.done(inputCompleteEvent);
_futures.remove(_input);
}
else if (t == SignalDetectorEvent.SIGNAL_DETECTED) {
if (_cmd.isSupervised()) {
_parent.dispatch(new MohoInputDetectedEvent<T>(_parent, e.getSignalString()));
}
}
else if (_dialect.isPromptCompleteEvent(e)) {
_parent.dispatch(new MohoOutputCompleteEvent<T>(_parent, OutputCompleteEvent.Cause.END, _input));
}
else {
final InputPattern pattern = patternMatched(t);
if (pattern == null) {
LOG.warn("Skipped " + e.getEventType() + " event! No InputPattern is found.");
return;
}
final MohoInputDetectedEvent<T> inputDetectedEvent;
if (pattern.getValue() == SpeechRecognitionEvent.START_OF_SPEECH) {
inputDetectedEvent = new MohoInputDetectedEvent<T>(_parent, true, false);
}
else if (pattern.getValue() == SpeechRecognitionEvent.END_OF_SPEECH) {
inputDetectedEvent = new MohoInputDetectedEvent<T>(_parent, false, true);
}
else {
Signal signal = Signal.parse(e.getSignalString());
if (signal != null) {
inputDetectedEvent = new MohoInputDetectedEvent<T>(_parent, signal);
}
else {
String input = null;
if (e instanceof SpeechRecognitionEvent) {
input = ((SpeechRecognitionEvent) e).getUserInput();
if (input == null) {
input = e.getSignalString();
}
}
else {
input = e.getSignalString();
}
inputDetectedEvent = new MohoInputDetectedEvent<T>(_parent, input);
if (e instanceof SpeechRecognitionEvent) {
final SpeechRecognitionEvent se = (SpeechRecognitionEvent) e;
String signalString = e.getSignalString();
if (signalString != null) {
inputDetectedEvent.setConcept(se.getTag());
inputDetectedEvent.setTag(se.getTag());
inputDetectedEvent.setConfidence(1.0F);
inputDetectedEvent.setInterpretation(signalString);
inputDetectedEvent.setInputMode(InputMode.DTMF);
}
else {
inputDetectedEvent.setTag(se.getTag());
inputDetectedEvent.setConcept(se.getTag());
}
final URL semanticResult = se.getSemanticResult();
if (semanticResult != null && "application/x-nlsml".equalsIgnoreCase(semanticResult.getHost())) {
try {
inputDetectedEvent.setNlsml(semanticResult.getPath());
final List<Map<String, String>> nlsml = NLSMLParser.parse(inputDetectedEvent.getNlsml());
for (final Map<String, String> reco : nlsml) {
final String conf = reco.get("_confidence");
if (conf != null && signalString == null) {
inputDetectedEvent.setConfidence(Float.parseFloat(conf));
}
final String interpretation = reco.get("_interpretation");
if (interpretation != null) {
inputDetectedEvent.setInterpretation(interpretation);
}
final String inputmode = reco.get("_inputmode");
if (inputmode != null && signalString == null) {
if (inputmode.equalsIgnoreCase("speech") || inputmode.equalsIgnoreCase("voice")) {
inputDetectedEvent.setInputMode(InputMode.SPEECH);
}
else {
inputDetectedEvent.setInputMode(InputMode.DTMF);
}
}
}
}
catch (final Exception e1) {
LOG.warn("No NLSML", e1);
}
}
}
else {
String signalString = e.getSignalString();
inputDetectedEvent.setConcept(signalString);
inputDetectedEvent.setConfidence(1.0F);
inputDetectedEvent.setInterpretation(signalString);
inputDetectedEvent.setInputMode(InputMode.DTMF);
}
inputDetectedEvent.setSISlots(_dialect.getSISlots(e));
}
}
_parent.dispatch(inputDetectedEvent);
}
}
private InputPattern patternMatched(final Qualifier qualifier) {
if (_patterns != null && _patterns.size() > 0) {
for (final InputPattern p : _patterns) {
if (qualifier == SignalDetectorEvent.PATTERN_MATCHING[p.getIndex()]) {
return p;
}
}
}
return null;
}
private InputPattern patternMatched(final EventType type) {
if (_patterns != null && _patterns.size() > 0) {
for (final InputPattern p : _patterns) {
if (type == SignalDetectorEvent.PATTERN_MATCHED[p.getIndex()]) {
return p;
}
}
}
return null;
}
}
protected class RecorderListener implements MediaEventListener<RecorderEvent> {
private RecordingImpl<T> _recording = null;
public RecorderListener(final RecordingImpl<T> recording) {
_recording = recording;
}
@Override
public void onEvent(final RecorderEvent e) {
final EventType t = e.getEventType();
if (t == RecorderEvent.RECORD_COMPLETED) {
getRecorder().removeListener(this);
RecordCompleteEvent.Cause cause = RecordCompleteEvent.Cause.UNKNOWN;
String errorText = null;
final Qualifier q = e.getQualifier();
if (q == RecorderEvent.DURATION_EXCEEDED) {
cause = RecordCompleteEvent.Cause.TIMEOUT;
}
else if (q == RecorderEvent.SILENCE) {
cause = RecordCompleteEvent.Cause.SILENCE;
}
else if (q == SpeechDetectorConstants.INITIAL_TIMEOUT_EXPIRED) {
cause = RecordCompleteEvent.Cause.INI_TIMEOUT;
}
else if (q == ResourceEvent.STOPPED) {
if (_recording.isNormalDisconnect()) {
cause = RecordCompleteEvent.Cause.DISCONNECT;
}
else {
cause = RecordCompleteEvent.Cause.CANCEL;
}
}
else if (q == ResourceEvent.RTC_TRIGGERED) {
cause = RecordCompleteEvent.Cause.CANCEL;
}
else if (q == ResourceEvent.NO_QUALIFIER) {
if (e.getError() != ResourceEvent.NO_ERROR) {
cause = RecordCompleteEvent.Cause.ERROR;
}
errorText = e.getError() + ": " + e.getErrorText();
}
final RecordCompleteEvent<T> recordCompleteEvent = new MohoRecordCompleteEvent<T>(_parent, cause,
e.getDuration(), errorText, _recording);
_parent.dispatch(recordCompleteEvent);
_recording.done(recordCompleteEvent);
_futures.remove(_recording);
}
else if (t == RecorderEvent.PAUSED) {
_parent.dispatch(new MohoRecordPausedEvent<T>(_parent));
_recording.pauseActionDone();
}
else if (t == RecorderEvent.RESUMED) {
if (e.getError() == MediaErr.UNKNOWN_ERROR) {
final RecordCompleteEvent<T> recordCompleteEvent = new MohoRecordCompleteEvent<T>(_parent,
RecordCompleteEvent.Cause.ERROR, e.getDuration(), _recording);
_parent.dispatch(recordCompleteEvent);
_recording.done(new MediaException(e.getErrorText()));
_futures.remove(_recording);
}
else {
_parent.dispatch(new MohoRecordResumedEvent<T>(_parent));
_recording.resumeActionDone();
}
}
else if (t == RecorderEvent.STARTED) {
_parent.dispatch(new MohoRecordStartedEvent<T>(_parent));
}
else if (_dialect.isPromptCompleteEvent(e)) {
_parent.dispatch(new MohoOutputCompleteEvent<T>(_parent, OutputCompleteEvent.Cause.END, _recording));
}
}
}
@SuppressWarnings("unchecked")
public void release(boolean isNormalDisconnect) {
// avoid ConcurrentModificationException
List<MediaOperation<?, ? extends MediaCompleteEvent<?>>> copy = new LinkedList<MediaOperation<?, ? extends MediaCompleteEvent<?>>>();
copy.addAll(_futures);
Iterator<MediaOperation<? extends EventSource, ? extends MediaCompleteEvent<?>>> ite = copy.iterator();
while (ite.hasNext()) {
MediaOperation<? extends EventSource, ? extends MediaCompleteEvent<?>> future = ite.next();
if (future instanceof RecordingImpl) {
RecordingImpl<T> recording = (RecordingImpl<T>) future;
recording.pauseActionDone();
recording.resumeActionDone();
if (recording.isPending()) {
recording.normalDisconnect(isNormalDisconnect);
try {
recording.stop();
}
catch (Exception e) {
LOG.warn("Exception when stopping record.", e);
}
}
}
else if (future instanceof InputImpl) {
InputImpl<T> input = (InputImpl<T>) future;
if (input.isPending()) {
input.normalDisconnect(isNormalDisconnect);
try {
input.stop();
}
catch (Exception e) {
LOG.warn("Exception when stopping input.", e);
}
}
}
else if (future instanceof CallRecordingImpl) {
CallRecordingImpl<T> recording = (CallRecordingImpl<T>) future;
if (!recording.isDone()) {
recording.normalDisconnect(isNormalDisconnect);
try {
recording.stop();
}
catch (Exception e) {
LOG.warn("Exception when stopping call record.", e);
}
}
}
else {
OutputImpl<T> output = (OutputImpl<T>) future;
output.pauseActionDone();
output.resumeActionDone();
output.speedActionDone();
output.volumeActionDone();
if (output.isPending()) {
output.normalDisconnect(isNormalDisconnect);
try {
output.stop();
}
catch (Exception e) {
LOG.warn("Exception when stopping output.", e);
}
}
}
}
}
class OutputCommandInputCommandPair {
private OutputCommand _outputCommand;
private InputCommand _inputCommand;
private int _repeat;
private InputImpl<T> _input;
private OutputImpl<T> _output;
private PromptImpl<T> _prompt;
public OutputCommandInputCommandPair(OutputCommand outputCommand, InputCommand inputCommand, InputImpl<T> input,
OutputImpl<T> output, int repeat, PromptImpl<T> prompt) {
super();
this._outputCommand = outputCommand;
this._inputCommand = inputCommand;
this._input = input;
this._output = output;
this._repeat = repeat;
this._prompt = prompt;
}
public PromptImpl<T> getPrompt() {
return _prompt;
}
public InputImpl<T> getInput() {
return _input;
}
public OutputImpl<T> getOutput() {
return _output;
}
public OutputCommand getOutputCommand() {
return _outputCommand;
}
public InputCommand getInputCommand() {
return _inputCommand;
}
public int getRepeat() {
return _repeat;
}
}
public class MaxCallRecordDurationTask extends InheritLogContextRunnable {
private CallRecordingImpl<T> future;
public MaxCallRecordDurationTask(CallRecordingImpl<T> future) {
super();
this.future = future;
}
@Override
public void run() {
LOG.warn("Max call record duration expired, stopping.");
future.setMaxDurationStop(true);
future.stop();
future.setMaxDurationTimerFuture(null);
}
}
}