package de.uhilger.tango.api; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import de.uhilger.tango.App; import de.uhilger.tango.Server; import static de.uhilger.tango.api.FileHandler.CONTENT_LENGTH; import static de.uhilger.tango.api.FileHandler.HTTP_GET; import static de.uhilger.tango.api.FileHandler.RANGE_HEADER; import static de.uhilger.tango.api.FileHandler.RB_NOT_FOUND; import static de.uhilger.tango.api.FileHandler.RB_WELCOME_FILE; import static de.uhilger.tango.api.FileHandler.SC_NOT_FOUND; import static de.uhilger.tango.api.FileHandler.SC_OK; import static de.uhilger.tango.api.FileHandler.STR_BLANK; import static de.uhilger.tango.api.FileHandler.STR_DOT; import de.uhilger.tango.entity.Ablageort; import de.uhilger.tango.entity.Abspielliste; import de.uhilger.tango.entity.Entity; import de.uhilger.tango.entity.Titel; import de.uhilger.tango.store.FileStorage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; /** * Der StreamHandler liefert ganze Abspiellisten als einzelnen Stream aus. Die in Tango mit dem * ListHandler erstellen Abspiellisten werden als ein zusammenhaengender Stream ausgegeben. * * Den Inhalt einer Abspielliste als Stream ausgeben HTTP GET /tango/api/stream/liste/[name] * * * * * die folgenden URLs ggf. wieder wegnehmen * * HTTP GET /tango/api/stream/play/liste/[name] * * HTTP GET /tango/api/stream/pause/liste/[name] HTTP GET /tango/api/stream/stop/liste/[name] HTTP * GET /tango/api/stream/seek/liste/[name]/[sekunden] * * Die Funktionen des StreamHandlers ergaenzen so die Ausgabe einzelner Media-Dateien als Stream, * wie sie mit dem FileHandler und seinen Subklassen sowie mit der MediaSteuerung erfolgen. * * @author Ulrich Hilger */ public class StreamHandler extends FileHandler { private static final Logger logger = Logger.getLogger(StreamHandler.class.getName()); public static final String PLAY = "play"; public static final String LISTE = "liste"; private HashMap listen; private HashMap files; private HashMap bytes; private HashMap kataloge; private String conf; public StreamHandler(String conf) { super(""); // wird spaeter gesetzt listen = new HashMap(); // abspiellistenname -> z.Zt. spielender Index kataloge = new HashMap(); // url -> abs. pfad files = new HashMap(); // abspiellistenname -> z zt spielende datei bytes = new HashMap(); // abspiellistenname -> gespielte bytes this.conf = conf; ablageorteLesen(); } @Override public void handle(HttpExchange e) throws IOException { String path = e.getRequestURI().toString(); String[] elems = path.split(Server.SLASH); String lName = elems[5]; Integer index; File file; FileStorage s = new FileStorage(conf); Object o = listen.get(lName); if (o instanceof Integer) { // liste spielt schon index = (Integer) o; file = files.get(lName); } else { index = 0; // liste spielt noch nicht listen.put(lName, index); file = getFileToPlay(s, lName, index.intValue()); files.put(lName, file); bytes.put(lName, Long.valueOf(0)); } //String fName = getFileName(e); String fName = file.getName(); if (fName.startsWith(STR_DOT)) { sendNotFound(e, fName); } else { Headers headers = e.getRequestHeaders(); int indexVal = index.intValue(); if (headers.containsKey(RANGE_HEADER)) { logger.info("range header present, serving list file parts"); serveListParts(e, s, lName, file, indexVal); //serveList(e, s, lName, file, indexVal); } else { //if (fName.length() < 1 || fName.endsWith(Server.SLASH)) { // ResourceBundle rb = ResourceBundle.getBundle(App.RB_NAME); // fName += getResString(RB_WELCOME_FILE); //} logger.info("no range header or header ignored, streaming whole files"); serveList(e, s, lName, file, indexVal); //while(file != null) { // files.put(lName, file); // listen.put(lName, index); // serveFile(e, file); // file = getFileToPlay(s, lName, ++indexVal); //} } } /* String response = lName; Headers headers = e.getResponseHeaders(); headers.add("Content-Type", "application/json"); e.sendResponseHeaders(200, response.length()); OutputStream os = e.getResponseBody(); os.write(response.getBytes()); os.close(); */ } private void serveListParts(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException { if (file.exists()) { setHeaders(e, file); //e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); //e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); //e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE); logger.info("playing " + file.getName()); logger.info("file length: " + file.length()); InputStream is = new FileInputStream(file); OutputStream os = e.getResponseBody(); while (file != null) { //if(is instanceof InputStream) { // is.close(); //} //is = new FileInputStream(file); files.put(lName, file); listen.put(lName, indexVal); file = serveFileParts(e, file, is, os, s, lName, indexVal); //serveFile(e, file); if(bytes.get(lName) == 0l) { file = getFileToPlay(s, lName, ++indexVal); if(is instanceof InputStream) { is.close(); } is = new FileInputStream(file); logger.info("file length: " + file.length()); } logger.info("playing " + file.getName()); } //os.flush(); if(is instanceof InputStream) { is.close(); } logger.info("fertig os flush und close "); os.flush(); os.close(); } else { sendNotFound(e, file.getName()); } logger.info("ende"); } protected File serveFileParts(HttpExchange e, File file, InputStream is, OutputStream os, FileStorage s, String lName, int indexVal) throws IOException { if (file.exists()) { setHeaders(e, file); //Long byteCount = bytes.get(lName); //logger.info("byteCount at start: " + byteCount); long responseLength = 0; long start = 0; long end; String hdr; RangeGroup rangeGroup = parseRanges(e, file); Iterator i = rangeGroup.getRanges(); Headers resHeaders = e.getResponseHeaders(); while (i.hasNext()) { Range range = i.next(); start = range.getStart(); //start = 0; end = range.getEnd(); //end = file.length(); //responseLength += (end - start); //start = 0; //end = responseLength; //range.setStart(start); //range.setEnd(end); hdr = contentRangeHdr(range, file); resHeaders.add(CONTENT_RANGE_HEADER, hdr); logger.info("added header " + hdr); responseLength += (end - start); } logger.info("responseLength: " + responseLength); e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength); //e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE); //logger.info("responseHeaders gesendet "); //if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) { //InputStream is = new FileInputStream(file); //OutputStream os = e.getResponseBody(); //long streamPos = bytes.get(lName); long count = 0; if (start > 0) { logger.info("skip to " + start); is.skip(start); } int byteRead = is.read(); logger.info("starte while mit count=" + count); while (/*byteRead > -1 && */ count < responseLength) { ++count; //++streamPos; os.write(byteRead); byteRead = is.read(); //logger.info("byteRead " + byteRead); if(byteRead < 0 && count < responseLength) { logger.info("dateiende, naechste Datei"); file = getFileToPlay(s, lName, ++indexVal); if(file != null) { logger.info("playing " + file.getName()); //streamPos = 0; is.close(); is = new FileInputStream(file); byteRead = is.read(); logger.info("neue Datei, count " + count + " responseLength " + responseLength); logger.info("file length: " + file.length()); } else { //logger.info("Liste zuende"); count = Long.MAX_VALUE; logger.info("Liste zuende"); } } } logger.info("while ende, count " + count); //if(streamPos != responseLength) { // bytes.put(lName, streamPos); // logger.info("streamPos " + streamPos); //} else { // bytes.put(lName, Long.valueOf(0)); // logger.info("streamPos " + 0); //} //byteCount = count; //logger.info("byteCount at end: " + byteCount); //os.flush(); //os.close(); // is.close(); } //} else { // sendNotFound(e, file.getName()); //} logger.info("ende"); return file; } protected String contentRangeHdr(Range range, File file) { StringBuilder sb = new StringBuilder(); sb.append(getResString(RB_BYTES)); sb.append(STR_BLANK); sb.append(range.getStart()); sb.append(getResString(RB_DASH)); sb.append(range.getEnd()); sb.append(Server.SLASH); //sb.append(file.length()); sb.append(Long.MAX_VALUE); return sb.toString(); } private void serveList(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException { if (file.exists()) { setHeaders(e, file); e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); //e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); InputStream in = new FileInputStream(file); OutputStream out = e.getResponseBody(); while (file != null) { files.put(lName, file); listen.put(lName, indexVal); serveFile(e, file, in, out); file = getFileToPlay(s, lName, ++indexVal); } out.flush(); out.close(); in.close(); } else { sendNotFound(e, file.getName()); } } /** * Den Inhalt einer Datei ausliefern * * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum * Anfertigen und Senden der Antwort * @param file die Datei, deren Inhalt ausgeliefert werden soll * @throws IOException falls etwas schief geht entsteht dieser Fehler */ protected void serveFile(HttpExchange e, File file, InputStream in, OutputStream out) throws IOException { logger.info("serving file " + file.getName()); //if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) { int b = in.read(); while (b > -1) { out.write(b); b = in.read(); } logger.info("done serving file " + file.getName()); //in.close(); //out.flush(); //} } private File getFileToPlay(FileStorage s, String lName, int index) { Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName); if (entity instanceof Abspielliste) { Abspielliste liste = (Abspielliste) entity; List titelListe = liste.getTitel(); if(titelListe.size() > index) { Titel t = titelListe.get(index); String katalogUrl = t.getKatalogUrl(); String pfad = kataloge.get(katalogUrl); return new File(pfad + t.getPfad(), t.getName()); } else { return null; } } else { // keine Abspielliste return null; } } private void ablageorteLesen() { String typ = Ablageort.class.getSimpleName(); FileStorage store = new FileStorage(conf); List orte = store.list(typ); Iterator i = orte.iterator(); while (i.hasNext()) { String ortName = i.next(); Entity e = store.read(typ, ortName); if (e instanceof Ablageort) { Ablageort ablageort = (Ablageort) e; Logger logger = Logger.getLogger(StreamHandler.class.getName()); //logger.log(Level.FINE, "{0}{1}", new Object[]{ctx, ablageort.getUrl()}); logger.fine(ablageort.getOrt() + " " + ablageort.getUrl()); kataloge.put(ablageort.getUrl(), ablageort.getOrt()); //server.createContext(ctx + ablageort.getUrl(), //new ListFileHandler(new File(ablageort.getOrt()).getAbsolutePath(), conf)); } } } }