package engine import ( "fmt" "os/exec" "runtime" "strings" ) // OpenPlayer attempts to open a media player with the given stream URL. // Returns the player name and the running command. // If override is set, it uses that command directly. // // The URL is required to be http(s) so a hostile-looking value (e.g. starting // with `--`) is not interpreted as a switch by mpv/vlc/xdg-open/open. The // `--` separator is also appended before the URL where the helper supports // it. func OpenPlayer(url, override string) (string, *exec.Cmd, error) { if !isSafePlayerURL(url) { return "", nil, fmt.Errorf("refusing to open non-http(s) URL") } if override != "" { cmd := exec.Command(override, "--", url) if err := cmd.Start(); err != nil { return override, nil, fmt.Errorf("start %s: %w", override, err) } return override, cmd, nil } // Try mpv first (best streaming support) if path, err := exec.LookPath("mpv"); err == nil { cmd := exec.Command(path, "--no-terminal", "--", url) if err := cmd.Start(); err == nil { return "mpv", cmd, nil } } // Try VLC if path, err := exec.LookPath("vlc"); err == nil { cmd := exec.Command(path, "--", url) if err := cmd.Start(); err == nil { return "vlc", cmd, nil } } // Try cvlc (VLC headless) if path, err := exec.LookPath("cvlc"); err == nil { cmd := exec.Command(path, "--", url) if err := cmd.Start(); err == nil { return "vlc (headless)", cmd, nil } } // Browser fallback name, cmd, err := openBrowser(url) if err != nil { return "", nil, fmt.Errorf("no player found: install mpv or vlc, or open %s manually", url) } return name, cmd, nil } func openBrowser(url string) (string, *exec.Cmd, error) { if !isSafePlayerURL(url) { return "", nil, fmt.Errorf("refusing to open non-http(s) URL") } switch runtime.GOOS { case "linux": if path, err := exec.LookPath("xdg-open"); err == nil { cmd := exec.Command(path, url) if err := cmd.Start(); err == nil { return "browser", cmd, nil } } case "darwin": cmd := exec.Command("/usr/bin/open", "--", url) if err := cmd.Start(); err == nil { return "browser", cmd, nil } case "windows": cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", url) if err := cmd.Start(); err == nil { return "browser", cmd, nil } } return "", nil, fmt.Errorf("no browser opener found") } // isSafePlayerURL guards the helpers above against URLs that could be // interpreted as command-line switches by the launched player. func isSafePlayerURL(url string) bool { return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") }