pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import { detect } from "detect-browser";
2
3import { usePlayerStore } from "@/stores/player/store";
4
5export interface ErrorDebugInfo {
6 timestamp: string;
7 error: {
8 message: string;
9 type: string;
10 stackTrace?: string;
11 };
12 device: {
13 userAgent: string;
14 browser: string;
15 os: string;
16 isMobile: boolean;
17 isTV: boolean;
18 screenResolution: string;
19 viewportSize: string;
20 };
21 player: {
22 status: string;
23 sourceId: string | null;
24 embedId: string | null;
25 currentQuality: string | null;
26 meta: {
27 title: string;
28 type: string;
29 tmdbId: string;
30 imdbId?: string;
31 releaseYear: number;
32 season?: number;
33 episode?: number;
34 } | null;
35 };
36 network: {
37 online: boolean;
38 connectionType?: string;
39 effectiveType?: string;
40 downlink?: number;
41 rtt?: number;
42 };
43 hls?: {
44 details: string;
45 fatal: boolean;
46 level?: number;
47 levelDetails?: {
48 url: string;
49 width: number;
50 height: number;
51 bitrate: number;
52 };
53 frag?: {
54 url: string;
55 baseurl: string;
56 duration: number;
57 start: number;
58 sn: number;
59 };
60 type: string;
61 url?: string;
62 };
63 url: {
64 pathname: string;
65 search: string;
66 hash: string;
67 };
68
69 performance: {
70 memory?: {
71 usedJSHeapSize: number;
72 totalJSHeapSize: number;
73 jsHeapSizeLimit: number;
74 };
75 timing: {
76 navigationStart: number;
77 loadEventEnd: number;
78 domContentLoadedEventEnd: number;
79 };
80 };
81}
82
83export function gatherErrorDebugInfo(error: any): ErrorDebugInfo {
84 const browserInfo = detect();
85 const isMobile = window.innerWidth <= 768;
86 const isTV =
87 /SmartTV|Tizen|WebOS|SamsungBrowser|HbbTV|Viera|NetCast|AppleTV|Android TV|GoogleTV|Roku|PlayStation|Xbox|Opera TV|AquosBrowser|Hisense|SonyBrowser|SharpBrowser|AFT|Chromecast/i.test(
88 navigator.userAgent,
89 );
90
91 const playerStore = usePlayerStore.getState();
92
93 // Get network information
94 const connection =
95 (navigator as any).connection ||
96 (navigator as any).mozConnection ||
97 (navigator as any).webkitConnection;
98
99 // Get performance information
100 const performanceInfo = performance.getEntriesByType(
101 "navigation",
102 )[0] as PerformanceNavigationTiming;
103 const memory = (performance as any).memory;
104
105 return {
106 timestamp: new Date().toISOString(),
107 error: {
108 message: error?.message || error?.key || String(error),
109 type: error?.type || "unknown",
110 stackTrace: error?.stackTrace || error?.stack,
111 },
112 device: {
113 userAgent: navigator.userAgent,
114 browser: browserInfo?.name || "unknown",
115 os: browserInfo?.os || "unknown",
116 isMobile,
117 isTV,
118 screenResolution: `${window.screen.width}x${window.screen.height}`,
119 viewportSize: `${window.innerWidth}x${window.innerHeight}`,
120 },
121 player: {
122 status: playerStore.status,
123 sourceId: playerStore.sourceId,
124 embedId: (playerStore as any).embedId ?? null,
125 currentQuality: playerStore.currentQuality,
126 meta: playerStore.meta
127 ? {
128 title: playerStore.meta.title,
129 type: playerStore.meta.type,
130 tmdbId: playerStore.meta.tmdbId,
131 imdbId: playerStore.meta.imdbId,
132 releaseYear: playerStore.meta.releaseYear,
133 season: playerStore.meta.season?.number,
134 episode: playerStore.meta.episode?.number,
135 }
136 : null,
137 },
138 network: {
139 online: navigator.onLine,
140 connectionType: connection?.type,
141 effectiveType: connection?.effectiveType,
142 downlink: connection?.downlink,
143 rtt: connection?.rtt,
144 },
145 hls: error?.hls
146 ? {
147 details: error.hls.details,
148 fatal: error.hls.fatal,
149 level: error.hls.level,
150 levelDetails: error.hls.levelDetails
151 ? {
152 url: error.hls.levelDetails.url,
153 width: error.hls.levelDetails.width,
154 height: error.hls.levelDetails.height,
155 bitrate: error.hls.levelDetails.bitrate,
156 }
157 : undefined,
158 frag: error.hls.frag
159 ? {
160 url: error.hls.frag.url,
161 baseurl: error.hls.frag.baseurl,
162 duration: error.hls.frag.duration,
163 start: error.hls.frag.start,
164 sn: error.hls.frag.sn,
165 }
166 : undefined,
167 type: error.hls.type,
168 url: error.hls.url,
169 }
170 : undefined,
171 url: {
172 pathname: window.location.pathname,
173 search: window.location.search,
174 hash: window.location.hash,
175 },
176 performance: {
177 memory: memory
178 ? {
179 usedJSHeapSize: memory.usedJSHeapSize,
180 totalJSHeapSize: memory.totalJSHeapSize,
181 jsHeapSizeLimit: memory.jsHeapSizeLimit,
182 }
183 : undefined,
184 timing: {
185 navigationStart: performanceInfo?.fetchStart || 0,
186 loadEventEnd: performanceInfo?.loadEventEnd || 0,
187 domContentLoadedEventEnd:
188 performanceInfo?.domContentLoadedEventEnd || 0,
189 },
190 },
191 };
192}
193
194export function formatErrorDebugInfo(info: ErrorDebugInfo): string {
195 const sections = [
196 `=== ERROR DEBUG INFO ===`,
197 `Timestamp: ${info.timestamp}`,
198 ``,
199 `=== ERROR DETAILS ===`,
200 `Type: ${info.error.type}`,
201 `Message: ${info.error.message}`,
202 info.error.stackTrace ? `Stack Trace:\n${info.error.stackTrace}` : "",
203 ``,
204 `=== DEVICE INFO ===`,
205 `Browser: ${info.device.browser} (${info.device.os})`,
206 `User Agent: ${info.device.userAgent}`,
207 `Screen: ${info.device.screenResolution}`,
208 `Viewport: ${info.device.viewportSize}`,
209 `Mobile: ${info.device.isMobile}`,
210 `TV: ${info.device.isTV}`,
211 ``,
212 `=== PLAYER STATE ===`,
213 `Status: ${info.player.status}`,
214 `Source ID: ${info.player.sourceId || "null"}`,
215 `Embed ID: ${info.player.embedId || "null"}`,
216 `Quality: ${info.player.currentQuality || "null"}`,
217 info.player.meta
218 ? [
219 `Media: ${info.player.meta.title} (${info.player.meta.type})`,
220 `TMDB ID: ${info.player.meta.tmdbId}`,
221 info.player.meta.imdbId ? `IMDB ID: ${info.player.meta.imdbId}` : "",
222 `Year: ${info.player.meta.releaseYear}`,
223 info.player.meta.season ? `Season: ${info.player.meta.season}` : "",
224 info.player.meta.episode
225 ? `Episode: ${info.player.meta.episode}`
226 : "",
227 ]
228 .filter(Boolean)
229 .join("\n")
230 : "No media loaded",
231 ``,
232 `=== NETWORK INFO ===`,
233 `Online: ${info.network.online}`,
234 info.network.connectionType
235 ? `Connection Type: ${info.network.connectionType}`
236 : "",
237 info.network.effectiveType
238 ? `Effective Type: ${info.network.effectiveType}`
239 : "",
240 info.network.downlink ? `Downlink: ${info.network.downlink} Mbps` : "",
241 info.network.rtt ? `RTT: ${info.network.rtt} ms` : "",
242 ``,
243 `=== URL INFO ===`,
244 `Path: ${info.url.pathname}`,
245 info.url.search ? `Query: ${info.url.search}` : "",
246 info.url.hash ? `Hash: ${info.url.hash}` : "",
247 ``,
248 info.hls
249 ? [
250 `=== HLS ERROR DETAILS ===`,
251 `Details: ${info.hls.details}`,
252 `Fatal: ${info.hls.fatal}`,
253 `Type: ${info.hls.type}`,
254 info.hls.level !== undefined ? `Level: ${info.hls.level}` : "",
255 info.hls.url ? `URL: ${info.hls.url}` : "",
256 info.hls.levelDetails
257 ? [
258 `Level Details:`,
259 ` URL: ${info.hls.levelDetails.url}`,
260 ` Resolution: ${info.hls.levelDetails.width}x${info.hls.levelDetails.height}`,
261 ` Bitrate: ${info.hls.levelDetails.bitrate} bps`,
262 ].join("\n")
263 : "",
264 info.hls.frag
265 ? [
266 `Fragment Details:`,
267 ` URL: ${info.hls.frag.url}`,
268 ` Base URL: ${info.hls.frag.baseurl}`,
269 ` Duration: ${info.hls.frag.duration}s`,
270 ` Start: ${info.hls.frag.start}s`,
271 ` Sequence: ${info.hls.frag.sn}`,
272 ].join("\n")
273 : "",
274 ]
275 .filter(Boolean)
276 .join("\n")
277 : "",
278 ``,
279 `=== PERFORMANCE ===`,
280 info.performance.memory
281 ? [
282 `Memory Used: ${Math.round(info.performance.memory.usedJSHeapSize / 1024 / 1024)} MB`,
283 `Memory Total: ${Math.round(info.performance.memory.totalJSHeapSize / 1024 / 1024)} MB`,
284 `Memory Limit: ${Math.round(info.performance.memory.jsHeapSizeLimit / 1024 / 1024)} MB`,
285 ].join("\n")
286 : "Memory info not available",
287 ];
288
289 return sections.filter(Boolean).join("\n");
290}