Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/components/FeedPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
to="/subscriptions"
/>
<a
v-if="getRssUrl"
:href="getRssUrl"
class="inline-block w-auto cursor-pointer rounded-sm bg-gray-300 py-2 text-gray-600 hover:bg-gray-500 hover:text-white max-md:px-2 md:px-4 dark:bg-dark-400 dark:text-gray-400 dark:hover:bg-dark-300"
:aria-label="$t('actions.rss_feed')"
Expand Down Expand Up @@ -94,7 +95,11 @@ const channelGroups = ref([]);

const getRssUrl = computed(() => {
if (isAuthenticated()) return authApiUrl() + "/feed/rss?authToken=" + getAuthToken();
else return authApiUrl() + "/feed/unauthenticated/rss?channels=" + getUnauthenticatedChannels();
else {
const channels = getUnauthenticatedChannels();
if (!channels) return null;
return authApiUrl() + "/feed/unauthenticated/rss?channels=" + channels;
}
});

const filteredVideos = computed(() => {
Expand All @@ -112,7 +117,7 @@ const filteredVideos = computed(() => {
function loadMoreVideos() {
if (!videosStore.value) return;
currentVideoCount = Math.min(currentVideoCount + videoStep, videosStore.value.length);
if (videos.value.length != videosStore.value.length) {
if (videos.value.length !== videosStore.value.length) {
fetchDeArrowContent(videosStore.value.slice(videos.value.length, currentVideoCount));
videos.value = videosStore.value.slice(0, currentVideoCount);
}
Expand Down
15 changes: 14 additions & 1 deletion src/components/PreferencesPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
for="chkMinimizeChapters"
>
<strong v-t="'actions.chapters_layout_mobile'" />

<select
id="ddlDefaultHomepage"
v-model="mobileChapterLayout"
Expand Down Expand Up @@ -199,6 +198,18 @@
</template>
<PreferenceSwitch id="chkHideWatched" v-model="hideWatched" @change="onChange" />
</PreferenceRow>
<PreferenceRow v-if="watchHistory" for-id="chkPersonalizedTrending">
<template #label>
<strong v-t="'actions.personalized_trending'" />
</template>
<PreferenceSwitch id="chkPersonalizedTrending" v-model="personalizedTrending" @change="onChange" />
</PreferenceRow>
<PreferenceRow v-if="watchHistory && personalizedTrending" for-id="chkPersonalizedTrendingOnly">
<template #label>
<strong v-t="'actions.personalized_trending_only'" />
</template>
<PreferenceSwitch id="chkPersonalizedTrendingOnly" v-model="personalizedTrendingOnly" @change="onChange" />
</PreferenceRow>
<PreferenceRow for-id="ddlEnabledCodecs">
<template #label>
<strong v-t="'actions.enabled_codecs'" />
Expand Down Expand Up @@ -529,6 +540,8 @@ const searchSuggestions = usePreferenceBoolean("searchSuggestions", true);
const watchHistory = usePreferenceBoolean("watchHistory", false);
const searchHistory = usePreferenceBoolean("searchHistory", false);
const hideWatched = usePreferenceBoolean("hideWatched", false);
const personalizedTrending = usePreferenceBoolean("personalizedTrending", false);
const personalizedTrendingOnly = usePreferenceBoolean("personalizedTrendingOnly", false);
const selectedLanguage = usePreferenceString("hl", "en");
const languages = [
{ code: "ar", name: "Arabic" },
Expand Down
97 changes: 87 additions & 10 deletions src/components/TrendingPage.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<template>
<h1 v-t="'titles.trending'" class="my-4 text-center font-bold" />

<hr />

<LoadingIndicatorPage
:show-content="videos.length != 0"
class="mx-2 grid grid-cols-1 gap-y-5 max-md:gap-x-3 sm:mx-0 sm:grid-cols-2 md:grid-cols-3 md:gap-x-6 lg:grid-cols-4 xl:grid-cols-5"
Expand All @@ -18,29 +16,108 @@ import { useI18n } from "vue-i18n";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import VideoItem from "./VideoItem.vue";
import { fetchJson, apiUrl } from "@/composables/useApi.js";
import { getPreferenceString } from "@/composables/usePreferences.js";
import { getPreferenceString, getPreferenceBoolean } from "@/composables/usePreferences.js";
import { updateWatched } from "@/composables/useMisc.js";
import { fetchDeArrowContent } from "@/composables/useSubscriptions.js";
import { getHomePage } from "@/composables/useMisc.js";

const route = useRoute();
const router = useRouter();
const { t } = useI18n();

const videos = ref([]);

async function fetchTrending(region) {
return await fetchJson(apiUrl() + "/trending", {
region: region || "US",
function idbCursorToPromise(store, fn) {
return new Promise((resolve, reject) => {
const req = store.openCursor();
req.onerror = () => reject(req.error);
req.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
fn(cursor.value);
cursor.continue();
} else {
resolve();
}
};
});
}

async function getPreferredChannels() {
if (!window.db || !getPreferenceBoolean("watchHistory", false)) return [];

const tx = window.db.transaction("watch_history", "readonly");
const store = tx.objectStore("watch_history");
const counts = new Map();

await idbCursorToPromise(store, video => {
if (video.uploaderUrl) {
const id = video.uploaderUrl.split("/").pop();
counts.set(id, (counts.get(id) || 0) + 1);
}
});

return Array.from(counts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([id]) => id);
}
Comment thread
quantumvoid0 marked this conversation as resolved.

async function fetchChannelVideos(channelIds) {
const results = await Promise.allSettled(channelIds.map(id => fetchJson(apiUrl() + "/channel/" + id)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the unauthenticated channel feeds api, since this is quite expensive on the server? You can pass multiple channel IDs to it too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

surely, ill implement the fix..but there seems to be another issue preventing the code in this PR from running, about a week ago i noticed that videos section in channels no longer load, and this affected the trending page too as it reads from there, i dont think its just my side, bcz the piped.video server also had the same issue.

return results
.filter(r => r.status === "fulfilled" && r.value?.relatedStreams)
.flatMap(r => r.value.relatedStreams.slice(0, 5));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

function interleave(trending, recommended) {
const out = [];
let ti = 0,
ri = 0;
while (ti < trending.length || ri < recommended.length) {
if (ti < trending.length) out.push(trending[ti++]);
if (ti < trending.length) out.push(trending[ti++]);
if (ti < trending.length) out.push(trending[ti++]);
if (ri < recommended.length) out.push(recommended[ri++]);
}
return out;
}

async function fetchTrending(region) {
const personalizedTrending = getPreferenceBoolean("personalizedTrending", false);
const personalizedTrendingOnly = getPreferenceBoolean("personalizedTrendingOnly", false);

if (!personalizedTrending) {
return await fetchJson(apiUrl() + "/trending", { region: region || "US" });
}

const [trending, preferredChannels] = await Promise.all([
personalizedTrendingOnly ? Promise.resolve([]) : fetchJson(apiUrl() + "/trending", { region: region || "US" }),
getPreferredChannels(),
]);

if (preferredChannels.length === 0) return trending;

const recommended = await fetchChannelVideos(preferredChannels);

const seen = new Set();
const dedup = arr =>
arr.filter(v => {
const id = v.url?.split("v=")[1];
if (!id || seen.has(id)) return false;
seen.add(id);
return true;
});

if (personalizedTrendingOnly) return dedup(recommended);

return interleave(dedup(trending), dedup(recommended));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

onMounted(() => {
if (route.path == import.meta.env.BASE_URL && getPreferenceString("homepage", "trending") == "feed") {
return;
}
let region = getPreferenceString("region", "US");

const region = getPreferenceString("region", "US");
fetchTrending(region).then(vids => {
videos.value = vids;
updateWatched(videos.value);
Expand All @@ -52,7 +129,7 @@ onActivated(() => {
document.title = t("titles.trending") + " - Piped";
if (videos.value.length > 0) updateWatched(videos.value);
if (route.path == import.meta.env.BASE_URL) {
let homepage = getHomePage();
const homepage = getHomePage();
if (homepage !== undefined) router.push(homepage);
}
});
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
"show_chapters": "Chapters",
"store_search_history": "Store Search History",
"hide_watched": "Hide watched videos in the feed",
"personalized_trending": "Personalize trending page",
"personalized_trending_only": "Show only recommendations (hide global trending)",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
"mark_as_watched": "Mark as Watched",
"mark_as_unwatched": "Mark as Unwatched",
"documentation": "Documentation",
Expand Down
Loading