document.addEventListener("DOMContentLoaded", () => { const trendingPage = document.getElementById("trending-page"); const searchResultsPage = document.getElementById("search-results-page"); const channelPage = document.getElementById("channel-page"); const trendingGrid = document.getElementById("trending-grid"); const trendingLoader = document.getElementById("trending-loader"); const searchGrid = document.getElementById("search-grid"); const searchLoader = document.getElementById("search-loader"); const searchQueryText = document.getElementById("search-query"); const searchCountText = document.getElementById("search-count"); const searchInput = document.getElementById("search-input"); const searchButton = document.getElementById("search-button"); const logo = document.getElementById("logo"); const channelVideosGrid = document.getElementById("channel-videos-grid"); const channelLoader = document.getElementById("channel-loader"); const channelBanner = document.getElementById("channel-banner"); const channelAvatar = document.getElementById("channel-avatar"); const channelTitle = document.getElementById("channel-title"); const channelVerified = document.getElementById("channel-verified"); const channelSubs = document.getElementById("channel-subs"); const channelDescription = document.getElementById("channel-description"); const videoCardTemplate = document.getElementById("video-card-template"); const skeletonTemplate = document.getElementById("skeleton-template"); let lastSearchQuery = ""; let activeChannelId = null; let channelNextPageData = null; let isLoadingMoreVideos = false; const trendingApiUrl = "/api/trending"; const searchApiUrl = "/api/search"; function formatViews(views) { if (views >= 1000000) { return `${(views / 1000000).toFixed(1)}M`; } else if (views >= 1000) { return `${(views / 1000).toFixed(1)}K`; } else { return views.toString(); } } function formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, "0")}:${secs .toString() .padStart(2, "0")}`; } else { return `${minutes}:${secs.toString().padStart(2, "0")}`; } } function formatSubscribers(count) { if (count >= 1000000) { return `${(count / 1000000).toFixed(1)}M subscribers`; } else if (count >= 1000) { return `${(count / 1000).toFixed(1)}K subscribers`; } else { return `${count} subscribers`; } } function getHashParams() { const hash = window.location.hash.substring(1); const params = {}; hash.split("&").forEach((param) => { if (param) { const [key, value] = param.split("="); params[key] = decodeURIComponent(value); } }); return params; } function updateHash(params = {}) { const hashParts = []; Object.entries(params).forEach(([key, value]) => { if (value) { hashParts.push(`${key}=${encodeURIComponent(value)}`); } }); if (hashParts.length > 0) { history.pushState("", document.title, `#${hashParts.join("&")}`); } else { history.pushState("", document.title, window.location.pathname); } } function switchPage(fromPageId, toPageId) { const fromPage = document.getElementById(fromPageId); const toPage = document.getElementById(toPageId); if (!fromPage || !toPage) return; fromPage.classList.add("zoom-out"); setTimeout(() => { fromPage.classList.remove("active"); fromPage.classList.remove("zoom-out"); toPage.classList.add("zoom-in"); toPage.classList.add("active"); requestAnimationFrame(() => { requestAnimationFrame(() => { toPage.classList.remove("zoom-in"); }); }); }, 300); } function showSkeletons(container, count = 12) { container.innerHTML = ""; for (let i = 0; i < count; i++) { const skeleton = skeletonTemplate.content.cloneNode(true); skeleton.firstElementChild.style.animationDelay = `${i * 0.05}s`; container.appendChild(skeleton); } container.style.display = "grid"; if (container === trendingGrid) { trendingLoader.style.display = "none"; } else if (container === searchGrid) { searchLoader.style.display = "none"; } } const observeElements = () => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("visible"); observer.unobserve(entry.target); } }); }, { root: null, threshold: 0.1, rootMargin: "0px 0px -50px 0px", } ); document .querySelectorAll(".animated-item:not(.visible)") .forEach((item) => { observer.observe(item); }); }; function openVideoPlayer(video) { sessionStorage.setItem('currentVideo', JSON.stringify(video)); sessionStorage.setItem('lastSearchQuery', lastSearchQuery); const videoId = video.url.replace("/watch?v=", ""); window.location.href = `video.html?v=${videoId}`; } function createVideoCard(video, index) { if (video.type !== "stream" && video.type !== undefined) { console.log( `Skipping non-video item: ${video.type} - ${video.title || video.name}` ); return null; } const card = videoCardTemplate.content.cloneNode(true); const thumbnail = card.querySelector(".thumbnail"); const duration = card.querySelector(".duration"); const title = card.querySelector(".video-title"); const uploaderAvatar = card.querySelector(".uploader-avatar"); const uploaderName = card.querySelector(".uploader-name"); const viewCount = card.querySelector(".view-count"); const uploadTime = card.querySelector(".upload-time"); const videoCard = card.querySelector(".video-card"); const uploader = card.querySelector(".uploader"); try { thumbnail.src = video.thumbnail || ""; thumbnail.alt = video.title || "Video thumbnail"; if (typeof video.duration === "number") { duration.textContent = formatDuration(video.duration); } else { duration.textContent = "0:00"; } title.textContent = video.title || "Untitled video"; uploaderAvatar.src = video.uploaderAvatar || ""; uploaderAvatar.alt = `${video.uploaderName || "Uploader"} avatar`; uploaderName.textContent = video.uploaderName || "Unknown uploader"; viewCount.textContent = formatViews(video.views || 0); uploadTime.textContent = video.uploadedDate || "Unknown date"; videoCard.style.animationDelay = `${index * 0.05}s`; videoCard.addEventListener("click", (e) => { if ( e.target === uploaderAvatar || e.target === uploaderName || (e.target.parentElement && e.target.parentElement === uploader) ) { e.stopPropagation(); if (video.uploaderUrl) { openChannelPage(video.uploaderUrl.replace("/channel/", "")); } } else { openVideoPlayer(video); } }); uploader.addEventListener("click", (e) => { e.stopPropagation(); if (video.uploaderUrl) { openChannelPage(video.uploaderUrl.replace("/channel/", "")); } }); } catch (error) { console.error("Error creating video card:", error, video); } return card; } async function fetchTrendingVideos() { try { trendingLoader.style.display = "flex"; trendingGrid.style.display = "none"; const response = await fetch(trendingApiUrl); if (!response.ok) { throw new Error("Network response was not ok"); } const videos = await response.json(); sessionStorage.setItem('trendingVideos', JSON.stringify(videos)); showSkeletons(trendingGrid); setTimeout(() => { trendingGrid.innerHTML = ""; videos.forEach((video, index) => { const card = createVideoCard(video, index); if (card) { trendingGrid.appendChild(card); } }); observeElements(); }, 600); } catch (error) { console.error("Error fetching trending videos:", error); trendingLoader.style.display = "none"; trendingGrid.style.display = "block"; trendingGrid.innerHTML = `

