@officebeats/matrix-iptv-cli 4.0.17 ā 4.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -201
- package/bin/cli.js +1 -192
- package/package.json +10 -15
- package/CHANGES.md +0 -186
- package/about.md +0 -32
- package/debug_dns.py +0 -11
- package/debug_redirect.py +0 -22
- package/debug_stream.py +0 -28
- package/image/README/1768788798110.png +0 -0
- package/index.html +0 -246
- package/matrix-iptv.exe +0 -0
- package/old_player.rs +0 -404
- package/response.html +0 -20
- package/scripts/benchmark_playlists.py +0 -50
- package/scripts/check_host.py +0 -49
- package/scripts/install-binary.js +0 -133
- package/scripts/proxy_server.js +0 -68
- package/test_bounds_debug.exe +0 -0
- package/test_bounds_debug.pdb +0 -0
- package/test_bounds_debug.rs +0 -34
- package/test_config.json +0 -85
package/old_player.rs
DELETED
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
use std::path::PathBuf;
|
|
2
|
-
use std::sync::Arc;
|
|
3
|
-
use std::sync::Mutex;
|
|
4
|
-
use crate::config::PlayerEngine;
|
|
5
|
-
|
|
6
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
7
|
-
use std::process::{Child, Command, Stdio};
|
|
8
|
-
|
|
9
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#[cfg(target_arch = "wasm32")]
|
|
16
|
-
use web_sys::window;
|
|
17
|
-
|
|
18
|
-
#[derive(Clone)]
|
|
19
|
-
pub struct Player {
|
|
20
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
21
|
-
process: Arc<Mutex<Option<Child>>>,
|
|
22
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
23
|
-
ipc_path: Arc<Mutex<Option<PathBuf>>>,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
impl Player {
|
|
27
|
-
pub fn new() -> Self {
|
|
28
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
29
|
-
{
|
|
30
|
-
Self {
|
|
31
|
-
process: Arc::new(Mutex::new(None)),
|
|
32
|
-
ipc_path: Arc::new(Mutex::new(None)),
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
#[cfg(target_arch = "wasm32")]
|
|
36
|
-
{
|
|
37
|
-
Self {}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/// Start the selected player engine and return the IPC pipe path for monitoring
|
|
42
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
43
|
-
pub async fn play(&self, url: &str, engine: PlayerEngine, use_default_mpv: bool, smooth_motion: bool) -> Result<(), anyhow::Error> {
|
|
44
|
-
// Pre-flight check: Verify stream is reachable before launching player
|
|
45
|
-
// This detects dead redirects/DNS issues early and notifies the user
|
|
46
|
-
self.check_stream_health(url).await?;
|
|
47
|
-
|
|
48
|
-
self.stop();
|
|
49
|
-
|
|
50
|
-
match engine {
|
|
51
|
-
PlayerEngine::Mpv => self.play_mpv(url, use_default_mpv, smooth_motion),
|
|
52
|
-
PlayerEngine::Vlc => self.play_vlc(url, smooth_motion),
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async fn check_stream_health(&self, url: &str) -> Result<(), anyhow::Error> {
|
|
57
|
-
// Build a client that mimics the player's behavior (Chrome UA)
|
|
58
|
-
let client = reqwest::Client::builder()
|
|
59
|
-
.timeout(std::time::Duration::from_secs(10))
|
|
60
|
-
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
|
61
|
-
.danger_accept_invalid_certs(true)
|
|
62
|
-
.build()
|
|
63
|
-
.unwrap_or_else(|_| reqwest::Client::new());
|
|
64
|
-
|
|
65
|
-
// We use a stream request but abort immediately to check connectivity/headers.
|
|
66
|
-
// HEAD often fails on some IPTV servers, so a started GET is safer logic-wise.
|
|
67
|
-
let mut result = client.get(url).send().await;
|
|
68
|
-
|
|
69
|
-
// Resilience: Fallback to DoH if DNS fails for the stream health check
|
|
70
|
-
if let Err(ref e) = result {
|
|
71
|
-
if crate::doh::is_dns_error(e) {
|
|
72
|
-
#[cfg(debug_assertions)]
|
|
73
|
-
println!("DEBUG: Health check DNS error detected for {}. Trying DoH fallback...", url);
|
|
74
|
-
|
|
75
|
-
if let Some(resp) = crate::doh::try_doh_fallback(&client, url).await {
|
|
76
|
-
result = Ok(resp);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
match result {
|
|
82
|
-
Ok(resp) => {
|
|
83
|
-
if resp.status().is_success() || resp.status().is_redirection() {
|
|
84
|
-
Ok(())
|
|
85
|
-
} else {
|
|
86
|
-
Err(anyhow::anyhow!("Stream returned error status: {} (Server might be offline/blocking)", resp.status()))
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
Err(e) => {
|
|
90
|
-
// Provide a user-friendly error description using shared DNS detection
|
|
91
|
-
if crate::doh::is_dns_error(&e) {
|
|
92
|
-
Err(anyhow::anyhow!("Stream Server Unreachable. The host likely does not exist or is blocked (DNS Error). Details: {}", e))
|
|
93
|
-
} else if e.is_connect() || e.is_timeout() {
|
|
94
|
-
Err(anyhow::anyhow!("Stream Connection Failed. Server may be slow or offline. Details: {}", e))
|
|
95
|
-
} else {
|
|
96
|
-
Err(anyhow::anyhow!("Stream Check Failed: {}", e))
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
103
|
-
fn play_mpv(&self, url: &str, use_default_mpv: bool, smooth_motion: bool) -> Result<(), anyhow::Error> {
|
|
104
|
-
|
|
105
|
-
// Find mpv executable, checking PATH and common installation locations
|
|
106
|
-
let mpv_path = crate::setup::get_mpv_path().ok_or_else(|| {
|
|
107
|
-
let hint = if cfg!(target_os = "macos") {
|
|
108
|
-
"\n\nHint: On macOS with Homebrew:\n\
|
|
109
|
-
- Apple Silicon: mpv is typically at /opt/homebrew/bin/mpv\n\
|
|
110
|
-
- Intel Mac: mpv is typically at /usr/local/bin/mpv\n\n\
|
|
111
|
-
To fix, add Homebrew to your PATH:\n\
|
|
112
|
-
export PATH=\"/opt/homebrew/bin:$PATH\"\n\
|
|
113
|
-
(Add this line to ~/.zshrc or ~/.bash_profile)"
|
|
114
|
-
} else {
|
|
115
|
-
""
|
|
116
|
-
};
|
|
117
|
-
anyhow::anyhow!(
|
|
118
|
-
"mpv not found. Please install mpv and ensure it's in your PATH.{}",
|
|
119
|
-
hint
|
|
120
|
-
)
|
|
121
|
-
})?;
|
|
122
|
-
|
|
123
|
-
// Create a unique IPC path for this session
|
|
124
|
-
let pipe_name = if cfg!(target_os = "windows") {
|
|
125
|
-
format!("\\\\.\\pipe\\mpv_ipc_{}", std::process::id())
|
|
126
|
-
} else {
|
|
127
|
-
format!("/tmp/mpv_ipc_{}", std::process::id())
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
let mut cmd = Command::new(&mpv_path);
|
|
131
|
-
cmd.arg(url)
|
|
132
|
-
.arg("--geometry=1280x720") // Start in 720p window (user preference)
|
|
133
|
-
.arg("--force-window") // Ensure window opens even if audio-only initially
|
|
134
|
-
.arg("--no-fs") // DISABLING FULLSCREEN - Force Windowed Mode
|
|
135
|
-
.arg("--osc=yes"); // Enable On Screen Controller for usability
|
|
136
|
-
|
|
137
|
-
// Apply smooth motion interpolation if enabled
|
|
138
|
-
if smooth_motion {
|
|
139
|
-
cmd.arg("--video-sync=display-resample") // Smooth motion sync (required for interpolation)
|
|
140
|
-
.arg("--interpolation=yes") // Frame generation / motion smoothing
|
|
141
|
-
.arg("--tscale=linear") // Soap opera effect - smooth motion blending (GPU friendly)
|
|
142
|
-
.arg("--tscale-clamp=0.0"); // Allow full blending for maximum smoothness
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Only apply optimizations if not using default MPV settings
|
|
146
|
-
if !use_default_mpv {
|
|
147
|
-
cmd.arg("--cache=yes")
|
|
148
|
-
.arg("--cache-pause=yes") // Pause when cache is starved
|
|
149
|
-
.arg("--cache-pause-wait=5") // Wait for 5 seconds of cache before resuming (builds a buffer against live edge)
|
|
150
|
-
.arg("--cache-pause-initial=yes") // Ensure we buffer 5 seconds initially
|
|
151
|
-
.arg("--network-timeout=10") // Increased timeout to prevent premature EOF closures
|
|
152
|
-
.arg("--hls-bitrate=max") // Force highest quality HLS to prevent bitrate switching loops
|
|
153
|
-
.arg("--tls-verify=no") // Ignore certificate errors for internal/sketchy HTTPS streams
|
|
154
|
-
.arg("--hr-seek=yes") // Precise seeking for better buffer recovery
|
|
155
|
-
// NETWORK TURBO MODE: Aggressive Caching for Stability
|
|
156
|
-
.arg("--demuxer-max-bytes=512MiB") // Doubled cache to 512MB
|
|
157
|
-
.arg("--demuxer-max-back-bytes=128MiB") // Increase back buffer for seeking/rewind
|
|
158
|
-
.arg("--demuxer-readahead-secs=60") // Buffer 1 full minute ahead (Adaptive Buffering)
|
|
159
|
-
.arg("--stream-buffer-size=8MiB") // Low-level socket buffer
|
|
160
|
-
.arg("--load-unsafe-playlists=yes") // Stay alive through malformed HLS fragments
|
|
161
|
-
.arg("--framedrop=vo") // Drop frames gracefully if GPU lags
|
|
162
|
-
.arg("--vd-lavc-fast") // Enable fast decoding optimizations
|
|
163
|
-
.arg("--vd-lavc-skiploopfilter=all") // Major CPU saver for low-end machines
|
|
164
|
-
.arg("--vd-lavc-threads=0") // Maximize thread usage for decoding
|
|
165
|
-
// LOW-END FRIENDLY UPSCALING (catmull_rom: good quality, low GPU cost)
|
|
166
|
-
.arg("--scale=catmull_rom") // Clean upscaling, ~25% faster than spline36
|
|
167
|
-
.arg("--cscale=catmull_rom") // Matching chroma scaler
|
|
168
|
-
.arg("--dscale=catmull_rom") // Consistent downscaling
|
|
169
|
-
.arg("--scale-antiring=0.7") // Reduce haloing
|
|
170
|
-
.arg("--cscale-antiring=0.7")
|
|
171
|
-
.arg("--hwdec=auto-copy") // More compatible hardware decoding
|
|
172
|
-
// RECONNECT TURBO: Auto-reconnect on network drops (avoiding aggressive EOF looping)
|
|
173
|
-
.arg("--stream-lavf-o=reconnect_on_http_error=4xx,5xx,reconnect_on_network_error=1,reconnect_streamed=1,reconnect_delay_max=5,fflags=+genpts+igndts");
|
|
174
|
-
|
|
175
|
-
if cfg!(target_os = "windows") {
|
|
176
|
-
cmd.arg("--d3d11-flip=yes") // Modern Windows presentation (faster)
|
|
177
|
-
.arg("--gpu-api=d3d11"); // Force D3D11 (faster than OpenGL on Windows)
|
|
178
|
-
} else if cfg!(target_os = "macos") {
|
|
179
|
-
cmd.arg("--gpu-api=opengl"); // Generally safe default for macOS mpv
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Common settings for both modes
|
|
184
|
-
cmd.arg("--msg-level=all=no")
|
|
185
|
-
.arg("--term-status-msg=no")
|
|
186
|
-
.arg("--input-terminal=no") // Ignore terminal for input
|
|
187
|
-
.arg("--terminal=no") // Completely disable terminal interactions
|
|
188
|
-
// USER AGENT MASQUERADE: Modern Chrome to avoid throttling
|
|
189
|
-
.arg("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
|
190
|
-
// Keep window open if playback fails to see error (optional, maybe off for prod)
|
|
191
|
-
.arg("--keep-open=no")
|
|
192
|
-
// Logging for troubleshooting
|
|
193
|
-
.arg("--log-file=mpv_playback.log")
|
|
194
|
-
// IPC for status monitoring
|
|
195
|
-
.arg(format!("--input-ipc-server={}", pipe_name));
|
|
196
|
-
|
|
197
|
-
// Disconnect from terminal input/output to prevent hotkey conflicts
|
|
198
|
-
cmd.stdin(Stdio::null())
|
|
199
|
-
.stdout(Stdio::null())
|
|
200
|
-
.stderr(Stdio::null());
|
|
201
|
-
|
|
202
|
-
let child = cmd.spawn();
|
|
203
|
-
|
|
204
|
-
match child {
|
|
205
|
-
Ok(child) => {
|
|
206
|
-
{
|
|
207
|
-
let mut guard = self.process.lock().map_err(|e| {
|
|
208
|
-
anyhow::anyhow!("Failed to lock process mutex: {}", e)
|
|
209
|
-
})?;
|
|
210
|
-
*guard = Some(child);
|
|
211
|
-
}
|
|
212
|
-
{
|
|
213
|
-
let mut ipc_guard = self.ipc_path.lock().map_err(|e| {
|
|
214
|
-
anyhow::anyhow!("Failed to lock IPC path mutex: {}", e)
|
|
215
|
-
})?;
|
|
216
|
-
*ipc_guard = Some(PathBuf::from(&pipe_name));
|
|
217
|
-
}
|
|
218
|
-
Ok(())
|
|
219
|
-
}
|
|
220
|
-
Err(e) => {
|
|
221
|
-
let hint = if cfg!(target_os = "macos") {
|
|
222
|
-
format!(
|
|
223
|
-
"\n\nSearched for mpv at: {}\n\
|
|
224
|
-
Hint: On Apple Silicon, Homebrew installs to /opt/homebrew/bin.\n\
|
|
225
|
-
Add this to your ~/.zshrc: export PATH=\"/opt/homebrew/bin:$PATH\"",
|
|
226
|
-
mpv_path
|
|
227
|
-
)
|
|
228
|
-
} else {
|
|
229
|
-
String::new()
|
|
230
|
-
};
|
|
231
|
-
Err(anyhow::anyhow!(
|
|
232
|
-
"Failed to start mpv: {}.{}",
|
|
233
|
-
e, hint
|
|
234
|
-
))
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
240
|
-
fn play_vlc(&self, url: &str, smooth_motion: bool) -> Result<(), anyhow::Error> {
|
|
241
|
-
// Find vlc executable
|
|
242
|
-
let vlc_path = crate::setup::get_vlc_path().ok_or_else(|| {
|
|
243
|
-
anyhow::anyhow!("VLC not found. Please install VLC.")
|
|
244
|
-
})?;
|
|
245
|
-
|
|
246
|
-
let mut cmd = Command::new(&vlc_path);
|
|
247
|
-
|
|
248
|
-
// Add Referrer validation (Common anti-scraping measure)
|
|
249
|
-
// Manual parsing to avoid adding 'url' crate dependency
|
|
250
|
-
if let Some(scheme_end) = url.find("://") {
|
|
251
|
-
let rest = &url[scheme_end + 3..];
|
|
252
|
-
if let Some(path_start) = rest.find('/') {
|
|
253
|
-
let host = &rest[..path_start];
|
|
254
|
-
let base = format!("{}://{}/", &url[..scheme_end], host);
|
|
255
|
-
cmd.arg(format!("--http-referrer={}", base));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
cmd.arg(url)
|
|
260
|
-
.arg("--no-video-title-show")
|
|
261
|
-
.arg("--http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
|
262
|
-
.arg("--http-reconnect")
|
|
263
|
-
.arg("--http-continuous")
|
|
264
|
-
.arg("--clock-jitter=500") // Allow more jitter in stream clock
|
|
265
|
-
.arg("--network-caching=15000") // 15 second buffer for TS streams
|
|
266
|
-
.arg("--gnutls-verify-trust-ee=no"); // For VLC HTTPS stability
|
|
267
|
-
|
|
268
|
-
// Apply smooth motion (deinterlacing) if enabled
|
|
269
|
-
if smooth_motion {
|
|
270
|
-
cmd.arg("--video-filter=deinterlace")
|
|
271
|
-
.arg("--deinterlace-mode=bob");
|
|
272
|
-
}
|
|
273
|
-
// DISCONNECT from terminal
|
|
274
|
-
cmd.stdin(Stdio::null())
|
|
275
|
-
.stdout(Stdio::null())
|
|
276
|
-
.stderr(Stdio::null());
|
|
277
|
-
|
|
278
|
-
#[cfg(windows)]
|
|
279
|
-
{
|
|
280
|
-
use std::os::windows::process::CommandExt;
|
|
281
|
-
const DETACHED_PROCESS: u32 = 0x00000008;
|
|
282
|
-
cmd.creation_flags(DETACHED_PROCESS);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let child = cmd.spawn()?;
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
let mut guard = self.process.lock().map_err(|e| {
|
|
289
|
-
anyhow::anyhow!("Failed to lock process mutex: {}", e)
|
|
290
|
-
})?;
|
|
291
|
-
*guard = Some(child);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
Ok(())
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/// Read the last few lines of the player logs to find errors
|
|
298
|
-
pub fn get_last_error_from_log(&self) -> Option<String> {
|
|
299
|
-
let logs = ["mpv_playback.log", "vlc_playback.log"];
|
|
300
|
-
|
|
301
|
-
for log_file in logs {
|
|
302
|
-
if let Ok(content) = std::fs::read_to_string(log_file) {
|
|
303
|
-
let lines: Vec<&str> = content.lines().rev().take(15).collect();
|
|
304
|
-
for line in lines {
|
|
305
|
-
let lower = line.to_lowercase();
|
|
306
|
-
if lower.contains("error") || lower.contains("failed") || lower.contains("fatal") {
|
|
307
|
-
// Clean up common VLC/MPV prefixes for cleaner UI display
|
|
308
|
-
let cleaned = line.split("]: ").last().unwrap_or(line);
|
|
309
|
-
return Some(cleaned.to_string());
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
None
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/// Check if MPV is still running (process alive)
|
|
318
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
319
|
-
pub fn is_running(&self) -> bool {
|
|
320
|
-
if let Ok(mut guard) = self.process.lock() {
|
|
321
|
-
if let Some(ref mut child) = *guard {
|
|
322
|
-
// try_wait returns Ok(Some(status)) if exited, Ok(None) if still running
|
|
323
|
-
match child.try_wait() {
|
|
324
|
-
Ok(Some(_)) => false, // Process has exited
|
|
325
|
-
Ok(None) => true, // Still running
|
|
326
|
-
Err(_) => false, // Error, assume not running
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
false
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
false // Mutex poisoned, assume not running
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/// Wait for MPV to actually start playing by polling process status
|
|
337
|
-
/// Returns Ok(true) if playback confirmed, Ok(false) if process died, Err on timeout
|
|
338
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
339
|
-
pub async fn wait_for_playback(&self, timeout_ms: u64) -> Result<bool, anyhow::Error> {
|
|
340
|
-
use tokio::time::{sleep, Duration, Instant};
|
|
341
|
-
|
|
342
|
-
let start = Instant::now();
|
|
343
|
-
let timeout = Duration::from_millis(timeout_ms);
|
|
344
|
-
|
|
345
|
-
// Give MPV a moment to initialize
|
|
346
|
-
sleep(Duration::from_millis(500)).await;
|
|
347
|
-
|
|
348
|
-
// Poll until process is confirmed running and has had time to buffer
|
|
349
|
-
while start.elapsed() < timeout {
|
|
350
|
-
if !self.is_running() {
|
|
351
|
-
// Process died, playback failed
|
|
352
|
-
return Ok(false);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Check if process has been alive for at least 2 seconds
|
|
356
|
-
// This indicates MPV successfully connected and is playing
|
|
357
|
-
if start.elapsed() > Duration::from_millis(2000) {
|
|
358
|
-
return Ok(true);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
sleep(Duration::from_millis(200)).await;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// If we reached here and process is still running, consider it a success
|
|
365
|
-
Ok(self.is_running())
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
#[cfg(target_arch = "wasm32")]
|
|
369
|
-
pub fn play(&self, url: &str, _engine: PlayerEngine, _use_default_mpv: bool, _smooth_motion: bool) -> Result<(), anyhow::Error> {
|
|
370
|
-
self.stop();
|
|
371
|
-
if let Some(win) = window() {
|
|
372
|
-
let _ = win.alert_with_message(&format!("Play stream: {}", url));
|
|
373
|
-
}
|
|
374
|
-
Ok(())
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
378
|
-
pub fn stop(&self) {
|
|
379
|
-
if let Ok(mut guard) = self.process.lock() {
|
|
380
|
-
if let Some(mut child) = guard.take() {
|
|
381
|
-
let _ = child.kill();
|
|
382
|
-
let _ = child.wait();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if let Ok(mut ipc_guard) = self.ipc_path.lock() {
|
|
387
|
-
*ipc_guard = None;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
#[cfg(target_arch = "wasm32")]
|
|
392
|
-
pub fn stop(&self) {
|
|
393
|
-
if let Some(_win) = window() {
|
|
394
|
-
web_sys::console::log_1(&"Stopping stream".into());
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
impl Drop for Player {
|
|
400
|
-
fn drop(&mut self) {
|
|
401
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
402
|
-
self.stop();
|
|
403
|
-
}
|
|
404
|
-
}
|
package/response.html
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html style="height:100%">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title></title>
|
|
7
|
-
<script type="text/javascript">
|
|
8
|
-
var url="https://myhomenetwork.att.com";
|
|
9
|
-
var reason=["phishing"];
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
</head>
|
|
13
|
-
<body style="margin: 0 auto; padding: 0 auto; width:100%; height: 100%; overflow : hidden;">
|
|
14
|
-
<iframe src="" id="frame" seamless="seamless" style="margin: 0 auto; padding: 0 auto; border: 0; width:100%; height: 100%" > </iframe>
|
|
15
|
-
|
|
16
|
-
<script type="text/javascript">
|
|
17
|
-
document.getElementById('frame').src = url+"/networkprotected/index.html?reason="+reason[0]+"&source="+encodeURIComponent(window.location.href);
|
|
18
|
-
</script>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import json
|
|
3
|
-
import time
|
|
4
|
-
|
|
5
|
-
accounts = [
|
|
6
|
-
{"name": "Strong 8K", "url": "http://pledge78502.cdn-akm.me:80", "user": "7c34d33c9e21", "pass": "037dacb169"},
|
|
7
|
-
{"name": "Trex", "url": "http://line.offcial-trex.pro", "user": "3a6aae52fb", "pass": "39c165888139"},
|
|
8
|
-
{"name": "Strong8k2-PC", "url": "http://zfruvync.duperab.xyz", "user": "PE1S9S8U", "pass": "11EZZUMW"},
|
|
9
|
-
{"name": "Mega OTT 1", "url": "http://line.4smart.in", "user": "45Z88W6", "pass": "Z7PHTX3"}
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
print("=== Matrix IPTV Performance Benchmark (Python) ===")
|
|
13
|
-
|
|
14
|
-
for acc in accounts:
|
|
15
|
-
print(f"\nProcessing: {acc['name']}")
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
# 1. Categories
|
|
19
|
-
start = time.time()
|
|
20
|
-
cat_url = f"{acc['url']}/player_api.php?username={acc['user']}&password={acc['pass']}&action=get_live_categories"
|
|
21
|
-
with urllib.request.urlopen(cat_url, timeout=30) as resp:
|
|
22
|
-
cats = json.loads(resp.read().decode())
|
|
23
|
-
duration = time.time() - start
|
|
24
|
-
print(f" ā
Categories: {len(cats)} items in {duration:.2f}s")
|
|
25
|
-
|
|
26
|
-
# 2. Streams & MSNBC
|
|
27
|
-
print(" š Searching for MSNBC...")
|
|
28
|
-
start = time.time()
|
|
29
|
-
streams_url = f"{acc['url']}/player_api.php?username={acc['user']}&password={acc['pass']}&action=get_live_streams"
|
|
30
|
-
with urllib.request.urlopen(streams_url, timeout=120) as resp:
|
|
31
|
-
streams = json.loads(resp.read().decode())
|
|
32
|
-
duration = time.time() - start
|
|
33
|
-
print(f" ā
Streams: {len(streams)} items in {duration:.2f}s")
|
|
34
|
-
|
|
35
|
-
msnbc = [s for s in streams if "MSNBC" in s.get("name", "")]
|
|
36
|
-
if msnbc:
|
|
37
|
-
print(f" š Found {len(msnbc)} MSNBC streams:")
|
|
38
|
-
for s in msnbc[:3]:
|
|
39
|
-
stream_id = s.get("stream_id")
|
|
40
|
-
name = s.get("name")
|
|
41
|
-
play_url = f"{acc['url']}/live/{acc['user']}/{acc['pass']}/{stream_id}.ts"
|
|
42
|
-
print(f" - [{stream_id}] {name}")
|
|
43
|
-
print(f" Link: {play_url}")
|
|
44
|
-
else:
|
|
45
|
-
print(" ā MSNBC NOT FOUND.")
|
|
46
|
-
|
|
47
|
-
except Exception as e:
|
|
48
|
-
print(f" ā Error: {e}")
|
|
49
|
-
|
|
50
|
-
print("\n=== Benchmark Complete ===")
|
package/scripts/check_host.py
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import socket
|
|
2
|
-
import requests
|
|
3
|
-
|
|
4
|
-
host = "zfruvync.duperab.xyz"
|
|
5
|
-
print(f"Checking host: {host}")
|
|
6
|
-
|
|
7
|
-
# 1. System DNS
|
|
8
|
-
try:
|
|
9
|
-
ip = socket.gethostbyname(host)
|
|
10
|
-
print(f"[System DNS] Resolved to: {ip}")
|
|
11
|
-
except Exception as e:
|
|
12
|
-
print(f"[System DNS] Failed: {e}")
|
|
13
|
-
|
|
14
|
-
# 2. Google DoH
|
|
15
|
-
try:
|
|
16
|
-
resp = requests.get(f"https://dns.google/resolve?name={host}", timeout=10)
|
|
17
|
-
data = resp.json()
|
|
18
|
-
if data.get("Status") == 0:
|
|
19
|
-
ips = [ans["data"] for ans in data.get("Answer", []) if ans["type"] == 1]
|
|
20
|
-
print(f"[Google DoH] Resolved to: {ips}")
|
|
21
|
-
else:
|
|
22
|
-
print(f"[Google DoH] Failed with status {data.get('Status')}")
|
|
23
|
-
except Exception as e:
|
|
24
|
-
print(f"[Google DoH] Error: {e}")
|
|
25
|
-
|
|
26
|
-
# 3. Cloudflare DoH
|
|
27
|
-
try:
|
|
28
|
-
resp = requests.get(f"https://cloudflare-dns.com/dns-query?name={host}", headers={"Accept": "application/dns-json"}, timeout=10)
|
|
29
|
-
data = resp.json()
|
|
30
|
-
if data.get("Status") == 0:
|
|
31
|
-
ips = [ans["data"] for ans in data.get("Answer", []) if ans["type"] == 1]
|
|
32
|
-
print(f"[Cloudflare DoH] Resolved to: {ips}")
|
|
33
|
-
else:
|
|
34
|
-
print(f"[Cloudflare DoH] Failed with status {data.get('Status')}")
|
|
35
|
-
except Exception as e:
|
|
36
|
-
print(f"[Cloudflare DoH] Error: {e}")
|
|
37
|
-
|
|
38
|
-
# 4. Quad9 DoH
|
|
39
|
-
try:
|
|
40
|
-
# Quad9 has a JSON API at https://dns.quad9.net/resolve?name=
|
|
41
|
-
resp = requests.get(f"https://dns.quad9.net/resolve?name={host}", timeout=10)
|
|
42
|
-
data = resp.json()
|
|
43
|
-
if data.get("Status") == 0:
|
|
44
|
-
ips = [ans["data"] for ans in data.get("Answer", []) if ans["type"] == 1]
|
|
45
|
-
print(f"[Quad9 DoH] Resolved to: {ips}")
|
|
46
|
-
else:
|
|
47
|
-
print(f"[Quad9 DoH] Failed with status {data.get('Status')}")
|
|
48
|
-
except Exception as e:
|
|
49
|
-
print(f"[Quad9 DoH] Error: {e}")
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const https = require("https");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
|
|
6
|
-
const binaryName =
|
|
7
|
-
os.platform() === "win32" ? "matrix-iptv.exe" : "matrix-iptv";
|
|
8
|
-
const binDir = path.join(__dirname, "..", "bin");
|
|
9
|
-
const binaryPath = path.join(binDir, binaryName);
|
|
10
|
-
|
|
11
|
-
const platformMap = {
|
|
12
|
-
win32: "windows.exe",
|
|
13
|
-
linux: "linux",
|
|
14
|
-
darwin: "macos",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const archMap = {
|
|
18
|
-
x64: "x64",
|
|
19
|
-
arm64: "arm64",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const platform = platformMap[os.platform()];
|
|
23
|
-
if (!platform) {
|
|
24
|
-
console.error(`Unsupported platform: ${os.platform()}`);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Note: Re-using the naming convention from install.ps1
|
|
29
|
-
// https://github.com/officebeats/matrix-iptv/releases/latest/download/matrix-iptv-windows.exe
|
|
30
|
-
const releaseUrl = `https://github.com/officebeats/matrix-iptv/releases/latest/download/matrix-iptv-${platform}`;
|
|
31
|
-
|
|
32
|
-
console.log(`[*] Matrix IPTV CLI // One-Click Install`);
|
|
33
|
-
console.log(`[*] Platform: ${os.platform()} (${os.arch()})`);
|
|
34
|
-
console.log(`[*] Downloading: ${releaseUrl}`);
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(binDir)) {
|
|
37
|
-
fs.mkdirSync(binDir, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function download(url, dest) {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
https
|
|
43
|
-
.get(url, (response) => {
|
|
44
|
-
if (response.statusCode === 302 || response.statusCode === 301 || response.statusCode === 307 || response.statusCode === 308) {
|
|
45
|
-
// Handle Redirect without leaving locks open
|
|
46
|
-
download(response.headers.location, dest).then(resolve).catch(reject);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (response.statusCode !== 200) {
|
|
51
|
-
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Only open the file stream if the download actually works
|
|
56
|
-
const file = fs.createWriteStream(dest);
|
|
57
|
-
response.pipe(file);
|
|
58
|
-
|
|
59
|
-
file.on("finish", () => {
|
|
60
|
-
file.close();
|
|
61
|
-
resolve();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
file.on("error", (err) => {
|
|
65
|
-
file.close();
|
|
66
|
-
fs.unlink(dest, () => {});
|
|
67
|
-
reject(err);
|
|
68
|
-
});
|
|
69
|
-
})
|
|
70
|
-
.on("error", (err) => {
|
|
71
|
-
reject(err);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const tempPath = binaryPath + ".tmp";
|
|
77
|
-
|
|
78
|
-
download(releaseUrl, tempPath)
|
|
79
|
-
.then(async () => {
|
|
80
|
-
console.log(`[+] Download complete.`);
|
|
81
|
-
|
|
82
|
-
// Replace old binary with robustness logic for Windows
|
|
83
|
-
let attempts = 0;
|
|
84
|
-
const maxAttempts = 5;
|
|
85
|
-
while (attempts < maxAttempts) {
|
|
86
|
-
try {
|
|
87
|
-
if (fs.existsSync(binaryPath)) {
|
|
88
|
-
if (os.platform() === "win32") {
|
|
89
|
-
const oldPath = binaryPath + ".old." + Date.now();
|
|
90
|
-
fs.renameSync(binaryPath, oldPath);
|
|
91
|
-
try {
|
|
92
|
-
fs.unlinkSync(oldPath);
|
|
93
|
-
} catch (e) {}
|
|
94
|
-
} else {
|
|
95
|
-
fs.unlinkSync(binaryPath);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
fs.renameSync(tempPath, binaryPath);
|
|
99
|
-
break;
|
|
100
|
-
} catch (e) {
|
|
101
|
-
attempts++;
|
|
102
|
-
if (attempts === maxAttempts) throw e;
|
|
103
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (os.platform() !== "win32") {
|
|
108
|
-
fs.chmodSync(binaryPath, "755");
|
|
109
|
-
console.log(`[+] Executable permissions set.`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log(`\nā
Matrix IPTV CLI installed successfully.`);
|
|
113
|
-
|
|
114
|
-
// Auto-launch if running in an interactive terminal
|
|
115
|
-
if (process.stdout.isTTY) {
|
|
116
|
-
console.log(`š Launching Matrix IPTV...`);
|
|
117
|
-
const { spawn } = require("child_process");
|
|
118
|
-
const child = spawn(binaryPath, [], { stdio: "inherit" });
|
|
119
|
-
|
|
120
|
-
child.on("close", (code) => {
|
|
121
|
-
process.exit(code);
|
|
122
|
-
});
|
|
123
|
-
} else {
|
|
124
|
-
console.log(`Type 'matrix-iptv' to start.`);
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
.catch((err) => {
|
|
128
|
-
console.error(`\nā Installation failed: ${err.message}`);
|
|
129
|
-
console.log(
|
|
130
|
-
`Please ensure the GitHub repository is public and has a 'latest' release.`
|
|
131
|
-
);
|
|
132
|
-
process.exit(1);
|
|
133
|
-
});
|