diff options
Diffstat (limited to 'src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri')
5 files changed, 865 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java new file mode 100644 index 0000000..8b964af --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIq.java @@ -0,0 +1,413 @@ +/* + * 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"; + + /** + * 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 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; + + /** + * XMPPError stores error details for {@link Status#FAILED}. + */ + private XMPPError error; + + /** + * 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; + + /** + * 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); + } + + 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"); + + /** + * 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; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java new file mode 100644 index 0000000..155853c --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriIqProvider.java @@ -0,0 +1,112 @@ +/* + * 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 org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.PacketParserUtils; + +import org.xmlpull.v1.*; + +/** + * Parses {@link JibriIq}. + */ +public class JibriIqProvider + implements IQProvider +{ + /** + * {@inheritDoc} + */ + @Override + public IQ parseIQ(XmlPullParser parser) + throws Exception + { + String namespace = parser.getNamespace(); + + // Check the namespace + if (!JibriIq.NAMESPACE.equals(namespace)) + { + return null; + } + + String rootElement = parser.getName(); + + JibriIq iq; + + if (JibriIq.ELEMENT_NAME.equals(rootElement)) + { + iq = new JibriIq(); + + String action + = parser.getAttributeValue("", JibriIq.ACTION_ATTR_NAME); + iq.setAction(JibriIq.Action.parse(action)); + + String status + = parser.getAttributeValue("", JibriIq.STATUS_ATTR_NAME); + iq.setStatus(JibriIq.Status.parse(status)); + + String room + = parser.getAttributeValue("", JibriIq.ROOM_ATTR_NAME); + if (!StringUtils.isNullOrEmpty(room)) + iq.setRoom(room); + + String streamId + = parser.getAttributeValue("", JibriIq.STREAM_ID_ATTR_NAME); + if (!StringUtils.isNullOrEmpty(streamId)) + iq.setStreamId(streamId); + } + else + { + return null; + } + + boolean done = false; + + while (!done) + { + switch (parser.next()) + { + case XmlPullParser.START_TAG: + { + String name = parser.getName(); + + if ("error".equals(name)) + { + XMPPError error = PacketParserUtils.parseError(parser); + iq.setXMPPError(error); + } + break; + } + case XmlPullParser.END_TAG: + { + String name = parser.getName(); + + if (rootElement.equals(name)) + { + done = true; + } + break; + } + } + } + + return iq; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java new file mode 100644 index 0000000..e046b68 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/JibriStatusPacketExt.java @@ -0,0 +1,121 @@ +/* + * 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 net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +import org.jitsi.util.*; + +import org.jivesoftware.smack.provider.*; + +/** + * Status extension included in MUC presence by Jibri to indicate it's status. + * One of: + * <li>idle</li> - the instance is idle and can be used for recording + * <li>busy</li> - the instance is currently recording or doing something very + * important and should not be disturbed + * + * + */ +public class JibriStatusPacketExt + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = JibriIq.NAMESPACE; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "jibri-status"; + + private static final String STATUS_ATTRIBUTE = "status"; + + /** + * Creates new instance of <tt>VideoMutedExtension</tt>. + */ + public JibriStatusPacketExt() + { + super(NAMESPACE, ELEMENT_NAME); + } + + static public void registerExtensionProvider() + { + ProviderManager.getInstance().addExtensionProvider( + ELEMENT_NAME, + NAMESPACE, + new DefaultPacketExtensionProvider<JibriStatusPacketExt>( + JibriStatusPacketExt.class) + ); + } + + public Status getStatus() + { + return Status.parse(getAttributeAsString(STATUS_ATTRIBUTE)); + } + + public void setStatus(Status status) + { + setAttribute(STATUS_ATTRIBUTE, String.valueOf(status)); + } + + public enum Status + { + IDLE("idle"), + BUSY("busy"), + UNDEFINED("undefined"); + + private String name; + + Status(String name) + { + this.name = name; + } + + @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; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java new file mode 100644 index 0000000..13177cf --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/RecordingStatus.java @@ -0,0 +1,126 @@ +/* + * 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 net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +import org.jivesoftware.smack.packet.*; + +import java.util.*; + +/** + * The packet extension added to Jicofo MUC presence to broadcast current + * recording status to all conference participants. + * + * Status meaning: + * <tt>{@link JibriIq.Status#UNDEFINED}</tt> - recording not available + * <tt>{@link JibriIq.Status#OFF}</tt> - recording stopped(available to start) + * <tt>{@link JibriIq.Status#PENDING}</tt> - starting recording + * <tt>{@link JibriIq.Status#ON}</tt> - recording in progress + */ +public class RecordingStatus + extends AbstractPacketExtension +{ + /** + * The namespace of this packet extension. + */ + public static final String NAMESPACE = JibriIq.NAMESPACE; + + /** + * XML element name of this packet extension. + */ + public static final String ELEMENT_NAME = "jibri-recording-status"; + + /** + * The name of XML attribute which holds the recording status. + */ + private static final String STATUS_ATTRIBUTE = "status"; + + public RecordingStatus() + { + super(NAMESPACE, ELEMENT_NAME); + } + + /** + * Returns the value of current recording status stored in it's attribute. + * @return one of {@link JibriIq.Status} + */ + public JibriIq.Status getStatus() + { + String statusAttr = getAttributeAsString(STATUS_ATTRIBUTE); + + return JibriIq.Status.parse(statusAttr); + } + + /** + * Sets new value for the recording status. + * @param status one of {@link JibriIq.Status} + */ + public void setStatus(JibriIq.Status status) + { + setAttribute(STATUS_ATTRIBUTE, String.valueOf(status)); + } + + /** + * Returns <tt>XMPPError</tt> associated with current + * {@link RecordingStatus}. + */ + public XMPPError getError() + { + XMPPErrorPE errorPe = getErrorPE(); + return errorPe != null ? errorPe.getError() : null; + } + + /** + * Gets <tt>{@link XMPPErrorPE}</tt> from the list of child packet + * extensions. + * @return {@link XMPPErrorPE} or <tt>null</tt> if not found. + */ + private XMPPErrorPE getErrorPE() + { + List<? extends PacketExtension> errorPe + = getChildExtensionsOfType(XMPPErrorPE.class); + + return (XMPPErrorPE) (!errorPe.isEmpty() ? errorPe.get(0) : null); + } + + /** + * Sets <tt>XMPPError</tt> on this <tt>RecordingStatus</tt>. + * @param error <tt>XMPPError</tt> to add error details to this + * <tt>RecordingStatus</tt> instance or <tt>null</tt> to have it removed. + */ + public void setError(XMPPError error) + { + if (error != null) + { + // Wrap and add XMPPError as packet extension + XMPPErrorPE errorPe = getErrorPE(); + if (errorPe == null) + { + errorPe = new XMPPErrorPE(error); + addChildExtension(errorPe); + } + errorPe.setError(error); + } + else + { + // Remove error PE + getChildExtensions().remove(getErrorPE()); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java new file mode 100644 index 0000000..a72f310 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jibri/XMPPErrorPE.java @@ -0,0 +1,93 @@ +/* + * 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.jivesoftware.smack.packet.*; + +import java.util.*; + +/** + * Wraps Smack's <tt>XMPPError</tt> into <tt>PacketExtension</tt>, so that it + * can be easily inserted into {@link RecordingStatus}. + */ +public class XMPPErrorPE + implements PacketExtension +{ + /** + * <tt>XMPPError</tt> wrapped into this <tt>XMPPErrorPE</tt>. + */ + private XMPPError error; + + /** + * Creates new instance of <tt>XMPPErrorPE</tt>. + * @param xmppError the instance of <tt>XMPPError</tt> that will be wrapped + * by the newly created <tt>XMPPErrorPE</tt>. + */ + public XMPPErrorPE(XMPPError xmppError) + { + setError(xmppError); + } + + /** + * Returns the underlying instance of <tt>XMPPError</tt>. + */ + public XMPPError getError() + { + return error; + } + + /** + * Sets new instance of <tt>XMPPError</tt> to be wrapped by this + * <tt>XMPPErrorPE</tt>. + * @param error <tt>XMPPError</tt> that will be wrapped by this + * <TT>XMPPErrorPE</TT>. + */ + public void setError(XMPPError error) + { + Objects.requireNonNull(error, "error"); + + this.error = error; + } + + /** + * {@inheritDoc} + */ + @Override + public String getElementName() + { + return "error"; + } + + /** + * {@inheritDoc} + */ + @Override + public String getNamespace() + { + return ""; + } + + /** + * {@inheritDoc} + */ + @Override + public String toXML() + { + return error.toXML(); + } +} |