eaccebc95d
- DM: add dl_missing_* fields to AppCtx for sequential queue - DM player: count_missing_in_season, find_next_missing, download_episode, download_missing_season_start/next - DM main onResult: auto-continue queue when dl_missing_active, updates status DL X/Y, finishes with done ok/fail - SdDownloader: file exists returns true (Already on SD) with total=file size, not false - SdDownloader: set_result_and_close minimal bundle (only success bool) to avoid internal heap OOM crash on close - SdDownloader: Cancel button becomes Close after done, calls tt_app_stop() directly (manual close per request, auto-close unreliable) - Fixes crash on close observed in demo mode (file exists) and after download done Tested on 192.168.68.133: SdDownloader shows Already on SD + Close, DM shows S10 5/6 after sequential, no crash on open/close cycles
293 lines
8.5 KiB
C
293 lines
8.5 KiB
C
#include "player.h"
|
||
#include "storage.h"
|
||
#include "ui.h"
|
||
#include "download.h"
|
||
|
||
#include <tt_lvgl.h>
|
||
#include <tt_app.h>
|
||
#include <tt_bundle.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "esp_log.h"
|
||
#include "esp_heap_caps.h"
|
||
|
||
void wait_play_exit(void) {
|
||
// No internal playback anymore, nothing to wait
|
||
}
|
||
|
||
void stop_playback_sync(void) {
|
||
// No internal playback, nothing to stop
|
||
G.is_playing = false;
|
||
G.is_paused = false;
|
||
}
|
||
|
||
static bool try_external_downloader(int idx) {
|
||
if(idx<0||idx>=G.ep_cnt) return false;
|
||
EpInfo* ep=&G.eps[idx];
|
||
if (strlen(ep->audio_url) < 8) {
|
||
ESP_LOGW(TAG, "Skipping DL for %s: empty URL", ep->slug);
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if (G.lbl_status) lv_label_set_text(G.lbl_status, "No URL – fetch failed?");
|
||
tt_lvgl_unlock();
|
||
return false;
|
||
}
|
||
char fpath[300];
|
||
make_sd_path(fpath, sizeof(fpath), ep->slug);
|
||
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
close_overlay();
|
||
if(G.dl_overlay) hide_dl_overlay();
|
||
tt_lvgl_unlock();
|
||
|
||
BundleHandle b = tt_bundle_alloc();
|
||
if (!b) {
|
||
ESP_LOGW(TAG, "Failed to alloc bundle for external DL");
|
||
return false;
|
||
}
|
||
tt_bundle_put_string(b, "url", ep->audio_url);
|
||
tt_bundle_put_string(b, "path", fpath);
|
||
tt_bundle_put_bool(b, "override", false);
|
||
|
||
ESP_LOGI(TAG, "Starting external SdDownloader for %s -> %s", ep->slug, fpath);
|
||
G.pending_dl_idx = idx;
|
||
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if (G.lbl_status) lv_label_set_text(G.lbl_status, "Starting downloader...");
|
||
tt_lvgl_unlock();
|
||
|
||
tt_app_start_with_bundle("one.tactility.sddownloader", b);
|
||
return true;
|
||
}
|
||
|
||
int count_missing_in_season(int season){
|
||
int cnt=0;
|
||
for(int i=0;i<G.ep_cnt;i++){
|
||
if(G.eps[i].season==season && !G.eps[i].seen && strlen(G.eps[i].audio_url)>=8) cnt++;
|
||
}
|
||
return cnt;
|
||
}
|
||
int find_next_missing_in_season(int season){
|
||
for(int i=0;i<G.ep_cnt;i++){
|
||
if(G.eps[i].season==season && !G.eps[i].seen && strlen(G.eps[i].audio_url)>=8) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
bool download_episode(int idx){
|
||
return try_external_downloader(idx);
|
||
}
|
||
bool download_missing_next(void){
|
||
if(!G.dl_missing_active) return false;
|
||
int next = find_next_missing_in_season(G.dl_missing_season);
|
||
if(next==-1){
|
||
// No more missing – done
|
||
G.dl_missing_active=false;
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status){
|
||
char msg[64];
|
||
snprintf(msg,sizeof(msg),"DL S%02d done %d ok %d fail", G.dl_missing_season, G.dl_missing_done, G.dl_missing_failed);
|
||
lv_label_set_text(G.lbl_status, msg);
|
||
}
|
||
tt_lvgl_unlock();
|
||
return false;
|
||
}
|
||
// Update UI for next file
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status){
|
||
char msg[64];
|
||
snprintf(msg,sizeof(msg),"DL %d/%d S%02d...", G.dl_missing_done+1, G.dl_missing_total, G.dl_missing_season);
|
||
lv_label_set_text(G.lbl_status, msg);
|
||
}
|
||
tt_lvgl_unlock();
|
||
return try_external_downloader(next);
|
||
}
|
||
bool download_missing_season_start(int season){
|
||
int missing = count_missing_in_season(season);
|
||
if(missing==0){
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Season already cached");
|
||
tt_lvgl_unlock();
|
||
G.dl_missing_active=false;
|
||
return true;
|
||
}
|
||
ESP_LOGI(TAG, "Starting sequential DL for S%02d, %d missing", season, missing);
|
||
G.dl_missing_active=true;
|
||
G.dl_missing_season=season;
|
||
G.dl_missing_total=missing;
|
||
G.dl_missing_done=0;
|
||
G.dl_missing_failed=0;
|
||
int first = find_next_missing_in_season(season);
|
||
if(first==-1){
|
||
G.dl_missing_active=false;
|
||
return true;
|
||
}
|
||
// Update status
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status){
|
||
char msg[64];
|
||
snprintf(msg,sizeof(msg),"DL 1/%d S%02d...", missing, season);
|
||
lv_label_set_text(G.lbl_status, msg);
|
||
}
|
||
tt_lvgl_unlock();
|
||
return try_external_downloader(first);
|
||
}
|
||
|
||
static bool try_external_downloader_batch(int season) {
|
||
// Sequential queue – safe single-file mode
|
||
return download_missing_season_start(season);
|
||
}
|
||
|
||
void start_idx_internal(int idx){
|
||
if(idx<0||idx>=G.ep_cnt) return;
|
||
EpInfo* ep=&G.eps[idx];
|
||
char fpath[300];
|
||
make_sd_path(fpath, sizeof(fpath), ep->slug);
|
||
|
||
if(!has_slug(ep->slug)){
|
||
ESP_LOGW(TAG, "start_idx_internal called but file missing %s", ep->slug);
|
||
start_idx(idx);
|
||
return;
|
||
}
|
||
|
||
int saved = 0;
|
||
if(G.cur_ep_idx!=idx){
|
||
saved = get_saved_pos_for_slug(ep->slug);
|
||
if(saved>5){
|
||
ESP_LOGI(TAG,"Restoring saved pos %d for %s", saved, ep->slug);
|
||
}
|
||
} else {
|
||
saved = G.pos_sec;
|
||
}
|
||
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
close_overlay();
|
||
if(G.dl_overlay) hide_dl_overlay();
|
||
tt_lvgl_unlock();
|
||
|
||
BundleHandle b = tt_bundle_alloc();
|
||
if(b){
|
||
tt_bundle_put_string(b, "file", fpath);
|
||
tt_bundle_put_string(b, "path", fpath);
|
||
tt_bundle_put_int32(b, "pos_sec", saved);
|
||
tt_bundle_put_int32(b, "position", saved);
|
||
tt_bundle_put_int32(b, "volume", G.volume);
|
||
ESP_LOGI(TAG, "Launching Mp3Player for %s pos %d", fpath, saved);
|
||
G.pending_dl_idx = idx;
|
||
G.cur_ep_idx=idx; G.cur_season=ep->season;
|
||
strncpy(G.cur_title,ep->title,sizeof(G.cur_title)-1);
|
||
strncpy(G.last_slug,ep->slug,sizeof(G.last_slug)-1);
|
||
save_state();
|
||
tt_app_start_with_bundle("one.tactility.mp3player", b);
|
||
return;
|
||
}
|
||
|
||
ESP_LOGW(TAG, "Failed to alloc bundle for Mp3Player");
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Failed to launch player");
|
||
tt_lvgl_unlock();
|
||
}
|
||
|
||
void start_idx(int idx){
|
||
if(idx<0||idx>=G.ep_cnt) return;
|
||
EpInfo* ep=&G.eps[idx];
|
||
if(!has_slug(ep->slug)){
|
||
// File not on SD, download via external downloader
|
||
if (try_external_downloader(idx)) {
|
||
return;
|
||
}
|
||
// Fallback: show message, cannot download internally anymore
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Install SdDownloader");
|
||
tt_lvgl_unlock();
|
||
return;
|
||
}
|
||
start_idx_internal(idx);
|
||
}
|
||
|
||
void select_ep(int idx){
|
||
if(idx<0||idx>=G.ep_cnt) return;
|
||
if(G.cur_ep_idx>=0 && G.cur_ep_idx < G.ep_cnt && G.cur_ep_idx!=idx){
|
||
save_current_ep_pos();
|
||
}
|
||
G.cur_ep_idx=idx;
|
||
EpInfo* ep=&G.eps[idx];
|
||
G.cur_season=ep->season;
|
||
strncpy(G.last_slug, ep->slug, sizeof(G.last_slug)-1);
|
||
|
||
int saved = get_saved_pos_for_slug(ep->slug);
|
||
G.pos_sec=saved;
|
||
G.last_pct=-1;
|
||
G.total_sec=0;
|
||
|
||
tt_lvgl_lock(portMAX_DELAY);
|
||
if(G.lbl_title) lv_label_set_text(G.lbl_title,ep->title);
|
||
if(G.lbl_meta){
|
||
char b[64];
|
||
if(saved>5){
|
||
char tbuf[16];
|
||
fmt_time(tbuf,saved);
|
||
snprintf(b,sizeof(b),"S%02d E%02d %s • %s",ep->season,ep->ep_num,ep->seen?"*":"",tbuf);
|
||
} else {
|
||
snprintf(b,sizeof(b),"S%02d E%02d %s",ep->season,ep->ep_num,ep->seen?"*":"");
|
||
}
|
||
lv_label_set_text(G.lbl_meta,b);
|
||
}
|
||
if(G.lbl_status){
|
||
if(saved>5) lv_label_set_text(G.lbl_status,"Resume – tap Play");
|
||
else lv_label_set_text(G.lbl_status,ep->seen?"On SD – tap Play":"Tap Play to download");
|
||
}
|
||
if(G.bar){
|
||
lv_bar_set_value(G.bar,0,LV_ANIM_OFF);
|
||
}
|
||
if(G.lbl_time){
|
||
if(saved>5){
|
||
char a[16];
|
||
fmt_time(a,saved);
|
||
char buf[40];
|
||
snprintf(buf,sizeof(buf),"%s / --:--",a);
|
||
lv_label_set_text(G.lbl_time,buf);
|
||
} else {
|
||
lv_label_set_text(G.lbl_time,"0:00 / --:--");
|
||
}
|
||
}
|
||
tt_lvgl_unlock();
|
||
save_state();
|
||
}
|
||
|
||
void go_next(){
|
||
if(G.ep_cnt==0) return;
|
||
save_current_ep_pos();
|
||
save_state();
|
||
int n=G.cur_ep_idx+1;
|
||
if(n>=G.ep_cnt) n=0;
|
||
select_ep(n);
|
||
// Do not auto-play next, per user request: no autoplay
|
||
}
|
||
|
||
void go_prev(){
|
||
if(G.ep_cnt==0) return;
|
||
if(G.pos_sec>5){
|
||
if(G.cur_ep_idx>=0){
|
||
set_saved_pos_for_slug(G.eps[G.cur_ep_idx].slug, 0);
|
||
save_state();
|
||
}
|
||
G.pos_sec=0;
|
||
int cur=G.cur_ep_idx;
|
||
if(cur<0) cur=0;
|
||
select_ep(cur);
|
||
return;
|
||
}
|
||
save_current_ep_pos();
|
||
save_state();
|
||
int p=G.cur_ep_idx-1;
|
||
if(p<0) p=G.ep_cnt-1;
|
||
select_ep(p);
|
||
}
|
||
|
||
// Called from UI for season batch download
|
||
bool download_season(int season) {
|
||
return try_external_downloader_batch(season);
|
||
}
|