package net.pms.remote; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import java.io.File; import java.io.IOException; import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import net.pms.PMS; import net.pms.configuration.FormatConfiguration; import net.pms.configuration.PmsConfiguration; import net.pms.configuration.WebRender; import net.pms.dlna.DLNAResource; import net.pms.dlna.Playlist; import net.pms.dlna.RootFolder; import net.pms.dlna.virtual.VirtualVideoAction; import net.pms.encoders.Player; import net.pms.formats.Format; import net.pms.formats.v2.SubtitleType; import net.pms.io.OutputParams; import net.pms.util.SubtitleUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RemotePlayHandler implements HttpHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RemotePlayHandler.class); private RemoteWeb parent; private static final PmsConfiguration configuration = PMS.getConfiguration(); public RemotePlayHandler(RemoteWeb parent) { this.parent = parent; } private String returnPage() { // special page to return return "<html><head><script>window.refresh=true;history.back()</script></head></html>"; } private void addNextByType(DLNAResource d, HashMap<String, Object> vars) { List<DLNAResource> children = d.getParent().getChildren(); boolean looping = configuration.getWebAutoLoop(d.getFormat()); int type = d.getType(); int size = children.size(); int mod = looping ? size : 9999; int self = children.indexOf(d); for (int step = -1; step < 2; step += 2) { int i = self; int offset = (step < 0 && looping) ? size : 0; DLNAResource next = null; while (true) { i = (offset + i + step) % mod; if (i >= size || i < 0 || i == self) { break; // Not found } next = children.get(i); if (next.getType() == type && !next.isFolder()) { break; // Found } next = null; } String pos = step > 0 ? "next" : "prev"; vars.put(pos + "Id", next != null ? next.getResourceId() : null); vars.put(pos + "Attr", next != null ? (" title=\"" + StringEscapeUtils.escapeHtml(next.resumeName()) + "\"") : " disabled"); } } private String mkPage(String id, HttpExchange t) throws IOException { HashMap<String, Object> vars = new HashMap<>(); vars.put("serverName", configuration.getServerDisplayName()); LOGGER.debug("Make play page " + id); RootFolder root = parent.getRoot(RemoteUtil.userName(t), t); if (root == null) { LOGGER.debug("root not found"); throw new IOException("Unknown root"); } WebRender renderer = (WebRender) root.getDefaultRenderer(); renderer.setBrowserInfo(RemoteUtil.getCookie("UMSINFO", t), t.getRequestHeaders().getFirst("User-agent")); //List<DLNAResource> res = root.getDLNAResources(id, false, 0, 0, renderer); DLNAResource r = root.getDLNAResource(id, renderer); if (r == null) { LOGGER.debug("Bad web play id: " + id); throw new IOException("Bad Id"); } if (!r.isCodeValid(r)) { LOGGER.debug("coded object with invalid code"); throw new IOException("Bad code"); } if (r instanceof VirtualVideoAction) { // for VVA we just call the enable fun directly // waste of resource to play dummy video if (((VirtualVideoAction) r).enable()) { renderer.notify(renderer.INFO, r.getName() + " enabled"); } else { renderer.notify(renderer.INFO, r.getName() + " disabled"); } return returnPage(); } Format format = r.getFormat(); boolean isImage = format.isImage(); boolean isVideo = format.isVideo(); boolean isAudio = format.isAudio(); String query = t.getRequestURI().getQuery(); boolean forceFlash = StringUtils.isNotEmpty(RemoteUtil.getQueryVars(query, "flash")); boolean forcehtml5 = StringUtils.isNotEmpty(RemoteUtil.getQueryVars(query, "html5")); boolean flowplayer = isVideo && (forceFlash || (!forcehtml5 && configuration.getWebFlash())); // hack here to ensure we got a root folder to use for recently played etc. root.getDefaultRenderer().setRootFolder(root); String id1 = URLEncoder.encode(id, "UTF-8"); String name = StringEscapeUtils.escapeHtml(r.resumeName()); String mime = root.getDefaultRenderer().getMimeType(r.mimeType(), r.getMedia()); String mediaType = isVideo ? "video" : isAudio ? "audio" : isImage ? "image" : ""; String auto = "autoplay"; @SuppressWarnings("unused") String coverImage = ""; if (isVideo) { if (mime.equals(FormatConfiguration.MIMETYPE_AUTO)) { if (r.getMedia() != null && r.getMedia().getMimeType() != null) { mime = r.getMedia().getMimeType(); } } if (!flowplayer) { if (!RemoteUtil.directmime(mime) || RemoteUtil.transMp4(mime, r.getMedia()) || r.isResume()) { WebRender render = (WebRender) r.getDefaultRenderer(); mime = render != null ? render.getVideoMimeType() : RemoteUtil.transMime(); } } } vars.put("isVideo", isVideo); vars.put("name", name); vars.put("id1", id1); vars.put("autoContinue", configuration.getWebAutoCont(format)); if (configuration.isDynamicPls()) { if (r.getParent() instanceof Playlist) { vars.put("plsOp", "del"); vars.put("plsSign", "-"); vars.put("plsAttr", RemoteUtil.getMsgString("Web.4", t)); } else { vars.put("plsOp", "add"); vars.put("plsSign", "+"); vars.put("plsAttr", RemoteUtil.getMsgString("Web.5", t)); } } addNextByType(r, vars); if (isImage) { // do this like this to simplify the code // skip all player crap since img tag works well int delay = configuration.getWebImgSlideDelay() * 1000; if (delay > 0 && configuration.getWebAutoCont(format)) { vars.put("delay", delay); } } else { vars.put("mediaType", mediaType); vars.put("auto", auto); vars.put("mime", mime); if (flowplayer) { if ( RemoteUtil.directmime(mime) && !RemoteUtil.transMp4(mime, r.getMedia()) && !r.isResume() && !forceFlash ) { vars.put("src", true); } } else { vars.put("width", renderer.getVideoWidth()); vars.put("height", renderer.getVideoHeight()); } } if (configuration.useWebControl()) { vars.put("push", true); } if (isVideo && configuration.getWebSubs()) { // only if subs are requested as <track> tags // otherwise we'll transcode them in boolean isFFmpegFontConfig = configuration.isFFmpegFontConfig(); if (isFFmpegFontConfig) { // do not apply fontconfig to flowplayer subs configuration.setFFmpegFontConfig(false); } OutputParams p = new OutputParams(configuration); p.sid = r.getMediaSubtitle(); Player.setAudioAndSubs(r.getName(), r.getMedia(), p); if (p.sid != null && p.sid.getType().isText()) { try { File subFile = SubtitleUtils.getSubtitles(r, r.getMedia(), p, configuration, SubtitleType.WEBVTT); LOGGER.debug("subFile " + subFile); if (subFile != null) { vars.put("sub", parent.getResources().add(subFile)); } } catch (Exception e) { LOGGER.debug("error when doing sub file " + e); } } configuration.setFFmpegFontConfig(isFFmpegFontConfig); // return back original fontconfig value } return parent.getResources().getTemplate(isImage ? "image.html" : flowplayer ? "flow.html" : "play.html").execute(vars); } @Override public void handle(HttpExchange t) throws IOException { if (RemoteUtil.deny(t)) { throw new IOException("Access denied"); } String p = t.getRequestURI().getPath(); if (p.contains("/play/")) { LOGGER.debug("got a play request " + t.getRequestURI()); String id = RemoteUtil.getId("play/", t); String response = mkPage(id, t); // LOGGER.trace("play page " + response); RemoteUtil.respond(t, response, 200, "text/html"); } else if (p.contains("/playerstatus/")) { String json = IOUtils.toString(t.getRequestBody(), "UTF-8"); LOGGER.trace("got player status: " + json); RemoteUtil.respond(t, "", 200, "text/html"); RootFolder root = parent.getRoot(RemoteUtil.userName(t), t); if (root == null) { LOGGER.debug("root not found"); throw new IOException("Unknown root"); } WebRender renderer = (WebRender) root.getDefaultRenderer(); ((WebRender.WebPlayer)renderer.getPlayer()).setData(json); } else if (p.contains("/playlist/")) { String[] tmp = p.split("/"); // sanity if (tmp.length < 3) { throw new IOException("Bad request"); } String op = tmp[tmp.length - 2]; String id = tmp[tmp.length - 1]; DLNAResource r = PMS.getGlobalRepo().get(id); if (r != null) { RootFolder root = parent.getRoot(RemoteUtil.userName(t), t); if (root == null) { LOGGER.debug("root not found"); throw new IOException("Unknown root"); } WebRender renderer = (WebRender) root.getDefaultRenderer(); if (op.equals("add")) { PMS.get().getDynamicPls().add(r); renderer.notify(renderer.OK, "Added '" + r.getDisplayName() + "' to dynamic playlist"); } else if (op.equals("del") && (r.getParent() instanceof Playlist)) { ((Playlist)r.getParent()).remove(r); renderer.notify(renderer.INFO, "Removed '" + r.getDisplayName() + "' from playlist"); } } RemoteUtil.respond(t, returnPage(), 200, "text/html"); } } }