diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java index 2abd708756b726b816ad8e478ccde9e33500f1ea..8ea44913a5f393ff74544d260d990a67d6e0a811 100644 --- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java @@ -23,6 +23,7 @@ import org.jitsi.impl.neomedia.codec.video.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.format.*; +import org.jitsi.impl.neomedia.recording.*; import org.jitsi.impl.neomedia.rtp.translator.*; import org.jitsi.impl.neomedia.transform.dtls.*; import org.jitsi.impl.neomedia.transform.sdes.*; @@ -33,6 +34,7 @@ import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; +import org.jitsi.service.neomedia.recording.*; import org.jitsi.service.resources.*; import org.jitsi.util.*; import org.jitsi.util.event.*; diff --git a/src/org/jitsi/impl/neomedia/RecorderImpl.java b/src/org/jitsi/impl/neomedia/recording/RecorderImpl.java similarity index 89% rename from src/org/jitsi/impl/neomedia/RecorderImpl.java rename to src/org/jitsi/impl/neomedia/recording/RecorderImpl.java index 7dcfbe7a81fa045cffa18a5eef77413fa6da7fc7..b677ee9cecb1d1663177995728deccddeba4456d 100644 --- a/src/org/jitsi/impl/neomedia/RecorderImpl.java +++ b/src/org/jitsi/impl/neomedia/recording/RecorderImpl.java @@ -4,7 +4,7 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package org.jitsi.impl.neomedia; +package org.jitsi.impl.neomedia.recording; import java.io.*; import java.util.*; @@ -14,6 +14,7 @@ import org.jitsi.impl.neomedia.device.*; import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.recording.*; import org.jitsi.service.neomedia.MediaException; import org.jitsi.util.*; @@ -23,6 +24,7 @@ * * @author Dmitri Melnikov * @author Lubomir Marinov + * @author Boris Grozev */ public class RecorderImpl implements Recorder @@ -49,6 +51,13 @@ public class RecorderImpl */ private final AudioMixerMediaDevice device; + /** + * The <tt>RecorderEventHandler</tt> which this <tt>Recorder</tt> + * should notify when events related to recording (such as start/end of a + * recording) occur. + */ + private RecorderEventHandler eventHandler = null; + /** * The <tt>MediaDeviceSession</tt> is used to create an output data source. */ @@ -281,6 +290,16 @@ else if (extensionBeginIndex == filename.length() - 1) exception); } } + + if (eventHandler != null) + { + RecorderEvent event = new RecorderEvent(); + event.setType(RecorderEvent.Type.RECORDING_STARTED); + event.setInstant(System.currentTimeMillis()); + event.setMediaType(MediaType.AUDIO); + event.setFilename(filename); + eventHandler.handleEvent(event); + } } } @@ -320,6 +339,16 @@ public void stop() } for (Recorder.Listener listener : listeners) listener.recorderStopped(this); + + if (eventHandler != null) + { + RecorderEvent event = new RecorderEvent(); + event.setType(RecorderEvent.Type.RECORDING_ENDED); + event.setInstant(System.currentTimeMillis()); + event.setMediaType(MediaType.AUDIO); + event.setFilename(filename); + eventHandler.handleEvent(event); + } } } @@ -349,4 +378,12 @@ public String getFilename() { return filename; } + + /** + * {@inheritDoc} + */ + public void setEventHandler(RecorderEventHandler eventHandler) + { + this.eventHandler = eventHandler; + } } diff --git a/src/org/jitsi/service/neomedia/MediaService.java b/src/org/jitsi/service/neomedia/MediaService.java index 8426661f8ba80bd6ff4830cd01255cb9bc322f38..4337bd40f2aa3f1ba5542fdb8aefcdc600fe0448 100644 --- a/src/org/jitsi/service/neomedia/MediaService.java +++ b/src/org/jitsi/service/neomedia/MediaService.java @@ -12,6 +12,7 @@ import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.format.*; +import org.jitsi.service.neomedia.recording.*; /** * The <tt>MediaService</tt> service is meant to be a wrapper of media libraries diff --git a/src/org/jitsi/service/neomedia/Recorder.java b/src/org/jitsi/service/neomedia/recording/Recorder.java similarity index 87% rename from src/org/jitsi/service/neomedia/Recorder.java rename to src/org/jitsi/service/neomedia/recording/Recorder.java index 949de7c71716ff49ef7ad0673f3697027eee7268..f298b7df457e5984ad52058e63c80d35fc2b6ae0 100644 --- a/src/org/jitsi/service/neomedia/Recorder.java +++ b/src/org/jitsi/service/neomedia/recording/Recorder.java @@ -4,7 +4,9 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package org.jitsi.service.neomedia; +package org.jitsi.service.neomedia.recording; + +import org.jitsi.service.neomedia.MediaException; import java.io.*; import java.util.*; @@ -15,6 +17,7 @@ * * @author Dmitri Melnikov * @author Lubomir Marinov + * @author Boris Grozev */ public interface Recorder { @@ -72,12 +75,12 @@ public interface Recorder * this <tt>Recorder</tt> is to be recorded * @throws IOException if anything goes wrong with the input and/or output * performed by this <tt>Recorder</tt> - * @throws MediaException if anything else goes wrong while starting the + * @throws org.jitsi.service.neomedia.MediaException if anything else goes wrong while starting the * recording of media performed by this <tt>Recorder</tt> */ public void start(String format, String filename) throws IOException, - MediaException; + MediaException; /** * Stops the recording of the media associated with this <tt>Recorder</tt> @@ -118,4 +121,12 @@ public interface Listener * null if not started. */ public String getFilename(); + + /** + * Sets the <tt>RecorderEventHandler</tt> which this <tt>Recorder</tt> + * should notify when events related to recording (such as start/end of a + * recording) occur. + * @param eventHandler the <tt>RecorderEventHandler</tt> to set. + */ + public void setEventHandler(RecorderEventHandler eventHandler); } diff --git a/src/org/jitsi/service/neomedia/recording/RecorderEvent.java b/src/org/jitsi/service/neomedia/recording/RecorderEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..4b2bd9b1cb814d241259765d0e0063fc495e5318 --- /dev/null +++ b/src/org/jitsi/service/neomedia/recording/RecorderEvent.java @@ -0,0 +1,393 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia.recording; + +import org.jitsi.service.neomedia.*; +import org.json.simple.*; + +/** + * Represents an event related to media recording, such as a new SSRC starting + * to be recorded. + * + * @author Boris Grozev + * @author Vladimir Marinov + */ +public class RecorderEvent +{ + /** + * The type of this <tt>RecorderEvent</tt>. + */ + private Type type = Type.OTHER; + + /** + * A timestamp for this <tt>RecorderEvent</tt>. + */ + private long instant = -1; + + /** + * The SSRC associated with this <tt>RecorderEvent</tt>. + */ + private long ssrc = -1; + + /** + * The SSRC of an audio stream associated with this <tt>RecorderEvent</tt>. + */ + private long audioSsrc = -1; + + /** + * An RTP timestamp for this <tt>RecorderEvent</tt>. + */ + private long rtpTimestamp = -1; + + /** + * An NTP timestamp (represented as a double in seconds) + * for this <tt>RecorderEvent</tt>. + */ + private double ntpTime = -1.0; + + /** + * Duration associated with this <tt>RecorderEvent</tt>. + */ + private long duration = -1; + + /** + * An aspect ratio associated with this <tt>RecorderEvent</tt>. + */ + private AspectRatio aspectRatio = AspectRatio.ASPECT_RATIO_UNKNOWN; + + /** + * A file name associated with this <tt>RecorderEvent</tt>. + */ + private String filename; + + /** + * The media type associated with this <tt>RecorderEvent</tt>. + */ + private MediaType mediaType = null; + + /** + * The name of the participant associated with this <tt>RecorderEvent</tt>. + */ + private String participantName = null; + + /** + * A textual description of the participant associated with this + * <tt>RecorderEvent</tt>. (human readable) + */ + private String participantDescription = null; + + private boolean disableOtherVideosOnTop = false; + + /** + * Constructs a <tt>RecorderEvent</tt>. + */ + public RecorderEvent() + { + } + + /** + * Constructs a <tt>RecorderEvent</tt> and tries to parse its fields from + * <tt>json</tt>. + * @param json a JSON object, containing fields with which to initialize + * the fields of this <tt>RecorderEvent</tt>. + */ + public RecorderEvent(JSONObject json) + { + Object o = json.get("type"); + if (o != null) + type = Type.parseString(o.toString()); + + o = json.get("instant"); + if (o != null && + (o instanceof Long || o instanceof Integer)) + instant = (Long)o; + + o = json.get("ssrc"); + if (o != null && + (o instanceof Long || o instanceof Integer)) + ssrc = (Long)o; + + o = json.get("audioSsrc"); + if (o != null && + (o instanceof Long || o instanceof Integer)) + audioSsrc = (Long)o; + + o = json.get("ntpTime"); + if (o != null && + (o instanceof Long || o instanceof Integer)) + ntpTime = (Long) o; + + o = json.get("duration"); + if (o != null && + (o instanceof Long || o instanceof Integer)) + duration = (Long) o; + + o = json.get("aspectRatio"); + if (o != null) + aspectRatio = AspectRatio.parseString(o.toString()); + + o = json.get("filename"); + if (o != null) + filename = o.toString(); + + o = json.get("participantName"); + if (o != null && o instanceof String) + participantName = (String) o; + + o = json.get("participantDescription"); + if (o != null && o instanceof String) + participantDescription = (String) o; + + o = json.get("mediaType"); + if (o != null) + { + try + { + mediaType = MediaType.parseString(o.toString()); + } + catch (IllegalArgumentException iae) + { + //complain? + } + } + + o = json.get("disableOtherVideosOnTop"); + if (o != null) + { + if (o instanceof Boolean) + disableOtherVideosOnTop = (boolean) disableOtherVideosOnTop; + else if (o instanceof String) + disableOtherVideosOnTop = Boolean.valueOf((String) o); + } + } + + public Type getType() + { + return type; + } + + public void setType(Type type) + { + this.type = type; + } + + public long getInstant() + { + return instant; + } + + public void setInstant(long instant) + { + this.instant = instant; + } + + public long getRtpTimestamp() + { + return rtpTimestamp; + } + + public void setRtpTimestamp(long rtpTimestamp) + { + this.rtpTimestamp = rtpTimestamp; + } + + public long getSsrc() + { + return ssrc; + } + + public void setSsrc(long ssrc) + { + this.ssrc = ssrc; + } + + public long getAudioSsrc() + { + return audioSsrc; + } + + public void setAudioSsrc(long audioSsrc) + { + this.audioSsrc = audioSsrc; + } + + public AspectRatio getAspectRatio() + { + return aspectRatio; + } + + public void setAspectRatio(AspectRatio aspectRatio) + { + this.aspectRatio = aspectRatio; + } + + public String getFilename() + { + return filename; + } + + public void setFilename(String filename) + { + this.filename = filename; + } + + public MediaType getMediaType() + { + return mediaType; + } + + public void setMediaType(MediaType mediaType) + { + this.mediaType = mediaType; + } + + public long getDuration() + { + return duration; + } + + public void setDuration(long duration) + { + this.duration = duration; + } + + public String getParticipantName() + { + return participantName; + } + + public void setParticipantName(String participantName) + { + this.participantName = participantName; + } + + public String getParticipantDescription() + { + return participantDescription; + } + + public void setParticipantDescription(String participantDescription) + { + this.participantDescription = participantDescription; + } + + public boolean getDisableOtherVideosOnTop() + { + return disableOtherVideosOnTop; + } + + public void setDisableOtherVideosOnTop(boolean disableOtherVideosOnTop) + { + this.disableOtherVideosOnTop = disableOtherVideosOnTop ; + } + + public double getNtpTime() + { + return ntpTime; + } + + public void setNtpTime(double ntpTime) + { + this.ntpTime = ntpTime; + } + + public String toString() + { + return "RecorderEvent: " + getType().toString() + " @" + getInstant() + + "(" + getMediaType() + ")"; + } + + /** + * <tt>RecorderEvent</tt> types. + */ + public enum Type + { + /** + * Indicates the start of a recording. + */ + RECORDING_STARTED("RECORDING_STARTED"), + + /** + * Indicates the end of a recording. + */ + RECORDING_ENDED("RECORDING_ENDED"), + + /** + * Indicates that the active speaker has changed. The 'audioSsrc' + * field indicates the SSRC of the audio stream which is now considered + * active, and the 'ssrc' field contains the SSRC of a video stream + * associated with the now active audio stream. + */ + SPEAKER_CHANGED("SPEAKER_CHANGED"), + + /** + * Indicates that a new stream was added. This is different than + * RECORDING_STARTED, because a new stream might be saved to an existing + * recording (for example, a new audio stream might be added to a mix) + */ + STREAM_ADDED("STREAM_ADDED"), + + /** + * Default value. + */ + OTHER("OTHER"); + + private String name; + + private Type(String name) + { + this.name = name; + } + + public String toString() + { + return name; + } + + public static Type parseString(String str) + { + for (Type type : Type.values()) + if (type.toString().equals(str)) + return type; + return OTHER; + } + } + + /** + * Video aspect ratio. + */ + public enum AspectRatio + { + ASPECT_RATIO_16_9("16_9", 16./9), + ASPECT_RATIO_4_3("4_3", 4./3), + ASPECT_RATIO_UNKNOWN("UNKNOWN", 1.); + + public double scaleFactor; + private String stringValue; + + private AspectRatio(String stringValue, double scaleFactor) + { + this.scaleFactor = scaleFactor; + this.stringValue = stringValue; + } + + @Override + public String toString() + { + return stringValue; + } + + public static AspectRatio parseString(String str) + { + for (AspectRatio aspectRatio : AspectRatio.values()) + if (aspectRatio.toString().equals(str)) + return aspectRatio; + return ASPECT_RATIO_UNKNOWN; + } + } + +} diff --git a/src/org/jitsi/service/neomedia/recording/RecorderEventHandler.java b/src/org/jitsi/service/neomedia/recording/RecorderEventHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..a24773d1006c3397db63a7be56d6c75cce3b4ca0 --- /dev/null +++ b/src/org/jitsi/service/neomedia/recording/RecorderEventHandler.java @@ -0,0 +1,28 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia.recording; + +/** + * An interface that allows handling of <tt>RecorderEvent</tt> instances, such + * as writing them to disk in some format. + * + * @author Boris Grozev + */ +public interface RecorderEventHandler +{ + /** + * Handle a specific <tt>RecorderEvent</tt> + * @param ev the event to handle. + * @return + */ + public boolean handleEvent(RecorderEvent ev); + + /** + * Closes the <tt>RecorderEventHandler</tt>. + */ + public void close(); +}