All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mobicents.javax.media.mscontrol.mediagroup.PlayerImpl Maven / Gradle / Ivy

package org.mobicents.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.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.media.mscontrol.MediaErr;
import javax.media.mscontrol.MediaEventListener;
import javax.media.mscontrol.MediaSession;
import javax.media.mscontrol.MsControlException;
import javax.media.mscontrol.Parameters;
import javax.media.mscontrol.Qualifier;
import javax.media.mscontrol.UnsupportedException;
import javax.media.mscontrol.Value;
import javax.media.mscontrol.mediagroup.MediaGroup;
import javax.media.mscontrol.mediagroup.Player;
import javax.media.mscontrol.mediagroup.PlayerEvent;
import javax.media.mscontrol.resource.RTC;

import org.mobicents.javax.media.mscontrol.spi.DriverImpl;

import org.mobicents.fsm.FSM;
import org.mobicents.fsm.Logger;
import org.mobicents.fsm.State;
import org.mobicents.fsm.StateEventHandler;
import org.mobicents.fsm.TransitionHandler;
import org.mobicents.fsm.UnknownTransitionException;
import org.mobicents.jsr309.mgcp.PackageAU;

/**
 * b
 * @author amit bhayani
 * 
 */
public class PlayerImpl implements Player, JainMgcpListener, Logger {

    public final static String STATE_NULL = "NULL";
    public final static String STATE_IDLE = "IDLE";
    public final static String STATE_ACTIVE = "ACTIVE";
    public final static String STATE_ACTIVATING = "ACTIVATING";
    public final static String STATE_PAUSED = "PAUSED";
    public final static String STATE_INVALID = "INVALID";
    
    public final static String SIGNAL_CREATE = "CREATE";
    public final static String SIGNAL_PLAY = "PLAY";
    public final static String SIGNAL_STARTED = "STARTED";
    public final static String SIGNAL_FAILED = "FAILED";
    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_PLAY_COMPLETED = "PLAY_COMPLETED";
    public final static String SIGNAL_RELEASE = "RELEASE";
    
    protected MediaGroupImpl parent = null;
    private FSM fsm;
    
    protected CopyOnWriteArrayList> listeners = new CopyOnWriteArrayList>();
    
    protected String uri;
    private String params;
    private String returnCode;
    private Qualifier qualifier;
    
    private ReentrantLock lock = new ReentrantLock();
    private Condition started = lock.newCondition();

    private long startTime;
    private long timeError = 0;    
    private long stopTime;
    private long reqDuration;
    
    private ConcurrentLinkedQueue playList = new ConcurrentLinkedQueue();
    
    private MgcpSender mgcpSender;
    
    //-------------------------------------------------------------------------------------------/
    
    protected PlayerImpl(MediaGroupImpl parent) throws MsControlException {
        this.parent = parent;
        mgcpSender=new MgcpSender();
        this.initFSM();
    }

