From a6c92b7dd87affce46b6dce6aa4c80c1a1d5cee4 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Wed, 20 Dec 2023 16:21:11 -0500 Subject: [PATCH 01/36] Continue watching - planning --- .../src/config/default_home_layout.yaml | 22 +++++++++++++++++++ playlet-lib/src/config/local_video_api.yaml | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 playlet-lib/src/config/local_video_api.yaml diff --git a/playlet-lib/src/config/default_home_layout.yaml b/playlet-lib/src/config/default_home_layout.yaml index 36f52a7f..ccb5e5fc 100644 --- a/playlet-lib/src/config/default_home_layout.yaml +++ b/playlet-lib/src/config/default_home_layout.yaml @@ -1,3 +1,25 @@ +# TODO:P0 +# - Add router that returns videos for continue watching, including watch date and progress (returns them sorted and paginated) +# - It would return small page size (like 5) but includes video info? +# - Add a node for storing videos in memory, and write to registry, with a limit of 100 videos +# - Should contain a subnode for each video, so that renderers can observe changes +# - Add settings +# - Enable/disable continue watching +# - Max days to keep videos (videos expire based on last watch date) +# - Video count (capped at 100) +# - A button to clear all videos +# - Videos with about 95% progress (capped at 1 minute) should be considered watched +# - Add progress bar to video cell (this means "progress" is going to be a field in VideoContentNode) +# - Add support for web app +# - Rendering the new feed +# - Casting / Queuing the video should continue from the last watch position +- title: Continue watching + feedSources: + - id: local_continue_watching + title: Continue watching + apiType: Local + endpoint: continue_watching + - title: Subscriptions feedSources: - id: inv_auth_feed diff --git a/playlet-lib/src/config/local_video_api.yaml b/playlet-lib/src/config/local_video_api.yaml new file mode 100644 index 00000000..73ba4d3d --- /dev/null +++ b/playlet-lib/src/config/local_video_api.yaml @@ -0,0 +1,3 @@ +continue_watching: + title: Continue watching + url: /api/continue-watching From a5d6b6f54c34a7953aa2bdb8ba268d9f8c96b354 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Thu, 21 Dec 2023 13:49:02 -0500 Subject: [PATCH 02/36] Continue watching --- .../ContentNode/VideoContentNode.xml | 1 + .../ContentNode/VideoProgressContentNode.xml | 11 +++ playlet-lib/src/components/MainScene.xml | 7 +- .../src/components/PlayQueue/PlayQueue.bs | 3 +- .../src/components/PlayQueue/PlayQueue.xml | 1 + .../PlaylistView/PlaylistContentTask.bs | 3 +- .../components/PlaylistView/PlaylistView.bs | 3 +- .../components/PlaylistView/PlaylistView.xml | 1 + .../InvidiousInstanceTestingTask.bs | 3 +- .../ContinueWatching/ContinueWatching.bs | 70 ++++++++++++++++ .../ContinueWatching/ContinueWatching.xml | 10 +++ .../ContinueWatching/ContinueWatchingUtils.bs | 33 ++++++++ .../Services/Invidious/Invidious.bs | 12 ++- .../Services/Invidious/InvidiousService.bs | 6 +- .../Invidious/InvidiousToContentNode.bs | 19 +++-- .../VideoFeed/VideoRowCell/VideoRowCell.bs | 34 ++++++++ .../VideoFeed/VideoRowCell/VideoRowCell.xml | 7 ++ .../src/components/VideoFeed/VideoRowList.bs | 3 +- .../src/components/VideoFeed/VideoRowList.xml | 1 + .../VideoFeed/VideoRowListRowContentTask.bs | 3 +- .../components/VideoPlayer/SponsorBlock.bs | 1 - .../src/components/VideoPlayer/VideoPlayer.bs | 80 +++++++++++++++++-- .../components/VideoPlayer/VideoPlayer.xml | 1 + .../Middleware/ContinueWatchingRouter.bs | 29 +++++++ .../Middleware/PlayQueueRouter.bs | 6 +- .../PlayletWebServer/Middleware/ViewRouter.bs | 6 +- .../Web/PlayletWebServer/PlayletWebServer.bs | 2 + .../Web/PlayletWebServer/PlayletWebServer.xml | 1 + .../src/config/default_home_layout.yaml | 1 + playlet-lib/src/source/utils/RegistryUtils.bs | 1 + 30 files changed, 331 insertions(+), 28 deletions(-) create mode 100644 playlet-lib/src/components/ContentNode/VideoProgressContentNode.xml create mode 100644 playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.bs create mode 100644 playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.xml create mode 100644 playlet-lib/src/components/Services/ContinueWatching/ContinueWatchingUtils.bs create mode 100644 playlet-lib/src/components/Web/PlayletWebServer/Middleware/ContinueWatchingRouter.bs diff --git a/playlet-lib/src/components/ContentNode/VideoContentNode.xml b/playlet-lib/src/components/ContentNode/VideoContentNode.xml index fe82985b..f4215916 100644 --- a/playlet-lib/src/components/ContentNode/VideoContentNode.xml +++ b/playlet-lib/src/components/ContentNode/VideoContentNode.xml @@ -5,6 +5,7 @@ + diff --git a/playlet-lib/src/components/ContentNode/VideoProgressContentNode.xml b/playlet-lib/src/components/ContentNode/VideoProgressContentNode.xml new file mode 100644 index 00000000..80cc620a --- /dev/null +++ b/playlet-lib/src/components/ContentNode/VideoProgressContentNode.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/MainScene.xml b/playlet-lib/src/components/MainScene.xml index 8b992195..e0e07cbb 100644 --- a/playlet-lib/src/components/MainScene.xml +++ b/playlet-lib/src/components/MainScene.xml @@ -52,12 +52,14 @@ + preferences="bind:../Preferences" + continueWatching="bind:../ContinueWatching" /> + + bookmarks="bind:../Bookmarks" + continueWatching="bind:../ContinueWatching" /> \ No newline at end of file diff --git a/playlet-lib/src/components/PlayQueue/PlayQueue.bs b/playlet-lib/src/components/PlayQueue/PlayQueue.bs index fa489591..202ba9ac 100644 --- a/playlet-lib/src/components/PlayQueue/PlayQueue.bs +++ b/playlet-lib/src/components/PlayQueue/PlayQueue.bs @@ -190,7 +190,8 @@ function LoadPlaylist(playlist as object) as void task = StartAsyncTask(PlaylistContentTask, { content: playlist, - invidious: m.top.invidious + invidious: m.top.invidious, + continueWatchingContent: m.top.continueWatching.content }, OnPlaylistContentTaskResult) m.pendingLoadTasks[task.id] = task end function diff --git a/playlet-lib/src/components/PlayQueue/PlayQueue.xml b/playlet-lib/src/components/PlayQueue/PlayQueue.xml index 1d3f7afd..aee219ac 100644 --- a/playlet-lib/src/components/PlayQueue/PlayQueue.xml +++ b/playlet-lib/src/components/PlayQueue/PlayQueue.xml @@ -4,6 +4,7 @@ + diff --git a/playlet-lib/src/components/PlaylistView/PlaylistContentTask.bs b/playlet-lib/src/components/PlaylistView/PlaylistContentTask.bs index 66b0df60..4b3dd9c9 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistContentTask.bs +++ b/playlet-lib/src/components/PlaylistView/PlaylistContentTask.bs @@ -6,6 +6,7 @@ import "pkg:/components/Services/Invidious/InvidiousToContentNode.bs" function PlaylistContentTask(input as object) as object contentNode = input.content invidiousNode = input.invidious + continueWatchingContent = input.continueWatchingContent if m.top.cancel contentNode.loadState = FeedLoadState.None @@ -33,7 +34,7 @@ function PlaylistContentTask(input as object) as object end if instance = service.GetInstance() - InvidiousContent.ToPlaylistContentNode(contentNode, metadata, instance) + InvidiousContent.ToPlaylistContentNode(contentNode, metadata, instance, continueWatchingContent) childCount = contentNode.getChildCount() if metadata.videos.Count() > 0 or childCount < metadata.videoCount diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.bs b/playlet-lib/src/components/PlaylistView/PlaylistView.bs index 0ddb51e5..55a93089 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.bs +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.bs @@ -224,7 +224,8 @@ function LoadPlaylist() as void m.top.content.loadState = FeedLoadState.Loading m.playlistLoadTask = StartAsyncTask(PlaylistContentTask, { content: m.top.content, - invidious: m.top.invidious + invidious: m.top.invidious, + continueWatchingContent: m.top.continueWatching.content }, OnPlaylistContentTaskResult) end function diff --git a/playlet-lib/src/components/PlaylistView/PlaylistView.xml b/playlet-lib/src/components/PlaylistView/PlaylistView.xml index ddc6f216..0e167304 100644 --- a/playlet-lib/src/components/PlaylistView/PlaylistView.xml +++ b/playlet-lib/src/components/PlaylistView/PlaylistView.xml @@ -5,6 +5,7 @@ + diff --git a/playlet-lib/src/components/Screens/SettingsScreen/InvidiousInstance/InvidiousInstanceTesting/InvidiousInstanceTestingTask.bs b/playlet-lib/src/components/Screens/SettingsScreen/InvidiousInstance/InvidiousInstanceTesting/InvidiousInstanceTestingTask.bs index a74e2d6c..de62001b 100644 --- a/playlet-lib/src/components/Screens/SettingsScreen/InvidiousInstance/InvidiousInstanceTesting/InvidiousInstanceTestingTask.bs +++ b/playlet-lib/src/components/Screens/SettingsScreen/InvidiousInstance/InvidiousInstanceTesting/InvidiousInstanceTestingTask.bs @@ -280,7 +280,8 @@ function CanFetchVideoThumbails(instance as string, testNode as object) as void return end if - contentNode = InvidiousContent.ToVideoContentNode(invalid, json, instance) + continueWatchingContentMock = CreateObject("roSGNode", "ContentNode") + contentNode = InvidiousContent.ToVideoContentNode(invalid, json, instance, continueWatchingContentMock) thumbnail = contentNode.thumbnail if StringUtils.IsNullOrEmpty(thumbnail) testNode.state = "failed" diff --git a/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.bs b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.bs new file mode 100644 index 00000000..14538498 --- /dev/null +++ b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.bs @@ -0,0 +1,70 @@ +import "pkg:/source/utils/RegistryUtils.bs" +import "pkg:/source/utils/Logging.bs" +import "pkg:/source/utils/Types.bs" + +function Init() + m.top.content = m.top.findNode("content") + m.continueWatchingString = "" + Load() +end function + +function Load() as void + continueWatchingString = RegistryUtils.Read(RegistryUtils.CONTINUE_WATCHING) + if continueWatchingString = invalid + return + end if + + m.continueWatchingString = continueWatchingString + continueWatching = ParseJson(continueWatchingString) + if continueWatching = invalid + LogWarn("Failed to parse continue watching json") + return + end if + + nodes = [] + for each video in continueWatching.videos + node = CreateObject("roSGNode", "VideoProgressContentNode") + node.id = video.id + node.videoId = video.id + node.date = ValidInt(video.date) + node.timestamp = ValidInt(video.timestamp) + node.duration = ValidInt(video.duration) + nodes.push(node) + end for + + content = m.top.content + content.removeChildrenIndex(content.getChildCount(), 0) + content.appendChildren(nodes) +end function + +function Save() as void + nodes = m.top.content.getChildren(-1, 0) + if nodes.Count() = 0 + RegistryUtils.Delete(RegistryUtils.CONTINUE_WATCHING) + return + end if + + videos = [] + for each node in nodes + video = {} + video.id = node.videoId + video.date = node.date + video.timestamp = node.timestamp + video.duration = node.duration + videos.push(video) + end for + + videos.SortBy("date", "r") + + continueWatchingString = FormatJson({ + "__version": m.top.__version, + "videos": videos + }) + + if m.continueWatchingString = continueWatchingString + return + end if + + RegistryUtils.Write(RegistryUtils.CONTINUE_WATCHING, continueWatchingString) + m.continueWatchingString = continueWatchingString +end function diff --git a/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.xml b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.xml new file mode 100644 index 00000000..a3d4b026 --- /dev/null +++ b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatching.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/playlet-lib/src/components/Services/ContinueWatching/ContinueWatchingUtils.bs b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatchingUtils.bs new file mode 100644 index 00000000..839b1b3e --- /dev/null +++ b/playlet-lib/src/components/Services/ContinueWatching/ContinueWatchingUtils.bs @@ -0,0 +1,33 @@ +namespace ContinueWatching + + function GetNodeForVideo(continueWatchingNode as object, videoId as string) as object + node = continueWatchingNode.content.findNode(videoId) + if node <> invalid + return node + end if + + node = CreateObject("roSGNode", "VideoProgressContentNode") + node.id = videoId + node.videoId = videoId + continueWatchingNode.content.appendChild(node) + return node + end function + + function MarkWatchDate(progressNode as object) + date = CreateObject("roDateTime") + progressNode.date = date.asSeconds() + parent = progressNode.getParent() + if parent <> invalid + parent.removeChild(progressNode) + parent.insertChild(progressNode, 0) + end if + end function + + function RemoveNodeForVideo(continueWatchingNode as object, videoId as string) + node = continueWatchingNode.content.findNode(videoId) + if node <> invalid + continueWatchingNode.content.removeChild(node) + end if + end function + +end namespace diff --git a/playlet-lib/src/components/Services/Invidious/Invidious.bs b/playlet-lib/src/components/Services/Invidious/Invidious.bs index 96ac1e56..b2f62b43 100644 --- a/playlet-lib/src/components/Services/Invidious/Invidious.bs +++ b/playlet-lib/src/components/Services/Invidious/Invidious.bs @@ -3,11 +3,21 @@ import "InvidiousSubscriptionsTask.bs" import "pkg:/source/asyncTask/asyncTask.bs" function Init() - m.top.apiDefinitions = ParseJson(ReadAsciiFile("libpkg:/config/invidious_video_api.yaml")) + apiDefinitions = LoadApiDefinition("libpkg:/config/invidious_video_api.yaml", "Invidious") + apiDefinitions.Append(LoadApiDefinition("libpkg:/config/local_video_api.yaml", "Local")) + m.top.apiDefinitions = apiDefinitions m.service = new Invidious.InvidiousService(m.top) m.top.authToken = m.service.GetAuthToken() end function +function LoadApiDefinition(path as string, apiType as string) as object + apiDefinitions = ParseJson(ReadAsciiFile(path)) + for each key in apiDefinitions + apiDefinitions[key].apiType = apiType + end for + return apiDefinitions +end function + function GetCurrentInstance(unused as dynamic) as string return m.service.GetInstance() end function diff --git a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs index 940bbfcf..618ba469 100644 --- a/playlet-lib/src/components/Services/Invidious/InvidiousService.bs +++ b/playlet-lib/src/components/Services/Invidious/InvidiousService.bs @@ -214,7 +214,11 @@ namespace Invidious } end if - instance = m.GetInstance() + if feedSource.apiType = "Local" + instance = "http://127.0.0.1:8888" + else + instance = m.GetInstance() + end if request = HttpClient.Get(instance + endpoint.url) diff --git a/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs b/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs index 6c921978..d072a73f 100644 --- a/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs +++ b/playlet-lib/src/components/Services/Invidious/InvidiousToContentNode.bs @@ -3,7 +3,7 @@ import "pkg:/source/utils/TimeUtils.bs" namespace InvidiousContent - function ToRowCellContentNode(item as object, instance as dynamic) as object + function ToRowCellContentNode(item as object, instance as dynamic, continueWatchingContent as object) as object if item.videoId <> invalid and (item.type = invalid or item.type = "shortVideo") item.type = "video" end if @@ -17,9 +17,9 @@ namespace InvidiousContent end if if item.type = "video" - return ToVideoContentNode(invalid, item, instance) + return ToVideoContentNode(invalid, item, instance, continueWatchingContent) else if item.type = "playlist" - return ToPlaylistContentNode(invalid, item, instance) + return ToPlaylistContentNode(invalid, item, instance, continueWatchingContent) else if item.type = "channel" return ToChannelContentNode(invalid, item, instance) else @@ -27,7 +27,7 @@ namespace InvidiousContent end if end function - function ToVideoContentNode(node as object, item as object, instance as dynamic) as object + function ToVideoContentNode(node as object, item as object, instance as dynamic, continueWatchingContent as object) as object if node = invalid node = CreateObject("roSGNode", "VideoContentNode") end if @@ -47,10 +47,17 @@ namespace InvidiousContent node.viewCountText = VideoGetViewCountText(item) SetIfExists(node, "index", item, "index") + if not StringUtils.IsNullOrEmpty(item.videoId) + progressNode = continueWatchingContent.findNode(item.videoId) + if progressNode <> invalid + node.progressNode = progressNode + end if + end if + return node end function - function ToPlaylistContentNode(node as object, item as object, instance as dynamic) as object + function ToPlaylistContentNode(node as object, item as object, instance as dynamic, continueWatchingContent as object) as object if node = invalid node = CreateObject("roSGNode", "PlaylistContentNode") node.loadState = FeedLoadState.None @@ -75,7 +82,7 @@ namespace InvidiousContent newNodes = [] for each video in item.videos video.type = "video" - videoNode = ToVideoContentNode(invalid, video, instance) + videoNode = ToVideoContentNode(invalid, video, instance, continueWatchingContent) if videoNode <> invalid index = video.index if index <> invalid and index > -1 and index < childCount diff --git a/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.bs b/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.bs index 0f3d9c4b..bd294948 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.bs +++ b/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.bs @@ -16,12 +16,17 @@ function FindChildren() m.durationRect = m.top.FindNode("durationRect") m.durationLabel = m.top.FindNode("durationLabel") m.thumbnail = m.top.FindNode("thumbnail") + m.progressRect = m.top.FindNode("progressRect") end function function OnContentSet() as void content = m.top.itemContent if content = invalid + if m.progressNode <> invalid + m.progressNode.unobserveFieldScoped("timestamp") + m.progressNode = invalid + end if return end if @@ -60,6 +65,8 @@ function OnContentSet() as void m.liveRect.visible = true end if end if + + SetupProgress(content) end function function SetDurationText(text as string) @@ -75,3 +82,30 @@ function SetDurationText(text as string) rect.translation = [rectParent.width - rect.width, rect.translation[1]] end function +function SetupProgress(content as object) + if m.progressNode <> invalid + m.progressNode.unobserveFieldScoped("timestamp") + m.progressNode = invalid + end if + + m.progressNode = content.progressNode + if m.progressNode <> invalid + m.progressNode.observeFieldScoped("timestamp", FuncName(SetProgress)) + end if + SetProgress() +end function + +function SetProgress() as void + if m.progressNode = invalid + m.progressRect.scale = [0, 1] + return + end if + + duration = m.progressNode.duration + timestamp = m.progressNode.timestamp + progress = 0 + if duration > 0 + progress = timestamp / duration + end if + m.progressRect.scale = [progress, 1] +end function diff --git a/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.xml b/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.xml index 323ebbac..81d6f534 100644 --- a/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.xml +++ b/playlet-lib/src/components/VideoFeed/VideoRowCell/VideoRowCell.xml @@ -56,6 +56,13 @@ font="font:SmallestBoldSystemFont" text="UPCOMING" /> + - + Date: Fri, 22 Dec 2023 16:28:48 -0500 Subject: [PATCH 17/36] Web app support --- playlet-web/src/App.svelte | 5 +++ playlet-web/src/lib/Api/InvidiousApi.ts | 14 ++++++- playlet-web/src/lib/Api/PlayletApi.ts | 5 +++ playlet-web/src/lib/Stores.ts | 2 + .../src/lib/VideoFeed/VideoListRow.svelte | 39 +++++++++++++++++-- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/playlet-web/src/App.svelte b/playlet-web/src/App.svelte index 88c379a4..76d5b77c 100644 --- a/playlet-web/src/App.svelte +++ b/playlet-web/src/App.svelte @@ -7,6 +7,7 @@ bookmarksStore, homeLayoutFileStore, invidiousVideoApiStore, + localVideoApiStore, playletStateStore, preferencesModelStore, searchHistoryStore, @@ -35,6 +36,10 @@ invidiousVideoApiStore.set(apiDefinitions); }); + PlayletApi.getLocalVideoApiFile().then((apiDefinitions) => { + localVideoApiStore.set(apiDefinitions); + }); + PlayletApi.getPreferencesFile().then((value) => { preferencesModelStore.set(value); }); diff --git a/playlet-web/src/lib/Api/InvidiousApi.ts b/playlet-web/src/lib/Api/InvidiousApi.ts index d3bddbe7..22f5b66b 100644 --- a/playlet-web/src/lib/Api/InvidiousApi.ts +++ b/playlet-web/src/lib/Api/InvidiousApi.ts @@ -1,4 +1,5 @@ import { PlayletApi } from "lib/Api/PlayletApi"; +import { getHost } from "./Host"; export class InvidiousApi { public instance: string; @@ -105,7 +106,14 @@ export class InvidiousApi { return null; } - let url = this.instance + endpoint.url + let url: string; + if (endpoint.apiType === "Local") { + url = `http://${getHost()}` + endpoint.url + } + else { + url = this.instance + endpoint.url + } + let queryParams = {} if (endpoint.authenticated) { @@ -113,7 +121,9 @@ export class InvidiousApi { if (!this.isLoggedIn) { return null; } - return await PlayletApi.invidiousAuthenticatedRequest(feedSource); + if (endpoint.apiType !== "Local") { + return await PlayletApi.invidiousAuthenticatedRequest(feedSource); + } } if (endpoint.queryParams !== undefined) { diff --git a/playlet-web/src/lib/Api/PlayletApi.ts b/playlet-web/src/lib/Api/PlayletApi.ts index 9f5881a0..fbbdb612 100644 --- a/playlet-web/src/lib/Api/PlayletApi.ts +++ b/playlet-web/src/lib/Api/PlayletApi.ts @@ -23,6 +23,11 @@ export class PlayletApi { return await response.json(); } + static async getLocalVideoApiFile() { + const response = await fetch(`${PlayletApi.host()}/config/local_video_api.yaml`); + return await response.json(); + } + static async invidiousAuthenticatedRequest(feedSource) { const url = PlayletApi.host() + "/invidious/authenticated-request?feed-source=" + encodeURIComponent(JSON.stringify(feedSource)); const response = await fetch(url); diff --git a/playlet-web/src/lib/Stores.ts b/playlet-web/src/lib/Stores.ts index 035f51fa..ee677f75 100644 --- a/playlet-web/src/lib/Stores.ts +++ b/playlet-web/src/lib/Stores.ts @@ -15,6 +15,8 @@ export const userPreferencesStore = writable({} as any); export const invidiousVideoApiStore = writable({} as any); +export const localVideoApiStore = writable({} as any); + export const homeLayoutFileStore = writable([] as any); export const bookmarksStore = writable([] as any); diff --git a/playlet-web/src/lib/VideoFeed/VideoListRow.svelte b/playlet-web/src/lib/VideoFeed/VideoListRow.svelte index 389cbe7d..7b74a7e5 100644 --- a/playlet-web/src/lib/VideoFeed/VideoListRow.svelte +++ b/playlet-web/src/lib/VideoFeed/VideoListRow.svelte @@ -1,6 +1,10 @@