/**
* Copyright 2010-2011 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.remote.impl.media;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.rayo.core.verb.SeekCommand;
import com.rayo.core.verb.SeekCommand.Direction;
import com.rayo.core.verb.VerbCompleteReason;
import com.rayo.core.verb.VerbRef;
import com.voxeo.moho.MediaException;
import com.voxeo.moho.common.event.MohoOutputCompleteEvent;
import com.voxeo.moho.common.util.SettableResultFuture;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.event.OutputCompleteEvent;
import com.voxeo.moho.media.Output;
import com.voxeo.moho.media.output.OutputCommand;
import com.voxeo.moho.remote.impl.JID;
import com.voxeo.moho.remote.impl.MediaServiceSupport;
import com.voxeo.moho.remote.impl.RayoListener;
import com.voxeo.rayo.client.XmppException;
import com.voxeo.rayo.client.xmpp.stanza.IQ;
import com.voxeo.rayo.client.xmpp.stanza.Presence;
//TODO exception and IQ error handling
public class OutputImpl<T extends EventSource> implements Output<T>, RayoListener {
private static final Logger LOG = Logger.getLogger(OutputImpl.class);
protected SettableResultFuture<OutputCompleteEvent<T>> _future = new SettableResultFuture<OutputCompleteEvent<T>>();
final Lock lock = new ReentrantLock();
protected VerbRef _verbRef;
protected MediaServiceSupport<T> _call;
protected OutputCommand _next;
protected T _todo;
protected boolean paused;
public OutputImpl(final VerbRef verbRef, final OutputCommand next, final MediaServiceSupport<T> call, T todo) {
_verbRef = verbRef;
_call = call;
_next = next;
_todo = todo;
_call.addComponentListener(_verbRef.getVerbId(), this);
}
public OutputImpl(final VerbRef verbRef, final MediaServiceSupport<T> call, T todo) {
this(verbRef, null, call, todo);
}
@SuppressWarnings("unchecked")
protected void done(final OutputCompleteEvent<T> event) {
_call.removeComponentListener(_verbRef.getVerbId());
if (_next == null) {
_future.setResult(event);
}
else {
//avoid dead wait on output
_call.getMohoRemote().getExecutor().execute(new Runnable() {
@Override
public void run() {
OutputImpl<T> outputImpl = ((OutputImpl<T>) _call.output(_next));
_verbRef = outputImpl._verbRef;
_next = outputImpl._next;
_call.addComponentListener(_verbRef.getVerbId(), OutputImpl.this);
}
});
}
}
protected void done(final MediaException exception) {
_future.setException(exception);
_call.removeComponentListener(_verbRef.getVerbId());
}
@Override
public void pause() {
if (!_future.isDone() && !paused) {
try {
IQ iq = _call.getMohoRemote().getRayoClient().pause(_verbRef);
if (iq.isError()) {
com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
throw new MediaException(error.getCondition() + error.getText());
}
else {
paused = true;
}
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public void resume() {
if (!_future.isDone() && paused) {
try {
IQ iq = _call.getMohoRemote().getRayoClient().resume(_verbRef);
if (iq.isError()) {
com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
throw new MediaException(error.getCondition() + error.getText());
}
else {
paused = false;
}
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public void stop() {
if (!_future.isDone()) {
try {
IQ iq = _call.getMohoRemote().getRayoClient().stop(_verbRef);
if (iq.isError()) {
com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
throw new MediaException(error.getCondition() + error.getText());
}
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public synchronized void jump(final int index) {
// TODO rayo client doesn't support this.
}
@Override
public synchronized void move(final boolean direction, final long time) {
if (!_future.isDone()) {
try {
SeekCommand command = new SeekCommand();
if (direction) {
command.setDirection(Direction.FORWARD);
command.setAmount((int) (time / 1000));
}
else {
command.setDirection(Direction.BACK);
command.setAmount((int) (time / 1000));
}
_call.getMohoRemote().getRayoClient().seek(_verbRef, command);
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public void speed(final boolean upOrDown) {
if (!_future.isDone()) {
try {
IQ iq = null;
if (upOrDown) {
iq = _call.getMohoRemote().getRayoClient().speedUp(_verbRef);
}
else {
iq = _call.getMohoRemote().getRayoClient().speedDown(_verbRef);
}
if (iq.isError()) {
com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
LOG.error(error.getCondition() + error.getText());
}
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public void volume(final boolean upOrDown) {
if (!_future.isDone()) {
try {
IQ iq = null;
if (upOrDown) {
iq = _call.getMohoRemote().getRayoClient().volumeUp(_verbRef);
}
else {
iq = _call.getMohoRemote().getRayoClient().volumeDown(_verbRef);
}
if (iq.isError()) {
com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
throw new MediaException(error.getCondition() + error.getText());
}
}
catch (XmppException e) {
LOG.error("", e);
throw new MediaException(e);
}
}
}
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
return _future.cancel(mayInterruptIfRunning);
}
@Override
public OutputCompleteEvent<T> get() throws InterruptedException, ExecutionException {
return _future.get();
}
@Override
public OutputCompleteEvent<T> get(final long timeout, final TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return _future.get(timeout, unit);
}
@Override
public boolean isCancelled() {
return _future.isCancelled();
}
@Override
public boolean isDone() {
return _future.isDone();
}
@Override
public void onRayoEvent(JID from, Presence presence) {
LOG.debug("OutputImpl Recived presence, processing:" + presence);
Object obj = presence.getExtension().getObject();
if (obj instanceof com.rayo.core.verb.VerbCompleteEvent) {
com.rayo.core.verb.VerbCompleteEvent event = (com.rayo.core.verb.VerbCompleteEvent) obj;
MohoOutputCompleteEvent<T> mohoEvent = new MohoOutputCompleteEvent<T>(_todo,
getMohoOutputCompleteReasonByRayoReason(event.getReason()), event.getErrorText(), this);
this.done(mohoEvent);
if (_next == null) {
_call.dispatch(mohoEvent);
}
}
else {
LOG.error("OutputImpl Can't process presence:" + presence);
}
}
@Override
public void onRayoCommandResult(JID from, IQ iq) {
LOG.warn("Unprocessed IQ:"+iq);
}
protected OutputCompleteEvent.Cause getMohoOutputCompleteReasonByRayoReason(VerbCompleteReason reason) {
if (reason instanceof com.rayo.core.verb.OutputCompleteEvent.Reason) {
if (reason == com.rayo.core.verb.OutputCompleteEvent.Reason.SUCCESS) {
return OutputCompleteEvent.Cause.END;
}
}
else if (reason instanceof com.rayo.core.verb.VerbCompleteEvent.Reason) {
if (reason == com.rayo.core.verb.VerbCompleteEvent.Reason.HANGUP) {
return OutputCompleteEvent.Cause.DISCONNECT;
}
else if (reason == com.rayo.core.verb.VerbCompleteEvent.Reason.STOP) {
return OutputCompleteEvent.Cause.CANCEL;
}
else if (reason == com.rayo.core.verb.VerbCompleteEvent.Reason.ERROR) {
return OutputCompleteEvent.Cause.ERROR;
}
}
return OutputCompleteEvent.Cause.ERROR;
}
public String getVerbId() {
return _verbRef.getVerbId();
}
}