    private void initFSM() {
        fsm = new FSM(parent.getMediaSession().getDriver().getScheduler());
        fsm.setLogger(this);
        
        fsm.createState(STATE_NULL);
        fsm.createState(STATE_IDLE);
        fsm.createState(STATE_ACTIVATING);
        fsm.createState(STATE_ACTIVE).setOnEnter(new OnStart());
        fsm.createState(STATE_PAUSED);
        fsm.createState(STATE_INVALID);
        
        fsm.setStart(STATE_NULL);
        fsm.setEnd(STATE_INVALID);
        
        //state NULL
        fsm.createTransition(SIGNAL_CREATE, STATE_NULL, STATE_IDLE);
        fsm.createTransition(SIGNAL_RELEASE, STATE_NULL, STATE_INVALID);
        
        //state IDLE
        //sending request to server and waiting response
        fsm.createTransition(SIGNAL_PLAY, STATE_IDLE, STATE_ACTIVATING).setHandler(new PlayRequest());
        //player started in suspended mode, nothing is sent to server in this case
        fsm.createTransition(SIGNAL_START_PAUSED, STATE_IDLE, STATE_PAUSED);
        //player was stopped during activation, ask to stop it now
        //TODO: Handle this transition!
        //fsm.createTransition(SIGNAL_STARTED, STATE_IDLE, STATE_IDLE).setHandler(new StopRequest());
        //Player release, silently go to INVALID state
        fsm.createTransition(SIGNAL_RELEASE, STATE_IDLE, STATE_INVALID);

        //state ACTIVATNG
        //server said - ok
        fsm.createTransition(SIGNAL_STARTED, STATE_ACTIVATING, STATE_ACTIVE);
        //server said follow to the hand. 
        fsm.createTransition(SIGNAL_FAILED, STATE_ACTIVATING, STATE_IDLE);
        //user asks to stop player         
        fsm.createTransition(SIGNAL_STOP, STATE_ACTIVATING, STATE_IDLE);
        //playback failure
        fsm.createTransition("FAILURE", STATE_ACTIVE, STATE_IDLE).setHandler(new PlayFailure(this)); 
        //Player has been released. Do not forget to notify server
        fsm.createTransition(SIGNAL_RELEASE, STATE_ACTIVATING, STATE_INVALID).setHandler(new StopRequest(this));
        
        //state ACTIVE
        //play finishes normaly
        fsm.createTransition(SIGNAL_PLAY_COMPLETED, STATE_ACTIVE, STATE_IDLE).setHandler(new PlayCompleted(this));
        //user asks to stop player
        fsm.createTransition(SIGNAL_STOP, STATE_ACTIVE, STATE_IDLE).setHandler(new StopRequest(this));
        //Player has been released. Do not forget to notify server
        fsm.createTransition(SIGNAL_RELEASE, STATE_ACTIVE, STATE_INVALID).setHandler(new StopRequest(this));

        //state PAUSED
        //user ask to stop player but it was in puased mode so no need to notify server
        fsm.createTransition(SIGNAL_STOP, STATE_PAUSED, STATE_IDLE);
        //this transition means that some trigger activated. do not send anything more to server
        fsm.createTransition(SIGNAL_RESUME, STATE_PAUSED, STATE_ACTIVE);
        //Player has been released.
        fsm.createTransition(SIGNAL_RELEASE, STATE_INVALID, STATE_INVALID);
        
        try {
            fsm.signal(SIGNAL_CREATE);
        } catch (UnknownTransitionException e) {
        }
        
    }
    


    // Player methods
    @SuppressWarnings("static-access")
    public void play(URI[] uris, RTC[] rtc, Parameters params) throws MsControlException {        
        if (rtc != null) {
            verifyRTC(rtc);
        }
        
        this.checkURI(uris);
        PlayTask task = new PlayTask(uris, rtc, params);
        
        //user calls play during the current work?
        if (fsm.getState().getName().equals(STATE_IDLE)) {
            playList.offer(task);
        } else {
            //no specific behaviour?
            if (params == null) {
                throw new MsControlException("Busy now: state=" + fsm.getState());
            }
            
            Value behaviour = (Value) params.get(Player.BEHAVIOUR_IF_BUSY);
            playList.offer(task);

            if (Player.FAIL_IF_BUSY.equals(behaviour)) {
                //queue task and send stop
                throw new MsControlException("Busy now");
            }
            
            if (Player.STOP_IF_BUSY.equals(behaviour)) {
                //queue task and send stop
                try {
                    fsm.signal(SIGNAL_STOP);
                } catch (UnknownTransitionException e) {
                }
            }
        }

        new Thread(new Starter()).start();
        
        lock.lock();
        try {
            try {
                started.await();
            } catch (InterruptedException e) {
            }
        } finally {
            lock.unlock();
        }        
    }

    @SuppressWarnings("static-access")
    public void play(URI uri, RTC[] rtc, Parameters params) throws MsControlException {
        play(new URI[]{uri}, rtc, params);
    }


    // Resource Methods
    public MediaGroup getContainer() {
        return this.parent;
    }

    // MediaEventNotifier methods
    public void addListener(MediaEventListener listener) {
        this.listeners.add(listener);
    }

    public void removeListener(MediaEventListener listener) {
        this.listeners.remove(listener);
    }

