package com.robonobo.plugin.mplayer; import java.io.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hsqldb.lib.StringUtil; import net.freeutils.httpserver.*; import net.freeutils.httpserver.HTTPServer.ContextHandler; import net.freeutils.httpserver.HTTPServer.Request; import net.freeutils.httpserver.HTTPServer.Response; import com.robonobo.common.pageio.buffer.PageBufferInputStream; import com.robonobo.common.util.ByteUtil; import com.robonobo.core.api.model.Stream; import com.robonobo.mina.external.buffer.PageBuffer; public class MplayerHttpServer { static final Pattern BYTE_RANGE_PAT = Pattern.compile("^bytes=(\\d+)-(\\d*)$"); private HTTPServer server; Log log = LogFactory.getLog(getClass()); public MplayerHttpServer() throws IOException { server = new HTTPServer(); } public void start() throws IOException { server.start(); } public void stop() { server.stop(); } public int getPort() { return server.getPort(); } public void addStream(Stream s, PageBuffer pb) { String sid = s.getStreamId(); server.getVirtualHost(null).addContext("/"+sid+".mp3", new StreamReqHandler(s, pb)); } public void removeStream(Stream s) { server.getVirtualHost(null).removeContext("/"+s.getStreamId()+".mp3"); } class StreamReqHandler implements ContextHandler { Stream s; PageBuffer pb; public StreamReqHandler(Stream s, PageBuffer pb) { this.s = s; this.pb = pb; } @Override public int serve(Request req, Response resp) throws IOException { log.debug("Handling http request from mplayer for stream "+s.getStreamId()); PageBufferInputStream pbis = new PageBufferInputStream(pb); try { resp.getHeaders().add("Connection", "close"); resp.getHeaders().add("Content-Type", "audio/mpeg"); String rangeStr = req.getHeaders().get("Range"); long sz = s.getSize(); if (StringUtil.isEmpty(rangeStr)) { // Normal GET resp.getHeaders().add("Accept-Ranges", "bytes"); resp.getHeaders().add("Content-Length", String.valueOf(sz)); log.debug("Handling normal get"); resp.sendHeaders(200); } else { // Partial content request Matcher m = BYTE_RANGE_PAT.matcher(rangeStr); if (!m.matches()) { log.error("Error parsing byte range: " + rangeStr); return 500; } int firstByte = Integer.parseInt(m.group(1)); if (firstByte >= sz) { log.debug("Duff byte range - returning 416"); return 416; // Range not satisfiable, fuck knows why mplayer asks for this } String endRange = m.group(2); int lastByte = StringUtil.isEmpty(endRange) ? (int) (sz - 1) : Integer.parseInt(endRange); int len = lastByte - firstByte + 1; resp.getHeaders().add("Accept-Ranges", "bytes"); resp.getHeaders().add("Content-Length", String.valueOf(len)); resp.getHeaders().add("Content-Range", "bytes "+firstByte+"-"+lastByte+"/"+sz); log.debug("Sending partial content, range "+firstByte+"-"+lastByte); resp.sendHeaders(206); // Partial content log.debug("Starting skip..."); pbis.skip(firstByte); log.debug("Finished skip"); } streamDump(pbis, resp.getBody()); log.debug("Request finished"); return 0; } finally { pbis.close(); log.debug("Finished http request for stream "+s.getStreamId()); } } void streamDump(InputStream is, OutputStream os) throws IOException { int logEvery = 50 * 1024; byte[] buf = new byte[1024]; int totalRead = 0; int readSinceLog = 0; int numRead; while((numRead = is.read(buf)) > 0) { os.write(buf, 0, numRead); totalRead += numRead; readSinceLog += numRead; if(readSinceLog > logEvery) { log.debug("Stream dumped "+totalRead+" bytes"); readSinceLog = 0; } } is.close(); os.close(); } } }