@@ -18,6 +18,7 @@ Materialious allows you to customize various settings by overwriting the default
|
||||
+ [Auto expand comments](#auto-expand-comments)
|
||||
+ [Auto expand description](#auto-expand-desc)
|
||||
+ [Low bandwidth mode](#low-bandwidth-mode)
|
||||
+ [Display thumbnail avatars](#)
|
||||
* [Player](#player)
|
||||
+ [Mini player](#mini-player)
|
||||
+ [Autoplay video](#autoplay-video)
|
||||
@@ -137,6 +138,11 @@ Avoids loading images.
|
||||
"lowBandwidthMode": true
|
||||
```
|
||||
|
||||
### Enable thumbnail avatars
|
||||
```json
|
||||
"displayThumbnailAvatars": true
|
||||
```
|
||||
|
||||
## Player
|
||||
|
||||
### Mini player
|
||||
|
||||
Generated
+33
@@ -146,6 +146,28 @@
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e3q" />
|
||||
<option name="id" value="e3q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24 Ultra" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="eos" />
|
||||
<option name="id" value="eos" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Eos" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
@@ -179,6 +201,17 @@
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="fogona" />
|
||||
<option name="id" value="fogona" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2024" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 77
|
||||
versionName "1.6.26"
|
||||
versionCode 78
|
||||
versionName "1.6.27"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
Generated
+4
-3
@@ -2358,11 +2358,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "33.3.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-33.3.1.tgz",
|
||||
"integrity": "sha512-Z7l2bVgpdKxHQMI4i0CirBX2n+iCYKOx5mbzNM3BpOyFELwlobEXKmzCmEnwP+3EcNeIhUQyIEBFQxN06QgdIw==",
|
||||
"version": "33.3.2",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-33.3.2.tgz",
|
||||
"integrity": "sha512-2pWr0frM9UrZGX1d7eoFdMROw10h2vXIWJmXdjwlKnSWWUm18GCrEOUeDUr+IMgz5EjO7JM7FQDHDMApeMgHyg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^20.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.6.26",
|
||||
"version": "1.6.27",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
Generated
+1696
-1352
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.6.26",
|
||||
"version": "1.6.27",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -62,6 +62,7 @@
|
||||
"he": "^1.2.0",
|
||||
"human-number": "^2.0.4",
|
||||
"iso-3166": "^4.3.0",
|
||||
"iso-639-1": "^3.1.4",
|
||||
"material-dynamic-colors": "^1.1.1",
|
||||
"media-captions": "^1.0.4",
|
||||
"media-icons": "^0.10.0",
|
||||
@@ -75,4 +76,4 @@
|
||||
"vidstack": "^1.12.9",
|
||||
"youtubei.js": "^12.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
getBestThumbnail,
|
||||
proxyVideoUrl,
|
||||
pullBitratePreference,
|
||||
setStatusBarColor,
|
||||
videoLength,
|
||||
type PhasedDescription
|
||||
} from './misc';
|
||||
@@ -30,6 +31,7 @@
|
||||
playerAndroidBgPlayer,
|
||||
playerAndroidLockOrientation,
|
||||
playerAutoPlayStore,
|
||||
playerDefaultLanguage,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
silenceSkipperStore,
|
||||
@@ -64,6 +66,8 @@
|
||||
|
||||
let sponsorBlockElements: Element[] = [];
|
||||
|
||||
let watchProgressTimeout: NodeJS.Timeout;
|
||||
|
||||
function setSponsorTimeline() {
|
||||
if (get(sponsorBlockTimelineStore)) return;
|
||||
if (segments.length === 0) return;
|
||||
@@ -233,6 +237,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Auto save watch progress every minute.
|
||||
watchProgressTimeout = setInterval(() => savePlayerPos(), 60000);
|
||||
|
||||
player.addEventListener('pause', () => {
|
||||
savePlayerPos();
|
||||
if (get(silenceSkipperStore)) {
|
||||
@@ -343,6 +350,19 @@
|
||||
}
|
||||
|
||||
player.addEventListener('dash-can-play', async () => {
|
||||
const defaultLanguage = get(playerDefaultLanguage);
|
||||
if (defaultLanguage) {
|
||||
let trackIndex = 0;
|
||||
for (const track of player.audioTracks) {
|
||||
console.log(player.audioTracks);
|
||||
if (track.label.toLowerCase().includes(defaultLanguage)) {
|
||||
player.remoteControl.changeAudioTrack(trackIndex);
|
||||
break;
|
||||
}
|
||||
trackIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (get(playerAutoPlayStore)) {
|
||||
player.play();
|
||||
}
|
||||
@@ -403,13 +423,33 @@
|
||||
await AudioPlayer.initialize(audioId);
|
||||
}
|
||||
|
||||
if (get(playerAndroidLockOrientation)) {
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
let initialFullscreen = true;
|
||||
|
||||
const videoFormats = data.video.adaptiveFormats.filter((format) =>
|
||||
format.type.startsWith('video/')
|
||||
);
|
||||
|
||||
originalOrigination = await ScreenOrientation.orientation();
|
||||
|
||||
player.addEventListener('fullscreen-change', async (event: FullscreenChangeEvent) => {
|
||||
// A bit of a hack to fix Android automatically
|
||||
// fullscreening when opening a video.
|
||||
if (initialFullscreen) {
|
||||
player.exitFullscreen();
|
||||
initialFullscreen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.detail) {
|
||||
// Ensure bar color is black while in fullscreen
|
||||
await StatusBar.setBackgroundColor({ color: '#000000' });
|
||||
} else {
|
||||
await setStatusBarColor();
|
||||
}
|
||||
|
||||
if (!get(playerAndroidLockOrientation)) return;
|
||||
|
||||
if (event.detail && videoFormats[0].resolution) {
|
||||
const widthHeight = videoFormats[0].resolution.split('x');
|
||||
|
||||
@@ -425,6 +465,7 @@
|
||||
} else {
|
||||
await StatusBar.setOverlaysWebView({ overlay: false });
|
||||
await StatusBar.show();
|
||||
|
||||
await ScreenOrientation.lock({
|
||||
orientation: (originalOrigination as ScreenOrientationResult).type
|
||||
});
|
||||
@@ -544,6 +585,9 @@
|
||||
if (typeof silenceSkipperInterval !== 'undefined') {
|
||||
clearInterval(silenceSkipperInterval);
|
||||
}
|
||||
if (watchProgressTimeout) {
|
||||
clearTimeout(watchProgressTimeout);
|
||||
}
|
||||
try {
|
||||
savePlayerPos();
|
||||
} catch (error) {}
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import ui from 'beercss';
|
||||
import { iso31661 } from 'iso-3166';
|
||||
import ISO6391 from 'iso-639-1';
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { ensureNoTrailingSlash, letterCase, titleCases } from './misc';
|
||||
import { ensureNoTrailingSlash, letterCase, titleCase, titleCases } from './misc';
|
||||
import {
|
||||
authStore,
|
||||
darkModeStore,
|
||||
@@ -20,6 +21,7 @@
|
||||
interfaceAmoledTheme,
|
||||
interfaceAutoExpandComments,
|
||||
interfaceAutoExpandDesc,
|
||||
interfaceDisplayThumbnailAvatars,
|
||||
interfaceForceCase,
|
||||
interfaceLowBandwidthMode,
|
||||
interfacePreviewVideoOnHoverStore,
|
||||
@@ -30,6 +32,7 @@
|
||||
playerAndroidLockOrientation,
|
||||
playerAutoPlayStore,
|
||||
playerAutoplayNextByDefaultStore,
|
||||
playerDefaultLanguage,
|
||||
playerListenByDefaultStore,
|
||||
playerMiniPlayerStore,
|
||||
playerProxyVideosStore,
|
||||
@@ -62,6 +65,11 @@
|
||||
let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore);
|
||||
let region = get(interfaceRegionStore);
|
||||
let forceCase = get(interfaceForceCase);
|
||||
let defaultLanguage = get(playerDefaultLanguage);
|
||||
|
||||
const languageNames = ISO6391.getAllCodes().map((code) =>
|
||||
ISO6391.getName(code).toLocaleLowerCase()
|
||||
);
|
||||
|
||||
const sponsorCategories = [
|
||||
{ name: $_('layout.sponsors.sponsor'), category: 'sponsor' },
|
||||
@@ -125,6 +133,15 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -236,6 +253,23 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.displayThumbnailAvatars')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfaceDisplayThumbnailAvatars}
|
||||
on:click={() =>
|
||||
interfaceDisplayThumbnailAvatars.set(!$interfaceDisplayThumbnailAvatars)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
@@ -363,6 +397,23 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="page padding" class:active={activeTab === 'player'}>
|
||||
<div class="margin"></div>
|
||||
<div class="field label suffix border">
|
||||
<select
|
||||
name="case"
|
||||
bind:value={defaultLanguage}
|
||||
on:change={() => playerDefaultLanguage.set(defaultLanguage)}
|
||||
>
|
||||
{#each languageNames as language}
|
||||
<option selected={$playerDefaultLanguage === language} value={language}
|
||||
>{titleCase(language)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<label for="case">{$_('player.defaultLanguage')}</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
authStore,
|
||||
deArrowEnabledStore,
|
||||
deArrowTitlesOnly,
|
||||
interfaceDisplayThumbnailAvatars,
|
||||
interfaceLowBandwidthMode,
|
||||
interfacePreviewVideoOnHoverStore,
|
||||
playerProxyVideosStore,
|
||||
@@ -86,6 +87,8 @@
|
||||
}
|
||||
|
||||
async function loadAuthor() {
|
||||
if (!get(interfaceDisplayThumbnailAvatars)) return;
|
||||
|
||||
try {
|
||||
const channel = await getChannel(video.authorId, { priority: 'low' });
|
||||
const img = new Image();
|
||||
@@ -370,7 +373,7 @@
|
||||
</div>
|
||||
|
||||
<div class="thumbnail-details video-title">
|
||||
{#if !sideways && !$interfaceLowBandwidthMode}
|
||||
{#if !sideways && !$interfaceLowBandwidthMode && $interfaceDisplayThumbnailAvatars}
|
||||
<div style="margin-right: 1em;">
|
||||
{#if authorImg}
|
||||
<img src={authorImg.src} alt="Author" class="circle small" />
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
interfaceAmoledTheme,
|
||||
interfaceAutoExpandComments,
|
||||
interfaceAutoExpandDesc,
|
||||
interfaceDisplayThumbnailAvatars,
|
||||
interfaceForceCase,
|
||||
interfaceLowBandwidthMode,
|
||||
interfacePreviewVideoOnHoverStore,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
playerAlwaysLoopStore,
|
||||
playerAutoPlayStore,
|
||||
playerAutoplayNextByDefaultStore,
|
||||
playerDefaultLanguage,
|
||||
playerListenByDefaultStore,
|
||||
playerMiniPlayerStore,
|
||||
playerProxyVideosStore,
|
||||
@@ -33,9 +35,9 @@ import {
|
||||
synciousStore,
|
||||
themeColorStore
|
||||
} from '$lib/store';
|
||||
import { get } from 'svelte/store';
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
|
||||
const persistedStores = [
|
||||
const persistedStores: { name: string, store: Writable<any>, type: 'string' | 'boolean' | 'array'; }[] = [
|
||||
{
|
||||
name: 'returnYTDislikesInstance',
|
||||
store: returnYTDislikesInstanceStore,
|
||||
@@ -195,6 +197,16 @@ const persistedStores = [
|
||||
name: 'lowBandwidthMode',
|
||||
store: interfaceLowBandwidthMode,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'displayThumbnailAvatars',
|
||||
store: interfaceDisplayThumbnailAvatars,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'defaultLanguage',
|
||||
store: playerDefaultLanguage,
|
||||
type: 'boolean'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"noPlaylists": "No playlists",
|
||||
"unableToLoadComments": "Unable to load comments",
|
||||
"addToPlaylist": "Add to playlist",
|
||||
"youtubeJsFallBack": "Local video fallback being used"
|
||||
"youtubeJsFallBack": "Local video fallback being used",
|
||||
"defaultLanguage": "Default Language"
|
||||
},
|
||||
"layout": {
|
||||
"interface": "Interface",
|
||||
@@ -104,6 +105,7 @@
|
||||
"logout": "Logout",
|
||||
"customize": "Customize",
|
||||
"lowBandwidthMode": "Low bandwidth mode",
|
||||
"displayThumbnailAvatars": "Display channel avatars on thumbnail (Slow)",
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"darkMode": "Dark mode",
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { pushState } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { StatusBar } from '@capacitor/status-bar';
|
||||
import he from 'he';
|
||||
import humanNumber from 'human-number';
|
||||
import type Peer from 'peerjs';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Image } from './api/model';
|
||||
import { instanceStore, interfaceForceCase } from './store';
|
||||
import { getDynamicTheme } from './theme';
|
||||
|
||||
|
||||
export type TitleCase = 'uppercase' | 'lowercase' | 'sentence case' | 'title case' | null;
|
||||
@@ -288,3 +291,11 @@ export function ensureNoTrailingSlash(url: any): string {
|
||||
|
||||
return url.endsWith('/') ? url.slice(0, -1) : url;
|
||||
}
|
||||
|
||||
export async function setStatusBarColor() {
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
await StatusBar.setBackgroundColor({
|
||||
color: (await getDynamicTheme())['--surface-container']
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ export const playerYouTubeJsFallback = persisted('youTubeJsFallback', true);
|
||||
export const playerYouTubeJsAlways = persisted('youTubeJsAlways', false);
|
||||
export const playerAndroidBgPlayer = persisted('androidBgPlayer', true);
|
||||
export const playerAndroidLockOrientation = persisted('androidLockOrientation', true);
|
||||
export const playerDefaultLanguage = persisted('defaultLanguage', '');
|
||||
|
||||
export const returnYtDislikesStore = persisted('returnYtDislikes', false);
|
||||
export const returnYTDislikesInstanceStore: Writable<string | null | undefined> = persisted(
|
||||
@@ -65,6 +66,7 @@ export const interfaceAutoExpandComments: Writable<boolean> = persisted('autoExp
|
||||
export const interfaceAutoExpandDesc: Writable<boolean> = persisted('autoExpandDesc', false);
|
||||
export const interfaceAmoledTheme = persisted('amoledTheme', false);
|
||||
export const interfaceLowBandwidthMode = persisted('lowBandwidthMode', false);
|
||||
export const interfaceDisplayThumbnailAvatars = persisted('disableThumbnailAvatars', false);
|
||||
|
||||
export const sponsorBlockStore = persisted('sponsorBlock', true);
|
||||
export const sponsorBlockUrlStore: Writable<string | null | undefined> = persisted(
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings';
|
||||
import Logo from '$lib/Logo.svelte';
|
||||
import MiniPlayer from '$lib/MiniPlayer.svelte';
|
||||
import { setStatusBarColor } from '$lib/misc';
|
||||
import PageLoading from '$lib/PageLoading.svelte';
|
||||
import Search from '$lib/Search.svelte';
|
||||
import Settings from '$lib/Settings.svelte';
|
||||
@@ -84,9 +85,11 @@
|
||||
setAmoledTheme();
|
||||
});
|
||||
|
||||
darkModeStore.subscribe(() => {
|
||||
darkModeStore.subscribe(async () => {
|
||||
setTheme();
|
||||
setAmoledTheme();
|
||||
|
||||
await setStatusBarColor();
|
||||
});
|
||||
|
||||
App.addListener('appUrlOpen', (data) => {
|
||||
@@ -182,14 +185,18 @@
|
||||
// So user preferences overwrite instance preferences.
|
||||
bookmarkletLoadFromUrl();
|
||||
|
||||
const themeHex = get(themeColorStore);
|
||||
let themeHex = get(themeColorStore);
|
||||
if (themeHex) {
|
||||
await ui('theme', themeHex);
|
||||
} else if (Capacitor.getPlatform() === 'android') {
|
||||
try {
|
||||
const colorPalette = await colorTheme.getColorPalette();
|
||||
await ui('theme', convertToHexColorCode(colorPalette.primary));
|
||||
} catch {}
|
||||
if (!themeHex) {
|
||||
try {
|
||||
const colorPalette = await colorTheme.getColorPalette();
|
||||
themeHex = convertToHexColorCode(colorPalette.primary);
|
||||
await ui('theme', themeHex);
|
||||
await setStatusBarColor();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
setTheme();
|
||||
|
||||
@@ -38,6 +38,9 @@ export default defineConfig({
|
||||
sveltekit()
|
||||
],
|
||||
optimizeDeps: {
|
||||
exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util']
|
||||
exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'],
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ['beercss']
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
|
||||
LATEST_VERSION = "1.6.26"
|
||||
LATEST_VERSION = "1.6.27"
|
||||
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
|
||||
|
||||
ROOT_PACKAGE = os.path.join(WORKING_DIR, "package.json")
|
||||
|
||||
Reference in New Issue
Block a user