From 5bf530d39a7e06bdd982a5c2495ff9a04fd83da4 Mon Sep 17 00:00:00 2001 From: undisclosed Date: Wed, 11 Jan 2023 17:38:22 +0000 Subject: [PATCH] Erste Versuche mit StreamHandler --- src/de/uhilger/tango/api/StreamHandler.java | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/tango_de_DE.properties | 1 src/de/uhilger/tango/api/FileHandler.java | 3 src/de/uhilger/tango/Server.java | 5 4 files changed, 377 insertions(+), 17 deletions(-) diff --git a/src/de/uhilger/tango/Server.java b/src/de/uhilger/tango/Server.java index 88be4db..d5d445a 100644 --- a/src/de/uhilger/tango/Server.java +++ b/src/de/uhilger/tango/Server.java @@ -25,6 +25,7 @@ import de.uhilger.tango.api.MediaSteuerung; import de.uhilger.tango.api.StopServerHandler; import de.uhilger.tango.api.StorageHandler; +import de.uhilger.tango.api.StreamHandler; import de.uhilger.tango.store.FileStorage; import de.uhilger.tango.entity.Ablageort; import java.io.File; @@ -54,7 +55,8 @@ public static final String RB_STORE = "store"; public static final String RB_STRG = "strg"; public static final String RB_GSTRG = "gstrg"; - public static final String RB_ALIST= "alist"; + public static final String RB_ALIST = "alist"; + public static final String RB_STRM = "strm"; //public static final String RB_UI_ROOT = "uiroot"; public static final String RB_STOP_SERVER = "stopServer"; //public static final String RB_ABLAGE_TEST = "testAblage"; @@ -121,6 +123,7 @@ server.createContext(ctx + rb.getString(RB_STRG), new MediaSteuerung(conf)); server.createContext(ctx + rb.getString(RB_GSTRG), new GeraetSteuerung(conf)); server.createContext(ctx + rb.getString(RB_ALIST), new ListHandler(conf)); + server.createContext(ctx + rb.getString(RB_STRM), new StreamHandler(conf)); server.createContext(ctx + rb.getString(RB_STOP_SERVER), new StopServerHandler()); //server.setExecutor(Executors.newFixedThreadPool(20)); server.setExecutor(Executors.newFixedThreadPool(5)); diff --git a/src/de/uhilger/tango/api/FileHandler.java b/src/de/uhilger/tango/api/FileHandler.java index 40edfed..d0299e1 100644 --- a/src/de/uhilger/tango/api/FileHandler.java +++ b/src/de/uhilger/tango/api/FileHandler.java @@ -288,7 +288,8 @@ if (values.length < 2) { // Fall 3 range.setStart(Long.parseLong(values[0])); - range.setEnd(file.length()); + //range.setEnd(file.length()); + range.setEnd(Long.MAX_VALUE); } else { if (values[0].length() < 1) { // Fall 1 diff --git a/src/de/uhilger/tango/api/StreamHandler.java b/src/de/uhilger/tango/api/StreamHandler.java index d19a82c..24bc407 100644 --- a/src/de/uhilger/tango/api/StreamHandler.java +++ b/src/de/uhilger/tango/api/StreamHandler.java @@ -1,22 +1,377 @@ 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)); + } + } + } + } diff --git a/src/tango_de_DE.properties b/src/tango_de_DE.properties index 27894da..b493ea4 100644 --- a/src/tango_de_DE.properties +++ b/src/tango_de_DE.properties @@ -20,6 +20,7 @@ alist=/api/alist strg=/api/strg gstrg=/api/gstrg +strm=/api/stream epliste=liste eplisteAlles=listealles stopServer=/api/server/stop -- Gitblit v1.9.3