#include "player.h" #include "storage.h" #include "ui.h" #include "download.h" #include #include #include #include #include #include #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; } static bool try_external_downloader_batch(int season) { // Reverted to single-file mode – batch array caused crashes in SdDownloader // Now just download first missing episode in season int first_missing = -1; for(int i=0;i=8){ first_missing = i; break; } } if(first_missing==-1){ tt_lvgl_lock(portMAX_DELAY); if(G.lbl_status) lv_label_set_text(G.lbl_status,"Season already cached"); tt_lvgl_unlock(); return true; } ESP_LOGI(TAG, "DL Missing (single-file revert) – downloading first missing S%02d idx %d", season, first_missing); return try_external_downloader(first_missing); } 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); }