mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2025-03-05 06:00:01 +01:00
add live status to playtwitch
This commit is contained in:
parent
d9dfa0587d
commit
d186e2b599
6 changed files with 134 additions and 5 deletions
|
@ -19,6 +19,7 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
exe.linkSystemLibrary("cimgui");
|
exe.linkSystemLibrary("cimgui");
|
||||||
exe.linkSystemLibrary("glfw3");
|
exe.linkSystemLibrary("glfw3");
|
||||||
exe.linkSystemLibrary("glew");
|
exe.linkSystemLibrary("glew");
|
||||||
|
exe.linkSystemLibrary("curl");
|
||||||
|
|
||||||
exe.strip = mode != .Debug;
|
exe.strip = mode != .Debug;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,13 @@ const log = std.log.scoped(.state);
|
||||||
pub const ChannelEntry = struct {
|
pub const ChannelEntry = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
comment: ?[]const u8,
|
comment: ?[]const u8,
|
||||||
|
live: Live = .loading,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Live = enum {
|
||||||
|
live,
|
||||||
|
offline,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
|
|
||||||
mutex: std.Thread.Mutex,
|
mutex: std.Thread.Mutex,
|
||||||
|
@ -27,6 +34,9 @@ channel_name_buf: [64]u8,
|
||||||
streamlink_memfd: ?std.fs.File,
|
streamlink_memfd: ?std.fs.File,
|
||||||
streamlink_out: ?[]align(std.mem.page_size) u8,
|
streamlink_out: ?[]align(std.mem.page_size) u8,
|
||||||
|
|
||||||
|
/// If the status of the channels is being loaded currently
|
||||||
|
live_status_loading: bool,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(win: *c.GLFWwindow) !*Self {
|
pub fn init(win: *c.GLFWwindow) !*Self {
|
||||||
|
@ -50,6 +60,8 @@ pub fn init(win: *c.GLFWwindow) !*Self {
|
||||||
|
|
||||||
.streamlink_memfd = null,
|
.streamlink_memfd = null,
|
||||||
.streamlink_out = null,
|
.streamlink_out = null,
|
||||||
|
|
||||||
|
.live_status_loading = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
std.mem.copy(u8, &self.quality_buf, "best");
|
std.mem.copy(u8, &self.quality_buf, "best");
|
||||||
|
|
|
@ -66,9 +66,13 @@ pub fn configLoaderThread(state: *State) !void {
|
||||||
.{ channels.items.len, end_time - start_time },
|
.{ channels.items.len, end_time - start_time },
|
||||||
);
|
);
|
||||||
|
|
||||||
state.mutex.lock();
|
{
|
||||||
defer state.mutex.unlock();
|
state.mutex.lock();
|
||||||
|
defer state.mutex.unlock();
|
||||||
|
|
||||||
state.channels_file_data = channels_data;
|
state.channels_file_data = channels_data;
|
||||||
state.channels = channels.toOwnedSlice();
|
state.channels = channels.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
try @import("live.zig").fetchChannelsLive(state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
|
@cInclude("curl/curl.h");
|
||||||
|
|
||||||
@cInclude("GL/glew.h");
|
@cInclude("GL/glew.h");
|
||||||
@cInclude("GLFW/glfw3.h");
|
@cInclude("GLFW/glfw3.h");
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,15 @@ pub fn winContent(state: *State) !void {
|
||||||
start = .channel_bar;
|
start = .channel_bar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.channels != null) {
|
||||||
|
c.igBeginDisabled(state.live_status_loading);
|
||||||
|
defer c.igEndDisabled();
|
||||||
|
if (c.igButton("Refresh Status", .{ .x = 0.0, .y = 0.0 })) {
|
||||||
|
(try std.Thread.spawn(.{}, @import("live.zig").reloadLiveThread, .{state}))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.channels != null and c.igBeginChild_Str(
|
if (state.channels != null and c.igBeginChild_Str(
|
||||||
"Quick Pick",
|
"Quick Pick",
|
||||||
.{ .x = 0.0, .y = 0.0 },
|
.{ .x = 0.0, .y = 0.0 },
|
||||||
|
@ -105,18 +114,24 @@ pub fn winContent(state: *State) !void {
|
||||||
)) {
|
)) {
|
||||||
_ = c.igBeginTable(
|
_ = c.igBeginTable(
|
||||||
"##qp_table",
|
"##qp_table",
|
||||||
2,
|
3,
|
||||||
c.ImGuiTableFlags_Resizable,
|
c.ImGuiTableFlags_Resizable,
|
||||||
.{ .x = 0.0, .y = 0.0 },
|
.{ .x = 0.0, .y = 0.0 },
|
||||||
0.0,
|
0.0,
|
||||||
);
|
);
|
||||||
defer c.igEndTable();
|
defer c.igEndTable();
|
||||||
|
|
||||||
|
c.igTableSetupColumn("Channel", 0, 0.0, 0);
|
||||||
|
c.igTableSetupColumn("Comment", 0, 0.0, 0);
|
||||||
|
c.igTableSetupColumn("Live?", c.ImGuiTableColumnFlags_WidthFixed, 80.0, 0);
|
||||||
|
|
||||||
c.igTableHeadersRow();
|
c.igTableHeadersRow();
|
||||||
_ = c.igTableSetColumnIndex(0);
|
_ = c.igTableSetColumnIndex(0);
|
||||||
c.igTableHeader("Channel");
|
c.igTableHeader("Channel");
|
||||||
_ = c.igTableSetColumnIndex(1);
|
_ = c.igTableSetColumnIndex(1);
|
||||||
c.igTableHeader("Comment");
|
c.igTableHeader("Comment");
|
||||||
|
_ = c.igTableSetColumnIndex(2);
|
||||||
|
c.igTableHeader("Live?");
|
||||||
|
|
||||||
for (state.channels.?) |ch, i| {
|
for (state.channels.?) |ch, i| {
|
||||||
var ch_buf: [256]u8 = undefined;
|
var ch_buf: [256]u8 = undefined;
|
||||||
|
@ -146,6 +161,16 @@ pub fn winContent(state: *State) !void {
|
||||||
if (ch.comment) |comment| {
|
if (ch.comment) |comment| {
|
||||||
igu.sliceText(comment);
|
igu.sliceText(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = c.igTableSetColumnIndex(2);
|
||||||
|
|
||||||
|
const live_label = switch (ch.live) {
|
||||||
|
.loading => "Loading...",
|
||||||
|
.live => "Live",
|
||||||
|
.offline => "Offline",
|
||||||
|
};
|
||||||
|
|
||||||
|
igu.sliceText(live_label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
85
scripts/playtwitch/src/live.zig
Normal file
85
scripts/playtwitch/src/live.zig
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const State = @import("State.zig");
|
||||||
|
const c = @import("ffi.zig").c;
|
||||||
|
const log = std.log.scoped(.live);
|
||||||
|
|
||||||
|
pub fn reloadLiveThread(s: *State) !void {
|
||||||
|
{
|
||||||
|
s.mutex.lock();
|
||||||
|
defer s.mutex.unlock();
|
||||||
|
|
||||||
|
for (s.channels.?) |chan| {
|
||||||
|
chan.live = .loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try fetchChannelsLive(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetchChannelsLive(s: *State) !void {
|
||||||
|
@atomicStore(bool, &s.live_status_loading, true, .Unordered);
|
||||||
|
defer @atomicStore(bool, &s.live_status_loading, false, .Unordered);
|
||||||
|
log.info("initiaizing cURL", .{});
|
||||||
|
var curl = c.curl_easy_init();
|
||||||
|
if (curl == null)
|
||||||
|
return error.CurlInitError;
|
||||||
|
defer c.curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
try handleCurlErr(c.curl_easy_setopt(
|
||||||
|
curl,
|
||||||
|
c.CURLOPT_WRITEFUNCTION,
|
||||||
|
&curlWriteCb,
|
||||||
|
));
|
||||||
|
try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_NOPROGRESS, @as(c_long, 1)));
|
||||||
|
try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 1)));
|
||||||
|
|
||||||
|
// the twitch info grabbinator works by downloading the web page
|
||||||
|
// and checking if it contains a string. this is the bufffer for the page.
|
||||||
|
//
|
||||||
|
// Fuck you, twitch! amazing API design!
|
||||||
|
var page_buf = std.ArrayList(u8).init(std.heap.c_allocator);
|
||||||
|
defer page_buf.deinit();
|
||||||
|
|
||||||
|
try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_WRITEDATA, &page_buf));
|
||||||
|
|
||||||
|
// we shouldn't need to aquire the mutex here, this data isnt being read and we're
|
||||||
|
// only doing atomic writes.
|
||||||
|
var fmt_buf: [512]u8 = undefined;
|
||||||
|
for (s.channels.?) |chan| {
|
||||||
|
page_buf.clearRetainingCapacity();
|
||||||
|
|
||||||
|
log.info("requesting live state for channel {s}", .{chan.name});
|
||||||
|
|
||||||
|
const url = try std.fmt.bufPrintZ(
|
||||||
|
&fmt_buf,
|
||||||
|
"https://www.twitch.tv/{s}",
|
||||||
|
.{chan.name},
|
||||||
|
);
|
||||||
|
try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_URL, url.ptr));
|
||||||
|
try handleCurlErr(c.curl_easy_perform(curl));
|
||||||
|
|
||||||
|
if (std.mem.containsAtLeast(u8, page_buf.items, 1, "live_user")) {
|
||||||
|
@atomicStore(State.Live, &chan.live, .live, .Unordered);
|
||||||
|
} else {
|
||||||
|
@atomicStore(State.Live, &chan.live, .offline, .Unordered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn curlWriteCb(
|
||||||
|
data: [*]const u8,
|
||||||
|
size: usize,
|
||||||
|
nmemb: usize,
|
||||||
|
out: *std.ArrayList(u8),
|
||||||
|
) callconv(.C) usize {
|
||||||
|
const realsize = size * nmemb;
|
||||||
|
out.writer().writeAll(data[0..realsize]) catch return 0;
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleCurlErr(code: c.CURLcode) !void {
|
||||||
|
if (code != c.CURLE_OK) {
|
||||||
|
log.err("Curl error: {s}", .{c.curl_easy_strerror(code)});
|
||||||
|
return error.CurlError;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue