Merge branch 'main' into weblate-materialious-materialious

This commit is contained in:
Ward
2025-05-29 18:45:30 +12:00
committed by GitHub
16 changed files with 185 additions and 106 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 108
versionName "1.8.4"
versionCode 109
versionName "1.8.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -54,20 +54,10 @@
</content_rating>
<releases>
<release version="1.8.4" date="2025-5-05">
<release version="1.8.5" date="2025-5-29">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.5</url>
</release>
<release version="1.8.4" date="2025-5-05">
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.4</url>
</release>
<release version="1.8.3" date="2025-5-05">
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.8.2",
"version": "1.8.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.8.2",
"version": "1.8.5",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -2492,9 +2492,9 @@
}
},
"node_modules/electron": {
"version": "35.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.2.1.tgz",
"integrity": "sha512-LO4xXLpzkPPUVLvAbdHMlW7N9Z+Qqz+7QsmSWXluTIQMeJk+v7o36nUTZghyA2I1s//tlMvuaCtle6Qo2W0Ktg==",
"version": "35.5.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.5.0.tgz",
"integrity": "sha512-16ScwDuKgnuL7tSrEgBvQe1Hm4CSK0vbOusPFrDs4oIs3QOdEFtrP9i8+4yKQGXpszj4f4F0MQjKv1tu9E4Gvg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.8.4",
"version": "1.8.5",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -44,4 +44,4 @@
"capacitor",
"electron"
]
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.8.4",
"version": "1.8.5",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -71,4 +71,4 @@
"terser": "^5.34.1",
"youtubei.js": "^13.4.0"
}
}
}
@@ -1,10 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
let { children } = $props();
let largeCol = $state('2');
+54 -24
View File
@@ -52,7 +52,7 @@
isSyncing?: boolean;
isEmbed?: boolean;
segments?: Segment[];
playerElement: HTMLMediaElement;
playerElement: HTMLMediaElement | undefined;
}
let {
@@ -126,6 +126,7 @@
}
function saveVolumePreference() {
if (!playerElement) return;
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
}
@@ -157,6 +158,7 @@
if (player) {
const timeGivenUrl = page.url.searchParams.get('time');
if (timeGivenUrl && !isNaN(parseFloat(timeGivenUrl))) {
if (!playerElement) return false;
playerElement.currentTime = Number(timeGivenUrl);
return true;
}
@@ -178,6 +180,35 @@
HttpFetchPlugin.cacheManager.clearCache();
player = new shaka.Player();
player.configure({
abr: {
enabled: true,
restrictions: {
maxWidth: 1920,
maxHeight: 1080,
maxBandwidth: data.video.ytjs
? Number(
data.video.ytjs?.video.page[0].player_config?.stream_selection_config.max_bitrate
)
: -1
}
},
streaming: {
autoLowLatencyMode: true,
bufferingGoal: 120,
rebufferingGoal: 0.01,
bufferBehind: 300,
retryParameters: {
maxAttempts: 30,
baseDelay: 1500,
backoffFactor: 2.5,
fuzzFactor: 0.7,
timeout: 120000
},
stallThreshold: 2,
stallSkip: 0.5
}
});
playerElement = document.getElementById('player') as HTMLMediaElement;
playerElementResizeObserver = new ResizeObserver(() => {
@@ -195,7 +226,6 @@
playerElement.volume = parseFloat(savedVolume);
}
await player.attach(playerElement);
shakaUi = new shaka.ui.Overlay(
player,
document.getElementById('shaka-container') as HTMLElement,
@@ -229,26 +259,6 @@
}
});
player.configure({
abr: {
enabled: true
},
streaming: {
bufferingGoal: 120,
rebufferingGoal: 0.01,
bufferBehind: 300,
retryParameters: {
maxAttempts: 30,
baseDelay: 1500,
backoffFactor: 2.5,
fuzzFactor: 0.7,
timeout: 120000
},
stallThreshold: 2,
stallSkip: 0.5
}
});
if (data.video.fallbackPatch === 'youtubejs' && data.video.ytjs) {
isLive = !!data.video.ytjs.video.basic_info.is_live;
isPostLiveDVR = !!data.video.ytjs.video.basic_info.is_post_live_dvr;
@@ -635,6 +645,8 @@
});
}
await player.attach(playerElement);
if (!data.video.hlsUrl) {
let dashUrl = data.video.dashUrl;
@@ -701,6 +713,8 @@
playerElement.addEventListener('timeupdate', () => {
segments.forEach((segment) => {
if (!playerElement) return;
if (
playerElement.currentTime >= segment.startTime &&
playerElement.currentTime <= segment.endTime
@@ -813,6 +827,8 @@
}
Mousetrap.bind('space', () => {
if (!playerElement) return;
if (playerElement.paused) {
playerElement.play();
} else {
@@ -822,11 +838,14 @@
});
Mousetrap.bind('right', () => {
if (!playerElement) return;
playerElement.currentTime = playerElement.currentTime + 10;
return false;
});
Mousetrap.bind('left', () => {
if (!playerElement) return;
playerElement.currentTime = playerElement.currentTime - 10;
return false;
});
@@ -854,16 +873,22 @@
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
if (!playerElement) return;
playerElement.requestFullscreen();
}
return false;
});
Mousetrap.bind('shift+left', () => {
if (!playerElement) return;
playerElement.playbackRate = playerElement.playbackRate - 0.25;
});
Mousetrap.bind('shift+right', () => {
if (!playerElement) return;
playerElement.playbackRate = playerElement.playbackRate + 0.25;
});
});
@@ -891,13 +916,18 @@
}
}
if (toSetTime > 0) playerElement.currentTime = toSetTime;
if (toSetTime > 0 && playerElement) playerElement.currentTime = toSetTime;
}
function savePlayerPos() {
if (data.video.hlsUrl) return;
if (get(playerSavePlaybackPositionStore) && player && playerElement.currentTime) {
if (
get(playerSavePlaybackPositionStore) &&
player &&
playerElement &&
playerElement.currentTime
) {
if (
playerElement.currentTime < playerElement.duration - 10 &&
playerElement.currentTime > 10
@@ -99,12 +99,13 @@
</script>
<form onsubmit={preventDefault(handleSubmit)}>
<div class="field prefix fill no-margin search">
<div class="field prefix fill no-margin search rounded">
<i class="front">search</i>
<input
id="search-box"
placeholder={$_('searchPlaceholder')}
bind:value={search}
class="rounded"
onclick={async () => {
showSearchBox = true;
await tick();
@@ -112,14 +113,13 @@
}}
/>
{#if showSearchBox}
<menu class="min suggestions-container">
<menu class="min suggestions-container rounded">
<div class="field large prefix suffix no-margin fixed">
<i class="front" onclick={() => dispatch('searchCancelled')}>arrow_back</i>
<input
placeholder={$_('searchPlaceholder')}
type="text"
id="search"
autofocus
required
bind:value={search}
onkeydown={handleKeyDown}
@@ -145,8 +145,4 @@
.transcript-text {
margin-left: 1em;
}
.transcript-line .chip {
padding: 0 1.5em;
}
</style>
@@ -17,7 +17,7 @@
let auth = get(authStore);
async function removePlaylistItem(indexId: string, videoId: string) {
async function removePlaylistItem(indexId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
}
@@ -27,25 +27,22 @@
<div class="space"></div>
<div class="grid">
{#each videos as video}
<ContentColumn>
<article class="no-padding" style="height: 100%;">
<Thumbnail
{video}
{playlistId}
/>
{#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video}
<div class="right-align" style="margin: 1em .5em;">
<button
onclick={async () => removePlaylistItem(video.indexId, video.videoId)}
class="tertiary circle small"
>
<i>delete</i>
<div class="tooltip">{$_('delete')}</div>
</button>
</div>
{/if}
</article>
</ContentColumn>
<ContentColumn>
<article class="no-padding" style="height: 100%;">
<Thumbnail {video} {playlistId} />
{#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video}
<div class="right-align" style="margin: 1em .5em;">
<button
onclick={async () => removePlaylistItem(video.indexId)}
class="tertiary circle small"
>
<i>delete</i>
<div class="tooltip">{$_('delete')}</div>
</button>
</div>
{/if}
</article>
</ContentColumn>
{/each}
</div>
</div>
+1 -6
View File
@@ -17,10 +17,6 @@ main.root {
overflow-y: auto;
}
article, dialog {
border-radius: .25rem !important;
}
.row {
padding: 1em !important;
}
@@ -79,6 +75,5 @@ button.row:hover {
position: fixed !important;
top: 20% !important;
width: 100% !important;
background-color: var(--surface-variant) !important;
}
}
}
@@ -123,7 +123,8 @@
transition: none !important;
opacity: 1 !important;
visibility: visible !important;
z-index: 9999 !important;
z-index: 99999999 !important;
pointer-events: auto !important;
pointer-events: auto !important;
}
@@ -157,10 +158,18 @@
padding-left: 0.3125rem; /* 5px */
}
.shaka-overflow-menu * {
z-index: 99999999 !important;
pointer-events: auto !important;
}
.youtube-theme .shaka-overflow-menu button,
.youtube-theme .shaka-settings-menu button {
padding: 0;
margin-bottom: 0.5em;
visibility: visible !important;
z-index: 99999999 !important;
pointer-events: auto !important;
}
.youtube-theme .shaka-overflow-menu button i {
@@ -174,6 +183,10 @@
outline: none;
height: 2.5rem; /* 40px */
flex: 0 0 100%;
visibility: visible !important;
z-index: 99999999 !important;
pointer-events: auto !important;
pointer-events: auto !important;
}
.shaka-settings-menu {
@@ -264,11 +277,65 @@
@media screen and (max-width: 650px) {
.youtube-theme .shaka-overflow-menu,
.youtube-theme .shaka-settings-menu {
min-width: 100%;
position: fixed;
top: 75%;
left: 50%;
height: 50vh;
transform: translate(-50%, -50%);
width: 100% !important;
min-width: 100% !important;
position: fixed !important;
top: 100% !important;
left: 50% !important;
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;
padding: 1rem !important;
box-shadow: var(--elevate2);
background-color: var(--surface-container);
z-index: 11;
inset: auto auto 0 0;
inline-size: 100%;
max-block-size: 50vh;
max-inline-size: none !important;
overflow-x: hidden;
overflow-y: auto;
font-size: 0.875rem;
font-weight: 400;
text-transform: none;
color: var(--on-surface);
line-height: normal;
text-align: start;
border-radius: 0.25rem;
transition:
all var(--speed2),
0s background-color;
}
.youtube-theme .shaka-overflow-menu::before,
.youtube-theme .shaka-settings-menu::before {
content: '' !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
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;
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
}
}
@@ -117,6 +117,10 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
const recommendedVideos: VideoBase[] = [];
video.watch_next_feed?.forEach((recommended: Record<string, any>) => {
if (!recommended.title) {
return;
}
recommendedVideos.push({
videoThumbnails: (recommended?.thumbnails as Thumbnail[]) || [],
videoId: recommended?.id || '',
+7 -7
View File
@@ -100,13 +100,13 @@ export class HttpFetchPlugin {
});
const timeoutMs = request.retryParameters.timeout;
if (timeoutMs) {
// const timer = new shaka.util.Timer(() => {
// abortStatus.timedOut = true;
// controller.abort();
// });
// timer.tickAfter(timeoutMs / 1000);
// op.finally(() => timer.stop());
if (timeoutMs && Capacitor.getPlatform() !== 'android') {
const timer = new shaka.util.Timer(() => {
abortStatus.timedOut = true;
controller.abort();
});
timer.tickAfter(timeoutMs / 1000);
op.finally(() => timer.stop());
}
return op;
@@ -41,7 +41,7 @@
let { data = $bindable() } = $props();
let playerElement: HTMLMediaElement;
let playerElement: HTMLMediaElement | undefined = $state();
let comments: Comments | null = $state(null);
data.streamed.comments?.then((streamedComments) => {
@@ -238,10 +238,11 @@
if (playerElement) {
playerElement.addEventListener('timeupdate', () => {
if (!playerElement) return;
playerCurrentTime = playerElement.currentTime;
});
playerElement.addEventListener('end', async () => {
playerElement.addEventListener('ended', async () => {
if (playlistVideos.length === 0) {
if ($playerAutoplayNextByDefaultStore) {
goto(`/watch/${data.video.recommendedVideos[0].videoId}`);
@@ -504,7 +505,7 @@
<i>width_wide</i>
<div class="tooltip">{$_('player.theatreMode')}</div>
</button>
{#if data.video.lengthSeconds > 60 && !data.video.hlsUrl}
{#if data.video.lengthSeconds > 360 && !data.video.hlsUrl}
<button
onclick={() => {
if (pauseTimeout) {
@@ -597,7 +598,9 @@
<h6 style="margin-bottom: .3em;">Chapters</h6>
{#each data.content.timestamps as timestamp}
<button
onclick={() => (playerElement.currentTime = timestamp.time)}
onclick={() => {
if (playerElement) playerElement.currentTime = timestamp.time;
}}
class="timestamps"
>{timestamp.timePretty}
{#if !timestamp.title.startsWith('-')}
@@ -646,7 +649,7 @@
</div>
{#if !theatreMode}
<div class="s12 m12 l3">
{#if showTranscript}
{#if showTranscript && playerElement}
<Transcript video={data.video} bind:playerElement />
{/if}
{#if playlist}
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.8.3"
LATEST_VERSION = "1.8.5"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(