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<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));
|
}
|
}
|
}
|
|
}
|