Merge pull request #1412 from Materialious/update/1.14.4

Update/1.14.4
This commit is contained in:
Ward
2026-02-13 16:30:48 +13:00
committed by GitHub
22 changed files with 131 additions and 59 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 207
versionName "1.14.3"
versionCode 208
versionName "1.14.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -72,7 +72,11 @@
<releases>
<release version="1.14.3" date="2026-2-13">
<release version="1.14.4" date="2026-2-13">
<url>https://github.com/Materialious/Materialious/releases/tag/1.14.4</url>
</release>
<release version="1.14.3" date="2026-2-13">
<url>https://github.com/Materialious/Materialious/releases/tag/1.14.3</url>
</release>
<release version="1.14.2" date="2026-2-12">
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.14.3",
"version": "1.14.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.14.3",
"version": "1.14.4",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.14.3",
"version": "1.14.4",
"description": "Modern material design for YouTube and Invidious.",
"author": {
"name": "Ward Pearce",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.14.3",
"version": "1.14.4",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
+1 -1
View File
@@ -252,7 +252,7 @@ export async function getFeed(
fetchOptions: RequestInit = {}
): Promise<Feed> {
if (isYTBackend()) {
return getFeedYTjs();
return getFeedYTjs(maxResults, page);
}
const path = buildPath('auth/feed');
+10 -6
View File
@@ -18,10 +18,15 @@ export async function getChannelYTjs(channelId: string): Promise<ChannelPage> {
const authorId = innerResults.metadata.url?.split('/')[4] ?? '';
let authorBanners: Image[] = [];
if (innerResults.header?.is(YTNodes.PageHeaderView)) {
authorBanners = innerResults.header.banner?.image ?? [];
if (
innerResults.header?.is(YTNodes.PageHeader) &&
innerResults.header.content?.is(YTNodes.PageHeaderView)
) {
authorBanners = innerResults.header.content.banner?.image ?? [];
}
const description = innerResults.metadata.description ?? '';
return {
type: 'channel',
allowedRegions: innerResults.metadata.available_countries ?? [],
@@ -38,8 +43,8 @@ export async function getChannelYTjs(channelId: string): Promise<ChannelPage> {
authorUrl: `/channel/${authorId}`,
subCount: 0,
autoGenerated: false,
description: '',
descriptionHml: ''
description,
descriptionHml: description
};
}
@@ -91,7 +96,6 @@ function fetchChannelContentVideosWithContinuation(
}
async function fetchChannelContentWithContinuation(
channelId: string,
innerResults: YT.Channel | YT.ChannelListContinuation,
author: string
): Promise<ChannelContent> {
@@ -131,5 +135,5 @@ export async function getChannelContentYTjs(
innerResults = await channel.getLiveStreams();
}
return fetchChannelContentWithContinuation(channelId, innerResults, author);
return fetchChannelContentWithContinuation(innerResults, author);
}
@@ -5,7 +5,11 @@ import { relativeTimestamp } from '$lib/time';
import { get } from 'svelte/store';
import type { Feed, Subscription, Thumbnail } from '../model';
import { getChannelYTjs } from './channel';
import { engineCooldownYTStore, engineCullYTStore } from '$lib/store';
import {
engineCooldownYTStore,
engineCullYTStore,
engineMaxConcurrentChannelsStore
} from '$lib/store';
export async function getSubscriptionsYTjs(): Promise<Subscription[]> {
const subscriptions: Subscription[] = [];
@@ -134,19 +138,28 @@ export async function parseChannelRSS(channelId: string): Promise<void> {
}
}
export async function getFeedYTjs(): Promise<Feed> {
export async function getFeedYTjs(maxResults: number, page: number): Promise<Feed> {
const channelSubscriptions = await localDb.channelSubscriptions.toArray();
const toUpdatePromises: Promise<void>[] = [];
const now = new Date();
let totalChannelsToParse = 0;
for (const channel of channelSubscriptions) {
const lastRSSFetch = new Date(channel.lastRSSFetch);
const timeDifference = now.getTime() - lastRSSFetch.getTime();
const cooldownTime = get(engineCooldownYTStore) * 60 * 60 * 1000;
if (timeDifference > cooldownTime) {
toUpdatePromises.push(parseChannelRSS(channel.channelId));
if (totalChannelsToParse < get(engineMaxConcurrentChannelsStore)) {
toUpdatePromises.push(parseChannelRSS(channel.channelId));
} else {
parseChannelRSS(channel.channelId);
}
}
totalChannelsToParse++;
}
if (toUpdatePromises) {
@@ -166,8 +179,11 @@ export async function getFeedYTjs(): Promise<Feed> {
videos = videos.slice(0, cullAfter);
}
const start = (page - 1) * maxResults;
const end = start + maxResults;
return {
notifications: [],
videos: videos
videos: videos.slice(start, end)
};
}
+4 -1
View File
@@ -84,10 +84,13 @@ export async function getVideoYTjs(videoId: string): Promise<VideoPlay> {
// Unsupported format fix
// https://github.com/LuanRT/googlevideo/issues/42
if (video.streaming_data)
if (video.streaming_data) {
video.streaming_data.adaptive_formats = video.streaming_data.adaptive_formats.filter(
(format) => format.xtags !== 'CgcKAnZiEgEx'
);
} else {
throw new Error('Video did not provide streaming data.');
}
const adaptiveFormats: AdaptiveFormats[] = [];
video.streaming_data?.adaptive_formats.forEach((format) => {
+19 -9
View File
@@ -1,5 +1,4 @@
<script lang="ts">
import { resolve } from '$app/paths';
import Mousetrap from 'mousetrap';
import { createEventDispatcher, onMount, tick } from 'svelte';
import { _ } from '$lib/i18n';
@@ -151,31 +150,31 @@
</div>
{#if $interfaceSearchSuggestionsStore}
{#each suggestionsForSearch as suggestion, index (index)}
<li>
<a
<li class="no-padding">
<button
onclick={() => {
search = suggestion;
onSubmit();
}}
class="transparent suggestion"
class:selected={index === selectedSuggestionIndex}
href={resolve(`/search/[search]`, { search: encodeURIComponent(suggestion) })}
>
<div>{suggestion}</div>
</a>
</button>
</li>
{/each}
{/if}
{#if !suggestionsForSearch.length && $interfaceSearchHistoryEnabled}
{#each $searchHistoryStore as history (history)}
<li>
<a
<li class="no-padding">
<button
onclick={() => {
search = history;
}}
href={resolve(`/search/[search]`, { search: encodeURIComponent(history) })}
class="transparent suggestion"
>
<div>{history}</div>
</a>
</button>
</li>
{/each}
{/if}
@@ -192,6 +191,17 @@
.selected {
background-color: var(--surface-variant);
}
button.suggestion {
width: 100%;
box-sizing: content-box;
justify-content: flex-start;
}
li:hover {
background-color: transparent;
}
@media screen and (max-width: 1140px) {
.search {
width: 100%;
@@ -250,7 +250,9 @@
<div class="max">
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
{isYTBackend() ? relativeTimestamp(video.published, false) : video.publishedText}
{isYTBackend() && video.published !== 0
? relativeTimestamp(video.published, false)
: video.publishedText}
</div>
{/if}
</div>
@@ -6,6 +6,7 @@
engineCooldownYTStore,
engineCullYTStore,
engineFallbacksStore,
engineMaxConcurrentChannelsStore,
instanceStore
} from '$lib/store';
import { useEngineFallback, type EngineFallback } from '$lib/api/misc';
@@ -115,10 +116,22 @@
engineCooldownYTStore.set(Number((event.target as HTMLInputElement).value));
}}
value={$engineCooldownYTStore}
name="cull"
name="cooldown"
type="number"
/>
<label for="cull">{$_('layout.backendEngine.cooldown')}</label>
<label for="cooldown">{$_('layout.backendEngine.cooldown')}</label>
</div>
<div class="field label prefix border">
<i>pending</i>
<input
oninput={(event: Event) => {
engineMaxConcurrentChannelsStore.set(Number((event.target as HTMLInputElement).value));
}}
value={$engineMaxConcurrentChannelsStore}
name="concurrent"
type="number"
/>
<label for="concurrent">{$_('layout.backendEngine.concurrent')}</label>
</div>
{#if $authStore && $instanceStore}
@@ -22,6 +22,7 @@
playerYouTubeJsFallback
} from '../../store';
import { playbackRates } from '$lib/player';
import { isYTBackend } from '$lib/misc';
let defaultLanguage = $state(get(playerDefaultLanguage));
@@ -121,7 +122,7 @@
<i>arrow_drop_down</i>
</div>
{#if Capacitor.isNativePlatform()}
{#if Capacitor.isNativePlatform() && !isYTBackend()}
<div class="field suffix border label">
<select
tabindex="0"
@@ -18,7 +18,7 @@
const tabs: { id: string; label: string; icon: string; component: Component }[] = [
{ id: 'interface', label: $_('layout.interface'), icon: 'grid_view', component: Interface },
{ id: 'player', label: $_('layout.player.title'), icon: 'smart_display', component: Player },
{ id: 'ryd', label: 'RYD', icon: 'thumb_down', component: Ryd },
{ id: 'ryd', label: 'Return YT Dislike', icon: 'thumb_down', component: Ryd },
{ id: 'api extended', label: 'API Extended', icon: 'sync', component: ApiExtended },
{ id: 'sponsorblock', label: 'Sponsorblock', icon: 'block', component: SponsorBlock },
{
@@ -39,7 +39,7 @@
tabs.splice(1, 0, {
id: 'engine',
label: $_('layout.engine'),
icon: 'build',
icon: 'rocket_launch',
component: Engine
});
}
@@ -179,6 +179,7 @@
"warning": "Advanced settings! For experienced users only. Do not change unless you know what youre doing.",
"cull": "Max feed items",
"cooldown": "Re-fetch channel hour cooldown",
"concurrent": "Max amount of channels to parse before feed return",
"fallbacks": "Fallbacks",
"importExport": "Import/Export subscriptions",
"exportToInvidious": "Export to Invidious from Materialious",
+9 -2
View File
@@ -33,11 +33,18 @@ export function goToSearch(searchValue: string) {
goto(resolve(`/search/[search]`, { search: encodeURIComponent(searchTrimmed) }));
if (get(interfaceSearchHistoryEnabled) && !get(searchHistoryStore).includes(searchTrimmed)) {
if (get(interfaceSearchHistoryEnabled)) {
const pastHistory = get(searchHistoryStore);
if (pastHistory.length > 15) {
const index = pastHistory.indexOf(searchTrimmed);
if (index !== -1) {
pastHistory.splice(index, 1);
}
if (pastHistory.length >= 15) {
pastHistory.pop();
}
searchHistoryStore.set([searchTrimmed, ...pastHistory]);
}
}
+5
View File
@@ -324,6 +324,11 @@ export const engineCooldownYTStore: Writable<number> = persist(
createStorage(),
'engineCooldownYT'
);
export const engineMaxConcurrentChannelsStore: Writable<number> = persist(
writable(100),
createStorage(),
'engineMaxConcurrentChannels'
);
export const engineFallbacksStore: Writable<EngineFallback[]> = persist(
writable([]),
@@ -36,32 +36,46 @@
async function loadMore(event: InfiniteEvent) {
if (typeof displayContent === 'undefined') return;
if (typeof displayContent.continuation === 'undefined') {
event.detail.complete();
return;
let completed = false;
let newContent: ChannelContent;
if (displayContent.getContinuation) {
newContent = await displayContent.getContinuation();
displayContent.getContinuation = newContent.getContinuation;
completed = newContent.getContinuation === undefined;
} else {
if (typeof displayContent.continuation === 'undefined') {
event.detail.complete();
return;
}
newContent = await getChannelContent(page.params.slug, {
type: tab,
continuation: displayContent.continuation,
sortBy: sortBy
});
completed = displayContent.continuation === newContent.continuation;
displayContent.continuation = newContent.continuation;
}
const newContent = await getChannelContent(page.params.slug, {
type: tab,
continuation: displayContent.continuation,
sortBy: sortBy
});
if ('videos' in newContent && 'videos' in displayContent) {
if (displayContent.continuation === newContent.continuation) {
if (completed) {
event.detail.complete();
} else {
event.detail.loaded();
}
displayContent.videos = [...displayContent.videos, ...newContent.videos];
} else if ('playlists' in displayContent && 'playlists' in newContent) {
if (displayContent.continuation === newContent.continuation) {
if (completed) {
event.detail.complete();
} else {
event.detail.loaded();
}
displayContent.playlists = [...displayContent.playlists, ...newContent.playlists];
}
displayContent.continuation = newContent.continuation;
}
async function changeTab(newTab: 'videos' | 'playlists' | 'streams' | 'shorts') {
@@ -73,7 +73,7 @@
const searchCacheItem = $searchCacheStore[data.searchStoreId];
if (searchCacheItem?.getContinuation) {
if (searchCacheItem.getContinuation) {
newSearch = await searchCacheItem.getContinuation();
if (newSearch.getContinuation) {
@@ -87,11 +87,6 @@
};
newSearch = await getSearch(data.slug, searchOptions);
// Set the continuation method if it exists
if (newSearch.getContinuation) {
searchCacheItem.getContinuation = newSearch.getContinuation;
}
}
if (newSearch.length === 0) {
@@ -6,16 +6,13 @@
import ItemsList from '$lib/components/ItemsList.svelte';
import { resolve } from '$app/paths';
import { _ } from '$lib/i18n';
import { isYTBackend } from '$lib/misc';
let currentPage = 1;
let videos: (VideoBase | Video | PlaylistPageVideo)[] = $state($feedCacheStore.subscription);
async function loadMore(event: InfiniteEvent) {
// Not supported or needed on YT backend
if (isYTBackend()) return;
currentPage++;
const feed = await getFeed(100, currentPage);
if (feed.videos.length === 0) {
event.detail.complete();
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.14.3"
LATEST_VERSION = "1.14.4"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")