/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, Telestax Inc and individual contributors
* by the @authors tag.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.restcomm.media.control.mgcp.pkg.au;
import java.net.MalformedURLException;
import java.util.Map;
import org.apache.log4j.Logger;
import org.restcomm.media.control.mgcp.command.param.NotifiedEntity;
import org.restcomm.media.control.mgcp.pkg.AbstractMgcpSignal;
import org.restcomm.media.control.mgcp.pkg.SignalType;
import org.restcomm.media.spi.ResourceUnavailableException;
import org.restcomm.media.spi.listener.TooManyListenersException;
import org.restcomm.media.spi.player.Player;
import org.restcomm.media.spi.player.PlayerEvent;
import org.restcomm.media.spi.player.PlayerListener;
import com.google.common.base.Function;
import com.google.common.base.Optional;
/**
* Plays an announcement in situations where there is no need for interaction with the user.
*
* <p>
* Because there is no need to monitor the incoming media stream this event is an efficient mechanism for treatments,
* informational announcements, etc.
* </p>
*
* @author Henrique Rosa (henrique.rosa@telestax.com)
*
*/
public class PlayAnnouncement extends AbstractMgcpSignal implements PlayerListener {
private static final Logger log = Logger.getLogger(PlayAnnouncement.class);
// Play Parameters (default values)
private static final String SIGNAL = "pa";
/**
* The maximum number of times an announcement is to be played.
*
* <p>
* A value of minus one (-1) indicates the announcement is to be repeated forever. Defaults to one (1).
* </p>
*/
private static final int ITERATIONS = 1;
/**
* The interval of silence to be inserted between iterative plays.
* <p>
* Specified in units of 100 milliseconds. Defaults to 10 (1 second).
* </p>
*/
private static final int INTERVAL = 10;
// Media Components
private final Player player;
private final Playlist playlist;
// Play operation
private final long duration;
private final long interval;
public PlayAnnouncement(Player player, int requestId, NotifiedEntity notifiedEntity, Map<String, String> parameters) {
super(AudioPackage.PACKAGE_NAME, SIGNAL, SignalType.TIME_OUT, requestId, notifiedEntity, parameters);
// Setup Play Parameters
this.duration = getDuration();
this.interval = getInterval();
this.playlist = new Playlist(getSegments(), getIterations());
// Media Player
this.player = player;
this.player.setDuration(this.duration);
this.player.setMediaTime(0);
try {
this.player.addListener(this);
} catch (TooManyListenersException e) {
log.error("Too many listeners for audio player", e);
}
}
public PlayAnnouncement(Player player, int requestId, Map<String, String> parameters) {
this(player, requestId, null, parameters);
}
private String[] getSegments() {
return Optional.fromNullable(getParameter(SignalParameters.ANNOUNCEMENT.symbol())).or("").split(",");
}
private int getIterations() {
return Optional.fromNullable(getParameter(SignalParameters.ITERATIONS.symbol()))
.transform(new Function<String, Integer>() {
@Override
public Integer apply(String input) {
try {
return (input == null || input.isEmpty()) ? null : Integer.parseInt(input);
} catch (Exception e) {
log.error(
"Could not parse ITERATIONS=" + input + " to integer. Using default value: " + ITERATIONS);
return null;
}
}
}).or(ITERATIONS);
}
private long getDuration() {
return Optional.fromNullable(getParameter(SignalParameters.DURATION.symbol())).transform(new Function<String, Long>() {
@Override
public Long apply(String input) {
try {
return (input == null || input.isEmpty()) ? null : Long.parseLong(input);
} catch (Exception e) {
log.error("Could not parse DURATION=" + input + " to long. Duration will be track length.");
return null;
}
}
}).or(-1L);
}
private long getInterval() {
return Optional.fromNullable(getParameter(SignalParameters.INTERVAL.symbol()))
.transform(new Function<String, Integer>() {
@Override
public Integer apply(String input) {
try {
return (input == null || input.isEmpty()) ? null : Integer.parseInt(input);
} catch (Exception e) {
log.error("Could not parse INTERVAL=" + input + " to long. Using default value: " + INTERVAL);
return null;
}
}
}).or(INTERVAL) * 1000000L;
}
@Override
protected boolean isParameterSupported(String name) {
// Check if parameter is valid
SignalParameters parameter = SignalParameters.fromSymbol(name);
if (parameter == null) {
return false;
}
// Check if parameter is supported
switch (parameter) {
case ANNOUNCEMENT:
case ITERATIONS:
case INTERVAL:
case DURATION:
case SPEED:
case VOLUME:
return true;
default:
return false;
}
}
private void playAnnouncement(String segment, long delay) {
if (log.isDebugEnabled()) {
log.debug("Playing announcement " + segment);
}
try {
this.player.setInitialDelay(delay);
this.player.setURL(segment);
this.player.activate();
} catch (MalformedURLException e) {
log.error("Cannot play audio track. Malformed URL: " + segment);
fireOF(ReturnCode.BAD_AUDIO_ID.code());
} catch (ResourceUnavailableException e) {
fireOF(ReturnCode.MISMATCH_BETWEEN_PLAY_SPECIFICATION_AND_PROVISIONED_DATA.code());
}
}
@Override
public void execute() {
if (this.executing.getAndSet(true)) {
throw new IllegalStateException("Already executing.");
}
// Play announcements
String announcement = this.playlist.next();
if (announcement == null || announcement.isEmpty()) {
this.executing.set(false);
fireOF(ReturnCode.BAD_AUDIO_ID.code());
} else {
playAnnouncement(announcement, 0);
}
}
@Override
public void cancel() {
if (this.executing.getAndSet(false)) {
this.player.deactivate();
}
}
@Override
public void process(PlayerEvent event) {
switch (event.getID()) {
case PlayerEvent.STOP:
if (log.isDebugEnabled()) {
log.debug("Announcement " + this.playlist.current() + " has completed.");
}
String announcement = this.playlist.next();
if (announcement.isEmpty() || !isExecuting()) {
this.player.removeListener(this);
fireOC(ReturnCode.SUCCESS.code());
} else {
playAnnouncement(announcement, this.interval);
}
break;
case PlayerEvent.FAILED:
if (this.executing.getAndSet(false)) {
this.player.removeListener(this);
fireOF(ReturnCode.UNSPECIFIED_FAILURE.code());
}
break;
default:
// Ignore other event types
break;
}
}
private void fireOC(int code) {
notify(this, new OperationComplete(getSymbol(), code));
}
private void fireOF(int code) {
notify(this, new OperationFailed(getSymbol(), code));
}
}