Merge branch 'main' into weblate-materialious-materialious
This commit is contained in:
@@ -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">
|
||||
|
||||
Generated
+5
-5
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 || '',
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user