From a2653eef57a2e9718507198f4d52312a32248cb3 Mon Sep 17 00:00:00 2001
From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com>
Date: Sun, 12 Apr 2026 18:24:19 +0200
Subject: [PATCH] Feat: Implement playlist download count tracking during
downloads with UI updates
---
public/index.html | 1 +
public/script/downloadstatus.js | 3 +++
public/script/progressBar.js | 10 ++++++++++
server/controller/download.controller.js | 21 +++++++++++++++++++--
server/routes/download.route.js | 3 ++-
server/services/download.services.js | 9 ++++++++-
6 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index db0f047..5c7323b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -265,6 +265,7 @@
diff --git a/public/script/downloadstatus.js b/public/script/downloadstatus.js
index 868ac9c..140d217 100644
--- a/public/script/downloadstatus.js
+++ b/public/script/downloadstatus.js
@@ -118,4 +118,7 @@ function resetProgressBar() {
const speedElement = document.getElementById("downloadSpeedText");
if (speedElement) speedElement.style.display = "none";
+
+ const playlistInfoElement = document.getElementById("playlistInfoText");
+ if (playlistInfoElement) playlistInfoElement.style.display = "none";
}
\ No newline at end of file
diff --git a/public/script/progressBar.js b/public/script/progressBar.js
index 681315c..4b8e5ce 100644
--- a/public/script/progressBar.js
+++ b/public/script/progressBar.js
@@ -4,8 +4,10 @@ const progressBarText = document.getElementById("downloadProgressText")
const speedElement = document.getElementById("downloadSpeedText");
const stageElement = document.getElementById("downloadStage");
+const playlistInfoElement = document.getElementById("playlistInfoText");
const speedEvt = new EventSource("/download/speed");
const stageEvt = new EventSource("/download/stage");
+const playlistInfoEvt = new EventSource("/download/playlist-info");
function startProgress() {
progressBar.style.width = "0%";
@@ -45,6 +47,9 @@ function resetProgress() {
stageElement.textContent = "";
stageElement.style.display = "none";
+ playlistInfoElement.textContent = "";
+ playlistInfoElement.style.display = "none";
+
// Hide stop button
const stopBtn = document.getElementById("stopBtn");
if (stopBtn) stopBtn.style.display = "none";
@@ -85,4 +90,9 @@ speedEvt.onmessage = e => {
stageEvt.onmessage = e => {
stageElement.style.display = "block";
stageElement.textContent = e.data; // ex: "📥 Downloading..."
+};
+
+playlistInfoEvt.onmessage = e => {
+ playlistInfoElement.style.display = "block";
+ playlistInfoElement.textContent = e.data; // ex: "Item 1 of 15"
};
\ No newline at end of file
diff --git a/server/controller/download.controller.js b/server/controller/download.controller.js
index b87ab33..c586e21 100644
--- a/server/controller/download.controller.js
+++ b/server/controller/download.controller.js
@@ -6,6 +6,7 @@ const { notifyDownloadFinished } = require("../helpers/notify.helpers");
const listeners = [];
const speedListeners = [];
const stageListeners = [];
+const playlistInfoListeners = [];
async function downloadController(req, res) {
try {
@@ -24,7 +25,7 @@ async function downloadController(req, res) {
}
// Get output folder when the download is finished
- const outputFolder = await fetchDownload(options, listeners, speedListeners, stageListeners);
+ const outputFolder = await fetchDownload(options, listeners, speedListeners, stageListeners, playlistInfoListeners);
notifyDownloadFinished(outputFolder);
res.send("Download Done !");
@@ -93,4 +94,20 @@ function cancelDownloadController(req, res) {
}
}
-module.exports = { downloadController, progressController, speedController, stageController, cancelDownloadController};
+function playlistInfoController(req, res) {
+ res.set({
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ });
+
+ const sendPlaylistInfo = info => res.write(`data: ${info}\n\n`);
+ playlistInfoListeners.push(sendPlaylistInfo);
+
+ req.on('close', () => {
+ playlistInfoListeners.splice(playlistInfoListeners.indexOf(sendPlaylistInfo), 1);
+ res.end();
+ });
+}
+
+module.exports = { downloadController, progressController, speedController, stageController, cancelDownloadController, playlistInfoController};
diff --git a/server/routes/download.route.js b/server/routes/download.route.js
index 5310285..1d814f6 100644
--- a/server/routes/download.route.js
+++ b/server/routes/download.route.js
@@ -1,6 +1,6 @@
const express = require("express");
const router = express.Router();
-const { downloadController, progressController, speedController, stageController, cancelDownloadController } = require("../controller/download.controller");
+const { downloadController, progressController, speedController, stageController, cancelDownloadController, playlistInfoController } = require("../controller/download.controller");
router.post("/", downloadController);
router.post("/cancel", cancelDownloadController);
@@ -9,5 +9,6 @@ router.post("/cancel", cancelDownloadController);
router.get("/progress", progressController);
router.get("/speed", speedController);
router.get("/stage", stageController);
+router.get("/playlist-info", playlistInfoController);
module.exports = router;
\ No newline at end of file
diff --git a/server/services/download.services.js b/server/services/download.services.js
index b36e6f1..b80d176 100644
--- a/server/services/download.services.js
+++ b/server/services/download.services.js
@@ -74,7 +74,7 @@ function createPlaylistFolder(basePath, playlistTitle) {
}
}
-function fetchDownload(options, listeners, speedListeners, stageListeners) {
+function fetchDownload(options, listeners, speedListeners, stageListeners, playlistInfoListeners) {
return new Promise((resolve, reject) => {
logger.info(`CONFIG createPlaylistFolders: ${configFeatures.createPlaylistFolders}`);
@@ -152,6 +152,13 @@ function fetchDownload(options, listeners, speedListeners, stageListeners) {
}
}
+ // Playlist item tracking
+ const itemMatch = line.match(/Downloading\s+(?:item|video)?\s*(\d+)\s+of\s+(\d+)/i);
+ if (itemMatch) {
+ const itemInfo = `Item ${itemMatch[1]} of ${itemMatch[2]}`;
+ playlistInfoListeners.forEach(fn => fn(itemInfo));
+ }
+
// Stage messages
if (line.includes("[Merger]")) {
stageListeners.forEach(fn => fn("Merging formats..."));