summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/api/handlers.go9
-rw-r--r--internal/service/browse.go1
-rw-r--r--internal/service/service.go1
-rw-r--r--internal/service/share.go1
-rw-r--r--web/js/player.js41
5 files changed, 35 insertions, 18 deletions
diff --git a/internal/api/handlers.go b/internal/api/handlers.go
index 5a289a1..411607d 100644
--- a/internal/api/handlers.go
+++ b/internal/api/handlers.go
@@ -127,7 +127,7 @@ func (s *Server) serveFileResult(w http.ResponseWriter, r *http.Request, res *se
}
if !attachment && probe.LooksLikeMPEGTS(res.Path) {
- s.serveRemuxedMP4(w, r, res, stat.Size())
+ s.serveRemuxedMP4(w, r, res)
return
}
@@ -143,10 +143,13 @@ func (s *Server) serveFileResult(w http.ResponseWriter, r *http.Request, res *se
http.ServeContent(w, r, res.FileName, stat.ModTime(), f)
}
-func (s *Server) serveRemuxedMP4(w http.ResponseWriter, r *http.Request, res *service.FileResult, size int64) {
+func (s *Server) serveRemuxedMP4(w http.ResponseWriter, r *http.Request, res *service.FileResult) {
w.Header().Set("Content-Type", "video/mp4")
w.Header().Set("Cache-Control", "no-store")
- s.logger.Info("api remux stream file", "file", res.FileName, "size", size, "range", r.Header.Get("Range"))
+ if res.Duration > 0 {
+ w.Header().Set("X-Duration", fmt.Sprintf("%f", res.Duration))
+ }
+ s.logger.Info("api remux stream file", "file", res.FileName, "size", res.FileSize, "range", r.Header.Get("Range"))
if err := s.remuxer.Remux(r.Context(), res.Path, w); err != nil {
s.logger.Error("remux media", "file", res.FileName, "err", err)
}
diff --git a/internal/service/browse.go b/internal/service/browse.go
index d1f32cc..b420d70 100644
--- a/internal/service/browse.go
+++ b/internal/service/browse.go
@@ -166,6 +166,7 @@ func (s *browseService) StreamMedia(ctx context.Context, mediaID, userID int64)
Path: media.AbsPath,
FileName: media.FileName,
FileSize: media.FileSizeBytes,
+ Duration: media.Duration,
}, nil
}
diff --git a/internal/service/service.go b/internal/service/service.go
index 818ae3f..6a5f6e6 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -233,6 +233,7 @@ type FileResult struct {
Path string
FileName string
FileSize int64
+ Duration float64 // DB-stored duration (seconds), used for remuxed streams.
}
// MediaDetail combines media with related data.
diff --git a/internal/service/share.go b/internal/service/share.go
index c87d8f6..2b917f7 100644
--- a/internal/service/share.go
+++ b/internal/service/share.go
@@ -129,6 +129,7 @@ func (s *shareService) StreamSharedMedia(ctx context.Context, token string) (*Fi
Path: media.AbsPath,
FileName: media.FileName,
FileSize: media.FileSizeBytes,
+ Duration: media.Duration,
}, nil
}
diff --git a/web/js/player.js b/web/js/player.js
index fea4a19..e179856 100644
--- a/web/js/player.js
+++ b/web/js/player.js
@@ -23,6 +23,20 @@ let slideshowTimer = null;
let slideshowPausedUntil = 0;
let cropMode = false;
+function currentMediaElement() {
+ const e = els();
+ if (currentMedia?.type === 'audio') return e.audio;
+ if (currentMedia?.type === 'video') return e.video;
+ return null;
+}
+
+function effectiveDuration() {
+ const m = currentMediaElement();
+ if (!m) return 0;
+ if (isFinite(m.duration) && m.duration > 0) return m.duration;
+ return currentMedia?.duration || 0;
+}
+
const els = () => ({
video: document.getElementById('media-video'),
audio: document.getElementById('media-audio'),
@@ -36,7 +50,7 @@ const els = () => ({
btnSlideshow: document.getElementById('btn-slideshow'),
btnMute: document.getElementById('btn-mute'),
btnFs: document.getElementById('btn-fullscreen'),
- btnMinimize: document.getElementById('btn-minimize'),
+ btnMinimize: document.getElementById('btn-minimize-player'),
btnRestore: document.getElementById('btn-restore-player'),
restoreTitle: document.getElementById('player-restore-title'),
bigPlay: document.getElementById('big-play'),
@@ -110,15 +124,17 @@ export function initPlayer(options = {}) {
[e.video, e.audio].forEach((m) => {
m.addEventListener('timeupdate', () => {
- if (!m.duration || isNaN(m.duration)) return;
- const pct = (m.currentTime / m.duration) * 100;
+ const dur = effectiveDuration();
+ if (!dur) return;
+ const pct = (m.currentTime / dur) * 100;
e.fill.style.width = pct.toFixed(2) + '%';
e.thumb.style.left = pct.toFixed(2) + '%';
e.timeElapsed.textContent = fmt(m.currentTime);
updateBufferedRanges(m);
});
m.addEventListener('loadedmetadata', () => {
- e.timeTotal.textContent = fmt(isFinite(m.duration) ? m.duration : 0);
+ const dur = effectiveDuration();
+ e.timeTotal.textContent = fmt(dur);
updateBufferedRanges(m);
});
['progress', 'durationchange', 'loadeddata', 'canplay', 'seeked'].forEach((event) => {
@@ -166,8 +182,9 @@ export function initPlayer(options = {}) {
let seeking = false;
const seekToFraction = (frac) => {
const m = currentMediaElement();
- if (!m || !m.duration) return;
- m.currentTime = Math.max(0, Math.min(1, frac)) * m.duration;
+ const dur = effectiveDuration();
+ if (!m || !dur) return;
+ m.currentTime = Math.max(0, Math.min(1, frac)) * dur;
};
const handlePointer = (ev) => {
const r = e.track.getBoundingClientRect();
@@ -188,9 +205,10 @@ export function initPlayer(options = {}) {
e.track?.addEventListener('touchend', () => { seeking = false; });
e.track?.addEventListener('keydown', (ev) => {
const m = currentMediaElement();
- if (!m || !m.duration) return;
+ const dur = effectiveDuration();
+ if (!m || !dur) return;
if (ev.key === 'ArrowLeft') { ev.preventDefault(); m.currentTime = Math.max(0, m.currentTime - 5); }
- if (ev.key === 'ArrowRight') { ev.preventDefault(); m.currentTime = Math.min(m.duration, m.currentTime + 5); }
+ if (ev.key === 'ArrowRight') { ev.preventDefault(); m.currentTime = Math.min(dur, m.currentTime + 5); }
});
}
@@ -454,13 +472,6 @@ function loadMedia(media, resumeFrom = 0) {
updateFloatingSize();
}
-function currentMediaElement() {
- const e = els();
- if (currentMedia?.type === 'audio') return e.audio;
- if (currentMedia?.type === 'video') return e.video;
- return null;
-}
-
function seekWhenMetadataReady(m, seconds) {
const target = Number(seconds || 0);
if (!m || !isFinite(target) || target <= 0) return;