{{ if hasPrefix .MimeType "audio/" }}
-
{{ else if hasPrefix .MimeType "video/" }}
-
diff --git a/ui/entry_enclosure_save_position.go b/ui/entry_enclosure_save_position.go
new file mode 100644
index 00000000..729fb163
--- /dev/null
+++ b/ui/entry_enclosure_save_position.go
@@ -0,0 +1,48 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package ui // import "miniflux.app/ui"
+
+import (
+ json2 "encoding/json"
+ "io"
+ "net/http"
+
+ "miniflux.app/http/request"
+ "miniflux.app/http/response/json"
+)
+
+type enclosurePositionSaveRequest struct {
+ Progression int64 `json:"progression"`
+}
+
+func (h *handler) saveEnclosureProgression(w http.ResponseWriter, r *http.Request) {
+ enclosureID := request.RouteInt64Param(r, "enclosureID")
+ enclosure, err := h.store.GetEnclosure(enclosureID)
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+ var postData enclosurePositionSaveRequest
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+
+ json2.Unmarshal(body, &postData)
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+ enclosure.MediaProgression = postData.Progression
+
+ err = h.store.UpdateEnclosure(enclosure)
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+
+ json.Created(w, r, map[string]string{"message": "saved"})
+}
diff --git a/ui/feed_edit.go b/ui/feed_edit.go
index ed9e5378..14cf6b9f 100644
--- a/ui/feed_edit.go
+++ b/ui/feed_edit.go
@@ -59,6 +59,7 @@ func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
AllowSelfSignedCertificates: feed.AllowSelfSignedCertificates,
FetchViaProxy: feed.FetchViaProxy,
Disabled: feed.Disabled,
+ NoMediaPlayer: feed.NoMediaPlayer,
HideGlobally: feed.HideGlobally,
CategoryHidden: feed.Category.HideGlobally,
}
diff --git a/ui/form/feed.go b/ui/form/feed.go
index 53181a7f..f93937aa 100644
--- a/ui/form/feed.go
+++ b/ui/form/feed.go
@@ -31,6 +31,7 @@ type FeedForm struct {
AllowSelfSignedCertificates bool
FetchViaProxy bool
Disabled bool
+ NoMediaPlayer bool
HideGlobally bool
CategoryHidden bool // Category has "hide_globally"
}
@@ -57,6 +58,7 @@ func (f FeedForm) Merge(feed *model.Feed) *model.Feed {
feed.AllowSelfSignedCertificates = f.AllowSelfSignedCertificates
feed.FetchViaProxy = f.FetchViaProxy
feed.Disabled = f.Disabled
+ feed.NoMediaPlayer = f.NoMediaPlayer
feed.HideGlobally = f.HideGlobally
return feed
}
@@ -86,6 +88,7 @@ func NewFeedForm(r *http.Request) *FeedForm {
AllowSelfSignedCertificates: r.FormValue("allow_self_signed_certificates") == "1",
FetchViaProxy: r.FormValue("fetch_via_proxy") == "1",
Disabled: r.FormValue("disabled") == "1",
+ NoMediaPlayer: r.FormValue("no_media_player") == "1",
HideGlobally: r.FormValue("hide_globally") == "1",
}
}
diff --git a/ui/static/css/common.css b/ui/static/css/common.css
index 821d10c9..f61720cf 100644
--- a/ui/static/css/common.css
+++ b/ui/static/css/common.css
@@ -1081,3 +1081,7 @@ details.entry-enclosures {
.disabled {
opacity: 20%;
}
+
+audio,video {
+ width: 100%;
+}
diff --git a/ui/static/js/app.js b/ui/static/js/app.js
index b8082fc8..308a22b7 100644
--- a/ui/static/js/app.js
+++ b/ui/static/js/app.js
@@ -618,3 +618,23 @@ function showToast(label, iconElement) {
function goToAddSubscription() {
window.location.href = document.body.dataset.addSubscriptionUrl;
}
+
+/**
+ * save player position to allow to resume playback later
+ * @param {Element} playerElement
+ */
+function handlePlayerProgressionSave(playerElement) {
+ const currentPositionInSeconds = Math.floor(playerElement.currentTime); // we do not need a precise value
+ const lastKnownPositionInSeconds = parseInt(playerElement.dataset.lastPosition, 10);
+ const recordInterval = 10;
+
+ // we limit the number of update to only one by interval. Otherwise, we would have multiple update per seconds
+ if (currentPositionInSeconds >= (lastKnownPositionInSeconds + recordInterval) ||
+ currentPositionInSeconds <= (lastKnownPositionInSeconds - recordInterval)
+ ) {
+ playerElement.dataset.lastPosition = currentPositionInSeconds.toString();
+ let request = new RequestBuilder(playerElement.dataset.saveUrl);
+ request.withBody({progression: currentPositionInSeconds});
+ request.execute();
+ }
+}
diff --git a/ui/static/js/bootstrap.js b/ui/static/js/bootstrap.js
index b4c46017..5ff97734 100644
--- a/ui/static/js/bootstrap.js
+++ b/ui/static/js/bootstrap.js
@@ -112,4 +112,12 @@ document.addEventListener("DOMContentLoaded", function () {
}
}
});
+
+ // enclosure media player position save & resume
+ const elements = document.querySelectorAll("audio[data-last-position],video[data-last-position]");
+ elements.forEach((element) => {
+ // we set the current time of media players
+ if (element.dataset.lastPosition){ element.currentTime = element.dataset.lastPosition; }
+ element.ontimeupdate = () => handlePlayerProgressionSave(element);
+ });
});
diff --git a/ui/ui.go b/ui/ui.go
index 15f5da78..cf7b53c0 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -95,6 +95,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
// Entry pages.
uiRouter.HandleFunc("/entry/status", handler.updateEntriesStatus).Name("updateEntriesStatus").Methods(http.MethodPost)
uiRouter.HandleFunc("/entry/save/{entryID}", handler.saveEntry).Name("saveEntry").Methods(http.MethodPost)
+ uiRouter.HandleFunc("/entry/enclosure/{enclosureID}/save-progression", handler.saveEnclosureProgression).Name("saveEnclosureProgression").Methods(http.MethodPost)
uiRouter.HandleFunc("/entry/download/{entryID}", handler.fetchContent).Name("fetchContent").Methods(http.MethodPost)
uiRouter.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", handler.mediaProxy).Name("proxy").Methods(http.MethodGet)
uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods(http.MethodPost)