From da09134e3710db7b06460907538dbd3b7cecabd5 Mon Sep 17 00:00:00 2001 From: an-lee Date: Sun, 18 Feb 2024 09:27:51 +0800 Subject: [PATCH] Feat: display download progress (#315) * cache tedtalk download url * improve tedtalk download ux * display progress bar * fix locale * update UI * display progress when downloading audbile --- enjoy/src/i18n/en.json | 4 +- enjoy/src/i18n/zh-CN.json | 4 +- enjoy/src/main/providers/audible-provider.ts | 2 + .../audios/audible-books-segment.tsx | 21 +++ .../components/videos/ted-talks-segment.tsx | 162 ++++++++++++------ 5 files changed, 143 insertions(+), 50 deletions(-) diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 3cd105850..aa6598841 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -448,5 +448,7 @@ "translationFailed": "Translation failed", "allRecordingsSynced": "All recordings synced", "syncingRecordings": "Syncing {{count}} recordings", - "failedToSyncRecordings": "Syncing recordings failed" + "failedToSyncRecordings": "Syncing recordings failed", + "downloadUrlNotResolved": "Download URL not resolved", + "resolvingDownloadUrl": "Resolving download URL" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 866965d63..7f5274290 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -447,5 +447,7 @@ "translationFailed": "翻译失败", "allRecordingsSynced": "所有录音已同步", "syncingRecordings": "{{count}} 条录音正在同步", - "failedToSyncRecordings": "同步录音失败" + "failedToSyncRecordings": "同步录音失败", + "downloadUrlNotResolved": "无法解析下载地址", + "resolvingDownloadUrl": "正在解析下载地址" } diff --git a/enjoy/src/main/providers/audible-provider.ts b/enjoy/src/main/providers/audible-provider.ts index e357f0ae9..05ab9dccb 100644 --- a/enjoy/src/main/providers/audible-provider.ts +++ b/enjoy/src/main/providers/audible-provider.ts @@ -17,11 +17,13 @@ export class AudibleProvider { const view = new BrowserView(); view.webContents.loadURL(this.baseURL + path); view.webContents.on("did-finish-load", () => { + logger.debug(`Scraped ${this.baseURL + path}`); view.webContents .executeJavaScript(`document.documentElement.innerHTML`) .then((html) => resolve(html as string)); }); view.webContents.on("did-fail-load", () => { + logger.error(`Failed to scrape ${this.baseURL + path}`); reject(); }); }); diff --git a/enjoy/src/renderer/components/audios/audible-books-segment.tsx b/enjoy/src/renderer/components/audios/audible-books-segment.tsx index 196278c78..0e86eeb42 100644 --- a/enjoy/src/renderer/components/audios/audible-books-segment.tsx +++ b/enjoy/src/renderer/components/audios/audible-books-segment.tsx @@ -9,6 +9,7 @@ import { DialogTitle, DialogContent, DialogFooter, + Progress, } from "@renderer/components/ui"; import { t } from "i18next"; import { MediaPlayer, MediaProvider } from "@vidstack/react"; @@ -27,10 +28,12 @@ export const AudibleBooksSegment = () => { null ); const [downloading, setDownloading] = useState(false); + const [progress, setProgress] = useState(0); const downloadSample = () => { if (!selectedBook.sample) return; + setProgress(0); setDownloading(true); EnjoyApp.audios .create(selectedBook.sample, { @@ -73,6 +76,22 @@ export const AudibleBooksSegment = () => { fetchAudibleBooks(); }, []); + useEffect(() => { + if (!selectedBook) return; + + EnjoyApp.download.onState((_, downloadState) => { + console.log(downloadState); + const { state, received, total } = downloadState; + if (state === "progressing") { + setProgress(Math.floor((received / total) * 100)); + } + }); + + return () => { + EnjoyApp.download.removeAllListeners(); + }; + }, [selectedBook]); + if (!books?.length) return null; return ( @@ -158,6 +177,8 @@ export const AudibleBooksSegment = () => { {t("downloadSample")} + + {downloading && progress > 0 && } diff --git a/enjoy/src/renderer/components/videos/ted-talks-segment.tsx b/enjoy/src/renderer/components/videos/ted-talks-segment.tsx index 13b332604..1f0b0ea78 100644 --- a/enjoy/src/renderer/components/videos/ted-talks-segment.tsx +++ b/enjoy/src/renderer/components/videos/ted-talks-segment.tsx @@ -1,5 +1,3 @@ -/** @format */ - import { useState, useEffect, useContext } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; import { @@ -11,8 +9,9 @@ import { DialogTitle, DialogContent, DialogFooter, + Progress, + toast, } from "@renderer/components/ui"; -import { LoaderSpin } from "@renderer/components"; import { t } from "i18next"; import { useNavigate } from "react-router-dom"; import { LoaderIcon } from "lucide-react"; @@ -33,6 +32,8 @@ export const TedTalksSegment = () => { audio: string; video: string; }>(); + const [resolving, setResolving] = useState(false); + const [progress, setProgress] = useState(0); const addToLibrary = (type: DownloadType) => { if (!downloadUrl) return; @@ -42,6 +43,7 @@ export const TedTalksSegment = () => { if (type === DownloadType.video) url = downloadUrl.video; setSubmitting(true); setSubmittingType(type); + setProgress(0); EnjoyApp.videos .create(url, { @@ -49,6 +51,11 @@ export const TedTalksSegment = () => { coverUrl: selectedTalk?.primaryImageSet[0].url, }) .then((record) => { + if (!record) { + toast.error(t("failedToDownload")); + return; + } + if (type === "video") { navigate(`/videos/${record.id}`); } else { @@ -61,16 +68,38 @@ export const TedTalksSegment = () => { }); }; - const downloadTalk = () => { + const resolveDowloadUrl = async () => { if (!selectedTalk?.canonicalUrl) return; + if (resolving) return; + setResolving(true); setDownloadUrl(null); - EnjoyApp.providers.ted - .downloadTalk(selectedTalk?.canonicalUrl) - .then((downloadUrl) => { - if (!downloadUrl) return; - setDownloadUrl(downloadUrl); - }); + + const cachedUrl: { + audio: string; + video: string; + } = await EnjoyApp.cacheObjects.get( + `tedtalk-download-url-${selectedTalk?.canonicalUrl}` + ); + if (cachedUrl) { + setDownloadUrl(cachedUrl); + setResolving(false); + } else { + EnjoyApp.providers.ted + .downloadTalk(selectedTalk?.canonicalUrl) + .then((url) => { + if (!url) return; + EnjoyApp.cacheObjects.set( + `tedtalk-download-url-${selectedTalk?.canonicalUrl}`, + url, + 60 * 60 * 24 * 7 + ); + setDownloadUrl(url); + }) + .finally(() => { + setResolving(false); + }); + } }; const fetchTalks = async () => { @@ -98,9 +127,25 @@ export const TedTalksSegment = () => { }, []); useEffect(() => { - downloadTalk(); + resolveDowloadUrl(); }, [selectedTalk]); + useEffect(() => { + if (!downloadUrl) return; + + EnjoyApp.download.onState((_, downloadState) => { + console.log(downloadState); + const { state, received, total } = downloadState; + if (state === "progressing") { + setProgress(Math.floor((received / total) * 100)); + } + }); + + return () => { + EnjoyApp.download.removeAllListeners(); + }; + }, [downloadUrl]); + if (!talks?.length) return null; return ( @@ -162,47 +207,68 @@ export const TedTalksSegment = () => { - {downloadUrl ? ( - - - - - {downloadUrl.audio && ( + + + + {downloadUrl ? ( + <> - )} - {downloadUrl.video && ( - + )} + {downloadUrl.video && ( + + )} + + ) : resolving ? ( +
+ + {t("resolvingDownloadUrl")} +
+ ) : ( +
+ {t("downloadUrlNotResolved")} + {". "} + - {submittingType === DownloadType.video && ( - - )} - {t("downloadVideo")} - - )} - - ) : ( - - )} + {t("retry")} + +
+ )} +
+ + {submitting && progress > 0 && }