diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle index ced51595..f6402ca6 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 109 - versionName "1.8.5" + versionCode 110 + versionName "1.8.6" 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/android/app/src/main/AndroidManifest.xml b/materialious/android/app/src/main/AndroidManifest.xml index bfc605c7..da6049ea 100644 --- a/materialious/android/app/src/main/AndroidManifest.xml +++ b/materialious/android/app/src/main/AndroidManifest.xml @@ -48,6 +48,22 @@ android:scheme="https" android:host="youtu.be" android:pathPrefix="/watch" /> + + + + diff --git a/materialious/electron/materialious.metainfo.xml b/materialious/electron/materialious.metainfo.xml index d98e74a6..5e98996c 100644 --- a/materialious/electron/materialious.metainfo.xml +++ b/materialious/electron/materialious.metainfo.xml @@ -54,7 +54,11 @@ - + + + https://github.com/Materialious/Materialious/releases/tag/1.8.6 + + https://github.com/Materialious/Materialious/releases/tag/1.8.5 diff --git a/materialious/electron/package-lock.json b/materialious/electron/package-lock.json index aa3b9685..357d004c 100644 --- a/materialious/electron/package-lock.json +++ b/materialious/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "Materialious", - "version": "1.8.5", + "version": "1.8.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Materialious", - "version": "1.8.5", + "version": "1.8.6", "license": "MIT", "dependencies": { "@capacitor-community/electron": "^5.0.0", @@ -2492,9 +2492,9 @@ } }, "node_modules/electron": { - "version": "35.5.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-35.5.0.tgz", - "integrity": "sha512-16ScwDuKgnuL7tSrEgBvQe1Hm4CSK0vbOusPFrDs4oIs3QOdEFtrP9i8+4yKQGXpszj4f4F0MQjKv1tu9E4Gvg==", + "version": "35.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-35.2.1.tgz", + "integrity": "sha512-LO4xXLpzkPPUVLvAbdHMlW7N9Z+Qqz+7QsmSWXluTIQMeJk+v7o36nUTZghyA2I1s//tlMvuaCtle6Qo2W0Ktg==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/materialious/electron/package.json b/materialious/electron/package.json index ab585488..7baf0a34 100644 --- a/materialious/electron/package.json +++ b/materialious/electron/package.json @@ -1,6 +1,6 @@ { "name": "Materialious", - "version": "1.8.5", + "version": "1.8.6", "description": "Modern material design for Invidious.", "author": { "name": "Ward Pearce", @@ -44,4 +44,4 @@ "capacitor", "electron" ] -} \ No newline at end of file +} diff --git a/materialious/package-lock.json b/materialious/package-lock.json index a41472c8..a525dfcf 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.8.1", + "version": "1.8.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.8.1", + "version": "1.8.6", "hasInstallScript": true, "dependencies": { "@capacitor-community/electron": "^5.0.1", diff --git a/materialious/package.json b/materialious/package.json index b72d5e08..45497fe3 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -1,6 +1,6 @@ { "name": "materialious", - "version": "1.8.5", + "version": "1.8.6", "private": true, "scripts": { "dev": "vite dev", diff --git a/materialious/src/lib/components/Settings/Interface.svelte b/materialious/src/lib/components/Settings/Interface.svelte index e53723ca..998963ca 100644 --- a/materialious/src/lib/components/Settings/Interface.svelte +++ b/materialious/src/lib/components/Settings/Interface.svelte @@ -1,6 +1,4 @@ {#if Capacitor.isNativePlatform()} - + @@ -101,6 +107,9 @@ + {#if invalidInstance} + + {/if} {/if} diff --git a/materialious/src/lib/components/Settings/Settings.svelte b/materialious/src/lib/components/Settings/Settings.svelte index ea623522..0b981626 100644 --- a/materialious/src/lib/components/Settings/Settings.svelte +++ b/materialious/src/lib/components/Settings/Settings.svelte @@ -3,19 +3,6 @@ import { onMount } from 'svelte'; import { _ } from 'svelte-i18n'; import { get } from 'svelte/store'; - import { - deArrowInstanceStore, - deArrowThumbnailInstanceStore, - instanceStore, - interfaceDefaultPage, - interfaceForceCase, - interfaceRegionStore, - playerDefaultLanguage, - returnYTDislikesInstanceStore, - sponsorBlockCategoriesStore, - sponsorBlockUrlStore, - synciousInstanceStore - } from '../../store'; import ApiExtended from './ApiExtended.svelte'; import DataPreferences from './DataPreferences.svelte'; import DeArrow from './DeArrow.svelte'; @@ -24,43 +11,6 @@ import Ryd from './RYD.svelte'; import SponsorBlock from './SponsorBlock.svelte'; - let sponsorCategoriesList: string[] = []; - sponsorBlockCategoriesStore.subscribe((value) => (sponsorCategoriesList = value)); - - let sponsorBlockInstance = get(sponsorBlockUrlStore); - let synciousInstance = get(synciousInstanceStore); - let returnYTInstance = get(returnYTDislikesInstanceStore); - let invidiousInstance = get(instanceStore); - let deArrowUrl = get(deArrowInstanceStore); - let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore); - let region = get(interfaceRegionStore); - let forceCase = get(interfaceForceCase); - let defaultLanguage = get(playerDefaultLanguage); - let defaultPage = get(interfaceDefaultPage); - - const languageNames = ISO6391.getAllCodes().map((code) => - ISO6391.getName(code).toLocaleLowerCase() - ); - - const sponsorCategories = [ - { name: $_('layout.sponsors.sponsor'), category: 'sponsor' }, - { name: $_('layout.sponsors.unpaidSelfPromotion'), category: 'selfpromo' }, - { name: $_('layout.sponsors.interactionReminder'), category: 'interaction' }, - { name: $_('layout.sponsors.intermissionIntroAnimation'), category: 'intro' }, - { name: $_('layout.sponsors.credits'), category: 'outro' }, - { name: $_('layout.sponsors.preViewRecapHook'), category: 'preview' }, - { name: $_('layout.sponsors.tangentJokes'), category: 'filler' } - ]; - - function toggleSponsor(category: string) { - if (sponsorCategoriesList.includes(category)) { - sponsorBlockCategoriesStore.set(sponsorCategoriesList.filter((value) => value !== category)); - } else { - sponsorCategoriesList.push(category); - sponsorBlockCategoriesStore.set(sponsorCategoriesList); - } - } - let activeTab: | 'interface' | 'player' @@ -84,15 +34,6 @@ checkWidth(); addEventListener('resize', () => checkWidth()); - - // Set default language if not set already based off browser. - if (!get(playerDefaultLanguage)) { - const languageCode = navigator.language || navigator.languages[0]; - const languageName = ISO6391.getName(languageCode.split('-')[0]); - if (languageName) { - playerDefaultLanguage.set(languageName.toLowerCase()); - } - } }); @@ -181,12 +122,11 @@ @media screen and (max-width: 1320px) { .tabbed { display: flex !important; - flex-direction: column !important; gap: 0 !important; flex: 0 !important; - margin-bottom: 1em !important; align-items: start !important; padding: 1em 0 !important; + overflow-x: scroll !important; background-color: transparent; border-bottom: solid 1px var(--outline-variant); } diff --git a/materialious/src/lib/components/Thumbnail.svelte b/materialious/src/lib/components/Thumbnail.svelte index 3a0c830d..55e70ebd 100644 --- a/materialious/src/lib/components/Thumbnail.svelte +++ b/materialious/src/lib/components/Thumbnail.svelte @@ -274,7 +274,7 @@ } .sideways-root { - height: 115px; + height: 110px; display: flex; align-items: flex-start; } diff --git a/materialious/src/lib/components/Transcript.svelte b/materialious/src/lib/components/Transcript.svelte index feba597b..bf35151d 100644 --- a/materialious/src/lib/components/Transcript.svelte +++ b/materialious/src/lib/components/Transcript.svelte @@ -40,6 +40,118 @@ } }); + export function dedupeAndMergeOverlappingCues(cues: VTTCue[], timeOffset = -1): VTTCue[] { + const merged: VTTCue[] = []; + + for (const cue of cues) { + const trimmedText = cue.text.trim(); + if (!trimmedText) { + continue; + } + + let wasMerged = false; + + for (let i = 0; i < merged.length; i++) { + const existing = merged[i]; + + const timeGap = Math.abs(cue.startTime - existing.endTime); + const isTimeAdjacent = timeGap <= 1.5; + + if (isTimeAdjacent && areCuesTextSimilar(existing.text, cue.text)) { + const mergedText = mergeCueText(existing.text, cue.text); + + const newStart = Math.max(0, Math.min(existing.startTime, cue.startTime) + timeOffset); + const newEnd = Math.max(existing.endTime, cue.endTime) + timeOffset; + + const mergedCue = new VTTCue(newStart, newEnd, mergedText); + copyCueMeta(mergedCue, cue); + + merged[i] = mergedCue; + wasMerged = true; + break; + } + } + + if (!wasMerged) { + const offsetCue = new VTTCue( + Math.max(0, cue.startTime + timeOffset), + cue.endTime + timeOffset, + cue.text + ); + copyCueMeta(offsetCue, cue); + merged.push(offsetCue); + } + } + + // Ensure no overlapping cues (each cue ends before the next starts) + for (let i = 0; i < merged.length - 1; i++) { + const current = merged[i]; + const next = merged[i + 1]; + if (current.endTime > next.startTime) { + current.endTime = Math.max(current.startTime, next.startTime - 0.01); // 10ms gap + } + } + + return merged; + } + + function areCuesTextSimilar(a: string, b: string): boolean { + if (a === b) return true; + + // Normalize and compare overlap ratio + const cleanA = normalizeText(a); + const cleanB = normalizeText(b); + + const overlap = getTextOverlap(cleanA, cleanB); + const minLength = Math.min(cleanA.length, cleanB.length); + + // Consider similar if 50% or more overlap + return overlap / minLength >= 0.5; + } + + function getTextOverlap(a: string, b: string): number { + const maxOverlap = Math.min(a.length, b.length); + for (let i = maxOverlap; i > 0; i--) { + if (a.endsWith(b.slice(0, i)) || b.endsWith(a.slice(0, i))) { + return i; + } + } + return 0; + } + + function normalizeText(text: string): string { + return text + .trim() + .replace(/\s+/g, ' ') + .replace(/[.,?!"'”“]/g, '') + .toLowerCase(); + } + + function mergeCueText(a: string, b: string): string { + // Avoid duplicating overlap + const overlapLen = getTextOverlap(a, b); + if (a.endsWith(b.slice(0, overlapLen))) { + return a + b.slice(overlapLen); + } + if (b.endsWith(a.slice(0, overlapLen))) { + return b + a.slice(overlapLen); + } + return `${a} ${b}`; + } + + function copyCueMeta(target: VTTCue, source: VTTCue): void { + target.region = source.region; + target.vertical = source.vertical; + target.snapToLines = source.snapToLines; + target.line = source.line; + target.lineAlign = source.lineAlign; + target.position = source.position; + target.positionAlign = source.positionAlign; + target.size = source.size; + target.align = source.align; + target.style = source.style; + } + async function loadTranscript() { if (!url) { transcript = null; @@ -48,9 +160,11 @@ isLoading = true; transcript = null; + const resp = await fetch(`${!video.fallbackPatch ? get(instanceStore) : ''}${url}`); transcript = await parseText(await resp.text(), { strict: false }); - transcriptCues = transcript.cues; + + transcriptCues = dedupeAndMergeOverlappingCues(transcript.cues); isLoading = false; } @@ -58,7 +172,7 @@ if (!transcript) return; if (search.trim() === '') { - transcriptCues = transcript.cues; + transcriptCues = dedupeAndMergeOverlappingCues(transcript.cues); return; } @@ -66,7 +180,8 @@ keys: ['text'] }); - transcriptCues = fuse.search(search).map((item) => item.item); + const results = fuse.search(search).map((item) => item.item); + transcriptCues = dedupeAndMergeOverlappingCues(results); } diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css index 402e29be..f5b16c8a 100644 --- a/materialious/src/lib/css/global.css +++ b/materialious/src/lib/css/global.css @@ -59,16 +59,6 @@ button.row:hover { } } -@media screen and (max-width: 650px) { - ::-webkit-scrollbar { - display: none !important; - } - - html { - scrollbar-width: none !important; - } -} - @media screen and (max-width: 1000px) { menu.mobile { z-index: 9999 !important; diff --git a/materialious/src/lib/css/shaka-player-theme.css b/materialious/src/lib/css/shaka-player-theme.css index 18b7bb4e..37cd5a59 100644 --- a/materialious/src/lib/css/shaka-player-theme.css +++ b/materialious/src/lib/css/shaka-player-theme.css @@ -285,7 +285,6 @@ height: 50vh !important; transform: translate(-50%, -50%) !important; pointer-events: all !important; - z-index: 99999999 !important; pointer-events: auto !important; opacity: 1 !important; visibility: visible !important; @@ -320,20 +319,18 @@ width: 100vw !important; height: 100vh !important; background: transparent !important; - z-index: 99999998 !important; pointer-events: auto !important; } .youtube-theme .shaka-overflow-menu:hover, .youtube-theme .shaka-settings-menu:hover { - z-index: 99999998 !important; pointer-events: auto !important; opacity: 1 !important; visibility: visible !important; } #shaka-container { - z-index: 99999998 !important; + z-index: 98 !important; pointer-events: auto !important; opacity: 1 !important; visibility: visible !important; diff --git a/materialious/src/lib/patches/youtubejs.ts b/materialious/src/lib/patches/youtubejs.ts index ecc6e638..edd9dfe0 100644 --- a/materialious/src/lib/patches/youtubejs.ts +++ b/materialious/src/lib/patches/youtubejs.ts @@ -42,7 +42,7 @@ export async function patchYoutubeJs(videoId: string): Promise { : window.electronAPI.generatePoToken; poTokenCacheStore.set( - await platformMinter(challengeResponse.bg_challenge, requestKey, visitorData) + await platformMinter(challengeResponse.bg_challenge, requestKey, visitorData) ); const extraArgs: Record = { diff --git a/materialious/src/routes/(app)/+layout.svelte b/materialious/src/routes/(app)/+layout.svelte index 15e200cf..42cc158f 100644 --- a/materialious/src/routes/(app)/+layout.svelte +++ b/materialious/src/routes/(app)/+layout.svelte @@ -21,6 +21,7 @@ darkModeStore, instanceStore, interfaceAmoledTheme, + interfaceDefaultPage, showWarningStore, syncPartyPeerStore, themeColorStore @@ -36,11 +37,8 @@ import { _ } from 'svelte-i18n'; import { get } from 'svelte/store'; import { pwaInfo } from 'virtual:pwa-info'; - interface Props { - children?: import('svelte').Snippet; - } - let { children }: Props = $props(); + let { children } = $props(); let mobileSearchShow = $state(false); @@ -238,7 +236,7 @@ {/if} - goto('/')} style="cursor: pointer;" class="m l"> + goto($interfaceDefaultPage)} style="cursor: pointer;" class="m l"> Materialious diff --git a/materialious/src/routes/(app)/watch/[slug]/+page.svelte b/materialious/src/routes/(app)/watch/[slug]/+page.svelte index dd7ff7df..67b39ea7 100644 --- a/materialious/src/routes/(app)/watch/[slug]/+page.svelte +++ b/materialious/src/routes/(app)/watch/[slug]/+page.svelte @@ -443,7 +443,7 @@ - + {#if !$interfaceLowBandwidthMode} {#await data.streamed.returnYTDislikes then returnYTDislikes} {#if returnYTDislikes} - + thumb_up {cleanNumber(returnYTDislikes.likes)} - + thumb_down_alt {cleanNumber(returnYTDislikes.dislikes)} {:else} - + thumb_up {cleanNumber(data.video.likeCount)} diff --git a/materialious/src/routes/+layout.ts b/materialious/src/routes/+layout.ts index 63cd57b5..3a5971d4 100644 --- a/materialious/src/routes/+layout.ts +++ b/materialious/src/routes/+layout.ts @@ -7,7 +7,7 @@ import { authStore, interfaceDefaultPage } from '$lib/store.js'; import { locale, waitLocale } from 'svelte-i18n'; import { get } from 'svelte/store'; -export let ssr = false; +export const ssr = false; export async function load({ url }) { if (url.pathname.startsWith('/@')) { @@ -18,7 +18,7 @@ export async function load({ url }) { if (resolvedUrl.pageType === 'WEB_PAGE_TYPE_CHANNEL') { goto(`/channel/${resolvedUrl.ucid}`); } - } catch { } + } catch {} } if (browser) { diff --git a/update_versions.py b/update_versions.py index b30b9e54..706565a8 100644 --- a/update_versions.py +++ b/update_versions.py @@ -3,7 +3,7 @@ import os import re from datetime import datetime -LATEST_VERSION = "1.8.5" +LATEST_VERSION = "1.8.6" RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D WORKING_DIR = os.path.join(