fix(ncm): 歌单切换两bug — ①暂停态跨歌单点歌旧信息(play同曲护栏改按曲目id判同曲而非裸队列下标,切歌单后同下标换歌被吞play→修play_is_same_song) ②切歌单封面残留(playlist_tick补cover_changed/ack+invalidate重画头块);附带占位行cover_request野id修复;宿主P15/covswitch PASS,热更ncm
This commit is contained in:
1 parent
62ec405dca
commit
6eccb17f9b
5 files changed
+150
-17
No files matched your search
@@ -156,20 +156,46 @@ void player_manager_set_quality(int level, int cache_gb)
|
|||||||
pthread_mutex_unlock(&g_play.mu);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 同曲重入护栏的「同曲」判据(任务:跨歌单切换后,同一队列下标指向的是**另一首歌**)。
|
||||||
|
* 裸下标(song_idx==cur_song)在换歌单(播放队列内容变化)后不再成立——实测真机日志
|
||||||
|
* 「play: re-enter current idx=0 state=2 -> no restart」:A 停在 idx=0,切到 B 后点 idx=0
|
||||||
|
* 被当「点正在播的曲」吞掉 → 新歌永不起播、播放页停在上首+暂停;恢复播放(旧曲播完自动顺延
|
||||||
|
* 到不同下标)后才正常切换。故改以「候选曲 id == 正在播快照 id」判同曲:确证同曲才免重播
|
||||||
|
* 聚焦;候选未物化/队列已换(id 不符)→ 非确证,按新播放意图起播(正确方向)。
|
||||||
|
* 候选经 appstate_song_copy/appstate_search_song 做**廉价窗/数组拷贝**,绝不触网络
|
||||||
|
* (占位行未物化即返回 0,交下方正常起播)。不持 g_play.mu 调用(内部自锁读快照 id)。 */
|
||||||
|
static int play_is_same_song(int song_idx)
|
||||||
|
{
|
||||||
|
ncm_song_t cand;
|
||||||
|
int have = (appstate_get_queue() == NCM_QUEUE_SEARCH)
|
||||||
|
? (appstate_search_song(song_idx, &cand) == NCM_OK)
|
||||||
|
: (appstate_song_copy(song_idx, &cand) == NCM_OK);
|
||||||
|
if (!have || !cand.id[0]) return 0; /* 未物化:不能确证同曲 → 交下方正常起播 */
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
int same = g_play.play_song_valid && g_play.play_song.id[0] &&
|
||||||
|
strcmp(cand.id, g_play.play_song.id) == 0;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
return same;
|
||||||
|
}
|
||||||
|
|
||||||
void player_manager_play(int song_idx)
|
void player_manager_play(int song_idx)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_play.mu);
|
pthread_mutex_lock(&g_play.mu);
|
||||||
/* 任务3:点「正在播的曲」(同 idx 且 PLAYING/PAUSED,非切档重播)→ 只聚焦不重播:
|
int is_cur = (song_idx == g_play.cur_song);
|
||||||
* 不 seq++、不置 BUFFER——旧曲继续出声/停原状态,UI 由调用方 nav 到播放页。
|
int pending = g_pending_replay;
|
||||||
* 注意:set_quality 已置 g_pending_replay=1(切音质后重播同曲须换档,P12),此护栏放行。
|
int st = g_play.state;
|
||||||
* 非 PLAYING/PAUSED(STOPPED/另一曲)不受影响,照旧起播。 */
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
if (song_idx == g_play.cur_song && !g_pending_replay &&
|
/* 任务3:点「正在播的曲」→ 只聚焦不重播;**但以曲目身份(id)而非裸下标判定**——跨歌单
|
||||||
(g_play.state == NCM_PC_PLAYING || g_play.state == NCM_PC_PAUSED)) {
|
* 切换后同一队列下标已指向另一首歌(真实日志 `play: re-enter current idx=0` 误吞跨歌单
|
||||||
pthread_mutex_unlock(&g_play.mu);
|
* 点歌),故在 PLAYING/PAUSED 且同下标时再校验候选曲 == 正在播快照:同曲才免重播,
|
||||||
meow_log(MEOW_LOG_INFO, "play: re-enter current idx=%d state=%d -> no restart",
|
* 不同曲(队列已换)/未物化 → 照常起播。注意:set_quality 已置 g_pending_replay=1
|
||||||
song_idx, g_play.state);
|
* (切音质后重播同曲须换档,P12),此护栏放行。非 PLAYING/PAUSED 不受影响,照旧起播。 */
|
||||||
|
if (is_cur && !pending && (st == NCM_PC_PLAYING || st == NCM_PC_PAUSED) &&
|
||||||
|
play_is_same_song(song_idx)) {
|
||||||
|
meow_log(MEOW_LOG_INFO, "play: re-enter current idx=%d -> no restart", song_idx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
g_pending_replay = 0; /* 消费掉(新播放意图落地,后续同曲 tap 恢复「只聚焦」) */
|
g_pending_replay = 0; /* 消费掉(新播放意图落地,后续同曲 tap 恢复「只聚焦」) */
|
||||||
g_play.cur_song = song_idx;
|
g_play.cur_song = song_idx;
|
||||||
g_play.cmd = 0;
|
g_play.cmd = 0;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h> /* unlink(covswitch PPM 清理) */
|
||||||
|
|
||||||
ncm_app_state_t g_st;
|
ncm_app_state_t g_st;
|
||||||
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER,
|
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER,
|
||||||
@@ -83,6 +84,36 @@ static void mock_qr(void)
|
|||||||
g_st.login_state = NCM_LOGIN_PENDING;
|
g_st.login_state = NCM_LOGIN_PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bug1 回归(covswitch):写一张 8x8 纯色 24-bit BMP,供 cover_host_inject 注入两张不同封面
|
||||||
|
* (stb_image 明确支持 BMP;PPM 在宿主 stb 版本回 NULL → 注入失败)。 */
|
||||||
|
static int cov_make_bmp(const char *path, unsigned char r, unsigned char g, unsigned char b)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
int w = 8, h = 8, row = w * 3, img = row * h;
|
||||||
|
unsigned char hdr[54];
|
||||||
|
memset(hdr, 0, sizeof hdr);
|
||||||
|
hdr[0] = 'B'; hdr[1] = 'M';
|
||||||
|
uint32_t fsz = (uint32_t)(54 + img);
|
||||||
|
hdr[2] = (unsigned char)(fsz & 0xFF); hdr[3] = (unsigned char)((fsz >> 8) & 0xFF);
|
||||||
|
hdr[4] = (unsigned char)((fsz >> 16) & 0xFF); hdr[5] = (unsigned char)((fsz >> 24) & 0xFF);
|
||||||
|
hdr[10] = 54; /* 像素数据偏移 */
|
||||||
|
hdr[14] = 40; /* BITMAPINFOHEADER 大小 */
|
||||||
|
uint32_t ww = (uint32_t)w, hh = (uint32_t)h;
|
||||||
|
hdr[18] = (unsigned char)(ww & 0xFF); hdr[19] = (unsigned char)((ww >> 8) & 0xFF);
|
||||||
|
hdr[20] = (unsigned char)((ww >> 16) & 0xFF); hdr[21] = (unsigned char)((ww >> 24) & 0xFF);
|
||||||
|
hdr[22] = (unsigned char)(hh & 0xFF); hdr[23] = (unsigned char)((hh >> 8) & 0xFF);
|
||||||
|
hdr[24] = (unsigned char)((hh >> 16) & 0xFF); hdr[25] = (unsigned char)((hh >> 24) & 0xFF);
|
||||||
|
hdr[26] = 1; hdr[28] = 24; /* planes=1, bpp=24 */
|
||||||
|
hdr[34] = (unsigned char)(img & 0xFF); hdr[35] = (unsigned char)((img >> 8) & 0xFF);
|
||||||
|
hdr[36] = (unsigned char)((img >> 16) & 0xFF); hdr[37] = (unsigned char)((img >> 24) & 0xFF);
|
||||||
|
if (fwrite(hdr, 1, 54, f) != 54) { fclose(f); return -1; }
|
||||||
|
for (int y = 0; y < h; y++) /* 自底向上 BGR */
|
||||||
|
for (int x = 0; x < w; x++) { fputc(b, f); fputc(g, f); fputc(r, f); }
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int page_id(const char *name)
|
static int page_id(const char *name)
|
||||||
{
|
{
|
||||||
if (strcmp(name, "login") == 0) return ACT_LOGIN;
|
if (strcmp(name, "login") == 0) return ACT_LOGIN;
|
||||||
@@ -98,6 +129,7 @@ static int page_id(const char *name)
|
|||||||
if (strcmp(name, "plsplay") == 0) return ACT_PLAYLIST; /* mini 栏目检(非播放页+播放态) */
|
if (strcmp(name, "plsplay") == 0) return ACT_PLAYLIST; /* mini 栏目检(非播放页+播放态) */
|
||||||
if (strcmp(name, "homeplay") == 0) return ACT_HOME; /* mini 栏目检(首页+播放态) */
|
if (strcmp(name, "homeplay") == 0) return ACT_HOME; /* mini 栏目检(首页+播放态) */
|
||||||
if (strcmp(name, "barhit") == 0) return ACT_PLAYLIST; /* mini 栏命中自检 */
|
if (strcmp(name, "barhit") == 0) return ACT_PLAYLIST; /* mini 栏命中自检 */
|
||||||
|
if (strcmp(name, "covswitch") == 0) return ACT_PLAYLIST;/* 歌单头块封面随全局封面刷新(Bug1) */
|
||||||
if (strcmp(name, "settings") == 0) return ACT_SETTINGS;
|
if (strcmp(name, "settings") == 0) return ACT_SETTINGS;
|
||||||
if (strcmp(name, "search") == 0) return ACT_SEARCH;
|
if (strcmp(name, "search") == 0) return ACT_SEARCH;
|
||||||
if (strcmp(name, "searchhist") == 0) return ACT_SEARCH; /* §23 空态=历史列表 */
|
if (strcmp(name, "searchhist") == 0) return ACT_SEARCH; /* §23 空态=历史列表 */
|
||||||
@@ -149,7 +181,8 @@ int main(int argc, char **argv)
|
|||||||
g_st.login_state = NCM_LOGIN_PENDING;
|
g_st.login_state = NCM_LOGIN_PENDING;
|
||||||
printf("live unikey=%s qr_url=%s size=%d\n", unikey, g_st.qr_url, sz);
|
printf("live unikey=%s qr_url=%s size=%d\n", unikey, g_st.qr_url, sz);
|
||||||
}
|
}
|
||||||
else if (strcmp(name, "playlist") == 0) { mock_playlists(); mock_songs(); }
|
else if (strcmp(name, "playlist") == 0 || strcmp(name, "covswitch") == 0)
|
||||||
|
{ mock_playlists(); mock_songs(); }
|
||||||
else if (strcmp(name, "player") == 0 || strcmp(name, "plcover") == 0 ||
|
else if (strcmp(name, "player") == 0 || strcmp(name, "plcover") == 0 ||
|
||||||
strcmp(name, "pllyric") == 0 || strcmp(name, "gestest") == 0 ||
|
strcmp(name, "pllyric") == 0 || strcmp(name, "gestest") == 0 ||
|
||||||
strcmp(name, "grid") == 0) {
|
strcmp(name, "grid") == 0) {
|
||||||
@@ -265,7 +298,7 @@ int main(int argc, char **argv)
|
|||||||
if (strcmp(name, "login") == 0 || strcmp(name, "loginlive") == 0) v = login_view_create();
|
if (strcmp(name, "login") == 0 || strcmp(name, "loginlive") == 0) v = login_view_create();
|
||||||
else if (strcmp(name, "home") == 0) v = home_view_create();
|
else if (strcmp(name, "home") == 0) v = home_view_create();
|
||||||
else if (strcmp(name, "playlist") == 0 || strcmp(name, "plsplay") == 0 ||
|
else if (strcmp(name, "playlist") == 0 || strcmp(name, "plsplay") == 0 ||
|
||||||
strcmp(name, "barhit") == 0) v = playlist_view_create();
|
strcmp(name, "barhit") == 0 || strcmp(name, "covswitch") == 0) v = playlist_view_create();
|
||||||
else if (page_id(name) == ACT_PLAYER) v = player_view_create(); /* player/lands/playerbuf/playererr/modetest */
|
else if (page_id(name) == ACT_PLAYER) v = player_view_create(); /* player/lands/playerbuf/playererr/modetest */
|
||||||
else if (page_id(name) == ACT_QUEUE) v = queue_view_create(); /* §NEW 播放队列页 */
|
else if (page_id(name) == ACT_QUEUE) v = queue_view_create(); /* §NEW 播放队列页 */
|
||||||
else if (strcmp(name, "settings") == 0) v = settings_view_create();
|
else if (strcmp(name, "settings") == 0) v = settings_view_create();
|
||||||
@@ -314,6 +347,30 @@ int main(int argc, char **argv)
|
|||||||
if (page_id(name) == ACT_PLAYER) {
|
if (page_id(name) == ACT_PLAYER) {
|
||||||
player_tick(meow_ui_time_ms());
|
player_tick(meow_ui_time_ms());
|
||||||
}
|
}
|
||||||
|
if (strcmp(name, "covswitch") == 0) {
|
||||||
|
/* Bug1 回归:歌单页活跃,playlist_tick 须消费 cover_changed(头块封面随全局封面刷新)。
|
||||||
|
* 修复前 playlist_tick 不判 cover_changed/ack → 注入后 cover_changed 恒 1(头块永不重画,
|
||||||
|
* 停留上一张);修复后 tick 判变并 ack → 归 0(下次换封面再变 1,再 ack → 收敛到新封面)。 */
|
||||||
|
int ok = (cov_make_bmp("/tmp/ncm_cov_a.bmp", 220, 40, 40) == 0 &&
|
||||||
|
cov_make_bmp("/tmp/ncm_cov_b.bmp", 40, 90, 220) == 0);
|
||||||
|
if (ok) {
|
||||||
|
cover_host_inject("/tmp/ncm_cov_a.bmp");
|
||||||
|
int c1 = cover_changed(); /* 注入后:新封面待绘制(gen>acked) */
|
||||||
|
playlist_tick(0); /* 修复点:消费 */
|
||||||
|
int c2 = cover_changed(); /* 应 ack 归 0 */
|
||||||
|
cover_host_inject("/tmp/ncm_cov_b.bmp");
|
||||||
|
int c3 = cover_changed(); /* 再注入:又待绘制 */
|
||||||
|
playlist_tick(0);
|
||||||
|
int c4 = cover_changed(); /* 再 ack 归 0 */
|
||||||
|
ok = (c1 && !c2 && c3 && !c4);
|
||||||
|
printf("covswitch: A->changed=%d tick->acked=%d B->changed=%d tickB->acked=%d -> %s\n",
|
||||||
|
c1, c2, c3, c4, ok ? "PASS" : "FAIL");
|
||||||
|
} else {
|
||||||
|
printf("covswitch: init FAIL (bmp write)\n");
|
||||||
|
}
|
||||||
|
if (!ok) return 1;
|
||||||
|
unlink("/tmp/ncm_cov_a.bmp"); unlink("/tmp/ncm_cov_b.bmp");
|
||||||
|
}
|
||||||
if (strcmp(name, "search") == 0 || strcmp(name, "searchtap") == 0) {
|
if (strcmp(name, "search") == 0 || strcmp(name, "searchtap") == 0) {
|
||||||
search_submit(g_st.search_query); /* 宿主:应用预置搜索结果 */
|
search_submit(g_st.search_query); /* 宿主:应用预置搜索结果 */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -743,13 +743,15 @@ void player_play_song(int idx)
|
|||||||
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:歌单窗口队列 */
|
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:歌单窗口队列 */
|
||||||
/* 黑胶封面用真实专辑图(song_detail 的 al.picUrl,经 ncm_cache 缓存/限幅);
|
/* 黑胶封面用真实专辑图(song_detail 的 al.picUrl,经 ncm_cache 缓存/限幅);
|
||||||
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§17)。
|
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§17)。
|
||||||
* §18:g_st.cover_url 只作「歌单封面兜底」。 */
|
* §18:g_st.cover_url 只作「歌单封面兜底」。占位行直点(appstate_song_copy 失败)时
|
||||||
|
* s 未初始化,**不能**把 s.id 传给 cover_request(会带出一段野曲 id 参与兜底判冷却)——
|
||||||
|
* 仅物化成功才带曲目 id,否则 NULL(歌单封面,不带兜底)。 */
|
||||||
ncm_song_t s;
|
ncm_song_t s;
|
||||||
const char *cover = (appstate_song_copy(idx, &s) == 0 && s.al_pic[0])
|
int have_s = (appstate_song_copy(idx, &s) == 0);
|
||||||
? s.al_pic : g_st.cover_url;
|
const char *cover = (have_s && s.al_pic[0]) ? s.al_pic : g_st.cover_url;
|
||||||
player_manager_play(idx);
|
player_manager_play(idx);
|
||||||
appstate_set_playing_song(idx, NULL);
|
appstate_set_playing_song(idx, NULL);
|
||||||
cover_request(cover, s.id); /* 曲目专辑封面:带 id,死链时 song/detail 兜底(REPORT §NEW 任务1) */
|
cover_request(cover, have_s ? s.id : NULL);
|
||||||
g_ppage = 0; /* 新曲 → 回封面页 */
|
g_ppage = 0; /* 新曲 → 回封面页 */
|
||||||
meow_view_page_push(ACT_PLAYER);
|
meow_view_page_push(ACT_PLAYER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,14 @@ void playlist_refresh(void)
|
|||||||
void playlist_tick(uint64_t now)
|
void playlist_tick(uint64_t now)
|
||||||
{
|
{
|
||||||
(void)now;
|
(void)now;
|
||||||
|
/* 歌单头块封面跨页同步:全局单封面(与播放页/mini 栏共享)由 cover worker 异步换入,
|
||||||
|
* 本页须判 cover_changed 并重画头块封面带——否则切歌单后(或从播放页回退时)头块永远
|
||||||
|
* 停留上一张(歌单封面不随切换刷新,实机 Bug1:切到 B 顶图仍是 A 的)。仅在播放页 tick
|
||||||
|
* 消费 cover_ack 是不够的(该页 inactive 不跑);此处 play 页活跃时消费并失效封面带。 */
|
||||||
|
if (cover_changed()) {
|
||||||
|
cover_ack();
|
||||||
|
meow_ui_invalidate(r_cov.x, r_cov.y, r_cov.w, r_cov.h);
|
||||||
|
}
|
||||||
if (!g_plthr_on) return;
|
if (!g_plthr_on) return;
|
||||||
if (g_plgen == g_placked) return;
|
if (g_plgen == g_placked) return;
|
||||||
g_placked = g_plgen;
|
g_placked = g_plgen;
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
* 修后 seq 变化即重启换档,状态回 PLAYING、新档位下载、eff_level 回填。
|
* 修后 seq 变化即重启换档,状态回 PLAYING、新档位下载、eff_level 回填。
|
||||||
* P13 陈旧 hires.mp3(flac 门禁前静默降级产物,hires 键下存 mp3):song_local_file
|
* P13 陈旧 hires.mp3(flac 门禁前静默降级产物,hires 键下存 mp3):song_local_file
|
||||||
* 迁移重命名到 exhigh 键(song_<id>_exhigh.mp3)+ 本档视为 miss 走下载(§NEW 任务2)。
|
* 迁移重命名到 exhigh 键(song_<id>_exhigh.mp3)+ 本档视为 miss 走下载(§NEW 任务2)。
|
||||||
|
* P14 歌单里点「正在播的同曲」→ 只聚焦不重播(任务3 re-entry,同曲 id 判据)。
|
||||||
|
* P15 跨歌单切换后同一队列下标点歌 → 不被同曲护栏吞,起播(Bug2 回归:真机日志
|
||||||
|
* `play: re-enter current idx=0 state=2 -> no restart`,新歌永不播)。
|
||||||
*
|
*
|
||||||
* 编译(宿主,ncm_app 目录下):
|
* 编译(宿主,ncm_app 目录下):
|
||||||
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
* gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||||
@@ -61,13 +64,16 @@ void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx) { (void)cb; (voi
|
|||||||
/* core/ncm_common.c 提供 ncm_now_ms(墙钟毫秒);本 sim 不链 ncm_common.o,提供同语义桩。 */
|
/* core/ncm_common.c 提供 ncm_now_ms(墙钟毫秒);本 sim 不链 ncm_common.o,提供同语义桩。 */
|
||||||
int64_t ncm_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec*1000 + ts.tv_nsec/1000000; }
|
int64_t ncm_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec*1000 + ts.tv_nsec/1000000; }
|
||||||
|
|
||||||
/* ---- appstate stub:窗内全命中,id=1000+idx;fee 可注入(§20 P8) ---- */
|
/* ---- appstate stub:窗内全命中,id=g_song_base+1000+idx;fee 可注入(§20 P8)----
|
||||||
|
* g_song_base 模拟「播放队列内容变化」:P14 跨歌单切换后,同一队列下标指向另一首歌
|
||||||
|
* (base 0→5000,idx=0 由 id=1000 变 id=6000),用于回归 Bug2 的同曲重入护栏。 */
|
||||||
|
static int g_song_base = 0;
|
||||||
static int g_fee = 0;
|
static int g_fee = 0;
|
||||||
int appstate_song_copy(int gidx, ncm_song_t *out)
|
int appstate_song_copy(int gidx, ncm_song_t *out)
|
||||||
{
|
{
|
||||||
if (gidx < 0 || gidx >= g_st.track_ids_count) return -1;
|
if (gidx < 0 || gidx >= g_st.track_ids_count) return -1;
|
||||||
memset(out, 0, sizeof *out);
|
memset(out, 0, sizeof *out);
|
||||||
snprintf(out->id, sizeof out->id, "%d", 1000 + gidx);
|
snprintf(out->id, sizeof out->id, "%d", g_song_base + 1000 + gidx);
|
||||||
snprintf(out->name, sizeof out->name, "Song%d", gidx);
|
snprintf(out->name, sizeof out->name, "Song%d", gidx);
|
||||||
out->duration_ms = 180000;
|
out->duration_ms = 180000;
|
||||||
out->fee = g_fee;
|
out->fee = g_fee;
|
||||||
@@ -77,6 +83,10 @@ int appstate_song_fetch(int gidx, ncm_song_t *out) { return appstate_song_copy(g
|
|||||||
/* §23:player_manager 改走 appstate_queue_*(队列来源)——sim 恒 PLAYLAST 队列 = 窗口 */
|
/* §23:player_manager 改走 appstate_queue_*(队列来源)——sim 恒 PLAYLAST 队列 = 窗口 */
|
||||||
int appstate_queue_count(void) { return g_st.track_ids_count; }
|
int appstate_queue_count(void) { return g_st.track_ids_count; }
|
||||||
int appstate_queue_song(int idx, ncm_song_t *out) { return appstate_song_copy(idx, out); }
|
int appstate_queue_song(int idx, ncm_song_t *out) { return appstate_song_copy(idx, out); }
|
||||||
|
/* P14 相关:player_manager_play 的同曲护栏(play_is_same_song)会读队列来源 + 搜索数组;
|
||||||
|
* sim 恒歌单窗口队列、搜索数组为空。appstate.c 未链入,两符号此处提供桩。 */
|
||||||
|
int appstate_get_queue(void) { return NCM_QUEUE_PLAYLIST; }
|
||||||
|
int appstate_search_song(int i, ncm_song_t *out) { (void)i; (void)out; return NCM_ERR_ARG; }
|
||||||
|
|
||||||
/* ---- 缓存/网络 stub ----
|
/* ---- 缓存/网络 stub ----
|
||||||
* g_cache_ok=1 → lookup 命中(给真实存在的 /tmp 文件,免下载);
|
* g_cache_ok=1 → lookup 命中(给真实存在的 /tmp 文件,免下载);
|
||||||
@@ -650,6 +660,36 @@ int main(void)
|
|||||||
|
|
||||||
usleep(200 * 1000);
|
usleep(200 * 1000);
|
||||||
player_manager_stop_thread();
|
player_manager_stop_thread();
|
||||||
|
|
||||||
|
printf("== P15: 跨歌单切换后同 idx 点歌不被「同曲只聚焦」吞(Bug2 回归)==\n");
|
||||||
|
/* 线程已停 → 纯逻辑判护栏(无竞态)。手工造「歌单 A 的歌停在 idx=0(id=1000)」的
|
||||||
|
* 播放快照态;然后模拟切到歌单 B(g_song_base 0→5000:同一队列下标 idx=0 现指向 id=6000
|
||||||
|
* 的另一首歌——真机日志实锤 `play: re-enter current idx=0 state=2 -> no restart`:新歌
|
||||||
|
* 永不起播、播放页停在上首+暂停)。护栏改按曲目 id 判同曲后,跨歌单同下标点歌必须起播。 */
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
g_play.cur_song = 0;
|
||||||
|
g_play.state = NCM_PC_PAUSED;
|
||||||
|
g_play.play_song_valid = 1;
|
||||||
|
memset(&g_play.play_song, 0, sizeof g_play.play_song);
|
||||||
|
snprintf(g_play.play_song.id, sizeof g_play.play_song.id, "1000");
|
||||||
|
g_play.seq = 90; g_play.pos_ms = 1000; g_play.err = 0;
|
||||||
|
g_play.dl_done = 0; g_play.dl_total = 0;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
int seq_before = g_play.seq; /* 线程已停,无锁读安全 */
|
||||||
|
|
||||||
|
/* (a) 同歌单点「正在播的同曲」(同下标、同 id)→ 护栏放行:确证同曲 → 只聚焦不重播 */
|
||||||
|
g_song_base = 0;
|
||||||
|
player_manager_play(0);
|
||||||
|
CHECK(player_manager_state() == NCM_PC_PAUSED && player_manager_playing_song() == 0 &&
|
||||||
|
g_play.seq == seq_before,
|
||||||
|
"same-playlist re-tap -> PAUSED/cur=0/seq=%d (expect 不重播)", g_play.seq);
|
||||||
|
|
||||||
|
/* (b) 切到歌单 B:队列内容变化(idx=0 现为 id=6000 的另一首)→ 同 idx 点歌必须起播 */
|
||||||
|
g_song_base = 5000;
|
||||||
|
player_manager_play(0);
|
||||||
|
CHECK(player_manager_state() == NCM_PC_BUFFER && g_play.seq == seq_before + 1,
|
||||||
|
"cross-playlist idx=0 tap -> BUFFER/seq=%d (expect 起播,不被吞)", g_play.seq);
|
||||||
|
|
||||||
printf(g_fail ? "== PLAYER FLOW SIM: FAIL ==\n" : "== PLAYER FLOW SIM: ALL PASS ==\n");
|
printf(g_fail ? "== PLAYER FLOW SIM: FAIL ==\n" : "== PLAYER FLOW SIM: ALL PASS ==\n");
|
||||||
return g_fail;
|
return g_fail;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user