pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Revert "feat: add skip source button during scraping"

This reverts commit bf14a85f3433fc4bee90c205d900c6bb65b7f409.

Pas 0576c9fe d1356405

+29 -184
-1
src/assets/locales/en.json
··· 852 852 "title": "Failed to play video!" 853 853 }, 854 854 "scraping": { 855 - "skip": "Skip source", 856 855 "items": { 857 856 "failure": "Error occurred", 858 857 "notFound": "Doesn't have the video (╥﹏╥)",
-1
src/backend/helpers/report.ts
··· 65 65 failure: "failed", 66 66 pending: null, 67 67 waiting: null, 68 - skipped: "notfound", 69 68 }; 70 69 71 70 export function scrapeSourceOutputToProviderMetric(
+1 -13
src/backend/providers/fetchers.ts
··· 1 1 import { 2 2 Fetcher, 3 3 makeSimpleProxyFetcher, 4 - makeStandardFetcher, 5 4 setM3U8ProxyUrl, 6 5 } from "@p-stream/providers"; 7 6 ··· 83 82 84 83 export function makeLoadBalancedSimpleProxyFetcher() { 85 84 const fetcher: Fetcher = async (a, b) => { 86 - const proxyUrl = getLoadbalancedProxyUrl(); 87 - 88 - // If no proxy URL is available, fall back to direct fetch 89 - if (!proxyUrl) { 90 - console.warn( 91 - "[makeLoadBalancedSimpleProxyFetcher] No proxy URL available, using direct fetch", 92 - ); 93 - const directFetcher = makeStandardFetcher(fetchButWithApiTokens); 94 - return directFetcher(a, b); 95 - } 96 - 97 85 const currentFetcher = makeSimpleProxyFetcher( 98 - proxyUrl, 86 + getLoadbalancedProxyUrl(), 99 87 fetchButWithApiTokens, 100 88 ); 101 89 return currentFetcher(a, b);
+1 -9
src/components/player/internals/ScrapeCard.tsx
··· 9 9 import { Transition } from "@/components/utils/Transition"; 10 10 11 11 export interface ScrapeItemProps { 12 - status: 13 - | "failure" 14 - | "pending" 15 - | "notfound" 16 - | "success" 17 - | "waiting" 18 - | "skipped"; 12 + status: "failure" | "pending" | "notfound" | "success" | "waiting"; 19 13 name: string; 20 14 id?: string; 21 15 percentage?: number; ··· 30 24 notfound: "player.scraping.items.notFound", 31 25 failure: "player.scraping.items.failure", 32 26 pending: "player.scraping.items.pending", 33 - skipped: "player.scraping.items.notFound", 34 27 }; 35 28 36 29 const statusMap: Record<ScrapeCardProps["status"], StatusCircleProps["type"]> = ··· 40 33 pending: "loading", 41 34 success: "success", 42 35 waiting: "waiting", 43 - skipped: "noresult", 44 36 }; 45 37 46 38 export function ScrapeItem(props: ScrapeItemProps) {
+2 -65
src/hooks/useProviderScrape.tsx
··· 22 22 name: string; 23 23 id: string; 24 24 embedId?: string; 25 - status: 26 - | "failure" 27 - | "pending" 28 - | "notfound" 29 - | "success" 30 - | "waiting" 31 - | "skipped"; 25 + status: "failure" | "pending" | "notfound" | "success" | "waiting"; 32 26 reason?: string; 33 27 error?: any; 34 28 percentage: number; ··· 42 36 const [sources, setSources] = useState<Record<string, ScrapingSegment>>({}); 43 37 const [sourceOrder, setSourceOrder] = useState<ScrapingItems[]>([]); 44 38 const [currentSource, setCurrentSource] = useState<string>(); 45 - const abortControllerRef = useRef<AbortController | null>(null); 46 39 const lastId = useRef<string | null>(null); 47 40 48 41 const initEvent = useCallback((evt: ScraperEvent<"init">) => { ··· 71 64 const lastIdTmp = lastId.current; 72 65 setSources((s) => { 73 66 if (s[id]) s[id].status = "pending"; 74 - // Only mark as success if it's pending - don't overwrite skipped status 75 - if (lastIdTmp && s[lastIdTmp] && s[lastIdTmp].status === "pending") { 67 + if (lastIdTmp && s[lastIdTmp] && s[lastIdTmp].status === "pending") 76 68 s[lastIdTmp].status = "success"; 77 - } 78 69 return { ...s }; 79 70 }); 80 71 setCurrentSource(id); 81 72 lastId.current = id; 82 - // Create new AbortController for this source 83 - abortControllerRef.current = new AbortController(); 84 73 }, []); 85 74 86 75 const updateEvent = useCallback((evt: ScraperEvent<"update">) => { ··· 139 128 return output; 140 129 }, []); 141 130 142 - const skipCurrentSource = useCallback(() => { 143 - if (currentSource) { 144 - // Get the parent source ID (remove embed suffix like "-0", "-1", etc.) 145 - const parentSourceId = currentSource.split("-")[0]; 146 - 147 - // Abort the current operation FIRST - abort all pending requests immediately 148 - if (abortControllerRef.current) { 149 - abortControllerRef.current.abort(); 150 - } 151 - 152 - // Mark the parent source and all its embeds as skipped AFTER aborting 153 - // This ensures the abort happens immediately and can interrupt ongoing operations 154 - setSources((s) => { 155 - Object.keys(s).forEach((key) => { 156 - // Check if this is the parent source or one of its embeds 157 - if (key === parentSourceId || key.startsWith(`${parentSourceId}-`)) { 158 - if (s[key]) { 159 - // Mark as skipped regardless of current status (even if it succeeded) 160 - s[key].status = "skipped"; 161 - s[key].reason = "Skipped by user"; 162 - s[key].percentage = 100; 163 - } 164 - } 165 - }); 166 - return { ...s }; 167 - }); 168 - } 169 - }, [currentSource]); 170 - 171 131 return { 172 132 initEvent, 173 133 startEvent, ··· 178 138 sources, 179 139 sourceOrder, 180 140 currentSource, 181 - skipCurrentSource, 182 - abortControllerRef, 183 141 }; 184 142 } 185 143 ··· 194 152 getResult, 195 153 startEvent, 196 154 startScrape, 197 - skipCurrentSource, 198 - abortControllerRef, 199 155 } = useBaseScrape(); 200 156 201 157 const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder); ··· 215 171 async (media: ScrapeMedia, startFromSourceId?: string) => { 216 172 const providerInstance = getProviders(); 217 173 const allSources = providerInstance.listSources(); 218 - 219 174 const playerState = usePlayerStore.getState(); 220 175 const failedSources = playerState.failedSources; 221 176 const failedEmbeds = playerState.failedEmbeds; ··· 279 234 : undefined; 280 235 281 236 const providerApiUrl = getLoadbalancedProviderApiUrl(); 282 - 283 237 if (providerApiUrl && !isExtensionActiveCached()) { 284 238 startScrape(); 285 239 const baseUrlMaker = makeProviderUrl(providerApiUrl); ··· 304 258 305 259 startScrape(); 306 260 const providers = getProviders(); 307 - 308 - // Create initial abort controller if it doesn't exist 309 - if (!abortControllerRef.current) { 310 - abortControllerRef.current = new AbortController(); 311 - } 312 - 313 - // Create a wrapper that always gets the current abort controller 314 - const getCurrentAbortController = () => abortControllerRef.current; 315 - 316 261 const output = await providers.runAll({ 317 262 media, 318 263 sourceOrder: filteredSourceOrder, 319 264 embedOrder: filteredEmbedOrder, 320 - abortController: { 321 - get signal() { 322 - const controller = getCurrentAbortController(); 323 - return controller ? controller.signal : undefined; 324 - }, 325 - } as AbortController, 326 265 events: { 327 266 init: initEvent, 328 267 start: startEvent, ··· 349 288 preferredEmbedOrder, 350 289 enableEmbedOrder, 351 290 disabledEmbeds, 352 - abortControllerRef, 353 291 ], 354 292 ); 355 293 ··· 366 304 sourceOrder, 367 305 sources, 368 306 currentSource, 369 - skipCurrentSource, 370 307 }; 371 308 } 372 309
+2 -30
src/pages/PlayerView.tsx
··· 38 38 episode?: string; 39 39 season?: string; 40 40 }>(); 41 - const [skipSourceFn, setSkipSourceFn] = useState<(() => void) | null>(null); 42 41 const [errorData, setErrorData] = useState<{ 43 42 sources: Record<string, ScrapingSegment>; 44 43 sourceOrder: ScrapingItems[]; ··· 205 204 ); 206 205 207 206 return ( 208 - <PlayerPart 209 - backUrl={backUrl} 210 - onMetaChange={metaChange} 211 - skipSourceFn={skipSourceFn} 212 - > 207 + <PlayerPart backUrl={backUrl} onMetaChange={metaChange}> 213 208 {status === playerStatus.IDLE ? ( 214 209 <MetaPart onGetMeta={handleMetaReceived} /> 215 210 ) : null} ··· 228 223 key={`scraping-${resumeFromSourceId || "default"}`} 229 224 media={scrapeMedia} 230 225 startFromSourceId={resumeFromSourceId || undefined} 231 - onSkipSourceReady={(fn) => setSkipSourceFn(() => fn)} 232 226 onResult={(sources, sourceOrder) => { 233 227 setErrorData({ 234 228 sourceOrder, ··· 238 232 // Clear resume state after scraping 239 233 setResumeFromSourceId(null); 240 234 }} 241 - onGetStream={(out, sources) => { 242 - // Check if the source was skipped by user 243 - if (out) { 244 - const outSourceId = out.sourceId; 245 - const parentSourceId = outSourceId.split("-")[0]; 246 - 247 - // Check both the parent and the specific embed 248 - const parentData = sources[parentSourceId]; 249 - const embedData = sources[outSourceId]; 250 - 251 - // If the source or embed was skipped by user, don't play it 252 - // Just ignore the result and let scraping continue to next source 253 - if ( 254 - parentData?.status === "skipped" || 255 - embedData?.status === "skipped" || 256 - parentData?.reason === "Skipped by user" || 257 - embedData?.reason === "Skipped by user" 258 - ) { 259 - return; 260 - } 261 - } 262 - playAfterScrape(out); 263 - }} 235 + onGetStream={playAfterScrape} 264 236 /> 265 237 ) 266 238 ) : null}
+2 -7
src/pages/parts/player/PlayerPart.tsx
··· 20 20 backUrl: string; 21 21 onLoad?: () => void; 22 22 onMetaChange?: (meta: PlayerMeta) => void; 23 - skipSourceFn?: (() => void) | null; 24 23 } 25 24 26 25 export function PlayerPart(props: PlayerPartProps) { ··· 140 139 </div> 141 140 </Player.TopControls> 142 141 143 - <Player.BottomControls 144 - show={showTargets || status === playerStatus.SCRAPING} 145 - > 142 + <Player.BottomControls show={showTargets}> 146 143 {status !== playerStatus.PLAYING && !manualSourceSelection && <Tips />} 147 144 <div className="flex items-center justify-center space-x-3 h-full"> 148 145 {status === playerStatus.SCRAPING ? ( 149 - <ScrapingPartInterruptButton 150 - skipCurrentSource={props.skipSourceFn || undefined} 151 - /> 146 + <ScrapingPartInterruptButton /> 152 147 ) : null} 153 148 {status === playerStatus.PLAYING ? ( 154 149 <>
+21 -57
src/pages/parts/player/ScrapingPart.tsx
··· 26 26 27 27 export interface ScrapingProps { 28 28 media: ScrapeMedia; 29 - onGetStream?: ( 30 - stream: AsyncReturnType<ProviderControls["runAll"]>, 31 - sources: Record<string, ScrapingSegment>, 32 - ) => void; 29 + onGetStream?: (stream: AsyncReturnType<ProviderControls["runAll"]>) => void; 33 30 onResult?: ( 34 31 sources: Record<string, ScrapingSegment>, 35 32 sourceOrder: ScrapingItems[], 36 33 ) => void; 37 34 startFromSourceId?: string; 38 - onSkipSourceReady?: (skipFn: () => void) => void; 39 35 } 40 36 41 37 export function ScrapingPart(props: ScrapingProps) { 42 38 const { report } = useReportProviders(); 43 - const { 44 - startScraping, 45 - resumeScraping, 46 - sourceOrder, 47 - sources, 48 - currentSource, 49 - skipCurrentSource, 50 - } = useScrape(); 39 + const { startScraping, resumeScraping, sourceOrder, sources, currentSource } = 40 + useScrape(); 51 41 const isMounted = useMountedState(); 52 42 const { t } = useTranslation(); 53 43 ··· 73 63 }, [sourceOrder, sources]); 74 64 75 65 const started = useRef<string | null>(null); 76 - 77 - // Pass skip function to parent 78 - useEffect(() => { 79 - props.onSkipSourceReady?.(skipCurrentSource); 80 - }, [skipCurrentSource, props]); 81 - 82 66 useEffect(() => { 83 67 // Only start scraping if we haven't started with this startFromSourceId before 84 68 const currentKey = props.startFromSourceId || "default"; 85 - if (started.current === currentKey) { 86 - return; 87 - } 69 + if (started.current === currentKey) return; 88 70 started.current = currentKey; 89 71 90 72 (async () => { 91 - try { 92 - const output = props.startFromSourceId 93 - ? await resumeScraping(props.media, props.startFromSourceId) 94 - : await startScraping(props.media); 95 - if (!isMounted()) { 96 - return; 97 - } 98 - props.onResult?.( 99 - resultRef.current.sources, 73 + const output = props.startFromSourceId 74 + ? await resumeScraping(props.media, props.startFromSourceId) 75 + : await startScraping(props.media); 76 + if (!isMounted()) return; 77 + props.onResult?.( 78 + resultRef.current.sources, 79 + resultRef.current.sourceOrder, 80 + ); 81 + report( 82 + scrapePartsToProviderMetric( 83 + props.media, 100 84 resultRef.current.sourceOrder, 101 - ); 102 - report( 103 - scrapePartsToProviderMetric( 104 - props.media, 105 - resultRef.current.sourceOrder, 106 - resultRef.current.sources, 107 - ), 108 - ); 109 - props.onGetStream?.(output, resultRef.current.sources); 110 - } catch (error) { 111 - setFailedStartScrape(true); 112 - } 113 - })(); 85 + resultRef.current.sources, 86 + ), 87 + ); 88 + props.onGetStream?.(output); 89 + })().catch(() => setFailedStartScrape(true)); 114 90 }, [startScraping, resumeScraping, props, report, isMounted]); 115 91 116 92 let currentProviderIndex = sourceOrder.findIndex( ··· 186 162 ); 187 163 } 188 164 189 - export function ScrapingPartInterruptButton(props: { 190 - skipCurrentSource?: () => void; 191 - }) { 165 + export function ScrapingPartInterruptButton() { 192 166 const { t } = useTranslation(); 193 167 194 168 return ( ··· 209 183 > 210 184 {t("notFound.reloadButton")} 211 185 </Button> 212 - {props.skipCurrentSource && ( 213 - <Button 214 - onClick={props.skipCurrentSource} 215 - theme="purple" 216 - padding="md:px-17 p-3" 217 - className="mt-6" 218 - > 219 - {t("player.scraping.skip")} 220 - </Button> 221 - )} 222 186 </div> 223 187 ); 224 188 }
-1
src/stores/player/slices/source.ts
··· 1 1 /* eslint-disable no-console */ 2 - 3 2 import { ScrapeMedia } from "@p-stream/providers"; 4 3 5 4 import { MakeSlice } from "@/stores/player/slices/types";