package org.myrobotlab.net;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import org.myrobotlab.image.SerializableImage;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.slf4j.Logger;
/**
* @author GroG
*
* mjpeg server - allows multiple jpeg streams to be sent to multiple
* clients extends the most excellent NanoHTTPD server - multi-part mime
* was done with little parts borg'd in from -
* http://www.servlets.com/cos/
* http://www.damonkohler.com/2010/10/mjpeg-streaming-protocol.html
*
*/
public class MjpegServer extends NanoHTTPD {
public class Connection {
boolean initialized = false;
Socket socket;
OutputStream os;
public Connection(Socket socket) throws IOException {
this.socket = socket;
os = socket.getOutputStream();
}
public void close() {
try {
socket.close();
socket = null;
} catch (IOException e) {
}
}
}
public class VideoWebClient extends Thread {
String feed;
ArrayList<Connection> connections = new ArrayList<Connection>();
BlockingQueue<SerializableImage> videoFeed;
VideoWebClient(BlockingQueue<SerializableImage> videoFeed, String feed, Socket socket) throws IOException {
// super(String.format("stream_%s:%s",
// socket.getInetAddress().getHostAddress(), socket.getPort()));
super(String.format("stream_%s", feed));
this.videoFeed = videoFeed;
this.feed = feed;
connections.add(new Connection(socket));
}
// TODO - look into buffered output stream
@Override
public void run() {
try {
while (true) {
SerializableImage frame = videoFeed.take();
// ++frameIndex;
// log.info("frame {}", frameIndex);
Logging.logTime(String.format("Mjpeg frameIndex %d %d", frame.frameIndex, System.currentTimeMillis()));
for (Iterator<Connection> iterator = connections.iterator(); iterator.hasNext();) {
Connection c = iterator.next();
try {
if (!c.initialized) {
c.os.write(
("HTTP/1.0 200 OK\r\n" + "Server: YourServerName\r\n" + "Connection: close\r\n" + "Max-Age: 0\r\n" + "Expires: 0\r\n" + "Cache-Control: no-cache, private\r\n"
+ "Pragma: no-cache\r\n" + "Content-Type: multipart/x-mixed-replace; " + "boundary=--BoundaryString\r\n\r\n").getBytes());
c.initialized = true;
}
byte[] bytes = frame.getBytes();
// begin jpg
c.os.write(("--BoundaryString\r\n" + "Content-type: image/jpg\r\n" + "Content-Length: " + bytes.length + "\r\n\r\n").getBytes());
// write the jpg
c.os.write(bytes);
// end
c.os.write("\r\n\r\n".getBytes());
// flush or not to flush that is the question
c.os.flush();
Logging.logTime(String.format("Mjpeg frameIndex %d %d SENT", frame.frameIndex, System.currentTimeMillis()));
} catch (Exception e) {
Logging.logError(e);
log.info("removing socket");
iterator.remove();
c.close();
}
} // for each socket
}
} catch (Exception e) {
// FIXME remove socket from list - continue to run
Logging.logError(e);
}
}
}
public final static Logger log = LoggerFactory.getLogger(MjpegServer.class.getCanonicalName());
transient public HashMap<String, BlockingQueue<SerializableImage>> videoFeeds = new HashMap<String, BlockingQueue<SerializableImage>>();
transient public HashMap<String, VideoWebClient> clients = new HashMap<String, VideoWebClient>();
/**
* @param args
*/
public static void main(String[] args) {
try {
LoggingFactory.init(Level.INFO);
MjpegServer server = new MjpegServer(9090);
server.start();
log.info("here");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public MjpegServer(int port) {
super(port);
}
@Override
public Response serve(String uri, String method, Properties header, Properties parms, Socket socket) {
log.info(method + " '" + uri + "' ");
Enumeration e = header.propertyNames();
while (e.hasMoreElements()) {
String value = (String) e.nextElement();
log.info(" HDR: '" + value + "' = '" + header.getProperty(value) + "'");
}
e = parms.propertyNames();
while (e.hasMoreElements()) {
String value = (String) e.nextElement();
log.info(" PRM: '" + value + "' = '" + parms.getProperty(value) + "'");
}
String feed = null;
// look for "file" requests
if (uri.contains(".")) {
return serveFile(uri, header, new File("."), true);
}
int pos0 = uri.lastIndexOf("/");
if (pos0 != -1) {
feed = uri.substring(pos0 + 1);
}
if (!videoFeeds.containsKey(feed)) {
StringBuffer response = new StringBuffer(String.format("<html><body align=center>video feeds<br/>", feed));
for (Map.Entry<String, BlockingQueue<SerializableImage>> o : videoFeeds.entrySet()) {
// Map.Entry<String,SerializableImage> pairs = o;
// response.append(String.format("<a href=\"http://%\" >%s</a><br/>",
// o.getKey()));
response.append(String.format("<img src=\"%s\" /><br/>%s<br/>", o.getKey(), o.getKey()));
log.info(o.getKey());
}
if (videoFeeds.size() == 0) {
response.append("no video feed exist - try attaching a VideoSource to the VideoStreamer");
}
response.append("</body></html>");
return new Response(HTTP_OK, MIME_HTML, response.toString());
} else {
try {
VideoWebClient client = new VideoWebClient(videoFeeds.get(feed), feed, socket);
client.start();
clients.put(feed, client);
} catch (IOException e1) {
Logging.logError(e1);
}
}
// new Response(HTTP_OK, MIME_HTML, "<html><body>Redirected: <a href=\""
// + uri + "\">" + uri + "</a></body></html>");
return null; // serveFile(uri, header, new File("."), true);
}
}