pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
at main 290 lines 8.8 kB view raw
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}