| | |
| | | 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. |
| | | * |
| | | * 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. |
| | | * |
| | | * 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 { |
| | | 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<String, File> files; |
| | | private HashMap<String, Long> bytes; |
| | | private HashMap<String, String> kataloge; |
| | | private String conf; |
| | | |
| | | public StreamHandler(String conf) { |
| | | super(""); // wird spaeter gesetzt |
| | | listen = new HashMap<String, Integer>(); // abspiellistenname -> z.Zt. spielender Index |
| | | kataloge = new HashMap(); // url -> abs. pfad |
| | | files = new HashMap<String, File>(); // abspiellistenname -> z zt spielende datei |
| | | bytes = new HashMap<String, Long>(); // 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<Range> 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<Titel> 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<String> orte = store.list(typ); |
| | | Iterator<String> 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)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |