Login
1 branch 0 tags
Ben (Desktop/Arch) Put the display to sleep when brightness==0 43a8a00 1 month ago 70 Commits
moon / src / playlist.c
#include "playlist.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "audio_player.h"
#include "bookmark.h"
#include "lvgl.h"
#include "storage.h"
#include "ui.h"

#define PLAYLIST_BUF_SIZE 4096

playlist_t playlist;

void playlist_load(void) {
	memset(&playlist, 0, sizeof(playlist));

	storage_file_t f = storage_open(PLAYLIST_FILE, "r");
	if (!f) {
		return;
	}

	char buf[PLAYLIST_BUF_SIZE];
	size_t n = storage_read(f, buf, sizeof(buf) - 1);
	storage_close(f);
	buf[n] = '\0';

	// Parse line by line
	char* line = buf;
	while (line && *line) {
		char* eol = strchr(line, '\n');
		size_t line_len = eol ? (size_t)(eol - line) : strlen(line);

		// Null-terminate line temporarily
		char saved = line[line_len];
		line[line_len] = '\0';

		// Parse index = N
		if (strncmp(line, "index", 5) == 0) {
			char* eq = strchr(line, '=');
			if (eq) {
				playlist.index = atoi(eq + 1);
			}
		}
		// Parse type = "music" or type = "audiobook"
		else if (strncmp(line, "type", 4) == 0 &&
		         playlist.count < PLAYLIST_MAX_ENTRIES) {
			char* eq = strchr(line, '=');
			if (eq) {
				char* q1 = strchr(eq, '"');
				if (q1) {
					q1++;
					if (strncmp(q1, "audiobook", 9) == 0) {
						playlist.entries[playlist.count].type =
						    PLAYLIST_AUDIOBOOK;
					} else {
						playlist.entries[playlist.count].type = PLAYLIST_MUSIC;
					}
				}
			}
		}
		// Parse path = "/some/path"
		else if (strncmp(line, "path", 4) == 0 &&
		         playlist.count < PLAYLIST_MAX_ENTRIES) {
			char* eq = strchr(line, '=');
			if (eq) {
				char* q1 = strchr(eq, '"');
				if (q1) {
					q1++;
					char* q2 = strchr(q1, '"');
					if (q2) {
						size_t plen = (size_t)(q2 - q1);
						if (plen < STORAGE_MAX_PATH) {
							memcpy(playlist.entries[playlist.count].path, q1,
							       plen);
							playlist.entries[playlist.count].path[plen] = '\0';
							playlist.count++;
						}
					}
				}
			}
		}

		line[line_len] = saved;
		if (!eol) {
			break;
		}
		line = eol + 1;
	}

	// Clamp index
	if (playlist.count > 0) {
		if (playlist.index < 0) {
			playlist.index = 0;
		}
		if (playlist.index >= playlist.count) {
			playlist.index = playlist.count - 1;
		}
	} else {
		playlist.index = 0;
	}
}

void playlist_save(void) {
	storage_mkdir(DATA_DIR);

	char buf[PLAYLIST_BUF_SIZE];
	int pos = 0;

	pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos,
	                "[playlist]\nindex = %d\n\n", playlist.index);

	for (int i = 0; i < playlist.count && pos < (int)sizeof(buf) - 128; i++) {
		const char* type_str = playlist.entries[i].type == PLAYLIST_AUDIOBOOK
		                           ? "audiobook"
		                           : "music";
		pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos,
		                "[[entry]]\ntype = \"%s\"\npath = \"%s\"\n\n", type_str,
		                playlist.entries[i].path);
	}

	storage_file_t f = storage_open(PLAYLIST_FILE, "w");
	if (f) {
		storage_write(f, buf, (size_t)pos);
		storage_close(f);
	}
}

void playlist_add(const char* path, playlist_type_t type) {
	if (playlist.count >= PLAYLIST_MAX_ENTRIES) {
		return;
	}
	playlist.entries[playlist.count].type = type;
	strncpy(playlist.entries[playlist.count].path, path, STORAGE_MAX_PATH - 1);
	playlist.entries[playlist.count].path[STORAGE_MAX_PATH - 1] = '\0';
	playlist.count++;
}

void playlist_clear(void) {
	memset(&playlist, 0, sizeof(playlist));
}

playlist_entry_t* playlist_current(void) {
	if (playlist.count == 0) {
		return NULL;
	}
	if (playlist.index < 0 || playlist.index >= playlist.count) {
		return NULL;
	}
	return &playlist.entries[playlist.index];
}

bool playlist_next(void) {
	if (playlist.index + 1 >= playlist.count) {
		return false;
	}
	playlist.index++;
	return true;
}

bool playlist_prev(void) {
	if (playlist.index <= 0) {
		return false;
	}
	playlist.index--;
	return true;
}

bool playlist_is_empty(void) {
	return playlist.count == 0;
}

int playlist_count(void) {
	return playlist.count;
}

void playlist_set_index(int i) {
	if (i >= 0 && i < playlist.count) {
		playlist.index = i;
	}
}

void playlist_tick(void) {
	static uint32_t last_slot = 0;

	playlist_entry_t* entry = playlist_current();
	if (!entry || entry->type != PLAYLIST_AUDIOBOOK) {
		return;
	}
	if (audio_player_get_state() != AUDIO_STATE_PLAYING) {
		return;
	}

	uint32_t slot = lv_tick_get() / PLAYLIST_BOOKMARK_INTERVAL_MS;
	if (slot == last_slot) {
		return;
	}
	last_slot = slot;

	uint32_t pos = audio_player_get_position_ms();
	if (pos > 0 && app_state.last_played_path[0]) {
		bookmark_save(app_state.last_played_path, pos);
	}
	// Also save chapter bookmark for directory audiobooks
	if (app_state.audiobook_dir[0]) {
		const char* filename = strrchr(app_state.last_played_path, '/');
		if (filename) {
			bookmark_save_string(app_state.audiobook_dir, filename + 1);
		}
	}
}