playtwitch now pops up error if streamlink crashes

This commit is contained in:
LordMZTE 2022-08-07 00:13:00 +02:00
parent c479629e25
commit e82c115878
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
2 changed files with 111 additions and 18 deletions

View file

@ -202,21 +202,76 @@ fn start(
return;
}
const channel_d = try state.udata_arena.dupe(u8, channel);
const quality_d = try state.udata_arena.dupe(u8, quality);
std.log.info(
"Starting for channel {s} with quality {s} (chatty: {})",
.{ channel, quality, chatty },
.{ channel_d, quality_d, chatty },
);
const url = try std.fmt.allocPrint(state.udata_arena, "https://twitch.tv/{s}", .{channel_d});
const streamlink_argv = [_][]const u8{ "streamlink", url, quality_d };
state.streamlink_child = std.ChildProcess.init(
try state.udata_arena.dupe([]const u8, &streamlink_argv),
state.alloc,
);
const url = try std.fmt.allocPrint(state.alloc, "https://twitch.tv/{s}", .{channel});
defer state.alloc.free(url);
const streamlink_argv = [_][]const u8{ "streamlink", url, quality };
var streamlink_child = std.ChildProcess.init(&streamlink_argv, state.alloc);
try streamlink_child.spawn();
state.streamlink_child = streamlink_child;
if (chatty) {
const chatty_argv = [_][]const u8{ "chatty", "-connect", "-channel", channel };
var chatty_child = std.ChildProcess.init(&chatty_argv, state.alloc);
try chatty_child.spawn();
state.chatty_child = chatty_child;
const chatty_argv = [_][]const u8{ "chatty", "-connect", "-channel", channel_d };
state.chatty_child = std.ChildProcess.init(
try state.udata_arena.dupe([]const u8, &chatty_argv),
state.alloc,
);
}
}
pub fn showStreamlinkErrorDialog(output: []const u8) void {
// TODO: instead of creating a new main loop, reuse the one used for the rest of the GUI
const main_loop = c.g_main_loop_new(null, 0);
defer c.g_main_loop_unref(main_loop);
const dialog = c.gtk_dialog_new_with_buttons(
"Streamlink Crashed!",
null,
c.GTK_DIALOG_MODAL,
"_Close",
c.GTK_RESPONSE_CLOSE,
@as(?*anyopaque, null),
);
ffi.connectSignal(
dialog,
"response",
@ptrCast(c.GCallback, onErrorDialogResponse),
main_loop,
);
const content = c.gtk_dialog_get_content_area(@ptrCast(*c.GtkDialog, dialog));
c.gtk_box_set_spacing(@ptrCast(*c.GtkBox, content), 5);
c.gtk_widget_set_margin_top(content, 5);
c.gtk_widget_set_margin_bottom(content, 5);
c.gtk_widget_set_margin_start(content, 5);
c.gtk_widget_set_margin_end(content, 5);
c.gtk_box_append(
@ptrCast(*c.GtkBox, content),
c.gtk_label_new("Streamlink Crashed! This is the output."),
);
const output_buf = c.gtk_text_buffer_new(null);
var start_iter: c.GtkTextIter = undefined;
c.gtk_text_buffer_get_start_iter(output_buf, &start_iter);
c.gtk_text_buffer_insert(output_buf, &start_iter, output.ptr, @intCast(c_int, output.len));
const output_view = c.gtk_text_view_new_with_buffer(output_buf);
c.gtk_widget_set_hexpand(output_view, 1);
c.gtk_text_view_set_editable(@ptrCast(*c.GtkTextView, output_view), 0);
c.gtk_box_append(@ptrCast(*c.GtkBox, content), output_view);
c.gtk_widget_show(dialog);
c.g_main_loop_run(main_loop);
}
fn onErrorDialogResponse(_: *c.GtkDialog, _: c_int, loop: *c.GMainLoop) void {
c.g_main_loop_quit(loop);
}

View file

@ -11,7 +11,7 @@ pub fn main() !u8 {
var udata_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
defer udata_arena.deinit();
var state = gui.GuiState {
var state = gui.GuiState{
.alloc = std.heap.c_allocator,
.udata_arena = udata_arena.allocator(),
};
@ -27,13 +27,51 @@ pub fn main() !u8 {
@ptrCast([*c][*c]u8, std.os.argv.ptr),
);
if (state.streamlink_child) |*ch| {
_ = try ch.wait();
}
if (state.chatty_child) |*ch| {
_ = try ch.wait();
if (state.streamlink_child) |*sl_child| {
try runChildren(sl_child, if (state.chatty_child) |*ch| ch else null);
}
return @intCast(u8, status);
}
fn runChildren(sl_child: *std.ChildProcess, chatty_child: ?*std.ChildProcess) !void {
var sl_alive = true;
var thread: ?std.Thread = null;
if (chatty_child) |chatty| {
thread = try std.Thread.spawn(
.{},
waitAndRunChatty,
.{ chatty, &sl_alive },
);
}
sl_child.stdout_behavior = .Pipe;
try sl_child.spawn();
const output = try sl_child.stdout.?.readToEndAlloc(
std.heap.c_allocator,
std.math.maxInt(usize),
);
defer std.heap.c_allocator.free(output);
const term = try sl_child.wait();
if (term == .Exited and term.Exited != 0) {
@atomicStore(bool, &sl_alive, false, .Unordered);
std.log.err("Streamlink died:\n{s}", .{output});
gui.showStreamlinkErrorDialog(std.mem.trimRight(u8, output, "\n\t "));
if (thread) |*t| {
t.detach();
}
} else {
if (thread) |*t| {
t.join();
}
}
}
// This function first waits a while, then checks if streamlink is still alive
// and then runs chatty.
fn waitAndRunChatty(chatty: *std.ChildProcess, sl_alive: *bool) !void {
std.time.sleep(5 * std.time.ns_per_s);
if (@atomicLoad(bool, sl_alive, .Unordered)) {
_ = try chatty.spawnAndWait();
}
}