diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle index 9f4336c8..b84c7b12 100644 --- a/materialious/android/app/build.gradle +++ b/materialious/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "us.materialio.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 29 - versionName "1.3.0" + versionCode 30 + versionName "1.3.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/materialious/electron/package.json b/materialious/electron/package.json index bc669ae8..688b1a05 100644 --- a/materialious/electron/package.json +++ b/materialious/electron/package.json @@ -1,6 +1,6 @@ { "name": "Materialious", - "version": "1.3.0", + "version": "1.3.1", "description": "Modern material design for Invidious.", "author": { "name": "Ward Pearce", diff --git a/materialious/package-lock.json b/materialious/package-lock.json index 14c8dcd7..bcb568b8 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.2.3", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.2.3", + "version": "1.3.1", "dependencies": { "@capacitor-community/electron": "^5.0.1", "@capacitor/android": "6.1.2", @@ -14,6 +14,7 @@ "@capacitor/browser": "^6.0.2", "@capacitor/cli": "6.1.2", "@capacitor/core": "^6.1.2", + "@ffmpeg/ffmpeg": "^0.12.10", "beercss": "^3.6.13", "fuse.js": "^7.0.0", "he": "^1.2.0", @@ -32,7 +33,6 @@ }, "devDependencies": { "@capacitor/assets": "^3.0.5", - "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", "@sveltejs/adapter-auto": "^3.2.4", "@sveltejs/adapter-static": "^3.0.1", @@ -2613,7 +2613,6 @@ "version": "0.12.10", "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.12.10.tgz", "integrity": "sha512-lVtk8PW8e+NUzGZhPTWj2P1J4/NyuCrbDD3O9IGpSeLYtUZKBqZO8CNj1WYGghep/MXoM8e1qVY1GztTkf8YYQ==", - "dev": true, "dependencies": { "@ffmpeg/types": "^0.12.2" }, @@ -2625,7 +2624,6 @@ "version": "0.12.2", "resolved": "https://registry.npmjs.org/@ffmpeg/types/-/types-0.12.2.tgz", "integrity": "sha512-NJtxwPoLb60/z1Klv0ueshguWQ/7mNm106qdHkB4HL49LXszjhjCCiL+ldHJGQ9ai2Igx0s4F24ghigy//ERdA==", - "dev": true, "engines": { "node": ">=16.x" } diff --git a/materialious/package.json b/materialious/package.json index f3c4b846..52b8b170 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -1,6 +1,6 @@ { "name": "materialious", - "version": "1.3.0", + "version": "1.3.1", "private": true, "scripts": { "dev": "vite dev", @@ -13,7 +13,6 @@ }, "devDependencies": { "@capacitor/assets": "^3.0.5", - "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", "@sveltejs/adapter-auto": "^3.2.4", "@sveltejs/adapter-static": "^3.0.1", @@ -46,6 +45,7 @@ "@capacitor/browser": "^6.0.2", "@capacitor/cli": "6.1.2", "@capacitor/core": "^6.1.2", + "@ffmpeg/ffmpeg": "^0.12.10", "beercss": "^3.6.13", "fuse.js": "^7.0.0", "he": "^1.2.0", @@ -62,4 +62,4 @@ "svelte-persisted-store": "^0.11.0", "vidstack": "^1.12.9" } -} \ No newline at end of file +} diff --git a/materialious/src/lib/Comment.svelte b/materialious/src/lib/Comment.svelte index 271962f2..7412c989 100644 --- a/materialious/src/lib/Comment.svelte +++ b/materialious/src/lib/Comment.svelte @@ -2,18 +2,13 @@ import { _ } from 'svelte-i18n'; import { getComments } from './Api'; import { type Comment, type Comments } from './Api/model'; - import { getBestThumbnail, numberWithCommas, proxyGoogleImage } from './misc'; + import { getBestThumbnail, loadPfp, numberWithCommas, proxyGoogleImage } from './misc'; export let comment: Comment; export let videoId: string; let replies: Comments | undefined = undefined; - async function loadPfp(url: string): Promise { - const resp = await fetch(url); - return URL.createObjectURL(await resp.blob()); - } - async function loadReplies(continuation: string) { try { replies = await getComments(videoId, { diff --git a/materialious/src/lib/i18n/locales/en.json b/materialious/src/lib/i18n/locales/en.json index 50dbee11..8935b9c6 100644 --- a/materialious/src/lib/i18n/locales/en.json +++ b/materialious/src/lib/i18n/locales/en.json @@ -73,7 +73,8 @@ "downloadSteps": { "merging": "Merging video & audio together", "video": "Downloading video", - "audio": "Downloading audio" + "audio": "Downloading audio", + "ffmpeg": "Downloading ffmpeg library & components" }, "noPlaylists": "No playlists", "unableToLoadComments": "Unable to load comments", diff --git a/materialious/src/lib/misc.ts b/materialious/src/lib/misc.ts index 906bdeaa..7effca4d 100644 --- a/materialious/src/lib/misc.ts +++ b/materialious/src/lib/misc.ts @@ -170,6 +170,11 @@ export function proxyVideoUrl(source: string): string { return rawSrc.toString(); } +export async function loadPfp(url: string): Promise { + const resp = await fetch(url); + return URL.createObjectURL(await resp.blob()); +} + export function proxyGoogleImage(source: string): string { if (source.startsWith('//')) source = `https:${source}`; diff --git a/materialious/src/lib/videoDownload.ts b/materialious/src/lib/videoDownload.ts index ac6049b9..314d142c 100644 --- a/materialious/src/lib/videoDownload.ts +++ b/materialious/src/lib/videoDownload.ts @@ -1,6 +1,3 @@ -import { FFmpeg } from '@ffmpeg/ffmpeg'; -import { proxyVideoUrl } from './misc'; - interface MediaQuality { bandwidth: string; videoUrl: string; @@ -69,7 +66,7 @@ async function fetchDASHManifest(manifestUrl: string) { export async function listCombinedQualities(manifestUrl: string): Promise { - const manifest = await fetchDASHManifest(manifestUrl); + const manifest = await fetchDASHManifest(`${manifestUrl}?local=true`); const qualities: MediaQuality[] = []; // Fetch all AdaptationSets (both video and audio) @@ -93,7 +90,7 @@ export async function listCombinedQualities(manifestUrl: string): Promise { const videoBandwidth = videoRep.getAttribute('bandwidth'); - const videoUrl = proxyVideoUrl(videoRep.getElementsByTagName('BaseURL')[0].textContent as string); + const videoUrl = videoRep.getElementsByTagName('BaseURL')[0].textContent as string; const width = videoRep.getAttribute('width'); const height = videoRep.getAttribute('height'); const resolution = width && height ? `${width}x${height}` : undefined; @@ -101,7 +98,7 @@ export async function listCombinedQualities(manifestUrl: string): Promise { - const audioUrl = proxyVideoUrl(audioRep.getElementsByTagName('BaseURL')[0].textContent as string); + const audioUrl = audioRep.getElementsByTagName('BaseURL')[0].textContent as string; const audioChannels = audioRep.getElementsByTagName('AudioChannelConfiguration')[0]?.getAttribute('value') || ''; const audioBandwidth = audioRep.getAttribute('bandwidth') || ''; @@ -128,9 +125,16 @@ export async function mergeMediaFromDASH( video?: (progress: number) => void, audio?: (progress: number) => void, merging?: (progress: number) => void, + loadingFfmpeg?: (completed: boolean) => void; } ) { - const ffmpeg = new FFmpeg(); + if (progressCallbacks?.loadingFfmpeg) { + progressCallbacks.loadingFfmpeg(false); + } + + const FFmpeg = await import('@ffmpeg/ffmpeg'); + + const ffmpeg = new FFmpeg.FFmpeg(); ffmpeg.on('progress', ({ progress }) => { if (progressCallbacks?.merging) { @@ -139,11 +143,16 @@ export async function mergeMediaFromDASH( }); await ffmpeg.load({ + classWorkerURL: await toBlobURL('/ffmpeg/ffmpeg-worker.js', 'text/javascript'), coreURL: await toBlobURL('/ffmpeg/ffmpeg-core.js', 'text/javascript'), wasmURL: await toBlobURL('/ffmpeg/ffmpeg-core.wasm', 'application/wasm'), workerURL: await toBlobURL('/ffmpeg/ffmpeg-core.worker.js', 'text/javascript') }); + if (progressCallbacks?.loadingFfmpeg) { + progressCallbacks.loadingFfmpeg(true); + } + const { videoUrl, audioUrl } = selectedQuality; ffmpeg.writeFile('input.mp4', await fetchFile(videoUrl, progressCallbacks?.video)); @@ -156,7 +165,7 @@ export async function mergeMediaFromDASH( const url = URL.createObjectURL(mergedFile); const a = document.createElement('a'); a.href = url; - a.download = `${fileName}.mp4`; + a.download = `${fileName} - ${selectedQuality.resolution}.mp4`; document.body.appendChild(a); a.click(); diff --git a/materialious/src/routes/(app)/watch/[slug]/+page.svelte b/materialious/src/routes/(app)/watch/[slug]/+page.svelte index a3378f77..296f85cf 100644 --- a/materialious/src/routes/(app)/watch/[slug]/+page.svelte +++ b/materialious/src/routes/(app)/watch/[slug]/+page.svelte @@ -44,6 +44,7 @@ import { _ } from 'svelte-i18n'; import { get } from 'svelte/store'; import type { MediaPlayerElement } from 'vidstack/elements'; + import { loadPfp } from '../../../../lib/misc.js'; export let data; @@ -413,6 +414,10 @@ downloadStage = undefined; } } + + function onLoadingFFmpeg(completed: boolean) { + downloadStage = $_('player.downloadSteps.ffmpeg'); + } @@ -447,11 +452,9 @@