package net.pms.dlna; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import net.pms.PMS; import net.pms.configuration.PmsConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ResumeObj { private static final PmsConfiguration configuration = PMS.getConfiguration(); private static final Logger LOGGER = LoggerFactory.getLogger(ResumeObj.class); private static final int DAYS = 3600 * 24 * 1000; public static final String CLEAN_REG = "_hash_(\\d+)"; private File file; private long offsetTime; private long resDuration; private long minDur; private static File resumePath() { File path = new File(configuration.getDataFile("resume")); path.mkdirs(); return path; } private static File resumeFile(DLNAResource r) { String wName = r.getName().replaceAll("[:\\[\\]\n\r]", "").trim(); String fName = wName + "_hash_" + r.resumeHash() + ".resume"; return new File(resumePath(), fName); } public static File[] resumeFiles() { File path = resumePath(); return path.listFiles(); } public static ResumeObj create(DLNAResource r) { if (!r.configuration.isResumeEnabled()) { // resume is off bail early return null; } File f = resumeFile(r); if (!f.exists()) { // no file no resume return null; } ResumeObj res = new ResumeObj(f); res.read(); if (res.noResume()) { return null; } if (r.getMedia() != null) { double dur = r.getMedia().getDurationInSeconds(); if (dur == 0.0 || dur == DLNAMediaInfo.TRANS_SIZE) { r.getMedia().setDuration(res.resDuration / 1000.0); } } res.setMinDuration(r.minPlayTime()); return res; } public static ResumeObj store(DLNAResource r, long startTime) { File f = resumeFile(r); ResumeObj obj = new ResumeObj(f); obj.setMinDuration(r.minPlayTime()); obj.stop(startTime, (long) r.getMedia().getDurationInSeconds() * 1000); if (obj.noResume()) { return null; } return obj; } public ResumeObj(File f) { offsetTime = 0; resDuration = 0; file = f; minDur = configuration.getMinimumWatchedPlayTime(); } public void setMinDuration(long dur) { if (dur == 0) { dur = configuration.getMinimumWatchedPlayTime(); } minDur = dur; } public void read() { try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { String str; while ((str = in.readLine()) != null) { String[] tmp = str.split(","); offsetTime = Long.parseLong(tmp[0]); if (tmp.length > 1) { resDuration = Long.parseLong(tmp[1]); } break; } } catch (IOException e) { } } private static void write(long time, long duration, File f) { try { try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8))) { out.write(time + "," + duration); out.flush(); out.close(); if (configuration.getResumeKeepTime() > 0) { PMS.get().addTempFile(f, configuration.getResumeKeepTime() * DAYS); } } } catch (IOException e) { } } public File getResumeFile() { return file; } public boolean noResume() { return (offsetTime == 0); } public long getTimeOffset() { if (isDone()) { return 0; } read(); return offsetTime; } public boolean isDone() { return !file.exists(); } public void update(Range.Time range, DLNAResource r) { if (range.isStartOffsetAvailable() && range.getStartOrZero() > 0.0) { long now = System.currentTimeMillis(); if (r.getMedia() != null) { stop(now + getTimeOffset() - (long) (range.getStart() * 1000), (long) (r.getMedia().getDuration() * 1000)); } else { stop(now + getTimeOffset() - (long) (range.getStart() * 1000), 0); } } } public void stop(long startTime, long expDuration) { long now = System.currentTimeMillis(); long thisPlay = now - startTime; long duration = thisPlay + offsetTime; if (expDuration > minDur && duration >= (expDuration * configuration.getResumeBackFactor())) { // We've seen the whole video (likely) file.delete(); return; } if (thisPlay < configuration.getResumeRewind()) { return; } if (thisPlay < minDur) { // too short to resume (at all) return; } offsetTime = duration - configuration.getResumeRewind(); resDuration = expDuration; LOGGER.debug("Resume stop. This segment " + thisPlay + " new time " + duration); write(offsetTime, expDuration, file); } }