Failed to load videos

Please try again later.

`; } } async function performSearch(query) { if (!query || query.trim() === "") return; lastSearchQuery = query.trim(); updateHash({ search: lastSearchQuery }); if (trendingPage.classList.contains("active")) { switchPage("trending-page", "search-results-page"); } else if (channelPage.classList.contains("active")) { switchPage("channel-page", "search-results-page"); } searchQueryText.textContent = `"${lastSearchQuery}"`; searchCountText.textContent = "Searching..."; searchGrid.style.display = "none"; searchLoader.style.display = "flex"; try { const url = `${searchApiUrl}?q=${encodeURIComponent(lastSearchQuery)}&filter=all`; console.log(`Fetching search results from: ${url}`); const response = await fetch(url); if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } const searchData = await response.json(); const results = searchData.items ? searchData.items.filter((item) => item.type === "stream") : []; sessionStorage.setItem('searchResults', JSON.stringify(results)); console.log(`Found ${results.length} video results out of ${searchData.items ? searchData.items.length : 0} total items`); searchCountText.textContent = `${results.length} videos`; if (results.length === 0) { searchLoader.style.display = "none"; searchGrid.style.display = "block"; searchGrid.innerHTML = `

No videos found

No video results found for "${lastSearchQuery}"

`; } else { showSkeletons(searchGrid, Math.min(12, results.length)); setTimeout(() => { searchGrid.innerHTML = ""; results.forEach((video, index) => { const card = createVideoCard(video, index); searchGrid.appendChild(card); }); observeElements(); }, 300); } } catch (error) { console.error("Error fetching search results:", error); searchLoader.style.display = "none"; searchGrid.style.display = "block"; searchGrid.innerHTML = `

