diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle index 0ac11bc7..38e393be 100644 --- a/materialious/android/app/build.gradle +++ b/materialious/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "us.materialio.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 130 - versionName "1.9.13" + versionCode 131 + versionName "1.9.14" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/materialious/electron/materialious.metainfo.xml b/materialious/electron/materialious.metainfo.xml index 2fa790b1..635024d5 100644 --- a/materialious/electron/materialious.metainfo.xml +++ b/materialious/electron/materialious.metainfo.xml @@ -65,7 +65,11 @@ - + + + https://github.com/Materialious/Materialious/releases/tag/1.9.14 + + https://github.com/Materialious/Materialious/releases/tag/1.9.13 diff --git a/materialious/electron/package-lock.json b/materialious/electron/package-lock.json index c81063ad..c24ee22d 100644 --- a/materialious/electron/package-lock.json +++ b/materialious/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "Materialious", - "version": "1.9.12", + "version": "1.9.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Materialious", - "version": "1.9.12", + "version": "1.9.14", "license": "MIT", "dependencies": { "@capacitor-community/electron": "^5.0.0", diff --git a/materialious/electron/package.json b/materialious/electron/package.json index a39f1b7b..378445e5 100644 --- a/materialious/electron/package.json +++ b/materialious/electron/package.json @@ -1,6 +1,6 @@ { "name": "Materialious", - "version": "1.9.13", + "version": "1.9.14", "description": "Modern material design for Invidious.", "author": { "name": "Ward Pearce", diff --git a/materialious/package.json b/materialious/package.json index b49cda38..2e1b9501 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -1,6 +1,6 @@ { "name": "materialious", - "version": "1.9.13", + "version": "1.9.14", "private": true, "scripts": { "dev": "vite dev", diff --git a/materialious/src/lib/api/apiExtended.ts b/materialious/src/lib/api/apiExtended.ts new file mode 100644 index 00000000..ec321213 --- /dev/null +++ b/materialious/src/lib/api/apiExtended.ts @@ -0,0 +1,59 @@ +// src/lib/requestQueue.ts +import { writable, type Writable } from 'svelte/store'; +import type { SynciousProgressModel } from './model'; +import { getVideoProgress } from '.'; + +const synciousCacheStore: Writable = writable(null); + +const videoIds: string[] = []; +const pendingResolves = new Map void>(); + +let timeout: ReturnType | null = null; +const DEBOUNCE_MS = 1000; +const BATCH_SIZE = 100; + +async function processBatches(): Promise { + const batches: string[][] = []; + + while (videoIds.length > 0) { + batches.push(videoIds.splice(0, BATCH_SIZE)); + } + + const results: SynciousProgressModel[] = []; + + for (const batch of batches) { + const res: SynciousProgressModel[] = await getVideoProgress(batch.join(',')); + results.push(...res); + + // Resolve pending promises for this batch + for (const videoId of batch) { + const match = res.find((item) => item.video_id === videoId); + const resolve = pendingResolves.get(videoId); + if (resolve) { + resolve(match); + pendingResolves.delete(videoId); + } + } + } + + synciousCacheStore.set(results); +} + +export function queueSyncious(videoId: string): Promise { + videoIds.push(videoId); + + const promise = new Promise((resolve) => { + pendingResolves.set(videoId, resolve); + }); + + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + processBatches().catch((e) => { + console.error('Failed to process batches:', e); + }); + }, DEBOUNCE_MS); + + return promise; +} + +export { synciousCacheStore }; diff --git a/materialious/src/lib/api/index.ts b/materialious/src/lib/api/index.ts index 39440178..d9d80f98 100644 --- a/materialious/src/lib/api/index.ts +++ b/materialious/src/lib/api/index.ts @@ -64,7 +64,7 @@ export function buildAuthHeaders(): { headers: Record } { if (authToken.startsWith('SID=')) { return { headers: { __sid_auth: authToken } }; } else { - return { headers: { Authorization: `Bearer ${get(authStore)?.token}` } }; + return { headers: { Authorization: `Bearer ${authToken}` } }; } } diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index d8ce271c..f9b3e932 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -20,12 +20,14 @@ import { _ } from '$lib/i18n'; import { get } from 'svelte/store'; import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '../api'; - import type { VideoPlay } from '../api/model'; + import type { PlaylistPageVideo, VideoPlay } from '../api/model'; import { authStore, darkModeStore, instanceStore, + isAndroidTvStore, playerAndroidLockOrientation, + playerAutoplayNextByDefaultStore, playerAutoPlayStore, playerDefaultLanguage, playerDefaultPlaybackSpeed, @@ -34,12 +36,14 @@ playerSavePlaybackPositionStore, playerStatisticsByDefault, playerYouTubeJsFallback, + playlistSettingsStore, sponsorBlockCategoriesStore, sponsorBlockDisplayToastStore, sponsorBlockStore, sponsorBlockUrlStore, synciousInstanceStore, synciousStore, + syncPartyConnectionsStore, themeColorStore } from '../store'; import { getDynamicTheme, setStatusBarColor } from '../theme'; @@ -47,7 +51,10 @@ import { patchYoutubeJs } from '$lib/patches/youtubejs'; import { playbackRates } from '$lib/const'; import { EndTimeElement } from '$lib/shaka-elements/endTime'; - import androidTv from '$lib/android/plugins/androidTv'; + import { loadEntirePlaylist } from '$lib/playlist'; + import { goto } from '$app/navigation'; + import { unsafeRandomItem } from '$lib/misc'; + import type { PlayerEvents } from '$lib/player'; interface Props { data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null }; @@ -65,12 +72,10 @@ }: Props = $props(); let snackBarAlert = $state(''); - let playerPosSet = false; let originalOrigination: ScreenOrientationResult | undefined; let watchProgressTimeout: NodeJS.Timeout; let playerElementResizeObserver: ResizeObserver | undefined; let showVideoRetry = $state(false); - let isAndroidTv = $state(false); let player: shaka.Player; let shakaUi: shaka.ui.Overlay; @@ -78,13 +83,25 @@ const STORAGE_KEY_VOLUME = 'shaka-preferred-volume'; async function updateSeekBarTheme() { + if (!shakaUi) return; await tick(); shakaUi.configure({ seekBarColors: { played: (await getDynamicTheme())['--primary'] } }); + setChapterMarkers(); + + const overflowMenuButton = document.querySelector('.shaka-overflow-menu-button'); + if (overflowMenuButton) { + overflowMenuButton.innerHTML = 'settings'; + } + + const backToOverflowButton = document.querySelector('.shaka-back-to-overflow-button'); + if (backToOverflowButton) { + backToOverflowButton.innerHTML = 'arrow_back_ios_new'; + } } themeColorStore.subscribe(updateSeekBarTheme); @@ -192,7 +209,7 @@ if ( Capacitor.getPlatform() === 'android' && data.video.adaptiveFormats.length > 0 && - !isAndroidTv + !$isAndroidTvStore ) { const videoFormats = data.video.adaptiveFormats.filter((format) => format.type.startsWith('video/') @@ -405,8 +422,6 @@ return; } - isAndroidTv = (await androidTv.isAndroidTv()).value; - HttpFetchPlugin.cacheManager.clearCache(); player = new shaka.Player(); @@ -473,13 +488,12 @@ 'statistics' ], playbackRates: playbackRates, - enableTooltips: false, - seekBarColors: { - played: (await getDynamicTheme())['--primary'] - } + enableTooltips: false }); - player.addEventListener('error', async (event) => { + updateSeekBarTheme(); + + player.addEventListener('error', (event) => { const error = (event as CustomEvent).detail as shaka.util.Error; console.error('Player error:', error); }); @@ -500,16 +514,6 @@ await androidHandleRotate(); - const overflowMenuButton = document.querySelector('.shaka-overflow-menu-button'); - if (overflowMenuButton) { - overflowMenuButton.innerHTML = 'settings'; - } - - const backToOverflowButton = document.querySelector('.shaka-back-to-overflow-button'); - if (backToOverflowButton) { - backToOverflowButton.innerHTML = 'arrow_back_ios_new'; - } - Mousetrap.bind('space', () => { if (!playerElement) return; @@ -521,18 +525,20 @@ return false; }); - Mousetrap.bind('right', () => { - if (!playerElement) return; - playerElement.currentTime = playerElement.currentTime + 10; - return false; - }); + if (!$isAndroidTvStore) { + Mousetrap.bind('right', () => { + if (!playerElement) return; + playerElement.currentTime = playerElement.currentTime + 10; + return false; + }); - Mousetrap.bind('left', () => { - if (!playerElement) return; + Mousetrap.bind('left', () => { + if (!playerElement) return; - playerElement.currentTime = playerElement.currentTime - 10; - return false; - }); + playerElement.currentTime = playerElement.currentTime - 10; + return false; + }); + } Mousetrap.bind('c', () => { const isVisible = player.isTextTrackVisible(); @@ -576,18 +582,55 @@ return false; }); - setChapterMarkers(); - - if (isAndroidTv) { - Mousetrap.bind('enter', () => { - if (playerElement?.paused) { - playerElement?.play(); - } else { - playerElement?.pause(); + playerElement.addEventListener('ended', async () => { + if (!data.playlistId) { + if ($playerAutoplayNextByDefaultStore) { + goto(`/watch/${data.video.recommendedVideos[0].videoId}`); } - return false; + + return; + } + + const playlist = await loadEntirePlaylist(data.playlistId); + const playlistVideoIds = playlist.videos.map((value) => { + return value.videoId; }); - } + + let goToVideo: PlaylistPageVideo | undefined; + + const shufflePlaylist = $playlistSettingsStore[data.playlistId]?.shuffle ?? false; + const loopPlaylist = $playlistSettingsStore[data.playlistId]?.loop ?? false; + + if (shufflePlaylist) { + goToVideo = unsafeRandomItem(playlist.videos); + } else { + const currentVideoIndex = playlistVideoIds.indexOf(data.video.videoId); + const newIndex = currentVideoIndex + 1; + if (currentVideoIndex !== -1 && newIndex < playlistVideoIds.length) { + goToVideo = playlist.videos[newIndex]; + } else if (loopPlaylist) { + // Loop playlist on end + goToVideo = playlist.videos[0]; + } + } + + if (typeof goToVideo !== 'undefined') { + if ($syncPartyConnectionsStore) { + $syncPartyConnectionsStore.forEach((conn) => { + if (typeof goToVideo === 'undefined') return; + + conn.send({ + events: [ + { type: 'change-video', videoId: goToVideo.videoId }, + { type: 'playlist', playlistId: data.playlistId } + ] + } as PlayerEvents); + }); + } + + goto(`/watch/${goToVideo.videoId}?playlist=${data.playlistId}`); + } + }); try { await loadVideo(); @@ -602,9 +645,6 @@ }); async function loadPlayerPos() { - if (playerPosSet) return; - playerPosSet = true; - if (loadTimeFromUrl($page)) return; let toSetTime = 0; @@ -628,11 +668,11 @@ } function savePlayerPos() { - if (data.video.hlsUrl) return; + if (data.video.liveNow) return; const synciousEnabled = $synciousStore && $synciousInstanceStore && $authStore; - if ($playerSavePlaybackPositionStore && player && playerElement && playerElement.currentTime) { + if ($playerSavePlaybackPositionStore && playerElement) { if ( playerElement.currentTime < playerElement.duration - 10 && playerElement.currentTime > 10 @@ -657,7 +697,7 @@ } onDestroy(async () => { - if (Capacitor.getPlatform() === 'android') { + if (Capacitor.getPlatform() === 'android' && !$isAndroidTvStore) { if (originalOrigination) { await StatusBar.setOverlaysWebView({ overlay: false }); await StatusBar.show(); @@ -667,7 +707,7 @@ } } - Mousetrap.unbind(['enter', 'left', 'right']); + Mousetrap.unbind(['left', 'right', 'space', 'c', 'f', 'shift+left', 'shift+right']); if (watchProgressTimeout) { clearTimeout(watchProgressTimeout); @@ -677,7 +717,6 @@ savePlayerPos(); } catch (error) {} - playerPosSet = false; HttpFetchPlugin.cacheManager.clearCache(); if (playerElementResizeObserver) { @@ -702,8 +741,8 @@
@@ -711,9 +750,9 @@ controls={false} autoplay={$playerAutoPlayStore} id="player" - poster={getBestThumbnail(data.video.videoThumbnails, 1251, 781)} + poster={getBestThumbnail(data.video.videoThumbnails, 9999, 9999)} > - {#if isEmbed} + {#if isEmbed && !isAndroidTvStore}
{data.video.title}
@@ -769,6 +808,11 @@ aspect-ratio: 16 / 9; } + video[poster] { + height: 100%; + width: 100%; + } + video { position: absolute; top: 50%; diff --git a/materialious/src/lib/components/Thumbnail.svelte b/materialious/src/lib/components/Thumbnail.svelte index 64ea8354..6a7c2aa5 100644 --- a/materialious/src/lib/components/Thumbnail.svelte +++ b/materialious/src/lib/components/Thumbnail.svelte @@ -5,7 +5,7 @@ import { onDestroy, onMount } from 'svelte'; import { _ } from '$lib/i18n'; import { get } from 'svelte/store'; - import { getDeArrow, getThumbnail, getVideoProgress } from '../api'; + import { getDeArrow, getThumbnail } from '../api'; import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model'; import { insecureRequestImageHandler, truncate } from '../misc'; import type { PlayerEvents } from '../player'; @@ -21,6 +21,7 @@ synciousStore } from '../store'; import { goto } from '$app/navigation'; + import { queueSyncious } from '$lib/api/apiExtended'; interface Props { video: VideoBase | Video | Notification | PlaylistPageVideo; @@ -32,7 +33,9 @@ let placeholderHeight: number = $state(0); - let watchUrl = new URL(`${location.origin}/watch/${video.videoId}`); + let watchUrl = new URL( + `${location.origin}/${$isAndroidTvStore ? 'tv' : 'watch'}/${video.videoId}` + ); if (playlistId !== '') { watchUrl.searchParams.set('playlist', playlistId); @@ -109,7 +112,7 @@ if (get(synciousStore) && get(synciousInstanceStore) && get(authStore)) { try { - progress = (await getVideoProgress(video.videoId, { priority: 'low' }))[0].time.toString(); + progress = (await queueSyncious(video.videoId))?.time?.toString() ?? undefined; } catch {} } }); @@ -138,7 +141,7 @@ function calcThumbnailPlaceholderHeight() { if ($isAndroidTvStore) { - placeholderHeight = innerWidth / 3; + placeholderHeight = 100; return; } if (!sideways) { @@ -152,7 +155,7 @@ placeholderHeight = innerWidth / 12; } } else { - placeholderHeight = 115; + placeholderHeight = 100; } } @@ -162,9 +165,7 @@ tabindex="0" role="button" onclick={async () => { - if ($isAndroidTvStore) { - goto(`${location.origin}/embed/${video.videoId}`); - } + goto(watchUrl); }} >
diff --git a/materialious/src/lib/components/Watch/Author.svelte b/materialious/src/lib/components/Watch/Author.svelte new file mode 100644 index 00000000..5b822426 --- /dev/null +++ b/materialious/src/lib/components/Watch/Author.svelte @@ -0,0 +1,62 @@ + + + diff --git a/materialious/src/lib/components/Watch/Description.svelte b/materialious/src/lib/components/Watch/Description.svelte new file mode 100644 index 00000000..9c777111 --- /dev/null +++ b/materialious/src/lib/components/Watch/Description.svelte @@ -0,0 +1,42 @@ + + +
+ + + +
+
+
+ {@html description} +
+
+ + +
diff --git a/materialious/src/lib/components/Watch/LikesDislikes.svelte b/materialious/src/lib/components/Watch/LikesDislikes.svelte new file mode 100644 index 00000000..2ab61085 --- /dev/null +++ b/materialious/src/lib/components/Watch/LikesDislikes.svelte @@ -0,0 +1,29 @@ + + +{#await returnYTDislikes then returnYTDislikes} + {#if returnYTDislikes} + + {:else} + + {/if} +{/await} diff --git a/materialious/src/lib/i18n/locales/en.json b/materialious/src/lib/i18n/locales/en.json index e4577f83..d3213ce4 100644 --- a/materialious/src/lib/i18n/locales/en.json +++ b/materialious/src/lib/i18n/locales/en.json @@ -5,6 +5,8 @@ "loadMore": "Load more", "views": "views", "login": "Login", + "recommendedVideos": "Recommended Videos", + "playlistVideos": "Playlist Videos", "invidiousLogin": "Please log in with your Invidious account", "invalidInstance": "Please verify the URL. If it's correct, the instance may be down or may not support third-party clients.", "invidiousBlockWarning": "Invidious is currently being blocked by Google. If videos aren't loading for this instance, please use this instance on Materialious on {android} or {desktop} to get around this with local video fallback.", diff --git a/materialious/src/lib/misc.ts b/materialious/src/lib/misc.ts index 77a53dc2..57b1c901 100644 --- a/materialious/src/lib/misc.ts +++ b/materialious/src/lib/misc.ts @@ -106,3 +106,10 @@ export function excludeDuplicateFeeds(currentItems: feedItems, newItems: feedIte return [...nonDuplicatedNewItems, ...currentItems]; } + +export function expandSummery(id: string) { + const element = document.getElementById(id); + if (element) { + element.click(); + } +} diff --git a/materialious/src/lib/patches/youtubejs.ts b/materialious/src/lib/patches/youtubejs.ts index 5a079d96..9389950c 100644 --- a/materialious/src/lib/patches/youtubejs.ts +++ b/materialious/src/lib/patches/youtubejs.ts @@ -81,7 +81,7 @@ export async function patchYoutubeJs(videoId: string): Promise { let dashUri: string | undefined; if (video.streaming_data) { - video.streaming_data.adaptive_formats = video.streaming_data.adaptive_formats.map((format) => { + video.streaming_data.adaptive_formats.forEach((format) => { const formatKey = fromFormat(format) || ''; format.url = `https://sabr?___key=${formatKey}`; format.signature_cipher = undefined; diff --git a/materialious/src/lib/playlist.ts b/materialious/src/lib/playlist.ts new file mode 100644 index 00000000..244c638e --- /dev/null +++ b/materialious/src/lib/playlist.ts @@ -0,0 +1,53 @@ +import { get } from 'svelte/store'; +import { getPlaylist } from './api'; +import type { PlaylistPage, PlaylistPageVideo } from './api/model'; +import { playlistCacheStore } from './store'; + +export async function loadEntirePlaylist( + playlistId: string +): Promise<{ videos: PlaylistPageVideo[]; info: PlaylistPage }> { + const cachedPlaylists = get(playlistCacheStore); + if (playlistId in cachedPlaylists) { + console.log('Using cache'); + return cachedPlaylists[playlistId]; + } + + let playlistVideos: PlaylistPageVideo[] = []; + let playlist: PlaylistPage | undefined = undefined; + + const ignoreVideos: string[] = []; + + for (let page = 1; page < Infinity; page++) { + const newPlaylist = await getPlaylist(playlistId, page); + if (page === 1) { + playlist = newPlaylist; + } + let newVideos = newPlaylist.videos; + if (newVideos.length === 0) { + break; + } + + newVideos = newVideos.filter((playlistVideo) => { + return playlistVideo.lengthSeconds > 0 && !ignoreVideos.includes(playlistVideo.videoId); + }); + + newVideos.forEach((playlistVideo) => { + ignoreVideos.push(playlistVideo.videoId); + }); + + playlistVideos = [...playlistVideos, ...newVideos].sort( + (a: PlaylistPageVideo, b: PlaylistPageVideo) => { + return a.index < b.index ? -1 : 1; + } + ); + } + + if (typeof playlist === 'undefined') { + throw new Error('Unable to fetch playlist'); + } + + const combined = { videos: playlistVideos, info: playlist }; + playlistCacheStore.set({ [playlistId]: combined }); + + return combined; +} diff --git a/materialious/src/lib/store.ts b/materialious/src/lib/store.ts index 1f21ef50..e1cbbc2e 100644 --- a/materialious/src/lib/store.ts +++ b/materialious/src/lib/store.ts @@ -4,7 +4,15 @@ import type { DataConnection } from 'peerjs'; import { persisted } from 'svelte-persisted-store'; import { writable, type Writable } from 'svelte/store'; import type { TitleCase } from './letterCasing'; -import type { Channel, HashTag, Playlist, PlaylistPageVideo, Video, VideoBase } from './api/model'; +import type { + Channel, + HashTag, + Playlist, + PlaylistPage, + PlaylistPageVideo, + Video, + VideoBase +} from './api/model'; import { ensureNoTrailingSlash } from './misc'; function platformDependentDefault(givenValue: any, defaultValue: any): any { @@ -130,5 +138,8 @@ export const searchCacheStore: Writable<{ [searchTypeAndQuery: string]: (Channel | Video | Playlist | HashTag)[]; }> = writable({}); export const feedLastItemId: Writable = writable(undefined); +export const playlistCacheStore: Writable<{ + [playlistId: string]: { videos: PlaylistPageVideo[]; info: PlaylistPage }; +}> = writable({}); export const isAndroidTvStore: Writable = writable(false); diff --git a/materialious/src/lib/watch.ts b/materialious/src/lib/watch.ts new file mode 100644 index 00000000..d0979319 --- /dev/null +++ b/materialious/src/lib/watch.ts @@ -0,0 +1,71 @@ +import { + amSubscribed, + getComments, + getDislikes, + getPersonalPlaylists, + getVideo, + postHistory +} from '$lib/api/index'; +import { loadEntirePlaylist } from '$lib/playlist'; +import { + authStore, + playerProxyVideosStore, + returnYTDislikesInstanceStore, + returnYtDislikesStore +} from '$lib/store'; +import { phaseDescription } from '$lib/timestamps'; +import { error } from '@sveltejs/kit'; +import { get } from 'svelte/store'; + +export async function getWatchDetails(videoId: string, url: URL) { + let video; + try { + video = await getVideo(videoId, get(playerProxyVideosStore), { priority: 'high' }); + } catch (errorMessage: any) { + error(500, errorMessage); + } + + let personalPlaylists; + if (get(authStore)) { + postHistory(video.videoId); + personalPlaylists = getPersonalPlaylists({ priority: 'low' }); + } else { + personalPlaylists = null; + } + + let comments; + try { + comments = video.liveNow + ? null + : getComments(videoId, { sort_by: 'top', source: 'youtube' }, { priority: 'low' }); + } catch { + comments = null; + } + + let returnYTDislikes; + const returnYTDislikesInstance = get(returnYTDislikesInstanceStore); + if (returnYTDislikesInstance && returnYTDislikesInstance !== '') { + try { + returnYTDislikes = get(returnYtDislikesStore) + ? getDislikes(videoId, { priority: 'low' }) + : null; + } catch {} + } + + const playlistId = url.searchParams.get('playlist'); + if (playlistId) { + await loadEntirePlaylist(playlistId); + } + + return { + video: video, + content: phaseDescription(video.videoId, video.descriptionHtml, video.fallbackPatch), + playlistId: playlistId, + streamed: { + personalPlaylists: personalPlaylists, + returnYTDislikes: returnYTDislikes, + comments: comments, + subscribed: amSubscribed(video.authorId) + } + }; +} diff --git a/materialious/src/routes/(app)/+layout.svelte b/materialious/src/routes/(app)/+layout.svelte index dbbf4780..fc293ddf 100644 --- a/materialious/src/routes/(app)/+layout.svelte +++ b/materialious/src/routes/(app)/+layout.svelte @@ -173,6 +173,19 @@ onMount(async () => { ui(); + let themeHex = get(themeColorStore); + if (themeHex) { + await ui('theme', themeHex); + } else if (Capacitor.getPlatform() === 'android') { + if (!themeHex) { + try { + const colorPalette = await colorTheme.getColorPalette(); + themeHex = convertToHexColorCode(colorPalette.primary); + await ui('theme', themeHex); + } catch {} + } + } + $isAndroidTvStore = (await androidTv.isAndroidTv()).value; if ($isAndroidTvStore) { @@ -192,19 +205,6 @@ // So user preferences overwrite instance preferences. bookmarkletLoadFromUrl(); - let themeHex = get(themeColorStore); - if (themeHex) { - await ui('theme', themeHex); - } else if (Capacitor.getPlatform() === 'android') { - if (!themeHex) { - try { - const colorPalette = await colorTheme.getColorPalette(); - themeHex = convertToHexColorCode(colorPalette.primary); - await ui('theme', themeHex); - } catch {} - } - } - await setStatusBarColor(); setTheme(); diff --git a/materialious/src/routes/(app)/playlist/[slug]/+page.svelte b/materialious/src/routes/(app)/playlist/[slug]/+page.svelte index 89e308be..c62a1730 100644 --- a/materialious/src/routes/(app)/playlist/[slug]/+page.svelte +++ b/materialious/src/routes/(app)/playlist/[slug]/+page.svelte @@ -1,56 +1,24 @@
- {#if videos} + {#if data.playlist.videos} {/if} -

{data.playlist.title}

+

{data.playlist.info.title}

- {cleanNumber(data.playlist.viewCount)} - {$_('views')} • {data.playlist.videoCount} + {cleanNumber(data.playlist.info.viewCount)} + {$_('views')} • {data.playlist.info.videoCount} {$_('videos')}

-

{data.playlist.description}

+

{data.playlist.info.description}

@@ -109,7 +79,7 @@ role="presentation" onclick={async () => { await Clipboard.write({ - string: `https://www.youtube.com/playlist?list=${data.playlist.playlistId}` + string: `https://www.youtube.com/playlist?list=${data.playlist.info.playlistId}` }); (document.activeElement as HTMLElement)?.blur(); }} @@ -120,6 +90,10 @@
-{#if videos} - +{#if data.playlist.videos} + {/if} diff --git a/materialious/src/routes/(app)/playlist/[slug]/+page.ts b/materialious/src/routes/(app)/playlist/[slug]/+page.ts index c87433bf..20422000 100644 --- a/materialious/src/routes/(app)/playlist/[slug]/+page.ts +++ b/materialious/src/routes/(app)/playlist/[slug]/+page.ts @@ -1,11 +1,11 @@ -import { getPlaylist } from '$lib/api/index'; +import { loadEntirePlaylist } from '$lib/playlist.js'; import { error } from '@sveltejs/kit'; export async function load({ params }) { let playlist; try { - playlist = await getPlaylist(params.slug); + playlist = await loadEntirePlaylist(params.slug); } catch (errorMessage: any) { error(500, errorMessage); } diff --git a/materialious/src/routes/(app)/watch/[slug]/+page.svelte b/materialious/src/routes/(app)/watch/[slug]/+page.svelte index 1266bac9..3edcfc25 100644 --- a/materialious/src/routes/(app)/watch/[slug]/+page.svelte +++ b/materialious/src/routes/(app)/watch/[slug]/+page.svelte @@ -1,33 +1,26 @@ + +{#key data.video.videoId} + +{/key} + +{#if showInfo} +
+
{letterCase(data.video.title)}
+ +
+ +
+ +
+ + {#if data.playlistId && data.playlistId in $playlistCacheStore} +
{$_('playlistVideos')}
+ +
+ {#each $playlistCacheStore[data.playlistId].videos as playlistVideo} + +
{ + showInfo = false; + }} + role="presentation" + id={playlistVideo.videoId} + class:border={playlistVideo.videoId === data.video.videoId} + > + {#key playlistVideo.videoId} + + {/key} +
+
+ {/each} +
+ {/if} +
{$_('recommendedVideos')}
+
+ {#each data.video.recommendedVideos as recommendedVideo} + +
{ + showInfo = false; + }} + role="presentation" + style="height: 100%;" + class="no-padding" + > + {#key recommendedVideo.videoId} + + {/key} +
+
+ {/each} +
+
+{/if} + + diff --git a/update_versions.py b/update_versions.py index 4c000d50..010f4d0c 100644 --- a/update_versions.py +++ b/update_versions.py @@ -3,7 +3,7 @@ import os import re from datetime import datetime -LATEST_VERSION = "1.9.13" +LATEST_VERSION = "1.9.14" 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")