feat: open streaming URLs without changing resolved handoffs

This commit is contained in:
2026-10-19 19:32:26 +02:00
parent d068c52e04
commit 9f1dca3081
17 changed files with 553 additions and 103 deletions
+19 -9
View File
@@ -7,21 +7,21 @@ enum PlayerCommand {
case launchSource(sourceFile: URL, newInstance: Bool, noConfig: Bool, progressFile: URL?)
static let usage = """
Usage: terebi [--new-instance] [--no-config] [FILE | YOUTUBE_URL]
terebi [--new-instance] [--no-config] --url YOUTUBE_URL
terebi --new-instance --url YOUTUBE_URL --progress-file PATH
Usage: terebi [--new-instance] [--no-config] [FILE | STREAM_URL]
terebi [--new-instance] [--no-config] --url STREAM_URL
terebi --new-instance --url STREAM_URL --progress-file PATH
terebi --new-instance --source-file PATH [--progress-file PATH]
terebi [--new-instance] [--no-config] -- FILE
terebi --help
terebi --default-config
Open one local file, YouTube video or resolved source and start playback,
Open one local file, YouTube video, Miruro episode or resolved source and start playback,
resuming saved video progress. With no arguments, open or activate the player.
Quote paths and URLs containing spaces or shell metacharacters.
--new-instance Launch a separate player, leaving existing playback untouched.
--no-config Use built-in settings instead of config.toml on a fresh launch.
--url URL Open a YouTube video URL.
--url URL Open a YouTube video or Miruro episode URL.
--source-file PATH Open a validated resolved HTTP(S) source in a new player.
--progress-file PATH Write JSONL playback facts to a new private file; requires
--new-instance. The GUI owns reporting after this CLI exits.
@@ -71,6 +71,16 @@ enum PlayerCommand {
}
return .launch(url, newInstance: newInstance, noConfig: noConfig, progressFile: progressFile)
}
func streamURL(_ text: String) throws -> URL {
switch try StreamURL.parse(text) {
case .youtube(let url): return url
case .miruro:
guard let url = URL(string: text.trimmingCharacters(in: .whitespacesAndNewlines)) else {
throw ArgumentError("Invalid stream URL.")
}
return url
}
}
if inputs.isEmpty { return try launch(nil) }
if inputs == ["--help"] || inputs == ["-h"] {
guard progressFile == nil else { throw ArgumentError("--progress-file cannot be combined with --help.") }
@@ -94,8 +104,8 @@ enum PlayerCommand {
noConfig: noConfig, progressFile: progressFile)
}
if inputs.first == "--url" {
guard inputs.count == 2 else { throw ArgumentError("--url requires exactly one YouTube URL.") }
return try launch(YouTubeResolver.validateURL(inputs[1]))
guard inputs.count == 2 else { throw ArgumentError("--url requires exactly one stream URL.") }
return try launch(streamURL(inputs[1]))
}
let filenameOnly = inputs.first == "--"
if filenameOnly {
@@ -104,7 +114,7 @@ enum PlayerCommand {
throw ArgumentError("Unknown option. Use -- before filenames beginning with '-'.")
}
guard inputs.count == 1, let input = inputs.first, !input.isEmpty else {
throw ArgumentError("Expected exactly one file or YouTube URL.")
throw ArgumentError("Expected exactly one file or stream URL.")
}
let file: URL
if !filenameOnly && input.contains("://") {
@@ -115,7 +125,7 @@ enum PlayerCommand {
}
file = url
} else {
return try launch(YouTubeResolver.validateURL(input))
return try launch(streamURL(input))
}
} else {
let base = directory.hasDirectoryPath ? directory : URL(fileURLWithPath: directory.path, isDirectory: true)