/*
* (C) Copyright 2013 Kurento (http://kurento.org/)
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library 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.
*
*/
package com.kurento.kmf.content.internal.base;
import javax.servlet.AsyncContext;
import com.kurento.kmf.common.exception.Assert;
import com.kurento.kmf.common.exception.KurentoMediaFrameworkException;
import com.kurento.kmf.content.ContentHandler;
import com.kurento.kmf.content.ContentSession;
import com.kurento.kmf.content.SdpContentSession;
import com.kurento.kmf.content.internal.ContentSessionManager;
import com.kurento.kmf.content.jsonrpc.JsonRpcRequest;
import com.kurento.kmf.content.jsonrpc.JsonRpcResponse;
import com.kurento.kmf.media.MediaElement;
import com.kurento.kmf.media.MediaPipeline;
import com.kurento.kmf.media.PlayerEndpoint;
import com.kurento.kmf.media.RecorderEndpoint;
import com.kurento.kmf.media.SdpEndpoint;
import com.kurento.kmf.media.events.ErrorEvent;
import com.kurento.kmf.media.events.MediaEventListener;
import com.kurento.kmf.media.events.MediaSessionStartedEvent;
import com.kurento.kmf.media.events.MediaSessionTerminatedEvent;
import com.kurento.kmf.repository.RepositoryHttpPlayer;
import com.kurento.kmf.repository.RepositoryHttpRecorder;
import com.kurento.kmf.repository.RepositoryItem;
/**
*
* Extension of Content Request to support encapsulated SDP in requests.
*
* @author Luis López (llopez@gsyc.es)
* @version 1.0.0
*/
public abstract class AbstractSdpContentSession extends AbstractContentSession
implements SdpContentSession {
/**
* Parameterized constructor; initial state here is HANDLING.
*
* @param manager
* Content request manager
* @param asyncContext
* Asynchronous context
* @param contentId
* Content identifier
*/
private RepositoryHttpPlayer repositoryHttpPlayer;
private RepositoryHttpRecorder repositoryHttpRecorder;
public AbstractSdpContentSession(
ContentHandler<? extends ContentSession> handler,
ContentSessionManager manager, AsyncContext asyncContext,
String contentId) {
super(handler, manager, asyncContext, contentId);
}
@Override
protected void processStartJsonRpcRequest(AsyncContext asyncCtx,
JsonRpcRequest message) {
Assert.notNull(
initialJsonRequest.getParams().getSdp(),
"SDP cannot be null on message with method "
+ message.getMethod(), 10024);
super.processStartJsonRpcRequest(asyncCtx, message);
}
/**
* Build an end point for SDP declaration in a MediaPipeline.
*
* @param mediaPipeline
* a MediaPipeline for which the SdpEndpoint is created
* @return a SdpEndpoint
* @throws Throwable
* Error/Exception
*/
protected abstract SdpEndpoint buildSdpEndpoint(MediaPipeline mediaPipeline);
/**
* Star media element implementation.
*
* @param sourceContentPath
* Path of outgoing media element
* @param sinkContentPath
* Path of ingoing media element
* @throws KurentoMediaFrameworkException
* Exception while sending an SDP answer to client
*/
@Override
public void start(String sourceContentPath, String sinkContentPath) {
try {
Assert.isTrue(
sourceContentPath != null || sinkContentPath != null,
"Cannot invoke start specifying null source and sink content paths",
1); // TODO
goToState(
STATE.STARTING,
"Cannot start SdpEndPoint in state "
+ getState()
+ ". This is probably due to an explicit session termination comming from another thread",
1); // TODO
internalStart(sourceContentPath, sinkContentPath);
} catch (KurentoMediaFrameworkException ke) {
internalTerminateWithError(null, ke.getCode(), ke.getMessage(),
null);
throw ke;
} catch (Throwable t) {
KurentoMediaFrameworkException kmfe = new KurentoMediaFrameworkException(
t.getMessage(), t, 20029);
internalTerminateWithError(null, kmfe.getCode(), kmfe.getMessage(),
null);
throw kmfe;
}
}
@Override
public void start(RepositoryItem sourceRepositoryItem,
RepositoryItem sinkRepositoryItem) {
try {
Assert.isTrue(
sourceRepositoryItem != null || sinkRepositoryItem != null,
"Cannot invoke start specifying null source and sink content paths",
1); // TODO
goToState(
STATE.STARTING,
"Cannot start SdpEndPoint in state "
+ getState()
+ ". This is probably due to an explicit session termination comming from another thread",
1); // TODO
if (sourceRepositoryItem != null) {
repositoryHttpPlayer = sourceRepositoryItem
.createRepositoryHttpPlayer();
}
if (sinkRepositoryItem != null) {
repositoryHttpRecorder = sinkRepositoryItem
.createRepositoryHttpRecorder();
}
String sourceContentPath = sourceRepositoryItem == null ? null
: repositoryHttpPlayer.getURL();
String sinkContentPath = sinkRepositoryItem == null ? null
: repositoryHttpRecorder.getURL();
internalStart(sourceContentPath, sinkContentPath);
} catch (KurentoMediaFrameworkException ke) {
internalTerminateWithError(null, ke.getCode(), ke.getMessage(),
null);
throw ke;
} catch (Throwable t) {
KurentoMediaFrameworkException kmfe = new KurentoMediaFrameworkException(
t.getMessage(), t, 20029);
internalTerminateWithError(null, kmfe.getCode(), kmfe.getMessage(),
null);
throw kmfe;
}
}
private void internalStart(String sourceContentPath, String sinkContentPath) {
MediaPipeline mediaPipeline = createMediaPipeline();
PlayerEndpoint playerEndpoint = null;
if (sourceContentPath != null) {
playerEndpoint = createSourceEndpoint(mediaPipeline,
sourceContentPath);
}
RecorderEndpoint recorderEndpoint = null;
if (sinkContentPath != null) {
recorderEndpoint = createSinkEndpoint(mediaPipeline,
sinkContentPath);
}
SdpEndpoint sdpEndpoint = buildAndConnectSdpEndpoint(mediaPipeline,
playerEndpoint, recorderEndpoint);
if (playerEndpoint != null)
playerEndpoint.play();
if (recorderEndpoint != null)
recorderEndpoint.record();
activateMedia(sdpEndpoint, null); // TODO. Ask Jose if
// playerEndpoint.play() can be
// in the Runnable
}
protected void internalStart(SdpEndpoint sdpEndpoint) {
try {
releaseOnTerminate(sdpEndpoint);
Assert.isTrue(sdpEndpoint != null,
"Cannot invoke start specifying null endPoint", 1); // TODO
goToState(
STATE.STARTING,
"Cannot start SdpEndPoint in state "
+ getState()
+ ". This is probably due to an explicit session termination comming from another thread",
1); // TODO
activateMedia(sdpEndpoint, null);
} catch (KurentoMediaFrameworkException ke) {
internalTerminateWithError(null, ke.getCode(), ke.getMessage(),
null);
throw ke;
} catch (Throwable t) {
KurentoMediaFrameworkException kmfe = new KurentoMediaFrameworkException(
t.getMessage(), t, 20029);
internalTerminateWithError(null, kmfe.getCode(), kmfe.getMessage(),
null);
throw kmfe;
}
}
private MediaPipeline createMediaPipeline() {
getLogger().info("Creating media pipeline ...");
MediaPipeline mediaPipeline = mediaPipelineFactory.create();
releaseOnTerminate(mediaPipeline);
return mediaPipeline;
}
private PlayerEndpoint createSourceEndpoint(MediaPipeline mediaPipeline,
String contentPath) {
getLogger().info("Creating PlayerEndpoint ...");
PlayerEndpoint playerEndpoint = mediaPipeline.newPlayerEndpoint(
contentPath).build();
return playerEndpoint;
}
private RecorderEndpoint createSinkEndpoint(MediaPipeline mediaPipeline,
String contentPath) {
getLogger().info("Creating RecorderEndpoint ...");
RecorderEndpoint recorderEndpoint = mediaPipeline.newRecorderEndpoint(
contentPath).build();
return recorderEndpoint;
}
private void activateMedia(SdpEndpoint sdpEndpoint,
final Runnable runOnContentStart) {
// Manage fatal errors in pipeline
sdpEndpoint.getMediaPipeline().addErrorListener(
new MediaEventListener<ErrorEvent>() {
@Override
public void onEvent(ErrorEvent error) {
getLogger().error(error.getDescription()); // TODO
internalTerminateWithError(null, error.getErrorCode(),
error.getDescription(), null);
}
});
// Invoke handler when content start
sdpEndpoint
.addMediaSessionStartedListener(new MediaEventListener<MediaSessionStartedEvent>() {
@Override
public void onEvent(MediaSessionStartedEvent event) {
callOnContentStartedOnHanlder();
if (runOnContentStart != null)
runOnContentStart.run();
}
});
// Manage end of media session
sdpEndpoint
.addMediaSessionTerminatedListener(new MediaEventListener<MediaSessionTerminatedEvent>() {
@Override
public void onEvent(MediaSessionTerminatedEvent event) {
internalTerminateWithoutError(null, 1, "TODO", null);// TODO
}
});
String answerSdp = sdpEndpoint.processOffer(initialJsonRequest
.getParams().getSdp());
getLogger().info("Answer SDP: " + answerSdp);
Assert.notNull(answerSdp,
"Received invalid null SDP from media server ... aborting",
20027);
Assert.isTrue(answerSdp.length() > 0,
"Received invalid empty SDP from media server ... aborting",
20028);
goToState(
STATE.ACTIVE,
"Cannot start session in sate "
+ getState()
+ ". This is probably due to an explicit termination of the session from a different threaad",
1);
protocolManager.sendJsonAnswer(initialAsyncCtx, JsonRpcResponse
.newStartSdpResponse(answerSdp, sessionId,
initialJsonRequest.getId()));
initialAsyncCtx = null;
initialJsonRequest = null;
}
private SdpEndpoint buildAndConnectSdpEndpoint(MediaPipeline mediaPipeline,
MediaElement sourceElement, MediaElement sinkElement) {
getLogger().info("Creating SdpEndpoint ...");
SdpEndpoint sdpEndpoint = buildSdpEndpoint(mediaPipeline);
releaseOnTerminate(sdpEndpoint);
// If no source is provided, jut loopback for having some media back
// to the client
if (sourceElement == null) {
sourceElement = sdpEndpoint; // This produces a loopback.
}
getLogger().info("Connecting media pads ...");
// TODO: should we double check constraints?
if (sinkElement != null) {
sdpEndpoint.connect(sinkElement);
}
if (sourceElement != null) {
sourceElement.connect(sdpEndpoint);
}
return sdpEndpoint;
}
@Override
protected synchronized void destroy() {
super.destroy();
if (repositoryHttpPlayer != null) {
repositoryHttpPlayer.stop();
repositoryHttpPlayer = null;
}
if (repositoryHttpRecorder != null) {
repositoryHttpRecorder.stop();
repositoryHttpRecorder = null;
}
}
}