mirror of
https://github.com/TeamPiped/Piped.git
synced 2026-06-02 04:44:30 +00:00
fix(player): resume from saved position and protect it across keep-alive
Clicking a video from history (or any cached watch page) restarted it from 0 instead of resuming, and history bars showed stale positions. Four shared-path bugs, all engine-agnostic: - The resume position was computed but never applied: Shaka's load(uri, startTime) doesn't perform the initial seek for lazily-fetched segment indexes, so playback began at 0. Apply the resume explicitly with a runtime seek once load() resolves. - initialSeekComplete (gates progress saving until the resume seek lands) was never reset per-load. On a reactivated player it stayed true from the previous play, so a timeupdate at currentTime=0 during rebuild churn overwrote the saved position before the resume read ran. Reset it at the start of loadVideo. - Leaving a watch page (destroy) empties the media element -> currentTime snaps to 0 and a stray timeupdate fires while initialSeekComplete is still true, clobbering the saved position. Gate the save on destroying as well. - HistoryPage: re-read watch_history in onActivated so progress bars reflect the current saved position instead of a stale first-mount snapshot. Kept off onMounted to avoid double-loading (both fire on first keep-alive mount). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onActivated, onDeactivated } from "vue";
|
import { ref, onActivated, onDeactivated } from "vue";
|
||||||
import VideoItem from "./VideoItem.vue";
|
import VideoItem from "./VideoItem.vue";
|
||||||
import SortingSelector from "./SortingSelector.vue";
|
import SortingSelector from "./SortingSelector.vue";
|
||||||
import ExportHistoryModal from "./ExportHistoryModal.vue";
|
import ExportHistoryModal from "./ExportHistoryModal.vue";
|
||||||
@@ -73,8 +73,8 @@ let currentVideoCount = 0;
|
|||||||
const videoStep = 100;
|
const videoStep = 100;
|
||||||
const videosStore = [];
|
const videosStore = [];
|
||||||
const videos = ref([]);
|
const videos = ref([]);
|
||||||
const autoDeleteHistory = ref(false);
|
const autoDeleteHistory = ref(getPreferenceBoolean("autoDeleteWatchHistory", false));
|
||||||
const autoDeleteDelayHours = ref("24");
|
const autoDeleteDelayHours = ref(getPreferenceString("autoDeleteWatchHistoryDelayHours", "24"));
|
||||||
const showExportModal = ref(false);
|
const showExportModal = ref(false);
|
||||||
const showImportModal = ref(false);
|
const showImportModal = ref(false);
|
||||||
|
|
||||||
@@ -109,11 +109,12 @@ function onChange() {
|
|||||||
setPreference("autoDeleteWatchHistoryDelayHours", autoDeleteDelayHours.value);
|
setPreference("autoDeleteWatchHistoryDelayHours", autoDeleteDelayHours.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
function loadHistory() {
|
||||||
autoDeleteHistory.value = getPreferenceBoolean("autoDeleteWatchHistory", false);
|
videosStore.length = 0;
|
||||||
autoDeleteDelayHours.value = getPreferenceString("autoDeleteWatchHistoryDelayHours", "24");
|
currentVideoCount = 0;
|
||||||
|
videos.value = [];
|
||||||
|
|
||||||
(async () => {
|
return (async () => {
|
||||||
if (window.db && getPreferenceBoolean("watchHistory", false)) {
|
if (window.db && getPreferenceBoolean("watchHistory", false)) {
|
||||||
var tx = window.db.transaction("watch_history", "readwrite");
|
var tx = window.db.transaction("watch_history", "readwrite");
|
||||||
var store = tx.objectStore("watch_history");
|
var store = tx.objectStore("watch_history");
|
||||||
@@ -148,10 +149,11 @@ onMounted(() => {
|
|||||||
})().then(() => {
|
})().then(() => {
|
||||||
loadMoreVideos();
|
loadMoreVideos();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
document.title = "Watch History - Piped";
|
document.title = "Watch History - Piped";
|
||||||
|
loadHistory();
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ async function updateProgressDatabase(time) {
|
|||||||
if (new Date().getTime() - lastUpdate.value < 500) return;
|
if (new Date().getTime() - lastUpdate.value < 500) return;
|
||||||
lastUpdate.value = new Date().getTime();
|
lastUpdate.value = new Date().getTime();
|
||||||
|
|
||||||
if (!initialSeekComplete.value || !props.video.id || !window.db) return;
|
if (!initialSeekComplete.value || destroying.value || !props.video.id || !window.db) return;
|
||||||
|
|
||||||
var tx = window.db.transaction("watch_history", "readwrite");
|
var tx = window.db.transaction("watch_history", "readwrite");
|
||||||
var store = tx.objectStore("watch_history");
|
var store = tx.objectStore("watch_history");
|
||||||
@@ -448,7 +448,6 @@ async function setPlayerAttrs(localPlayer, el, uri, mime, shaka) {
|
|||||||
|
|
||||||
if (time) {
|
if (time) {
|
||||||
startTime = parseTimeParam(time);
|
startTime = parseTimeParam(time);
|
||||||
initialSeekComplete.value = true;
|
|
||||||
} else if (window.db && getPreferenceBoolean("watchHistory", false)) {
|
} else if (window.db && getPreferenceBoolean("watchHistory", false)) {
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
var tx = window.db.transaction("watch_history", "readonly");
|
var tx = window.db.transaction("watch_history", "readonly");
|
||||||
@@ -464,18 +463,19 @@ async function setPlayerAttrs(localPlayer, el, uri, mime, shaka) {
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
tx.oncomplete = () => {
|
|
||||||
initialSeekComplete.value = true;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
initialSeekComplete.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInstance
|
playerInstance
|
||||||
.load(uri, startTime, mime)
|
.load(uri, null, mime)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
// Player.load()'s startTime arg does not reliably perform the
|
||||||
|
// initial seek; apply it here. See shaka-project/shaka-player#6241.
|
||||||
|
if (startTime > 0) {
|
||||||
|
el.currentTime = startTime;
|
||||||
|
await new Promise(resolve => el.addEventListener("seeked", resolve, { once: true }));
|
||||||
|
}
|
||||||
|
initialSeekComplete.value = true;
|
||||||
let lang = "en";
|
let lang = "en";
|
||||||
const prefLang = getPreferenceString("hl", "en").substr(0, 2);
|
const prefLang = getPreferenceString("hl", "en").substr(0, 2);
|
||||||
const audioTracks = playerInstance.getAudioTracks();
|
const audioTracks = playerInstance.getAudioTracks();
|
||||||
@@ -573,6 +573,8 @@ async function setPlayerAttrs(localPlayer, el, uri, mime, shaka) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadVideo() {
|
async function loadVideo() {
|
||||||
|
initialSeekComplete.value = false;
|
||||||
|
|
||||||
updateSponsors();
|
updateSponsors();
|
||||||
|
|
||||||
const el = videoEl.value;
|
const el = videoEl.value;
|
||||||
|
|||||||
Reference in New Issue
Block a user