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