diff --git a/README.md b/README.md index fb0eaf4..3e34f4a 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ click a number to edit it (turn changes it, click confirms), hold to go back. Entries: * **Save as favourite / Remove from favourites** — the camera that was playing; - saving asks for one of the 50 slots (it jumps to the first empty one; picking a used slot overwrites it). + saving asks for one of the 100 slots (it jumps to the first empty one; picking a used slot overwrites it). * **Favourites** — the saved cameras; click one to play it. A saved camera shows a yellow *FAV n* marker while it plays. -* **Source** — *Random* draws random listing pages, *Popular* walks the sites' - top-rated listings in rank order from the top, *Favourites* cycles the saved - slots. +* **Source** — *Random* draws random listing pages and skips anything shown in + the last 1200 cameras, *Popular* walks the sites' top-rated listings in rank + order from the top, *Favourites* cycles the saved slots. * **Provider** — *insecam*, *opentopia* or *Mixed* (a random site per batch). * **Time per camera**, **Listing** (mixed / top rated / newest), **Screen brightness**, **Ring brightness**, **Caption**. @@ -109,6 +109,10 @@ it stays out of the way. The CC1101 board's BQ27220 gauge is not supported. * `insecam.c` fetches one listing page (`/en/byrating/` or `/en/bynew/`, random page) — six cameras per request, so insecam sees one hit every two minutes. + insecam's whole catalogue is only ~1,700 cameras (287 pages × 6) and + opentopia's rated list ~3,000, so Random keeps a ring of the last 1,200 + cameras shown and skips them; without it the birthday effect makes repeats + common within an hour. * `opentopia.c` does the same for opentopia (`hiddencam.php`, rated or newest), then, just before a camera plays, reads its page in *Live Feed* mode for the camera's own MJPEG URL (or *Current Still* when there is no video). The site is @@ -126,7 +130,7 @@ it stays out of the way. The CC1101 board's BQ27220 gauge is not supported. elapses or the knob moves, skip dead cameras, keep history for "previous". * `ui.c` is LVGL 9 via `esp_lvgl_port`: a canvas for the frame, a translucent caption bar, a LIVE/PAUSED badge, a hairline progress bar and a six-row menu page. -* `settings.c` keeps the settings blob and the 50 favourite slots in NVS +* `settings.c` keeps the settings blob and the 100 favourite slots in NVS (namespace `widget`); `menu.c` is the knob-driven menu that edits them; `power.c` is the deep-sleep power-off with ext1 wake on the encoder pins. diff --git a/main/settings.h b/main/settings.h index 1ff9541..fb00003 100644 --- a/main/settings.h +++ b/main/settings.h @@ -30,7 +30,7 @@ void settings_save(void); void settings_request_portal(void); bool settings_take_portal_request(void); -#define FAV_SLOTS 50 +#define FAV_SLOTS 100 const cam_info_t *fav_get(int slot); // NULL when empty void fav_set(int slot, const cam_info_t *cam); void fav_clear(int slot); diff --git a/main/slideshow.c b/main/slideshow.c index d7bd8b8..82b09a2 100644 --- a/main/slideshow.c +++ b/main/slideshow.c @@ -26,7 +26,8 @@ static const char *TAG = "slideshow"; #define HISTORY_LEN 24 -#define PAGE_ITEMS 6 +#define PAGE_ITEMS 20 // insecam pages hold 6 cameras, opentopia 19 +#define SEEN_LEN 1200 // recently shown cameras skipped in Random mode #define CHUNK 4096 #define CONNECT_TIMEOUT_MS 3000 #define RECV_SLICE_MS 250 // per-recv timeout; keeps the knob responsive @@ -43,8 +44,10 @@ static const char *TAG = "slideshow"; typedef enum { LEAVE_NEXT, LEAVE_PREV, LEAVE_ERROR, LEAVE_MENU } leave_t; -static cam_info_t page_cams[PAGE_ITEMS]; +static cam_info_t *page_cams; // PAGE_ITEMS, in PSRAM static int page_count, page_next; +static uint32_t *seen; // ring of provider|id keys, in PSRAM +static int seen_next, seen_count; static int total_pages[2][2] = {{287, 287}, {162, 162}}; // [provider][listing] static int popular_page[2] = {1, 1}; // next rated page per provider in Popular mode static int popular_turn; @@ -61,6 +64,22 @@ static bool paused; static int64_t now_ms(void) { return esp_timer_get_time() / 1000; } +static uint32_t seen_key(const cam_info_t *c) { return ((uint32_t)c->provider << 28) | (c->id & 0x0fffffff); } + +static bool seen_recently(const cam_info_t *c) +{ + uint32_t k = seen_key(c); + for (int i = 0; i < seen_count; i++) if (seen[i] == k) return true; + return false; +} + +static void mark_seen(const cam_info_t *c) +{ + seen[seen_next] = seen_key(c); + seen_next = (seen_next + 1) % SEEN_LEN; + if (seen_count < SEEN_LEN) seen_count++; +} + // Shows "78%" with a battery glyph while running on the battery; hidden on USB power. static void update_battery(void) { @@ -153,9 +172,18 @@ static cam_info_t *next_camera(bool backwards) if (c) { history_push(c); return &history[hist_pos]; } } } - if (page_next >= page_count) refill_queue(); - history_push(&page_cams[page_next++]); - return &history[hist_pos]; + // Random mode: skip cameras shown recently; after a few fully-seen pages give up and repeat. + for (int pages_tried = 0; ; ) { + if (page_next >= page_count) { + refill_queue(); + pages_tried++; + } + cam_info_t *c = &page_cams[page_next++]; + if (settings.source == SOURCE_RANDOM && pages_tried < 4 && seen_recently(c)) continue; + mark_seen(c); + history_push(c); + return &history[hist_pos]; + } } static void set_paused(bool p) @@ -372,9 +400,11 @@ void slideshow_start(void) { jpeg_cap = (size_t)CONFIG_WIDGET_MAX_JPEG_KB * 1024; jpeg_buf = heap_caps_malloc(jpeg_cap, MALLOC_CAP_SPIRAM); + page_cams = heap_caps_calloc(PAGE_ITEMS, sizeof *page_cams, MALLOC_CAP_SPIRAM); + seen = heap_caps_calloc(SEEN_LEN, sizeof *seen, MALLOC_CAP_SPIRAM); for (int i = 0; i < 2; i++) fb[i] = heap_caps_aligned_calloc(64, LCD_H_RES * LCD_V_RES, sizeof(uint16_t), MALLOC_CAP_SPIRAM); - if (!jpeg_buf || !fb[0] || !fb[1]) { + if (!jpeg_buf || !fb[0] || !fb[1] || !page_cams || !seen) { ESP_LOGE(TAG, "out of PSRAM"); abort(); }