Search failed

Error searching videos. Please try again later.

`; } } function goToTrendingPage() { updateHash(); if (searchResultsPage.classList.contains("active")) { switchPage("search-results-page", "trending-page"); } else if (channelPage.classList.contains("active")) { switchPage("channel-page", "trending-page"); } fetchTrendingVideos(); } async function fetchChannelData(channelId) { try { if (channelId === activeChannelId) { return; } activeChannelId = channelId; channelNextPageData = null; updateHash({ channelId }); if (trendingPage.classList.contains("active")) { switchPage("trending-page", "channel-page"); } else if (searchResultsPage.classList.contains("active")) { switchPage("search-results-page", "channel-page"); } channelLoader.style.display = "flex"; channelVideosGrid.style.display = "none"; document.getElementById("channel-load-more").style.display = "none"; channelTitle.textContent = ""; channelSubs.textContent = ""; channelDescription.textContent = ""; channelAvatar.src = ""; channelBanner.src = ""; channelVerified.style.display = "none"; const url = `https://pipedapi.wireway.ch/channel/${channelId}`; console.log(`Fetching channel data from: ${url}`); const response = await fetch(url); if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } const channelData = await response.json(); console.log("Channel data:", channelData); if (!channelData || typeof channelData !== "object") { throw new Error("Invalid channel data received"); } channelNextPageData = channelData.nextpage || null; channelTitle.textContent = channelData.name || "Unknown Channel"; channelSubs.textContent = formatSubscribers(channelData.subscriberCount || 0); if (channelData.description) { channelDescription.textContent = channelData.description.replace(/\\n/g, "\n"); } else { channelDescription.textContent = "No description available"; } if (channelData.avatarUrl) { channelAvatar.src = channelData.avatarUrl; channelAvatar.style.display = "block"; } else { channelAvatar.style.display = "none"; } if (channelData.bannerUrl) { channelBanner.src = channelData.bannerUrl; channelBanner.style.display = "block"; } else { channelBanner.style.display = "none"; } channelVerified.style.display = channelData.verified ? "inline-block" : "none"; const relatedStreams = Array.isArray(channelData.relatedStreams) ? channelData.relatedStreams : []; const videoCount = relatedStreams.length; showSkeletons(channelVideosGrid, Math.min(12, videoCount || 6)); setTimeout(() => { channelVideosGrid.innerHTML = ""; if (relatedStreams.length > 0) { relatedStreams.forEach((video, index) => { if (!video.uploaderName) { video.uploaderName = channelData.name || "Unknown Channel"; } if (!video.uploaderUrl) { video.uploaderUrl = `/channel/${channelData.id}`; } if (!video.uploaderAvatar) { video.uploaderAvatar = channelData.avatarUrl || ""; } const card = createVideoCard(video, index); if (card) { channelVideosGrid.appendChild(card); } }); observeElements(); const loadMoreContainer = document.getElementById("channel-load-more"); if (channelNextPageData) { loadMoreContainer.style.display = "flex"; } else { loadMoreContainer.style.display = "none"; } } else { channelVideosGrid.innerHTML = `

