package org.jcodec.samples.streaming; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.nio.channels.Channels; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.util.MultiPartOutputStream; import org.jcodec.common.StringUtils; import org.jcodec.common.model.Packet; import org.jcodec.common.model.TapeTimecode; import org.jcodec.player.filters.MediaInfo; import org.jcodec.player.filters.MediaInfo.VideoInfo; import org.jcodec.samples.streaming.Adapter.AdapterTrack; import org.jcodec.samples.streaming.Adapter.AudioAdapterTrack; import org.jcodec.samples.streaming.Adapter.VideoAdapterTrack; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Streaming component compatible with JCodec streaming protocol * * @author The JCodec project * */ public class StreamingServlet extends HttpServlet { private File base; private Map<File, Adapter> map = new HashMap<File, Adapter>(); public StreamingServlet(File file) { this.base = file; } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String[] path = StringUtils.split(req.getPathInfo(), "/"); File file = null; Integer track = null, frameS = null, frameE = null; for (int i = path.length - 1; i >= 0; i--) { File test = new File(base, StringUtils.join(path, "/", 0, i + 1)); if (test.exists() && !test.isDirectory()) { track = i < path.length - 1 ? Integer.parseInt(path[i + 1]) : null; if (i < path.length - 2) { String[] split = StringUtils.split(path[i + 2], ':'); if (split.length == 1) { frameS = frameE = Integer.parseInt(split[0]); } else { frameS = Integer.parseInt(split[0]); frameE = Integer.parseInt(split[1]); } } file = test; break; } } Adapter adapter = getAdapter(file); try { if (frameS != null) { frame(adapter, track, frameS, frameE, resp); } else if (track != null) { if (req.getParameter("pts") != null) search(adapter, track, Long.parseLong(req.getParameter("pts")), resp); else info(adapter, resp, track); } else { info(adapter, resp); } } catch (IOException e) { e.printStackTrace(); throw e; } catch (Throwable e2) { e2.printStackTrace(); } } protected AdapterFactory factory = new AdapterFactory(); protected Adapter getAdapter(File mvFile) throws IOException { Adapter adapter = map.get(mvFile); if (adapter == null) { adapter = factory.createAdaptor(mvFile); map.put(mvFile, adapter); } return adapter; } protected void search(Adapter demuxer, int trackNo, long pts, HttpServletResponse resp) throws IOException { AdapterTrack track = demuxer.getTrack(trackNo); int frameNo = track.search(pts); if (frameNo == -1) resp.sendError(404); else frame(demuxer, trackNo, frameNo, frameNo, resp); } protected void frame(Adapter demuxer, int trackNo, int frameS, int frameE, HttpServletResponse resp) throws IOException { AdapterTrack track = demuxer.getTrack(trackNo); if (track instanceof VideoAdapterTrack) { outGOPs(resp, (VideoAdapterTrack) track, frameS, frameE); } else { outFrames(resp, (AudioAdapterTrack) track, frameS, frameE); } } private void outFrames(HttpServletResponse resp, AudioAdapterTrack track, int frameS, int frameE) throws IOException { Packet packet = ((AudioAdapterTrack) track).getFrame(frameS++); if (packet == null) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } MultiPartOutputStream out = new MultiPartOutputStream(resp.getOutputStream()); resp.setContentType("multipart/mixed; boundary=" + out.getBoundary()); outFrame(out, packet); while (frameS < frameE) { packet = ((AudioAdapterTrack) track).getFrame(frameS++); if (packet == null) break; outFrame(out, packet); } out.close(); } private void outFrame(MultiPartOutputStream out, Packet packet) throws IOException { List<String> headers = new ArrayList<String>(); headers.add("JCodec-PTS: " + packet.getPts()); headers.add("JCodec-Duration: " + packet.getDuration()); headers.add("JCodec-FrameNo: " + packet.getFrameNo()); headers.add(String.format("Content-Disposition: attachment; filename=frame%08d.raw", packet.getFrameNo())); out.startPart("application/octet-stream", headers.toArray(new String[0])); Channels.newChannel(out).write(packet.getData()); } private void outGOPs(HttpServletResponse resp, VideoAdapterTrack track, int frameS, int frameE) throws IOException { Packet[] gop = ((VideoAdapterTrack) track).getGOP(frameS); if (gop == null) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } MultiPartOutputStream out = new MultiPartOutputStream(resp.getOutputStream()); resp.setContentType("multipart/mixed; boundary=" + out.getBoundary()); outGOP(gop, out); frameS = nextGop(track, frameS); while (frameS != -1 && frameS < frameE) { gop = ((VideoAdapterTrack) track).getGOP(frameS); outGOP(gop, out); frameS = nextGop(track, frameS); } out.close(); } private int nextGop(VideoAdapterTrack track, int frameS) { int curGop = track.gopId(frameS); if (curGop == -1) return -1; while (curGop == track.gopId(frameS)) frameS++; return track.gopId(frameS) == -1 ? -1 : frameS; } private void outGOP(Packet[] gop, MultiPartOutputStream out) throws IOException { for (int i = 0; i < gop.length; i++) { Packet packet = gop[i]; List<String> headers = new ArrayList<String>(); headers.add("JCodec-PTS: " + packet.getPts()); headers.add("JCodec-Duration: " + packet.getDuration()); headers.add("JCodec-FrameNo: " + packet.getFrameNo()); headers.add("JCodec-Key: " + packet.isKeyFrame()); headers.add("JCodec-DisplayOrder: " + packet.getDisplayOrder()); if (packet.getTapeTimecode() != null) headers.add("JCodec-TapeTimecode: " + formatTapeTimecode(packet.getTapeTimecode())); headers.add(String.format("Content-Disposition: attachment; filename=frame%08d.mpg", packet.getFrameNo())); out.startPart("application/octet-stream", headers.toArray(new String[0])); Channels.newChannel(out).write(packet.getData()); } } private String formatTapeTimecode(TapeTimecode tapeTimecode) { return String.format("%02d", tapeTimecode.getHour()) + ":" + String.format("%02d", tapeTimecode.getMinute()) + ":" + String.format("%02d", tapeTimecode.getSecond()) + (tapeTimecode.isDropFrame() ? ";" : ":") + String.format("%02d", tapeTimecode.getFrame()); } protected void info(Adapter demuxer, HttpServletResponse resp) throws IOException { PrintStream out = new PrintStream(resp.getOutputStream()); out.println("["); List<AdapterTrack> tracks = demuxer.getTracks(); for (int i = 0; i < tracks.size(); i++) { MediaInfo mediaInfo = tracks.get(i).getMediaInfo(); trackInfo(out, mediaInfo, i); out.print(","); } out.println("]"); } private void trackInfo(PrintStream out, MediaInfo mediaInfo, int ind) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); out.println("{"); out.println("\"id\": \"" + ind + "\","); out.println("\"type\": \"" + (mediaInfo instanceof VideoInfo ? "video" : "audio") + "\","); out.print("\"info\":"); out.println(gson.toJson(mediaInfo)); out.println("}"); } protected void info(Adapter adapter, HttpServletResponse resp, int trackNo) throws IOException { trackInfo(new PrintStream(resp.getOutputStream()), adapter.getTrack(trackNo).getMediaInfo(), trackNo); } }