From d4ff88b6b87560f513ceceeeb2996bcacc45b6f4 Mon Sep 17 00:00:00 2001 From: Boris Grozev <boris@jitsi.org> Date: Fri, 20 Jun 2014 10:27:22 +0200 Subject: [PATCH] Adds a RecorderEventHandler implementation that saves events in JSON format. --- .../RecorderEventHandlerJSONImpl.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/org/jitsi/impl/neomedia/recording/RecorderEventHandlerJSONImpl.java diff --git a/src/org/jitsi/impl/neomedia/recording/RecorderEventHandlerJSONImpl.java b/src/org/jitsi/impl/neomedia/recording/RecorderEventHandlerJSONImpl.java new file mode 100644 index 00000000..b37a22c3 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/recording/RecorderEventHandlerJSONImpl.java @@ -0,0 +1,238 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.recording; + +import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.recording.*; +import org.jitsi.util.*; +import org.json.simple.*; + +import java.io.*; +import java.util.*; + +/** + * Implements a <tt>RecorderEventHandler</tt> which handles + * <tt>RecorderEvents</tt> by writing them to a file in JSON format. + * + * @author Boris Grozev + */ +public class RecorderEventHandlerJSONImpl + implements RecorderEventHandler +{ + /** + * The <tt>Logger</tt> used by the <tt>RecorderEventHandlerJSONImpl</tt> + * class and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(RecorderEventHandlerJSONImpl.class); + + /** + * Compares <tt>RecorderEvent</tt>s by their instant (e.g. timestamp). + */ + private static final Comparator<RecorderEvent> eventComparator + = new Comparator<RecorderEvent>() { + @Override + public int compare(RecorderEvent a, RecorderEvent b) + { + return ((Long)a.getInstant()).compareTo(b.getInstant()); + } + }; + + File file; + private boolean closed = false; + + private final List<RecorderEvent> audioEvents + = new LinkedList<RecorderEvent>(); + + private final List<RecorderEvent> videoEvents + = new LinkedList<RecorderEvent>(); + + /** + * {@inheritDoc} + */ + public RecorderEventHandlerJSONImpl(String filename) + throws IOException + { + file = new File(filename); + if (!file.createNewFile()) + throw new IOException("File exists or cannot be created: " + file); + + if (!file.canWrite()) + throw new IOException("Cannot write to file: " + file); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean handleEvent(RecorderEvent ev) + { + if (closed) + return false; + + MediaType mediaType = ev.getMediaType(); + RecorderEvent.Type type = ev.getType(); + long duration = ev.getDuration(); + long ssrc = ev.getSsrc(); + + /* + * For a RECORDING_ENDED event without a valid instant, find it's + * associated (i.e. with the same SSRC) RECORDING_STARTED event and + * compute the RECORDING_ENDED instance based on its duration. + */ + if (RecorderEvent.Type.RECORDING_ENDED.equals(type) + && ev.getInstant() == -1 + && duration != -1) + { + List<RecorderEvent> events = + MediaType.AUDIO.equals(mediaType) + ? audioEvents + : videoEvents; + + RecorderEvent start = null; + for (RecorderEvent e : events) + { + if (RecorderEvent.Type.RECORDING_STARTED.equals(e.getType()) + && e.getSsrc() == ssrc) + { + start = e; + break; + } + } + + if (start != null) + ev.setInstant(start.getInstant() + duration); + } + + if (MediaType.AUDIO.equals(mediaType)) + audioEvents.add(ev); + else if (MediaType.VIDEO.equals(mediaType)) + videoEvents.add(ev); + + try + { + writeAllEvents(); + } + catch (IOException ioe) + { + logger.warn("Failed to write recorder events to file: ", ioe); + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void close() + { + //XXX do we want to write everything again? + try + { + writeAllEvents(); + } + catch (IOException ioe) + { + logger.warn("Failed to write recorder events to file: " + ioe); + } + finally + { + closed = true; + } + } + + private void writeAllEvents() + throws IOException + { + Collections.sort(audioEvents, eventComparator); + Collections.sort(videoEvents, eventComparator); + int nbAudio = audioEvents.size(); + int nbVideo = videoEvents.size(); + + if (nbAudio + nbVideo > 0) + { + FileWriter writer = new FileWriter(file, false); + + writer.write("{\n"); + + if (nbAudio > 0) + { + writer.write(" \"audio\" : [\n"); + writeEvents(audioEvents, writer); + + if (nbVideo > 0) + writer.write(" ],\n\n"); + else + writer.write(" ]\n\n"); + } + + if (nbVideo > 0) + { + writer.write(" \"video\" : [\n"); + writeEvents(videoEvents, writer); + writer.write(" ]\n"); + } + + writer.write("}\n"); + + writer.close(); + } + } + + private void writeEvents(List<RecorderEvent> events, + FileWriter writer) + throws IOException + { + int idx = 0; + int size = events.size(); + for (RecorderEvent ev : events) + { + if (++idx == size) + writer.write(" " + getJSON(ev) + "\n"); + else + writer.write(" " + getJSON(ev)+",\n"); + } + } + + private String getJSON(RecorderEvent ev) + { + JSONObject json = new JSONObject(); + json.put("instant", ev.getInstant()); + + json.put("type", ev.getType().toString()); + + MediaType mediaType = ev.getMediaType(); + if (mediaType != null) + json.put("mediaType", mediaType.toString()); + + json.put("ssrc", ev.getSsrc()); + + long audioSsrc = ev.getAudioSsrc(); + if (audioSsrc != -1) + json.put("audioSsrc", audioSsrc); + + RecorderEvent.AspectRatio aspectRatio = ev.getAspectRatio(); + if (aspectRatio != RecorderEvent.AspectRatio.ASPECT_RATIO_UNKNOWN) + json.put("aspectRatio", aspectRatio.toString()); + + String filename = ev.getFilename(); + if (filename != null) + { + String bareFilename = filename; + int idx = filename.lastIndexOf('/'); + int len = filename.length(); + if (idx != -1 && idx != len-1) + bareFilename = filename.substring(1 + idx, len); + + json.put("filename", bareFilename); + } + + return json.toJSONString(); + } +} -- GitLab