/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.resource.player.audio;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.log4j.Logger;
import org.restcomm.media.ComponentType;
import org.restcomm.media.component.AbstractSource;
import org.restcomm.media.component.audio.AudioInput;
import org.restcomm.media.resource.player.Track;
import org.restcomm.media.resource.player.audio.gsm.GsmTrackImpl;
import org.restcomm.media.resource.player.audio.mpeg.AMRTrackImpl;
import org.restcomm.media.resource.player.audio.tone.ToneTrackImpl;
import org.restcomm.media.resource.player.audio.tts.TtsTrackImpl;
import org.restcomm.media.resource.player.audio.wav.WavTrackImpl;
import org.restcomm.media.scheduler.PriorityQueueScheduler;
import org.restcomm.media.spi.ResourceUnavailableException;
import org.restcomm.media.spi.dsp.Processor;
import org.restcomm.media.spi.format.AudioFormat;
import org.restcomm.media.spi.format.FormatFactory;
import org.restcomm.media.spi.listener.Listeners;
import org.restcomm.media.spi.listener.TooManyListenersException;
import org.restcomm.media.spi.memory.Frame;
import org.restcomm.media.spi.player.Player;
import org.restcomm.media.spi.player.PlayerListener;
import org.restcomm.media.spi.pooling.PooledObject;
import org.restcomm.media.spi.resource.TTSEngine;
/**
* @author yulian oifa
* @author Henrique Rosa (henrique.rosa@telestax.com)
*/
public class AudioPlayerImpl extends AbstractSource implements Player, TTSEngine, PooledObject {
private static final long serialVersionUID = 8321615909592642344L;
private final static Logger log = Logger.getLogger(AudioPlayerImpl.class);
// define natively supported formats
private final static AudioFormat LINEAR = FormatFactory.createAudioFormat("linear", 8000, 16, 1);
private final static long period = 20000000L;
private final static int packetSize = (int) (period / 1000000) * LINEAR.getSampleRate() / 1000 * LINEAR.getSampleSize() / 8;
// Media Components
private Processor dsp;
private final AudioInput input;
// audio track
private Track track;
private int volume;
private String voiceName = "kevin";
// Listeners
private final Listeners<PlayerListener> listeners;
private final RemoteStreamProvider remoteStreamProvider;
/**
* Creates new instance of the Audio player.
*
* @param name the name of the AudioPlayer to be created.
* @param scheduler EDF job scheduler
* @param vc the TTS voice cache.
*/
public AudioPlayerImpl(String name, PriorityQueueScheduler scheduler, RemoteStreamProvider remoteStreamProvider) {
super(name, scheduler, PriorityQueueScheduler.INPUT_QUEUE);
this.input = new AudioInput(ComponentType.PLAYER.getType(), packetSize);
this.listeners = new Listeners<PlayerListener>();
this.connect(this.input);
this.remoteStreamProvider = remoteStreamProvider;
}
public AudioInput getAudioInput() {
return this.input;
}
/**
* Assigns the digital signaling processor of this component. The DSP allows to get more output formats.
*
* @param dsp the dsp instance
*/
public void setDsp(Processor dsp) {
this.dsp = dsp;
}
/**
* Gets the digital signaling processor associated with this media source
*
* @return DSP instance.
*/
public Processor getDsp() {
return this.dsp;
}
@Override
public void setURL(String passedURI) throws ResourceUnavailableException, MalformedURLException {
// close previous track if was opened
if (this.track != null) {
track.close();
track = null;
}
// let's disallow to assign file is player is not connected
if (!this.isConnected()) {
throw new IllegalStateException("Component should be connected");
}
URL targetURL;
// now using extension we have to determine the suitable stream parser
int pos = passedURI.lastIndexOf('.');
// extension is not specified?
if (pos == -1) {
throw new MalformedURLException("Unknow file type: " + passedURI);
}
String ext = passedURI.substring(pos + 1).toLowerCase();
targetURL = new URL(passedURI);
// creating required extension
try {
// check scheme, if its file, we should try to create dirs
if (ext.matches(Extension.WAV)) {
track = new WavTrackImpl(targetURL, remoteStreamProvider);
} else if (ext.matches(Extension.GSM)) {
track = new GsmTrackImpl(targetURL);
} else if (ext.matches(Extension.TONE)) {
track = new ToneTrackImpl(targetURL);
} else if (ext.matches(Extension.TXT)) {
track = new TtsTrackImpl(targetURL, voiceName, null);
} else if (ext.matches(Extension.MOV) || ext.matches(Extension.MP4) || ext.matches(Extension.THREE_GP)) {
track = new AMRTrackImpl(targetURL);
} else {
throw new ResourceUnavailableException("Unknown extension: " + passedURI);
}
} catch (Exception e) {
throw new ResourceUnavailableException(e);
}
// update duration
this.duration = track.getDuration();
}
@Override
public void activate() {
if (track == null) {
throw new IllegalStateException("The media source is not specified");
}
start();
listeners.dispatch(new AudioPlayerEvent(this, AudioPlayerEvent.START));
}
@Override
public void deactivate() {
stop();
if (track != null) {
track.close();
track = null;
}
}
@Override
protected void stopped() {
listeners.dispatch(new AudioPlayerEvent(this, AudioPlayerEvent.STOP));
}
@Override
protected void completed() {
super.completed();
listeners.dispatch(new AudioPlayerEvent(this, AudioPlayerEvent.STOP));
}
@Override
public Frame evolve(long timestamp) {
try {
// Null check is necessary when a DTMF detector stops the announcement earlier causing the player
// to stop and the track to null
if (track == null) {
return null;
}
Frame frame = track.process(timestamp);
if (frame == null)
return null;
frame.setTimestamp(timestamp);
if (frame.isEOM()) {
if(log.isInfoEnabled()) {
log.info("End of file reached");
}
}
// do the transcoding job
if (dsp != null) {
try {
frame = dsp.process(frame, frame.getFormat(), LINEAR);
} catch (Exception e) {
// transcoding error , print error and try to move to next frame
log.error(e);
}
}
if (frame.isEOM() && track != null) {
track.close();
}
return frame;
} catch (IOException e) {
log.error(e);
if (track != null) {
track.close();
}
}
return null;
}
@Override
public void setVoiceName(String voiceName) {
this.voiceName = voiceName;
}
@Override
public String getVoiceName() {
return voiceName;
}
@Override
public void setVolume(int volume) {
this.volume = volume;
}
@Override
public int getVolume() {
return volume;
}
@Override
public void setText(String text) {
track = new TtsTrackImpl(text, voiceName, null);
}
@Override
public void addListener(PlayerListener listener) throws TooManyListenersException {
listeners.add(listener);
}
@Override
public void removeListener(PlayerListener listener) {
listeners.remove(listener);
}
@Override
public void clearAllListeners() {
listeners.clear();
}
@Override
public void checkIn() {
clearAllListeners();
track = null;
}
@Override
public void checkOut() {
// TODO Auto-generated method stub
}
}