    public MediaSession getMediaSession() {
        return this.parent.getMediaSession();
    }

    protected void update(PlayerEvent anEvent) {
    }

    private void checkURI(URI[] uris) throws MsControlException {
        if (uris == null) {
            throw new MsControlException("URI[] cannot be null");
        }

        for (URI uri : uris) {

            if (uri == null) {
                throw new MsControlException("URI cannot be null");
            }

            if (uri.getScheme().equalsIgnoreCase("data")) {
                continue;
            }
        }
    }

    public void stop(boolean stopAll) {
        try {
            fsm.signal(SIGNAL_STOP);
        } catch (UnknownTransitionException e) {
        }
    }
    
    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");
            }
        }
    }
    
    protected void fireEvent(PlayerEvent event) {
        new Thread(new EventSender(event)).start();
    }
    
    
    private String createParams(URI[] uris, Parameters params) {
        StringBuilder buff = new StringBuilder();
        
        //add URI list of the player
        if (uris.length == 1) {
            buff.append("an=").append(uris[0].toString());
        } else {
            buff.append("an=").append(uris[0].toString());
            for (int i = 1; i < uris.length; i++) {
                buff.append(";");
                buff.append(uris[i].toString());
            }
        }
        
        this.qualifier = PlayerEvent.END_OF_PLAY_LIST;
        
        //max duraion if specified
        if (params != null && params.containsKey(Player.MAX_DURATION)) {
            long du = ((Integer)params.get(Player.MAX_DURATION));
            buff.append(" ");
            buff.append("du=").append(du);
            
            if (du > 0) {
                this.qualifier = PlayerEvent.DURATION_EXCEEDED;
            }
            
            this.reqDuration = du;
        }

        //start offset if specified
        if (params != null && params.containsKey(Player.START_OFFSET)) {
            buff.append(" ");
            buff.append("of=").append(params.get(Player.START_OFFSET));
        }
        
        //iterations if specified
        if (params != null && params.containsKey(Player.REPEAT_COUNT)) {
            int it = 0;
            
            if (params.get(Player.REPEAT_COUNT) instanceof Integer) {
                try {
                    it = ((Integer)params.get(Player.REPEAT_COUNT));
                } catch (Exception e) {
                }
            }
            
            if (it < 0) it = 0;
            buff.append(" ");
            buff.append("it=").append(it);
        }

        //intervals if specified
        if (params != null && params.containsKey(Player.INTERVAL)) {
            buff.append(" ");
            buff.append("iv=").append(params.get(Player.INTERVAL));
        }
        
        return buff.toString();
    }
    
    private void requestAnnouncement() {
        //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);
        
        RequestedAction[] actions = new RequestedAction[] { RequestedAction.NotifyImmediately };

        ArrayList signalList = new ArrayList();
        ArrayList eventList = new ArrayList();

        //player signals     
        signalList.add(new EventName(PackageAU.Name, PackageAU.pa.withParm(params)));

        //player events
        eventList.add(new RequestedEvent(new EventName(PackageAU.Name, MgcpEvent.oc), actions));
        eventList.add(new RequestedEvent(new EventName(PackageAU.Name, MgcpEvent.of), actions));
        
        EventName[] signals = new EventName[signalList.size()];        
        signalList.toArray(signals);
        
        RequestedEvent[] events = new RequestedEvent[eventList.size()];
        eventList.toArray(events);
        
        //new EventName(auPackageName, MgcpEvent.oc, connId)        
        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 requestStop() {
        //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);
    }
    
    /**
     * Fires event.
     * 
     * @param evt the event to be fired
     */
    private void fireEvent(EventName eventName) {
        this.returnCode = eventName.getEventIdentifier().getParms();
        switch (eventName.getEventIdentifier().intValue()) {
            case MgcpEvent.REPORT_ON_COMPLETION :
                try {
                    fsm.signal(SIGNAL_PLAY_COMPLETED);
                } catch (UnknownTransitionException e) {
                    e.printStackTrace();
                }
                break;
            case MgcpEvent.REPORT_FAILURE :
                try {
                    fsm.signal("FAILURE");
                } catch (UnknownTransitionException e) {
                    e.printStackTrace();
                }
                break;            
        }
        
    }
    
    public void stopCompleted()
    {
    	if(mgcpSender.waiting)
    		mgcpSender.run();
    }
    
    private class PlayRequest implements TransitionHandler {

        public void process(State state) {
            requestAnnouncement();
        }
        
    }

    private class PlayCompleted implements TransitionHandler {
        private PlayerImpl player;
        
        protected PlayCompleted(PlayerImpl player) {
            this.player = player;
        }
        
        public void process(State state) {
            stopTime = System.currentTimeMillis();
            if (Math.abs(stopTime - startTime - reqDuration) > 1000) {
                qualifier = PlayerEvent.END_OF_PLAY_LIST;
            }
            
            PlayerEventImpl evt = new PlayerEventImpl(player, PlayerEvent.PLAY_COMPLETED, true, qualifier, null);
            evt.setOffset((int)(stopTime - startTime));
            fireEvent(evt);
            
            if (!playList.isEmpty()) {
                new Thread(new Starter()).start();
            }
        }       
    }

    private class PlayFailure implements TransitionHandler {
        private PlayerImpl player;
        
        protected PlayFailure(PlayerImpl player) {
            this.player = player;
        }
        
        public void process(State state) {
            MediaErr error = null;
            
            if (returnCode.equals("rc=301")) {
                error = MediaErr.BAD_ARG;
            } else if (returnCode.equals("rc=312")) {
                error = MediaErr.NOT_FOUND;
            }
            
            stopTime = System.currentTimeMillis();
            PlayerEventImpl evt = new PlayerEventImpl(player, PlayerEvent.PLAY_COMPLETED, false, PlayerEvent.NO_QUALIFIER, null, error, "");
            evt.setOffset((int)(stopTime - startTime));
            fireEvent(evt);
            
            if (!playList.isEmpty()) {
                new Thread(new Starter()).start();
            }
        }
        
    }
    
    private class StopRequest implements TransitionHandler {
        private PlayerImpl player;
        
        protected StopRequest(PlayerImpl player) {
            this.player = player;
        }

        public void process(State state) {
            requestStop();
            PlayerEventImpl evt = new PlayerEventImpl(player, PlayerEvent.PLAY_COMPLETED, true, PlayerEvent.STOPPED, null);
            evt.setOffset((int)(stopTime - startTime));
            fireEvent(evt);
        }        
    }    
    
    private class OnStart implements StateEventHandler {

        public void onEvent(State state) {
            startTime = System.currentTimeMillis() + timeError;
            //notify that player started
            lock.lock();
            try {
                started.signal();
            } finally {
                lock.unlock();
            }
        }
        
    }

    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_STARTED);
                } catch (UnknownTransitionException e) {
                }
                break;
            default :
                try {
                    fsm.signal(SIGNAL_FAILED);
                } catch (UnknownTransitionException e) {                	
                }
        }
    }
    
    private class PlayTask {
        private URI[] uris;
        private RTC[] rtc;
        private Parameters params;
        
        protected PlayTask(URI[] uris, RTC[] rtc, Parameters params) {
            this.uris = uris;
            this.rtc = rtc;
            this.params = params;
        }
    }
    
    private class Starter implements Runnable {

        public void run() {
            PlayTask task = playList.poll();
            params = createParams(task.uris, task.params);
            
            try {
            	fsm.signal(SIGNAL_PLAY);
            } catch (UnknownTransitionException e) {
            }
        }
        
    }

    private class EventSender implements Runnable {
        
        private PlayerEvent evt;
        
        public EventSender(PlayerEvent evt) {
            this.evt = evt;
        }
        
        public void run() {
        	//needed to disable Busy Now Errors.
        	try{ 
                Thread.sleep(2);
             } catch( InterruptedException e ) {                 
             }

             
            for (MediaEventListener l : listeners) {
                l.onEvent(evt);
            }
        }
    }
    
    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;
        }
    }
    
    public void info(String s) {
        parent.info(s);
    }

    public void debug(String s) {
        parent.debug(s);
    }

    public void warn(String s) {
        parent.warn(s);
    }    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy