const builtin = @import("builtin"); const std = @import("std"); const vaxis = @import("vaxis"); const aio = @import("aio"); const coro = @import("coro"); pub const panic = vaxis.panic_handler; const Event = union(enum) { key_press: vaxis.Key, winsize: vaxis.Winsize, }; const Loop = vaxis.aio.Loop(Event); const Video = enum { no_state, ready, end }; const Audio = enum { no_state, ready, end }; fn downloadTask(allocator: std.mem.Allocator, url: []const u8) ![]const u8 { var client: std.http.Client = .{ .allocator = allocator }; defer client.deinit(); var body = std.ArrayList(u8).init(allocator); _ = try client.fetch(.{ .location = .{ .url = url }, .response_storage = .{ .dynamic = &body }, .max_append_size = 1.6e+7, }); return try body.toOwnedSlice(); } fn audioTask(allocator: std.mem.Allocator) !void { errdefer coro.yield(Audio.end) catch {}; // var child = std.process.Child.init(&.{ "aplay", "-Dplug:default", "-q", "-f", "S16_LE", "-r", "8000" }, allocator); var child = std.process.Child.init(&.{ "mpv", "--audio-samplerate=16000", "--audio-channels=mono", "--audio-format=s16", "-" }, allocator); child.stdin_behavior = .Pipe; child.stdout_behavior = .Ignore; child.stderr_behavior = .Ignore; child.spawn() catch return; // no sound defer _ = child.kill() catch {}; const sound = blk: { var tpool: coro.ThreadPool = .{}; try tpool.start(allocator, 1); defer tpool.deinit(); break :blk try tpool.yieldForCompletition(downloadTask, .{ allocator, "https://keroserene.net/lol/roll.s16" }); }; defer allocator.free(sound); try coro.yield(Audio.ready); var audio_off: usize = 0; while (audio_off < sound.len) { var written: usize = 0; try coro.io.single(aio.Write{ .file = child.stdin.?, .buffer = sound[audio_off..], .out_written = &written }); audio_off += written; } // the audio is already fed to the player and the defer // would kill the child stay here chilling coro.yield(Audio.end) catch {}; } fn videoTask(writer: std.io.AnyWriter) !void { defer coro.yield(Video.end) catch {}; var socket: std.posix.socket_t = undefined; try coro.io.single(aio.Socket{ .domain = std.posix.AF.INET, .flags = std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, .protocol = std.posix.IPPROTO.TCP, .out_socket = &socket, }); defer std.posix.close(socket); const address = std.net.Address.initIp4(.{ 44, 224, 41, 160 }, 1987); try coro.io.single(aio.Connect{ .socket = socket, .addr = &address.any, .addrlen = address.getOsSockLen(), }); try coro.yield(Video.ready); var buf: [1024]u8 = undefined; while (true) { var read: usize = 0; try coro.io.single(aio.Recv{ .socket = socket, .buffer = &buf, .out_read = &read }); if (read == 0) break; _ = try writer.write(buf[0..read]); } } fn loadingTask(vx: *vaxis.Vaxis, writer: std.io.AnyWriter) !void { var color_idx: u8 = 30; var dir: enum { up, down } = .up; while (true) { try coro.io.single(aio.Timeout{ .ns = 8 * std.time.ns_per_ms }); const style: vaxis.Style = .{ .fg = .{ .rgb = [_]u8{ color_idx, color_idx, color_idx } } }; const segment: vaxis.Segment = .{ .text = vaxis.logo, .style = style }; const win = vx.window(); win.clear(); var loc = vaxis.widgets.alignment.center(win, 28, 4); _ = try loc.printSegment(segment, .{ .wrap = .grapheme }); switch (dir) { .up => { color_idx += 1; if (color_idx == 255) dir = .down; }, .down => { color_idx -= 1; if (color_idx == 30) dir = .up; }, } try vx.render(writer); } } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var tty = try vaxis.Tty.init(); defer tty.deinit(); var vx = try vaxis.init(allocator, .{}); defer vx.deinit(allocator, tty.anyWriter()); var scheduler = try coro.Scheduler.init(allocator, .{}); defer scheduler.deinit(); var loop = try Loop.init(); try loop.spawn(&scheduler, &vx, &tty, null, .{}); defer loop.deinit(&vx, &tty); try vx.enterAltScreen(tty.anyWriter()); try vx.queryTerminalSend(tty.anyWriter()); var buffered_tty_writer = tty.bufferedWriter(); const loading = try scheduler.spawn(loadingTask, .{ &vx, buffered_tty_writer.writer().any() }, .{}); const audio = try scheduler.spawn(audioTask, .{allocator}, .{}); const video = try scheduler.spawn(videoTask, .{buffered_tty_writer.writer().any()}, .{}); main: while (try scheduler.tick(.blocking) > 0) { while (try loop.popEvent()) |event| switch (event) { .key_press => |key| { if (key.matches('c', .{ .ctrl = true })) { break :main; } }, .winsize => |ws| try vx.resize(allocator, buffered_tty_writer.writer().any(), ws), }; if (audio.state(Video) == .ready and video.state(Audio) == .ready) { loading.cancel(); audio.wakeup(); video.wakeup(); } else if (audio.state(Audio) == .end and video.state(Video) == .end) { break :main; } try buffered_tty_writer.flush(); } }