Merge branch 'p-stream:production' into chromecast-rework

This commit is contained in:
Vasilis Manetas 2025-08-25 15:47:14 +02:00 committed by GitHub
commit 575521cc88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 126 additions and 50 deletions

View file

@ -1,7 +1,7 @@
{
"packageManager": "pnpm@9.14.4",
"name": "P-Stream",
"version": "5.1.0",
"version": "5.1.1",
"private": true,
"homepage": "https://github.com/p-stream/p-stream",
"scripts": {

View file

@ -8,6 +8,28 @@
<lastBuildDate>Mon, 28 Jul 2025 21:53:00 GMT</lastBuildDate>
<atom:link href="https://pstream.mov/notifications.xml" rel="self" type="application/rss+xml" />
<item>
<guid>notification-037</guid>
<title>P-Stream v5.1.1 released!</title>
<description>Minor updates to fix bugs and add some QoL features.
- Now anyone can watch trailers on the details modal! A custom proxy or the extension no longer required. (Other IMDb info still requires the extension or proxy)
- Added a resume watching page.
- Added checks and label to see what embed a source is currently on. (e.x. fox for xprime)
- Updated curated movie lists and editor picks!
- Added an onboarding reminder for content that is not found but there are other sources that may have it.
- Using a custom subtitle source with no baked in ads.
- Fixed a broken URL issue caused by the Dood source.
- Fixed 2x boost overlay sizing.
- Fixed speed boost not working on mobile.
- Other bug fixes!
P.S. I wanted to highlight TrinityHades, a community member, who is working on adding Safari support to the P-Stream extension! It's not quire ready yet but soon mac users will be able to use the extension to watch P-Stream on Safari!
</description>
<pubDate>Sun, 24 Aug 2025 22:37:00 MST</pubDate>
<category>update</category>
</item>
<item>
<guid>notification-036</guid>
<title>Some sources are currently down</title>

View file

@ -106,16 +106,18 @@ export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
undefined,
undefined,
formattedLanguage,
data.type,
);
// Transform the data to match the expected format
if (
typeof imdbMetadata.imdb_rating === "number" &&
typeof imdbMetadata.votes === "number"
(typeof imdbMetadata.imdb_rating === "number" &&
typeof imdbMetadata.votes === "number") ||
imdbMetadata.trailer_url
) {
setImdbData({
rating: imdbMetadata.imdb_rating,
votes: imdbMetadata.votes,
trailer_url: imdbMetadata.trailer_url || null,
trailer_url: imdbMetadata.trailer_url,
});
} else {
setImdbData(null);

View file

@ -12,14 +12,23 @@ export function TrailerOverlay({ trailerUrl, onClose }: TrailerOverlayProps) {
className="relative w-[90%] max-w-6xl aspect-video"
onClick={(e) => e.stopPropagation()}
>
<video
className="w-full h-full object-contain"
autoPlay
controls
playsInline
>
<source src={trailerUrl} type="video/mp4" />
</video>
{trailerUrl.includes("youtube.com/embed") ? (
<iframe
src={trailerUrl}
className="w-full h-full"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
) : (
<video
className="w-full h-full object-contain"
autoPlay
controls
playsInline
>
<source src={trailerUrl} type="video/mp4" />
</video>
)}
{/* Close Button */}
<button

View file

@ -20,7 +20,7 @@ export function SpeedChangedPopout() {
show={showSpeedIndicator && currentOverlay === "speed"}
className="absolute inset-x-0 top-4 flex justify-center pointer-events-none"
>
<Flare.Base className="hover:flare-enabled pointer-events-auto bg-video-context-background pl-4 pr-6 py-3 group w-72 h-full rounded-lg transition-colors text-video-context-type-main">
<Flare.Base className="hover:flare-enabled pointer-events-auto bg-video-context-background pl-4 pr-6 py-3 group w-20 h-full rounded-lg transition-colors text-video-context-type-main">
<Flare.Light
enabled
flareSize={200}

View file

@ -29,6 +29,7 @@ export function EmbedOption(props: {
routerId: string;
}) {
const { t } = useTranslation();
const currentEmbedId = usePlayerStore((s) => s.embedId);
const unknownEmbedName = t("player.menus.sources.unknownOption");
const embedName = useMemo(() => {
@ -45,7 +46,12 @@ export function EmbedOption(props: {
);
return (
<SelectableLink loading={loading} error={errored} onClick={run}>
<SelectableLink
loading={loading}
error={errored}
onClick={run}
selected={props.embedId === currentEmbedId}
>
<span className="flex flex-col">
<span>{embedName}</span>
</span>

View file

@ -149,7 +149,7 @@ export function LegalPage() {
<br />
<br />
We strongly recommend using VPN services for enhanced privacy
and security while browsing.
and security while browsing. Downloading is not advised.
<br />
<br />
Please respect intellectual property rights and be mindful of

View file

@ -13,8 +13,9 @@ import { SourceSliceSource, StreamType } from "@/stores/player/utils/qualities";
const testMeta: PlayerMeta = {
releaseYear: 2010,
title: "Sintel",
tmdbId: "",
tmdbId: "45745",
type: "movie",
poster: "https://image.tmdb.org/t/p/w342//4BMG9hk9NvSBeQvC82sVmVRK140.jpg",
};
const testStreams: Record<StreamType, string> = {

View file

@ -171,7 +171,13 @@ export function FeaturedCarousel({
if (!hasExtension.current || !currentMedia?.external_ids?.imdb_id) return;
try {
const imdbData = await scrapeIMDb(currentMedia.external_ids.imdb_id);
const imdbData = await scrapeIMDb(
currentMedia.external_ids.imdb_id,
undefined,
undefined,
undefined,
currentMedia.type,
);
// Only update if we have both rating and votes as non-null numbers
if (
typeof imdbData.imdb_rating === "number" &&

View file

@ -67,6 +67,14 @@ export const EDITOR_PICKS_MOVIES = shuffleArray([
{ id: 18971, type: "movie" }, // Rosencrantz and Guildenstern Are Dead
{ id: 26388, type: "movie" }, // Buried
{ id: 152601, type: "movie" }, // Her
{ id: 11886, type: "movie" }, // Robin Hood
{ id: 1362, type: "movie" }, // The Hobbit 1977
{ id: 578, type: "movie" }, // Jaws
{ id: 78, type: "movie" }, // Blade Runner
{ id: 348, type: "movie" }, // Alien
{ id: 198184, type: "movie" }, // Chappie
{ id: 405774, type: "movie" }, // Bird Box
{ id: 333339, type: "movie" }, // Ready Player One
]);
export const EDITOR_PICKS_TV_SHOWS = shuffleArray([
@ -86,6 +94,17 @@ export const EDITOR_PICKS_TV_SHOWS = shuffleArray([
{ id: 93405, type: "show" }, // Squid Game
{ id: 87108, type: "show" }, // Chernobyl
{ id: 105248, type: "show" }, // Cyberpunk: Edgerunners
{ id: 82738, type: "show" }, // IRODUKU: The World in Colors
{ id: 615, type: "show" }, // Futurama
{ id: 4625, type: "show" }, // The New Batman Adventures
{ id: 513, type: "show" }, // Batman Beyond
{ id: 110948, type: "show" }, // The Snoopy Show
{ id: 110492, type: "show" }, // Peacemaker
{ id: 125988, type: "show" }, // Silo
{ id: 87917, type: "show" }, // For All Mankind
{ id: 42009, type: "show" }, // Black Mirror
{ id: 86831, type: "show" }, // Love, Death & Robots
{ id: 261579, type: "show" }, // Secret Level
]);
/**

View file

@ -40,30 +40,30 @@ function getImdbLanguageCode(language: string): string {
}
interface IMDbMetadata {
title: string;
original_title: string;
title_type: string;
year: number | null;
end_year: number | null;
day: number | null;
month: number | null;
date: string;
runtime: number | null;
age_rating: string;
imdb_rating: number | null;
votes: number | null;
plot: string;
poster_url: string;
trailer_url: string;
url: string;
genre: string[];
cast: string[];
directors: string[];
writers: string[];
keywords: string[];
countries: string[];
languages: string[];
locations: string[];
title?: string;
original_title?: string;
title_type?: string;
year?: number | null;
end_year?: number | null;
day?: number | null;
month?: number | null;
date?: string;
runtime?: number | null;
age_rating?: string;
imdb_rating?: number | null;
votes?: number | null;
plot?: string;
poster_url?: string;
trailer_url?: string;
url?: string;
genre?: string[];
cast?: string[];
directors?: string[];
writers?: string[];
keywords?: string[];
countries?: string[];
languages?: string[];
locations?: string[];
season?: number;
episode?: number;
episode_title?: string;
@ -115,12 +115,23 @@ export async function scrapeIMDb(
season?: number,
episode?: number,
language?: string,
type?: "movie" | "show",
): Promise<IMDbMetadata> {
// Check if we have a proxy or extension
const hasExtension = await isExtensionActive();
const hasProxy = Boolean(useAuthStore.getState().proxySet);
if (!hasExtension && !hasProxy) {
// Custom API for trailers:
const trailerResponse = await fetch(
`https://fed-trailers.pstream.mov/${type === "movie" ? "movie" : "tv"}/${imdbId}`,
).then((res) => res.json());
if (trailerResponse.trailer?.embed_url) {
return {
trailer_url: trailerResponse.trailer.embed_url,
};
}
// END CUSTOM API
throw new Error(
"IMDb scraping requires either the browser extension or a custom proxy to be set up. " +
"Please install the extension or set up a proxy in the settings.",
@ -300,17 +311,17 @@ export function printIMDbMetadata(metadata: IMDbMetadata): void {
}
console.log("Type:", metadata.title_type);
console.log("Year:", metadata.year);
console.log("Runtime:", formatRuntime(metadata.runtime));
console.log("Runtime:", formatRuntime(metadata.runtime || null));
console.log("Date:", metadata.date);
console.log("Age Rating:", metadata.age_rating);
console.log("Genre:", arrayToString(metadata.genre));
console.log("Cast:", arrayToString(metadata.cast));
console.log("Directed by:", arrayToString(metadata.directors));
console.log("Writers:", arrayToString(metadata.writers));
console.log("Countries:", arrayToString(metadata.countries));
console.log("Filming Locations:", arrayToString(metadata.locations));
console.log("Languages:", arrayToString(metadata.languages));
console.log("Keywords:", arrayToString(metadata.keywords));
console.log("Genre:", arrayToString(metadata.genre || []));
console.log("Cast:", arrayToString(metadata.cast || []));
console.log("Directed by:", arrayToString(metadata.directors || []));
console.log("Writers:", arrayToString(metadata.writers || []));
console.log("Countries:", arrayToString(metadata.countries || []));
console.log("Filming Locations:", arrayToString(metadata.locations || []));
console.log("Languages:", arrayToString(metadata.languages || []));
console.log("Keywords:", arrayToString(metadata.keywords || []));
if (metadata.season && metadata.episode) {
console.log("\nEpisode Details:");