diff --git a/docs/DOCKER.md b/docs/DOCKER.md index c0c11f25..9427d24e 100644 --- a/docs/DOCKER.md +++ b/docs/DOCKER.md @@ -95,6 +95,8 @@ The following Invidious values must be set in your config. ## Step 3: Docker Please ensure you have followed the previous steps before doing this! +### Docker Compose + ```yaml version: "3" services: @@ -121,6 +123,71 @@ services: # URL to DeArrow thumbnail instance VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE: "https://dearrow-thumb.ajay.app" + + # Look at "Overwriting Materialious defaults" for all the accepted values. + VITE_DEFAULT_SETTINGS: '{"themeColor": "#2596be"}' +``` + +### Overwriting Materialious defaults +Materialious allows you to overwrite the default values using `VITE_DEFAULT_SETTINGS` + +```json +{ + // Set to true to enable dark mode, or false to disable it. + "darkMode": true, + + // Specifies the theme color in hexadecimal format (e.g., #ff0000 for red). + "themeColor": "#ff0000", + + // Set to true to enable autoplay, or false to disable it. + "autoPlay": false, + + // Set to true to always loop videos, or false to loop only when specified. + "alwaysLoop": false, + + // Set to true to proxy videos, or false to play them directly. + "proxyVideos": true, + + // Set to true to enable listening by default, or false to disable it. + "listenByDefault": true, + + // Set to true to save playback position, or false to reset it. + "savePlaybackPosition": true, + + // Set to true to enable DASH playback, or false to use other formats. + "dashEnabled": true, + + // Set to true to enable theatre mode by default, or false to disable it. + "theatreModeByDefault": false, + + // Set to true to autoplay next video by default, or false to disable it. + "autoplayNextByDefault": true, + + // Set to true to return YouTube dislikes, or false to hide them. + "returnYtDislikes": false, + + // Set to true to enable search suggestions, or false to disable them. + "searchSuggestions": true, + + // Set to true to preview video on hover, or false to disable it. + "previewVideoOnHover": true, + + // Set to true to enable sponsor block, or false to disable it. + "sponsorBlock": false, + + // Specifies the categories for sponsor block as comma-separated values. + // https://wiki.sponsor.ajay.app/w/Types + "sponsorBlockCategories": "sponsor,interaction", + + // Set to true to enable deArrow, or false to disable it. + "deArrowEnabled": true, + + // Set to true to enable mini player, or false to disable it. + "playerMiniPlayer": true, + + // Set to true to enable syncious, or false to disable it. + "syncious": true, +} ``` ## Step 4 (Optional, but recommended): Self-host RYD-Proxy diff --git a/materialious/Dockerfile b/materialious/Dockerfile index 0b4cc4e7..7c47e49c 100644 --- a/materialious/Dockerfile +++ b/materialious/Dockerfile @@ -18,6 +18,7 @@ RUN echo "VITE_DEFAULT_INVIDIOUS_INSTANCE=VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEH echo "VITE_DEFAULT_PEERJS_PATH=VITE_DEFAULT_PEERJS_PATH_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_PEERJS_PORT=VITE_DEFAULT_PEERJS_PORT_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_SYNCIOUS_INSTANCE=VITE_DEFAULT_SYNCIOUS_INSTANCE_PLACEHOLDER" >> .env && \ + echo "VITE_DEFAULT_SETTINGS=VITE_DEFAULT_SETTINGS_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE=VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE_PLACEHOLDER" >> .env # Install dependencies and build the project diff --git a/materialious/replace_env_vars.sh b/materialious/replace_env_vars.sh index 2800ab14..ba74d461 100644 --- a/materialious/replace_env_vars.sh +++ b/materialious/replace_env_vars.sh @@ -11,3 +11,4 @@ find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VI find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_PEERJS_PATH_PLACEHOLDER|'"$VITE_DEFAULT_PEERJS_PATH"'|g' {} + find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_PEERJS_PORT_PLACEHOLDER|'"$VITE_DEFAULT_PEERJS_PORT"'|g' {} + find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SYNCIOUS_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_SYNCIOUS_INSTANCE"'|g' {} + +find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SETTINGS_PLACEHOLDER|'"$VITE_DEFAULT_SETTINGS"'|g' {} + diff --git a/materialious/src/lib/Settings.svelte b/materialious/src/lib/Settings.svelte index 35979121..f82e3c43 100644 --- a/materialious/src/lib/Settings.svelte +++ b/materialious/src/lib/Settings.svelte @@ -1,5 +1,5 @@ + +{#if !Capacitor.isNativePlatform()} + + await navigator.clipboard.writeText(`${location.origin}watch/${video.videoId}`)} + > +
{$_('player.share.materialiousLink')}
+{/if} + + await navigator.clipboard.writeText(`https://redirect.invidious.io/watch?v=${video.videoId}`)} +> +
{$_('player.share.invidiousRedirect')}
+ await navigator.clipboard.writeText(`https://www.youtube.com/watch?v=${video.videoId}`)} +> +
{$_('player.share.youtubeLink')}
diff --git a/materialious/src/lib/SyncParty.svelte b/materialious/src/lib/SyncParty.svelte index fb60da8e..7db3f1bb 100644 --- a/materialious/src/lib/SyncParty.svelte +++ b/materialious/src/lib/SyncParty.svelte @@ -33,7 +33,7 @@ const peerId = crypto.randomUUID(); setWindowQueryFlag('sync', peerId); - $syncPartyPeerStore = peerJs(peerId); + $syncPartyPeerStore = await peerJs(peerId); if ($syncPartyPeerStore) { $syncPartyPeerStore.on('connection', (conn) => { @@ -68,7 +68,7 @@ const currentUrl = get(page).url; const syncId = currentUrl.searchParams.get('sync'); if (syncId) { - $syncPartyPeerStore = peerJs(crypto.randomUUID()); + $syncPartyPeerStore = await peerJs(crypto.randomUUID()); $syncPartyPeerStore.on('open', () => { if (!$syncPartyPeerStore) return; diff --git a/materialious/src/lib/Thumbnail.svelte b/materialious/src/lib/Thumbnail.svelte index f1cd2b1b..f8c08538 100644 --- a/materialious/src/lib/Thumbnail.svelte +++ b/materialious/src/lib/Thumbnail.svelte @@ -1,5 +1,5 @@ -
(showVideoPreview = false)} - on:focus={() => {}} - role="region" -> - (showVideoPreview = false)} + on:focus={() => {}} + role="region" > - {#if loading} - - {:else if loaded} - {#if showVideoPreview && videoPreview} -
- -
- {:else} - Thumbnail for video - {/if} - {:else} -

{$_('thumbnail.failedToLoadImage')}

- {/if} - {#if progress} - - {/if} - {#if !('liveVideo' in video) || !video.liveVideo} - {#if video.lengthSeconds !== 0} -
-  {videoLength(video.lengthSeconds)}  -
- {/if} - {:else} -
- {$_('thumbnail.live')} -
- {/if} -
- {#if showVideoPreview && videoPreview} - - {/if} -
- -
- -
+ + {#if showVideoPreview && videoPreview} + + {/if} + + +
+ +
+{/if} diff --git a/materialious/src/lib/VideoList.svelte b/materialious/src/lib/VideoList.svelte index 729b7b01..f84bd895 100644 --- a/materialious/src/lib/VideoList.svelte +++ b/materialious/src/lib/VideoList.svelte @@ -1,21 +1,52 @@
{#each videos as video} -
-
- -
-
+ {#if !hiddenVideos.includes(video.videoId)} +
+
+ (hiddenVideos = [...hiddenVideos, video.videoId])} + {video} + {playlistId} + /> + {#if auth && decodeURIComponent(auth.username) === playlistAuthor && 'indexId' in video} +
+ +
+ {/if} +
+
+ {/if} {/each}
diff --git a/materialious/src/lib/bookmarklet.ts b/materialious/src/lib/externalSettings.ts similarity index 83% rename from materialious/src/lib/bookmarklet.ts rename to materialious/src/lib/externalSettings.ts index 21c426bf..9ce4ba3f 100644 --- a/materialious/src/lib/bookmarklet.ts +++ b/materialious/src/lib/externalSettings.ts @@ -143,6 +143,39 @@ const persistedStores = [ } ]; +function setStores(toSet: Record) { + persistedStores.forEach((store) => { + let userOverwritten: boolean = false; + try { + userOverwritten = localStorage.getItem(store.name) !== null; + } catch { } + + let paramValue = toSet[store.name]; + if (typeof paramValue !== 'undefined' && !userOverwritten) { + let value: any; + + if (store.type === 'array') { + value = paramValue.split(','); + } else if (store.type === 'boolean') { + value = paramValue === 'true'; + } else { + value = paramValue; + } + + store.store.set(value); + } + }); +} + +export function loadSettingsFromEnv() { + try { + if (typeof import.meta.env.VITE_DEFAULT_SETTINGS !== 'undefined') { + const defaultSettings = JSON.parse(import.meta.env.VITE_DEFAULT_SETTINGS); + setStores(defaultSettings); + } + } catch { } +} + export function bookmarkletSaveToUrl(): string { const url = new URL(location.origin); @@ -159,20 +192,11 @@ export function bookmarkletSaveToUrl(): string { export function bookmarkletLoadFromUrl() { const currentPage = get(page); - persistedStores.forEach((store) => { - let paramValue = currentPage.url.searchParams.get(store.name); - if (paramValue) { - let value: any; + const toSet: Record = {}; - if (store.type === 'array') { - value = paramValue.split(','); - } else if (store.type === 'boolean') { - value = paramValue === 'true'; - } else { - value = paramValue; - } - - store.store.set(value); - } + currentPage.url.searchParams.forEach((value, key) => { + toSet[key] = value; }); + + setStores(toSet); } diff --git a/materialious/src/lib/i18n/locales/en.json b/materialious/src/lib/i18n/locales/en.json index 59748fb7..9f25ce92 100644 --- a/materialious/src/lib/i18n/locales/en.json +++ b/materialious/src/lib/i18n/locales/en.json @@ -8,9 +8,11 @@ "create": "Create", "title": "Title", "searchPlaceholder": "Search...", + "delete": "Delete", "deleteAllHistory": "Delete all history", "skipping": "Skipping", "replies": "replies", + "hideVideo": "Hide video", "syncParty": { "userJoined": "User joined your watch party.", "userLeft": "User left your watch party." @@ -116,4 +118,4 @@ } }, "subscribe": "Subscribe" -} +} \ No newline at end of file diff --git a/materialious/src/lib/misc.ts b/materialious/src/lib/misc.ts index 82f52046..a9a1cbdc 100644 --- a/materialious/src/lib/misc.ts +++ b/materialious/src/lib/misc.ts @@ -1,7 +1,7 @@ import { pushState } from '$app/navigation'; import { page } from '$app/stores'; import humanNumber from 'human-number'; -import { Peer } from 'peerjs'; +import type Peer from 'peerjs'; import { get } from 'svelte/store'; import { instanceStore } from '../store'; import type { Image } from './Api/model'; @@ -134,8 +134,13 @@ export function removeWindowQueryFlag(key: string) { pushState(currentPage.url, currentPage.state); } -export function peerJs(peerId: string): Peer { - return new Peer( +let PeerInstance: typeof Peer; +export async function peerJs(peerId: string): Promise { + // https://github.com/peers/peerjs/issues/819 + if (typeof PeerInstance === 'undefined') { + PeerInstance = (await import('peerjs')).Peer; + } + return new PeerInstance( peerId, { host: import.meta.env.VITE_DEFAULT_PEERJS_HOST || '0.peerjs.com', diff --git a/materialious/src/routes/+layout.svelte b/materialious/src/routes/+layout.svelte index d41cb590..205627fc 100644 --- a/materialious/src/routes/+layout.svelte +++ b/materialious/src/routes/+layout.svelte @@ -10,7 +10,7 @@ import Settings from '$lib/Settings.svelte'; import SyncParty from '$lib/SyncParty.svelte'; import Thumbnail from '$lib/Thumbnail.svelte'; - import { bookmarkletLoadFromUrl } from '$lib/bookmarklet'; + import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings'; import { App } from '@capacitor/app'; import { Browser } from '@capacitor/browser'; import { Capacitor } from '@capacitor/core'; @@ -125,8 +125,6 @@ onMount(async () => { ui(); - bookmarkletLoadFromUrl(); - const isDark = get(darkModeStore); if (isDark === null) { @@ -150,6 +148,11 @@ await ui('theme', themeHex); } + loadSettingsFromEnv(); + // Should always be loaded after env settings + // So user preferences overwrite instance preferences. + bookmarkletLoadFromUrl(); + if (isLoggedIn) { loadNotifications().catch(() => authStore.set(null)); } @@ -204,10 +207,12 @@ {#if mobileSearchShow} (mobileSearchShow = false)} /> {:else} - + {#if !Capacitor.isNativePlatform()} + + {/if} {#if isLoggedIn} + {#if data.downloadOptions.length > 0}