Login
1 branch 0 tags
Ben (Desktop/Arch) Show paused icon in topbar bff5fa6 1 month ago 62 Commits
moon / src / playlist.c
#include "playlist.h"
#include "audio_player.h"
#include "bookmark.h"
#include "lvgl.h"
#include "storage.h"
#include "ui.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.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);
    }
  }
}