No videos available

This channel hasn't uploaded any videos yet.

`; } channelLoader.style.display = "none"; channelVideosGrid.style.display = "grid"; }, 300); } catch (error) { console.error("Error fetching channel data:", error); channelLoader.style.display = "none"; channelVideosGrid.style.display = "block"; channelVideosGrid.innerHTML = `

Failed to load channel

Error loading channel. Please try again later.

`; } } async function loadMoreChannelVideos() { if (!channelNextPageData || isLoadingMoreVideos) { return; } try { isLoadingMoreVideos = true; const loadMoreButton = document.getElementById("channel-load-more-button"); const loadMoreSpinner = document.getElementById("channel-load-more-spinner"); loadMoreButton.style.display = "none"; loadMoreSpinner.style.display = "block"; const url = `https://pipedapi.wireway.ch/nextpage/channel/${activeChannelId}?nextpage=${encodeURIComponent(channelNextPageData)}`; console.log(`Fetching next page data from: ${url}`); const response = await fetch(url); if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } const nextPageData = await response.json(); console.log("Next page data:", nextPageData); if (!nextPageData || !Array.isArray(nextPageData.relatedStreams)) { throw new Error("Invalid next page data received"); } channelNextPageData = nextPageData.nextpage || null; const relatedStreams = nextPageData.relatedStreams; const startIndex = document.querySelectorAll(".channel-videos-grid .video-card").length; if (relatedStreams.length > 0) { relatedStreams.forEach((video, index) => { if (!video.uploaderName) { video.uploaderName = channelTitle.textContent || "Unknown Channel"; } if (!video.uploaderUrl) { video.uploaderUrl = `/channel/${activeChannelId}`; } if (!video.uploaderAvatar) { video.uploaderAvatar = channelAvatar.src || ""; } const card = createVideoCard(video, startIndex + index); if (card) { channelVideosGrid.appendChild(card); } }); observeElements(); } if (channelNextPageData) { loadMoreButton.style.display = "block"; } else { document.getElementById("channel-load-more").style.display = "none"; } } catch (error) { console.error("Error loading more videos:", error); const loadMoreContainer = document.getElementById("channel-load-more"); loadMoreContainer.innerHTML = `

Error loading more videos. Click to try again.

`; loadMoreContainer.addEventListener('click', () => { loadMoreContainer.innerHTML = ` `; document.getElementById("channel-load-more-button").addEventListener("click", loadMoreChannelVideos); }); } finally { document.getElementById("channel-load-more-spinner").style.display = "none"; isLoadingMoreVideos = false; } } function openChannelPage(channelId) { fetchChannelData(channelId); } function checkInitialHash() { const params = getHashParams(); if (params.search) { searchInput.value = params.search; performSearch(params.search); } else if (params.channelId) { fetchChannelData(params.channelId); } } function setupInfiniteScroll() { window.addEventListener("scroll", function () { if (!channelPage.classList.contains("active") || !channelNextPageData || isLoadingMoreVideos) { return; } const scrollPos = window.scrollY + window.innerHeight; const loadMoreContainer = document.getElementById("channel-load-more"); const loadMorePosition = loadMoreContainer.offsetTop; if (scrollPos > loadMorePosition - 300) { loadMoreChannelVideos(); } }); document.getElementById("channel-load-more-button").addEventListener("click", loadMoreChannelVideos); } logo.addEventListener("click", () => { goToTrendingPage(); }); searchButton.addEventListener("click", () => { performSearch(searchInput.value); }); searchInput.addEventListener("keypress", (e) => { if (e.key === "Enter") { performSearch(searchInput.value); } }); window.addEventListener("hashchange", () => { const params = getHashParams(); if (params.search) { performSearch(params.search); } else if (params.channelId) { fetchChannelData(params.channelId); } else { goToTrendingPage(); } }); fetchTrendingVideos(); checkInitialHash(); setupInfiniteScroll(); });