/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.protocol.jabber.extensions.jibri;
import org.jitsi.util.*;
import org.jivesoftware.smack.packet.*;
import java.util.*;
/**
* The IQ used to control conference recording with Jibri component.
*
* Start the recording:
*
* 1. Send Jibri IQ with {@link Action#START} to Jibri.
* 2. Jibri replies with RESULT and status {@link Status#PENDING}.
* 3. Jibri sends SET IQ with status {@link Status#ON} once recording actually
* starts.
*
* Stop the recording:
*
* 1. Send Jibri IQ with {@link Action#STOP} to Jibri.
* 2. Jibri replies with {@link Status#OFF} immediately if the recording has
* been stopped already or sends separate Jibri SET IQ later on if it takes
* more time.
*
* @author lishunyang
* @author Pawel Domas
*/
public class JibriIq
extends IQ
{
/**
* Attribute name of "action".
*/
public static final String ACTION_ATTR_NAME = "action";
/**
* The name of XML attribute name which holds the display name which will be
* used by Jibri participant when it enters Jitsi Meet conference.
* The value is "displayname".
*/
static final String DISPLAY_NAME_ATTR_NAME = "displayname";
/**
* XML element name of the Jibri IQ.
*/
public static final String ELEMENT_NAME = "jibri";
/**
* XML namespace of the Jibri IQ.
*/
public static final String NAMESPACE = "http://jitsi.org/protocol/jibri";
/**
* The name of XML attribute which stores SIP address. The value is
* "sipaddress".
*/
static final String SIP_ADDRESS_ATTR_NAME = "sipaddress";
/**
* The name of XML attribute which stores the recording status.
*/
static final String STATUS_ATTR_NAME = "status";
/**
* The name of XML attribute which stores the stream id.
*/
static final String STREAM_ID_ATTR_NAME = "streamid";
/**
* The name of XML attribute which stores the name of the conference room to
* be recorded.
*/
static final String ROOM_ATTR_NAME = "room";
/**
* Holds the action.
*/
private Action action = Action.UNDEFINED;
/**
* The display name which will be used by Jibri participant.
*/
private String displayName;
/**
* XMPPError stores error details for {@link Status#FAILED}.
*/
private XMPPError error;
/**
* The SIP address of remote peer.
*/
private String sipAddress;
/**
* Holds recording status.
*/
private Status status = Status.UNDEFINED;
/**
* The ID of the stream which will be used to record the conference. The
* value depends on recording service provider.
*/
private String streamId = null;
/**
* The name of the conference room to be recorded.
*/
private String room = null;
/**
* @return the value for {@link #DISPLAY_NAME_ATTR_NAME}
*/
public String getDisplayName()
{
return displayName;
}
/**
* Sets new value for {@link #DISPLAY_NAME_ATTR_NAME}
* @param displayName the new display name to be set
*/
public void setDisplayName(String displayName)
{
this.displayName = displayName;
}
/**
* @return the value for {@link #SIP_ADDRESS_ATTR_NAME}
*/
public String getSipAddress()
{
return this.sipAddress;
}
/**
* Sets new value for {@link #SIP_ADDRESS_ATTR_NAME}
* @param sipAddress the new SIP address to be set
*/
public void setSipAddress(String sipAddress)
{
this.sipAddress = sipAddress;
}
/**
* Returns the value of {@link #STREAM_ID_ATTR_NAME} attribute.
* @return a <tt>String</tt> which contains the value of "stream id"
* attribute or <tt>null</tt> if empty.
*/
public String getStreamId()
{
return streamId;
}
/**
* Sets the value for {@link #STREAM_ID_ATTR_NAME} attribute.
* @param streamId a <tt>String</tt> for the stream id attribute or
* <tt>null</tt> to remove it from XML element.
*/
public void setStreamId(String streamId)
{
this.streamId = streamId;
}
/**
* Returns the value of {@link #ROOM_ATTR_NAME} attribute.
* @return a <tt>String</tt> which contains the value of the room attribute
* or <tt>null</tt> if empty.
* @see #room
*/
public String getRoom()
{
return room;
}
/**
* Sets the value for {@link #ROOM_ATTR_NAME} attribute.
* @param room a <tt>String</tt> for the room attribute or <tt>null</tt> to
* remove it from XML element.
* @see #room
*/
public void setRoom(String room)
{
this.room = room;
}
/**
* {@inheritDoc}
*/
@Override
public String getChildElementXML()
{
StringBuilder xml = new StringBuilder();
xml.append('<').append(ELEMENT_NAME);
xml.append(" xmlns='").append(NAMESPACE).append("' ");
if (action != Action.UNDEFINED)
{
printStringAttribute(xml, ACTION_ATTR_NAME, action.toString());
}
if (status != Status.UNDEFINED)
{
printStringAttribute(xml, STATUS_ATTR_NAME, status.toString());
}
if (room != null)
{
printStringAttribute(xml, ROOM_ATTR_NAME, room);
}
if (streamId != null)
{
printStringAttribute(xml, STREAM_ID_ATTR_NAME, streamId);
}
if (displayName != null)
{
printStringAttribute(xml, DISPLAY_NAME_ATTR_NAME, displayName);
}
if (sipAddress != null)
{
printStringAttribute(xml, SIP_ADDRESS_ATTR_NAME, sipAddress);
}
Collection<PacketExtension> extensions = getExtensions();
if (extensions.size() > 0)
{
xml.append(">");
for (PacketExtension extension : extensions)
{
xml.append(extension.toXML());
}
xml.append("</").append(ELEMENT_NAME).append(">");
}
else
{
xml.append("/>");
}
return xml.toString();
}
private void printStringAttribute(
StringBuilder xml, String attrName, String attr)
{
if (!StringUtils.isNullOrEmpty(attr))
{
attr = org.jivesoftware.smack.util.StringUtils.escapeForXML(attr);
xml.append(attrName).append("='")
.append(attr).append("' ");
}
}
/**
* Sets the value of 'action' attribute.
*
* @param action the value to be set as 'action' attribute of this IQ.
*/
public void setAction(Action action)
{
this.action = action;
}
/**
* Returns the value of 'action' attribute.
*/
public Action getAction()
{
return action;
}
/**
* Sets the value of 'status' attribute.
*/
public void setStatus(Status status)
{
this.status = status;
}
/**
* Returns the value of 'status' attribute.
*/
public Status getStatus()
{
return status;
}
/**
* Sets the <tt>XMPPError</tt> which will provide details about Jibri
* failure. It is expected to be set when this IQ's status value is
* {@link Status#FAILED}.
*
* @param error <tt>XMPPError</tt> to be set on this <tt>JibriIq</tt>
* instance.
*/
public void setXMPPError(XMPPError error)
{
this.error = error;
}
/**
* Returns {@link XMPPError} with Jibri error details when the status is
* {@link Status#FAILED}.
*/
public XMPPError getError()
{
return error;
}
/**
* Enumerative value of attribute "action" in recording extension.
*
* @author lishunyang
* @author Pawel Domas
*
*/
public enum Action
{
/**
* Start the recording.
*/
START("start"),
/**
* Stop the recording.
*/
STOP("stop"),
/**
* Unknown/uninitialized
*/
UNDEFINED("undefined");
private String name;
Action(String name)
{
this.name = name;
}
@Override
public String toString()
{
return name;
}
/**
* Parses <tt>Action</tt> from given string.
*
* @param action the string representation of <tt>Action</tt>.
*
* @return <tt>Action</tt> value for given string or
* {@link #UNDEFINED} if given string does not
* reflect any of valid values.
*/
public static Action parse(String action)
{
if (StringUtils.isNullOrEmpty(action))
return UNDEFINED;
try
{
return Action.valueOf(action.toUpperCase());
}
catch(IllegalArgumentException e)
{
return UNDEFINED;
}
}
}
/**
* The enumeration of recording status values.
*/
public enum Status
{
/**
* Recording is in progress.
*/
ON("on"),
/**
* Recording stopped.
*/
OFF("off"),
/**
* Starting the recording process.
*/
PENDING("pending"),
/**
* The recorder has failed and the service is retrying on another
* instance.
*/
RETRYING("retrying"),
/**
* An error occurred any point during startup, recording or shutdown.
*/
FAILED("failed"),
/**
* There are Jibri instances connected to the system, but all of them
* are currently busy.
*/
BUSY("busy"),
/**
* Unknown/uninitialized.
*/
UNDEFINED("undefined"),
/**
* Used by {@link SipGatewayStatus} to signal that there are Jibris
* available. SIP gateway does not use ON, OFF, PENDING nor RETRYING
* states, because gateway availability and each SIP call's states are
* signalled separately.
* Check {@link SipGatewayStatus} and {@link SipCallState} for more
* info.
*/
AVAILABLE("available");
/**
* Status name holder.
*/
private String name;
/**
* Creates new {@link Status} instance.
* @param name a string corresponding to one of {@link Status} values.
*/
Status(String name)
{
this.name = name;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return name;
}
/**
* Parses <tt>Status</tt> from given string.
*
* @param status the string representation of <tt>Status</tt>.
*
* @return <tt>Status</tt> value for given string or
* {@link #UNDEFINED} if given string does not
* reflect any of valid values.
*/
public static Status parse(String status)
{
if (StringUtils.isNullOrEmpty(status))
return UNDEFINED;
try
{
return Status.valueOf(status.toUpperCase());
}
catch(IllegalArgumentException e)
{
return UNDEFINED;
}
}
}
}