100 favourite slots; Random skips recently shown cameras

Random mode keeps a ring of the last 1200 cameras (provider|id) and
skips them, giving up after four fully-seen pages. Opentopia pages now
contribute all 19 cameras instead of the first six.
This commit is contained in:
Juli committed 2026-09-17 15:19:49 +02:00
1 parent e82f0ac137
commit 836e815f30
3 files changed
+46 -12

No files matched your search

+9 -5
View File
@@ -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.
+1 -1
View File
@@ -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);
+36 -6
View File
@@ -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();
}