@@ -9,6 +9,7 @@ const js = struct {
extern "js" fn log(ptr: [*]const u8, len: usize) void;
extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
extern "js" fn send(ptr: [*]const u8, len: usize) void;
extern "js" fn timestamp() i64;
};
pub const std_options: std.Options = .{
@@ -57,7 +58,10 @@ fn Slice(T: type) type {
var db = Db.empty;
var message_buffer: std.ArrayListAlignedUnmanaged(u8, @alignOf(u32)) = .{};
var server_timestamp: i64 = 0;
/// Nanoseconds.
var server_base_timestamp: i64 = 0;
/// Milliseconds.
var client_base_timestamp: i64 = 0;
/// Resizes the message buffer to be the correct length; returns the pointer to
/// the query string.
@@ -79,7 +83,8 @@ export fn message_end() void {
}
fn currentTimeMessage(msg_bytes: []u8) void {
server_timestamp = @bitCast(msg_bytes[1..][0..8].*);
client_base_timestamp = js.timestamp();
server_base_timestamp = @bitCast(msg_bytes[1..][0..8].*);
}
fn fullLibraryMessage(msg_bytes: []u8) error{OutOfMemory}!void {
@@ -168,6 +173,25 @@ export fn enqueueAlbum(index: Db.Album.Index) void {
send(std.mem.asBytes(&message));
}
export fn play() void {
const message: protocol.Command = .unpause;
send(std.mem.asBytes(&message));
}
export fn pause() void {
const message: protocol.Command = .pause;
send(std.mem.asBytes(&message));
}
export fn stop() void {
const message: protocol.Seek = .{
.command = .seek_pause,
.item = currentPlayQueueItemId() orelse return,
.position = Db.Duration.zero,
};
send(std.mem.asBytes(&message));
}
export fn queue_len() usize {
return db.queue_items.entries.len;
}
@@ -177,9 +201,40 @@ export fn queue_item_file(index: u32) Db.File.Index {
}
export fn file_title(index: Db.File.Index) String {
return String.init(db.stringToSlice(db.files.keys()[@intFromEnum(index)].title));
return String.init(db.stringToSlice(db.file(index).title));
}
fn send(bytes: []const u8) void {
js.send(bytes.ptr, bytes.len);
}
/// Nanoseconds passed since a server timestamp.
fn nsSince(server_timestamp: i64) i64 {
const ms_passed = js.timestamp() - client_base_timestamp;
const ns_passed = server_timestamp - server_base_timestamp;
return ns_passed + ms_passed * std.time.ns_per_ms;
}
fn currentPlayQueueItemId() ?Db.QueueItem.Id {
const started_item_id = db.state.started_item.unwrap() orelse return null;
if (!db.state.flags.playing) return started_item_id;
var seek_timestamp = nsSince(db.state.seek_timestamp);
const started_item_index = db.queueItemIndexById(started_item_id).?;
const list = db.queue_items.keys()[@intFromEnum(started_item_index)..];
for (list) |*item| {
const file = db.file(item.file);
const song_ns = @divTrunc(
(@as(i64, file.duration.sample_count) * std.time.ns_per_s),
@as(i64, file.duration.sample_rate),
);
if (seek_timestamp > song_ns) {
seek_timestamp -= song_ns;
continue;
}
return item.id;
}
return null;
}