libvaxis/examples/aio.zig
Jari Vetoniemi 9b78bb8a78 aio: update to latest, windows, code reuse
Update to latest aio, which has minor changes such as the thread pool
argument and special aio.ReadTty operation.

In future the aio.ReadTty might have option to translate to vt escape
sequences, but for vaxis it will use the direct mode.

I was not really able to test the windows at all actually as wine did
not seem to play nice with any vaxis example, but it compiles and ...
runs?
2024-06-30 08:44:04 -07:00

171 lines
5.4 KiB
Zig

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, .{});
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();
}
}