/** * Sets the values of the properties of a specific <tt>ColibriConferenceIQ</tt> to the values of * the respective properties of this instance. Thus, the specified <tt>iq</tt> may be thought of * as a description of this instance. * * <p><b>Note</b>: The copying of the values is deep i.e. the <tt>Contents</tt>s of this instance * are described in the specified <tt>iq</tt>. * * @param iq the <tt>ColibriConferenceIQ</tt> to set the values of the properties of this instance * on */ public void describeDeep(ColibriConferenceIQ iq) { describeShallow(iq); if (isRecording()) { ColibriConferenceIQ.Recording recordingIQ = new ColibriConferenceIQ.Recording(State.ON.toString()); recordingIQ.setDirectory(getRecordingDirectory()); iq.setRecording(recordingIQ); } for (Content content : getContents()) { ColibriConferenceIQ.Content contentIQ = iq.getOrCreateContent(content.getName()); for (Channel channel : content.getChannels()) { if (channel instanceof SctpConnection) { ColibriConferenceIQ.SctpConnection sctpConnectionIQ = new ColibriConferenceIQ.SctpConnection(); channel.describe(sctpConnectionIQ); contentIQ.addSctpConnection(sctpConnectionIQ); } else { ColibriConferenceIQ.Channel channelIQ = new ColibriConferenceIQ.Channel(); channel.describe(channelIQ); contentIQ.addChannel(channelIQ); } } } }
/** * Notifies this <tt>Conference</tt> that the ordered list of <tt>Endpoint</tt>s of {@link * #speechActivity} i.e. the dominant speaker history has changed. * * <p>This instance notifies the video <tt>Channel</tt>s about the change so that they may update * their last-n lists and report to this instance which <tt>Endpoint</tt>s are to be asked for * video keyframes. */ private void speechActivityEndpointsChanged() { List<Endpoint> endpoints = null; for (Content content : getContents()) { if (MediaType.VIDEO.equals(content.getMediaType())) { Set<Endpoint> endpointsToAskForKeyframes = null; endpoints = speechActivity.getEndpoints(); for (Channel channel : content.getChannels()) { if (!(channel instanceof RtpChannel)) continue; RtpChannel rtpChannel = (RtpChannel) channel; List<Endpoint> channelEndpointsToAskForKeyframes = rtpChannel.speechActivityEndpointsChanged(endpoints); if ((channelEndpointsToAskForKeyframes != null) && !channelEndpointsToAskForKeyframes.isEmpty()) { if (endpointsToAskForKeyframes == null) { endpointsToAskForKeyframes = new HashSet<>(); } endpointsToAskForKeyframes.addAll(channelEndpointsToAskForKeyframes); } } if ((endpointsToAskForKeyframes != null) && !endpointsToAskForKeyframes.isEmpty()) { content.askForKeyframes(endpointsToAskForKeyframes); } } } }
/** * Finds a <tt>Channel</tt> of this <tt>Conference</tt> which receives a specific SSRC and is with * a specific <tt>MediaType</tt>. * * @param receiveSSRC the SSRC of a received RTP stream whose receiving <tt>Channel</tt> in this * <tt>Conference</tt> is to be found * @param mediaType the <tt>MediaType</tt> of the <tt>Channel</tt> to be found * @return the <tt>Channel</tt> in this <tt>Conference</tt> which receives the specified * <tt>ssrc</tt> and is with the specified <tt>mediaType</tt>; otherwise, <tt>null</tt> */ public Channel findChannelByReceiveSSRC(long receiveSSRC, MediaType mediaType) { for (Content content : getContents()) { if (mediaType.equals(content.getMediaType())) { Channel channel = content.findChannelByReceiveSSRC(receiveSSRC); if (channel != null) return channel; } } return null; }
/** * {@inheritDoc} * * <p>Creates a <tt>TransportManager</tt> instance suitable for an <tt>SctpConnection</tt> (e.g. * with 1 component only). */ protected TransportManager createTransportManager(String xmlNamespace) throws IOException { if (IceUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) { Content content = getContent(); return new IceUdpTransportManager( content.getConference(), isInitiator(), 1 /* num components */, content.getName()); } else if (RawUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) { // TODO: support RawUdp once RawUdpTransportManager is updated // return new RawUdpTransportManager(this); throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace); } else { throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace); } }
/** * Expires this <tt>Conference</tt>, its <tt>Content</tt>s and their respective <tt>Channel</tt>s. * Releases the resources acquired by this instance throughout its life time and prepares it to be * garbage collected. */ public void expire() { synchronized (this) { if (expired) return; else expired = true; } EventAdmin eventAdmin = videobridge.getEventAdmin(); if (eventAdmin != null) eventAdmin.sendEvent(EventFactory.conferenceExpired(this)); setRecording(false); if (recorderEventHandler != null) { recorderEventHandler.close(); recorderEventHandler = null; } Videobridge videobridge = getVideobridge(); try { videobridge.expireConference(this); } finally { // Expire the Contents of this Conference. for (Content content : getContents()) { try { content.expire(); } catch (Throwable t) { logger.warn( "Failed to expire content " + content.getName() + " of conference " + getID() + "!", t); if (t instanceof InterruptedException) Thread.currentThread().interrupt(); else if (t instanceof ThreadDeath) throw (ThreadDeath) t; } } // Close the transportManagers of this Conference. Normally, there // will be no TransportManager left to close at this point because // all Channels have expired and the last Channel to be removed from // a TransportManager closes the TransportManager. However, a // Channel may have expired before it has learned of its // TransportManager and then the TransportManager will not close. closeTransportManagers(); if (logger.isInfoEnabled()) { logger.info( "Expired conference " + getID() + ". " + videobridge.getConferenceCountString()); } } }
/** * Checks whether media recording is currently enabled for this <tt>Conference</tt>. * * @return <tt>true</tt> if media recording is currently enabled for this <tt>Conference</tt>, * false otherwise. */ public boolean isRecording() { boolean recording = this.recording; // if one of the contents is not recording, stop all recording if (recording) { synchronized (contents) { for (Content content : contents) { MediaType mediaType = content.getMediaType(); if (!MediaType.VIDEO.equals(mediaType) && !MediaType.AUDIO.equals(mediaType)) continue; if (!content.isRecording()) recording = false; } } } if (this.recording != recording) setRecording(recording); return this.recording; }
/** * Expires a specific <tt>Content</tt> of this <tt>Conference</tt> (i.e. if the specified * <tt>content</tt> is not in the list of <tt>Content</tt>s of this <tt>Conference</tt>, does * nothing). * * @param content the <tt>Content</tt> to be expired by this <tt>Conference</tt> */ public void expireContent(Content content) { boolean expireContent; synchronized (contents) { if (contents.contains(content)) { contents.remove(content); expireContent = true; } else expireContent = false; } if (expireContent) content.expire(); }
/** * Returns an XML <tt>String</tt> representation of this <tt>IQ</tt>. * * @return an XML <tt>String</tt> representation of this <tt>IQ</tt> */ @Override public String getChildElementXML() { StringBuilder xml = new StringBuilder(); xml.append('<').append(ELEMENT_NAME); xml.append(" xmlns='").append(NAMESPACE).append('\''); String id = getID(); if (id != null) xml.append(' ').append(ID_ATTR_NAME).append("='").append(id).append('\''); List<Content> contents = getContents(); if (contents.size() == 0) { xml.append(" />"); } else { xml.append('>'); for (Content content : contents) content.toXML(xml); xml.append("</").append(ELEMENT_NAME).append('>'); } return xml.toString(); }
/** * Gets a <tt>Content</tt> of this <tt>Conference</tt> which has a specific name. If a * <tt>Content</tt> of this <tt>Conference</tt> with the specified <tt>name</tt> does not exist at * the time the method is invoked, the method initializes a new <tt>Content</tt> instance with the * specified <tt>name</tt> and adds it to the list of <tt>Content</tt>s of this * <tt>Conference</tt>. * * @param name the name of the <tt>Content</tt> which is to be returned * @return a <tt>Content</tt> of this <tt>Conference</tt> which has the specified <tt>name</tt> */ public Content getOrCreateContent(String name) { Content content; synchronized (contents) { for (Content aContent : contents) { if (aContent.getName().equals(name)) { aContent.touch(); // It seems the content is still active. return aContent; } } content = new Content(this, name); if (isRecording()) { content.setRecording(true, getRecordingPath()); } contents.add(content); } if (logger.isInfoEnabled()) { /* * The method Videobridge.getChannelCount() should better be * executed outside synchronized blocks in order to reduce the risks * of causing deadlocks. */ Videobridge videobridge = getVideobridge(); logger.info( "Created content " + name + " of conference " + getID() + ". " + videobridge.getConferenceCountString()); } return content; }
/** * Attempts to enable or disable media recording for this <tt>Conference</tt>. * * @param recording whether to enable or disable recording. * @return the state of the media recording for this <tt>Conference</tt> after the attempt to * enable (or disable). */ public boolean setRecording(boolean recording) { if (recording != this.recording) { if (recording) { // try enable recording if (logger.isDebugEnabled()) { logger.debug("Starting recording for conference with id=" + getID()); } String path = getRecordingPath(); boolean failedToStart = !checkRecordingDirectory(path); if (!failedToStart) { RecorderEventHandler handler = getRecorderEventHandler(); if (handler == null) failedToStart = true; } if (!failedToStart) { EndpointRecorder endpointRecorder = getEndpointRecorder(); if (endpointRecorder == null) { failedToStart = true; } else { for (Endpoint endpoint : getEndpoints()) endpointRecorder.updateEndpoint(endpoint); } } /* * The Recorders of the Contents need to share a single * Synchronizer, we take it from the first Recorder. */ boolean first = true; Synchronizer synchronizer = null; for (Content content : contents) { MediaType mediaType = content.getMediaType(); if (!MediaType.VIDEO.equals(mediaType) && !MediaType.AUDIO.equals(mediaType)) { continue; } if (!failedToStart) failedToStart = !content.setRecording(true, path); if (failedToStart) break; if (first) { first = false; synchronizer = content.getRecorder().getSynchronizer(); } else { Recorder recorder = content.getRecorder(); if (recorder != null) recorder.setSynchronizer(synchronizer); } content.feedKnownSsrcsToSynchronizer(); } if (failedToStart) { recording = false; logger.warn("Failed to start media recording for conference " + getID()); } } // either we were asked to disable recording, or we failed to // enable it if (!recording) { if (logger.isDebugEnabled()) { logger.debug("Stopping recording for conference with id=" + getID()); } for (Content content : contents) { MediaType mediaType = content.getMediaType(); if (MediaType.AUDIO.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) { content.setRecording(false, null); } } if (recorderEventHandler != null) recorderEventHandler.close(); recorderEventHandler = null; recordingPath = null; recordingDirectory = null; if (endpointRecorder != null) endpointRecorder.close(); endpointRecorder = null; } this.recording = recording; } return this.recording; }
/** * Returns a <tt>Content</tt> from the list of <tt>Content</tt>s of this <tt>conference</tt> IQ * which has a specific name. If no such <tt>Content</tt> exists, returns <tt>null</tt>. * * @param contentName the name of the <tt>Content</tt> to be returned * @return a <tt>Content</tt> from the list of <tt>Content</tt>s of this <tt>conference</tt> IQ * which has the specified <tt>contentName</tt> if such a <tt>Content</tt> exists; otherwise, * <tt>null</tt> */ public Content getContent(String contentName) { for (Content content : getContents()) if (contentName.equals(content.getName())) return content; return null; }