fix(ncm §25): 切音质后重播同一曲被卡「缓冲中」——play intent 以 seq 标识(同 idx 再 play 重启换档)
真机反馈:切音质后再播同一首歌,声音已出但播放页恒显示「缓冲中」。
根因(app/play/player_manager.c):同 idx 的新播放意图不被解码主循环识别。
切音质后重播同曲 = player_manager_play(idx),而 idx 与在播曲相同——cur_song 不变
(仍是 idx)、state 由 PLAYING 置 BUFFER、seq++。旧仍_current(cur==idx && state==BUFFER
恒真)与解码主循环(只认 cur!=idx / STOPPED)均不放过,于是:
①旧曲(旧档位)继续出声、state 停在 BUFFER → UI 恒「缓冲中…」(dl_done=0 无百分比);
②新档位下载/缓存条目从未发起;
③旧曲收尾后外层把 BUFFER 当「自然播完」顺延下一首——新档位永不生效。
三条取数路径(缓存命中/边下边播/.part续传)统一受影响(根因在 intent 层)。
修法:play intent 以 seq 标识,层层判别「我的 intent 是否仍有效」:
- 新增 still_intent(idx, my_seq)(cur==idx && state==BUFFER && seq==my_seq),取代
仍_current(同 idx 重播时 seq 变即判过期),贯穿 预缓冲/重试退避/物化/解码 open 各护栏;
- run_song 入口捕获 my_seq,沿 song_local_file/song_download_attempt 下传;
- 解码主循环 seq != my_seq → break 回外层重跑 run_song(换档重播);
- 外层「自然播完」收紧为 state==PLAYING,不把 BUFFER(挂起新 intent)当播完顺延下一首。
验证:player_flow_sim 新增 P12(同 idx 重播换档重启)——修前恒 PLAYING 不了/键仍旧档/
downloads=0/eff=-1;修后 state 回 PLAYING、下载新档位、缓存键 1000_lossless、eff_level
按非会员钳顶回填 EXHIGH。P1-P11 零回归(宿主+qemu-mipsel ALL PASS)。build_ncm.sh
自有代码 -Wall -Wextra 0 warning。0 真实 NCM 请求。未部署真机(留主会话)。
REPORT.md 另记(docs commit)。
This commit is contained in:
1 parent
51281e609f
commit
a01e5ca18a
4 files changed
+79
-18
No files matched your search
@@ -17,6 +17,12 @@
|
|||||||
* 下载完成由播放线程 commit(rename);缓存命中用 final=size 的静态 stream_src(P1:
|
* 下载完成由播放线程 commit(rename);缓存命中用 final=size 的静态 stream_src(P1:
|
||||||
* 不再 mmap 整文件);http 30s 绝对 deadline 已改空闲超时(P2)。.part 续传/LRU 防淘汰
|
* 不再 mmap 整文件);http 30s 绝对 deadline 已改空闲超时(P2)。.part 续传/LRU 防淘汰
|
||||||
* 留阶段 3。
|
* 留阶段 3。
|
||||||
|
* §25:切音质后重播同一曲——同 idx 再 play 时 cur/state 都不变(cur 同、state 由 PLAYING
|
||||||
|
* 置 BUFFER),解码主循环此前只认 cur!=idx / STOPPED,导致:
|
||||||
|
* ①旧曲(旧档)继续出声而 state 停在 BUFFER(UI 恒「缓冲中」);②旧曲收尾后外层把
|
||||||
|
* BUFFER 当「自然播完」顺延下一首,新档位永不生效。修法:以 seq 标识 play intent
|
||||||
|
* (still_intent 取代仍_current),decode 主循环 seq 变化即 break,外层「自然播完」仅指
|
||||||
|
* state==PLAYING——同 idx 新 intent 回到外层重跑 run_song(换档重播)。
|
||||||
*/
|
*/
|
||||||
#include "../ncm_app_internal.h"
|
#include "../ncm_app_internal.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -292,12 +298,16 @@ static void audio_cache_key(char *out, size_t sz, const char *id, int level)
|
|||||||
snprintf(out, sz, "%s_%s", id, ncm_api_level_key(level));
|
snprintf(out, sz, "%s_%s", id, ncm_api_level_key(level));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 切歌竞态护栏(§18):物化/下载/预缓冲期间用户可能已切歌——只有仍是当前目标且处于
|
/* 同曲重播护栏(§25 切音质后重播同一曲,替代 §18 的 still_current):仅 cur/state 不够——
|
||||||
* BUFFER(本次播放意图未过期)才继续,否则直接回外层(外层会以新 cur 重跑 run_song)。 */
|
* 切音质时 play_manager_play
|
||||||
static int still_current(int idx)
|
* 用的 idx 与在播曲相同(cur 不变)且置 state=BUFFER,仍会被 still_current 当「我的 intent
|
||||||
|
* 有效」而漏判。play/next 都会 seq++(新播放意图),故以 seq 判别「本次 intent 是否仍有效」。
|
||||||
|
* 调用方需持有 run_song 开始时捕获的 my_seq(本曲 intent 的 seq)。 */
|
||||||
|
static int still_intent(int idx, int my_seq)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&g_play.mu);
|
pthread_mutex_lock(&g_play.mu);
|
||||||
int ok = (g_play.cur_song == idx) && (g_play.state == NCM_PC_BUFFER);
|
int ok = (g_play.cur_song == idx) && (g_play.state == NCM_PC_BUFFER) &&
|
||||||
|
(g_play.seq == my_seq);
|
||||||
pthread_mutex_unlock(&g_play.mu);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@@ -306,7 +316,7 @@ static int still_current(int idx)
|
|||||||
* 返回:NCM_OK=src 就绪(下载在后台继续);DL_RETRYABLE=预缓冲前网络失败(调用方整首重试);
|
* 返回:NCM_OK=src 就绪(下载在后台继续);DL_RETRYABLE=预缓冲前网络失败(调用方整首重试);
|
||||||
* DL_ABORTED=切歌/退出(静默放弃,不重试);其它负数=硬错误(不重试)。
|
* DL_ABORTED=切歌/退出(静默放弃,不重试);其它负数=硬错误(不重试)。
|
||||||
* *real_err 回填预缓冲失败的真实错误码。失败时 ss->w/src/dl_thr 已清理干净。 */
|
* *real_err 回填预缓冲失败的真实错误码。失败时 ss->w/src/dl_thr 已清理干净。 */
|
||||||
static int song_download_attempt(const ncm_song_t *s, int idx, const char *key, int want,
|
static int song_download_attempt(const ncm_song_t *s, int idx, int seq, const char *key, int want,
|
||||||
song_src_t *ss, char *path, size_t psz, int64_t *bytes,
|
song_src_t *ss, char *path, size_t psz, int64_t *bytes,
|
||||||
int *real_err)
|
int *real_err)
|
||||||
{
|
{
|
||||||
@@ -366,16 +376,16 @@ static int song_download_attempt(const ncm_song_t *s, int idx, const char *key,
|
|||||||
ss->dl_thr_on = 1;
|
ss->dl_thr_on = 1;
|
||||||
|
|
||||||
/* 预缓冲:≥128KB(minimp3 强制门槛,F3)才放行 open_cb;final/failed 提前出。
|
/* 预缓冲:≥128KB(minimp3 强制门槛,F3)才放行 open_cb;final/failed 提前出。
|
||||||
* still_current 护栏覆盖本阻塞点:等待期间切歌/停止 → 保留 .part 静默放弃。 */
|
* still_intent 护栏覆盖本阻塞点:等待期间切歌/停止/同曲重播 → 保留 .part 静默放弃。 */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int wr = stream_src_wait(ss->src, PREBUFFER_BYTES, 100);
|
int wr = stream_src_wait(ss->src, PREBUFFER_BYTES, 100);
|
||||||
if (wr != 0) break;
|
if (wr != 0) break;
|
||||||
if (g_app_quit || !still_current(idx)) {
|
if (g_app_quit || !still_intent(idx, seq)) {
|
||||||
stream_src_abort(ss->src);
|
stream_src_abort(ss->src);
|
||||||
pthread_join(ss->dl_thr, NULL); ss->dl_thr_on = 0;
|
pthread_join(ss->dl_thr, NULL); ss->dl_thr_on = 0;
|
||||||
ncm_cache_abort_part(ss->w); ss->w = NULL; /* 切歌/退出:保留残体供下次续传 */
|
ncm_cache_abort_part(ss->w); ss->w = NULL; /* 切歌/退出:保留残体供下次续传 */
|
||||||
stream_src_close(ss->src); ss->src = NULL;
|
stream_src_close(ss->src); ss->src = NULL;
|
||||||
return DL_ABORTED; /* 调用方 still_current 复核后静默返回 */
|
return DL_ABORTED; /* 调用方 still_intent 复核后静默返回 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stream_src_aborted(ss->src)) {
|
if (stream_src_aborted(ss->src)) {
|
||||||
@@ -411,7 +421,7 @@ static int song_download_attempt(const ncm_song_t *s, int idx, const char *key,
|
|||||||
* (8=免费听低音质,播放允许)。本地缓存命中优先于门禁(缓存可播不拦)。
|
* (8=免费听低音质,播放允许)。本地缓存命中优先于门禁(缓存可播不拦)。
|
||||||
* - VIP 档上限:用户档超会员权限不硬拒,播放时先钳到会员允许的最高档再逐级降级
|
* - VIP 档上限:用户档超会员权限不硬拒,播放时先钳到会员允许的最高档再逐级降级
|
||||||
* (免去注定空 URL 的高档请求);实际生效档回填 g_play.eff_level。 */
|
* (免去注定空 URL 的高档请求);实际生效档回填 g_play.eff_level。 */
|
||||||
static int song_local_file(const ncm_song_t *s, int idx, song_src_t *ss,
|
static int song_local_file(const ncm_song_t *s, int idx, int seq, song_src_t *ss,
|
||||||
char *path, size_t psz, int64_t *bytes)
|
char *path, size_t psz, int64_t *bytes)
|
||||||
{
|
{
|
||||||
memset(ss, 0, sizeof *ss);
|
memset(ss, 0, sizeof *ss);
|
||||||
@@ -467,10 +477,10 @@ static int song_local_file(const ncm_song_t *s, int idx, song_src_t *ss,
|
|||||||
meow_log(MEOW_LOG_WARN, "play: download retry %d/%d id=%s backoff=%dms",
|
meow_log(MEOW_LOG_WARN, "play: download retry %d/%d id=%s backoff=%dms",
|
||||||
attempt, STREAM_RETRY_MAX, id, STREAM_RETRY_BACKOFF_MS);
|
attempt, STREAM_RETRY_MAX, id, STREAM_RETRY_BACKOFF_MS);
|
||||||
usleep(STREAM_RETRY_BACKOFF_MS * 1000); /* 退避:不轰炸 */
|
usleep(STREAM_RETRY_BACKOFF_MS * 1000); /* 退避:不轰炸 */
|
||||||
if (g_app_quit || !still_current(idx)) return NCM_ERR_IO;
|
if (g_app_quit || !still_intent(idx, seq)) return NCM_ERR_IO;
|
||||||
}
|
}
|
||||||
int real_err = NCM_ERR_NET;
|
int real_err = NCM_ERR_NET;
|
||||||
int rc = song_download_attempt(s, idx, key, want, ss, path, psz, bytes, &real_err);
|
int rc = song_download_attempt(s, idx, seq, key, want, ss, path, psz, bytes, &real_err);
|
||||||
if (rc == NCM_OK) return NCM_OK;
|
if (rc == NCM_OK) return NCM_OK;
|
||||||
if (rc == DL_RETRYABLE) { last_rc = real_err; continue; } /* 重试整首 */
|
if (rc == DL_RETRYABLE) { last_rc = real_err; continue; } /* 重试整首 */
|
||||||
return rc; /* DL_ABORTED / 硬错误:不重试 */
|
return rc; /* DL_ABORTED / 硬错误:不重试 */
|
||||||
@@ -492,12 +502,21 @@ static void run_song(int idx)
|
|||||||
{
|
{
|
||||||
/* §17 滑窗兼容:物化目标曲(窗内命中 / 盘缓存 / song_detail 单曲)并整体快照进
|
/* §17 滑窗兼容:物化目标曲(窗内命中 / 盘缓存 / song_detail 单曲)并整体快照进
|
||||||
* g_play.play_song——之后本曲所有字段读快照,窗口滑出当前播放曲不影响播放。 */
|
* g_play.play_song——之后本曲所有字段读快照,窗口滑出当前播放曲不影响播放。 */
|
||||||
|
/* §25:本条 run_song 服务的 play intent 用 seq 标识(play/next 递增)。切音质后重播
|
||||||
|
* 同一曲(同 idx 再 play)也会 seq++,故「我的 intent 是否仍有效」必须以 seq 判别
|
||||||
|
* (仅 cur/state 不够:同 idx 重播时 cur 不变、state 仍 BUFFER,详见 still_intent)。 */
|
||||||
|
pthread_mutex_lock(&g_play.mu);
|
||||||
|
int my_seq = g_play.seq;
|
||||||
|
int my_cur = g_play.cur_song;
|
||||||
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
if (my_cur != idx) return; /* 外层已同步过,防御 */
|
||||||
|
|
||||||
ncm_song_t s;
|
ncm_song_t s;
|
||||||
if (appstate_queue_song(idx, &s) != 0) { /* §23:按 queue_src 取(窗口 或 搜索队列) */
|
if (appstate_queue_song(idx, &s) != 0) { /* §23:按 queue_src 取(窗口 或 搜索队列) */
|
||||||
set_err(NCM_ERR_ARG, "load failed");
|
set_err(NCM_ERR_ARG, "load failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!still_current(idx)) return; /* 物化期间已切歌/停止:不写过期快照 */
|
if (!still_intent(idx, my_seq)) return; /* 物化期间已切歌/重播:不写过期快照 */
|
||||||
pthread_mutex_lock(&g_play.mu);
|
pthread_mutex_lock(&g_play.mu);
|
||||||
g_play.play_song = s;
|
g_play.play_song = s;
|
||||||
g_play.play_song_valid = 1;
|
g_play.play_song_valid = 1;
|
||||||
@@ -506,16 +525,16 @@ static void run_song(int idx)
|
|||||||
song_src_t ss;
|
song_src_t ss;
|
||||||
int64_t file_bytes = 0;
|
int64_t file_bytes = 0;
|
||||||
char path[512];
|
char path[512];
|
||||||
int rc = song_local_file(&s, idx, &ss, path, sizeof path, &file_bytes);
|
int rc = song_local_file(&s, idx, my_seq, &ss, path, sizeof path, &file_bytes);
|
||||||
if (rc != NCM_OK) {
|
if (rc != NCM_OK) {
|
||||||
if (!still_current(idx)) return; /* 下载期间已切歌:静默放弃(已落盘部分不亏) */
|
if (!still_intent(idx, my_seq)) return; /* 期间已切歌/同曲重播:静默放弃(已落盘部分不亏) */
|
||||||
const char *m = (rc == NCM_ERR_VIP) ? "VIP only (fee gate)"
|
const char *m = (rc == NCM_ERR_VIP) ? "VIP only (fee gate)"
|
||||||
: (rc == NCM_ERR_NOTFOUND) ? "no stream (VIP/copyright)"
|
: (rc == NCM_ERR_NOTFOUND) ? "no stream (VIP/copyright)"
|
||||||
: (rc == NCM_ERR_AUTH) ? "session expired" : "load failed";
|
: (rc == NCM_ERR_AUTH) ? "session expired" : "load failed";
|
||||||
set_err(rc, m);
|
set_err(rc, m);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!still_current(idx)) { /* 预缓冲完成但已切歌:勿开解码/勿动状态 */
|
if (!still_intent(idx, my_seq)) { /* 预缓冲完成但已切歌/同曲重播:勿开解码/勿动状态 */
|
||||||
song_src_dispose(&ss);
|
song_src_dispose(&ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -531,7 +550,7 @@ static void run_song(int idx)
|
|||||||
int64_t total_frames = 0;
|
int64_t total_frames = 0;
|
||||||
if (ncm_dec_open_src(&dec, ss.src, &rate, &ch, &bits, &total_frames) != 0) {
|
if (ncm_dec_open_src(&dec, ss.src, &rate, &ch, &bits, &total_frames) != 0) {
|
||||||
song_src_dispose(&ss);
|
song_src_dispose(&ss);
|
||||||
if (!still_current(idx)) return; /* open 期间(flac 元数据等待)已切歌 */
|
if (!still_intent(idx, my_seq)) return; /* open 期间(flac 元数据等待)已切歌/重播 */
|
||||||
set_err(NCM_ERR_IO, "decode open failed"); return;
|
set_err(NCM_ERR_IO, "decode open failed"); return;
|
||||||
}
|
}
|
||||||
if (dur <= 0 && total_frames > 0 && rate > 0) dur = total_frames * 1000 / rate;
|
if (dur <= 0 && total_frames > 0 && rate > 0) dur = total_frames * 1000 / rate;
|
||||||
@@ -570,10 +589,16 @@ static void run_song(int idx)
|
|||||||
int64_t seek_ms = g_play.cmd_seek_ms;
|
int64_t seek_ms = g_play.cmd_seek_ms;
|
||||||
int cur = g_play.cur_song;
|
int cur = g_play.cur_song;
|
||||||
int st = g_play.state;
|
int st = g_play.state;
|
||||||
|
int seq = g_play.seq;
|
||||||
pthread_mutex_unlock(&g_play.mu);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
|
|
||||||
if (cur != idx) { meow_log(MEOW_LOG_INFO, "play: switch idx=%d -> %d", idx, cur); break; } /* 切歌 → 回外层 */
|
if (cur != idx) { meow_log(MEOW_LOG_INFO, "play: switch idx=%d -> %d", idx, cur); break; } /* 切歌 → 回外层 */
|
||||||
if (st == NCM_PC_STOPPED) { meow_log(MEOW_LOG_INFO, "play: stopped idx=%d", idx); break; }
|
if (st == NCM_PC_STOPPED) { meow_log(MEOW_LOG_INFO, "play: stopped idx=%d", idx); break; }
|
||||||
|
if (seq != my_seq) { /* §25:同 idx 新播放意图(切音质后重播同一曲)——seq 变
|
||||||
|
* 即新 intent,必须打断当前曲回外层重跑(换档重播)。 */
|
||||||
|
meow_log(MEOW_LOG_INFO, "play: new intent idx=%d seq=%d->%d -> restart", idx, my_seq, seq);
|
||||||
|
break; /* 当前曲(旧档)立即停,外层按新 cur/state 重跑 run_song */
|
||||||
|
}
|
||||||
|
|
||||||
if (cmd == NCM_CTL_SEEK && seek_ms >= 0) { need_seek_ms = (int)seek_ms; }
|
if (cmd == NCM_CTL_SEEK && seek_ms >= 0) { need_seek_ms = (int)seek_ms; }
|
||||||
|
|
||||||
@@ -688,9 +713,11 @@ static void *player_thread(void *arg)
|
|||||||
int cur2 = g_play.cur_song;
|
int cur2 = g_play.cur_song;
|
||||||
int st2 = g_play.state;
|
int st2 = g_play.state;
|
||||||
pthread_mutex_unlock(&g_play.mu);
|
pthread_mutex_unlock(&g_play.mu);
|
||||||
/* 自然播完且无新命令 → 顺延下一首;否则回到循环等待 */
|
/* 自然播完(decode 循环正常退出,state 仍 PLAYING)且无新命令 → 顺延下一首;
|
||||||
|
* 若是因同 idx 重播(seq 变、state=BUFFER)返回,则循环顶会按当前 cur/state 重跑
|
||||||
|
* run_song(§25:切音质后重播同一曲换档生效),**不得**当播完顺延下一首。 */
|
||||||
int qc = appstate_queue_count();
|
int qc = appstate_queue_count();
|
||||||
if (cur2 >= 0 && cur2 < qc && st2 != NCM_PC_STOPPED && cur2 == cur) {
|
if (cur2 >= 0 && cur2 < qc && st2 == NCM_PC_PLAYING && cur2 == cur) {
|
||||||
int nx = cur + 1;
|
int nx = cur + 1;
|
||||||
if (nx < qc) { player_manager_next(1); }
|
if (nx < qc) { player_manager_next(1); }
|
||||||
else { player_manager_stop(); }
|
else { player_manager_stop(); }
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
* P4 下载途中切歌 → 旧曲下载完成后**不得**把状态改回 PLAYING/写过期快照
|
* P4 下载途中切歌 → 旧曲下载完成后**不得**把状态改回 PLAYING/写过期快照
|
||||||
* (still_current 护栏;旧代码在切歌后 ~下载完成点 会把 BUFFER 覆盖成 PLAYING);
|
* (still_current 护栏;旧代码在切歌后 ~下载完成点 会把 BUFFER 覆盖成 PLAYING);
|
||||||
* P5 错误后 play(cur) 重试 → 状态重新走 BUFFER→PLAYING,err 清零。
|
* P5 错误后 play(cur) 重试 → 状态重新走 BUFFER→PLAYING,err 清零。
|
||||||
|
* P12 切音质后重播同一曲(§25):同 idx 在播时再 play —— 修前被解码主循环忽略
|
||||||
|
* (旧曲继续出声、state 恒 BUFFER/「缓冲中」,收尾被外层当播完顺延下一首);
|
||||||
|
* 修后 seq 变化即重启换档,状态回 PLAYING、新档位下载、eff_level 回填。
|
||||||
*
|
*
|
||||||
* 编译(宿主,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 \
|
||||||
@@ -508,6 +511,37 @@ int main(void)
|
|||||||
CHECK(player_manager_error() != 0, "err set after retry exhausted (rc=%d)", player_manager_error());
|
CHECK(player_manager_error() != 0, "err set after retry exhausted (rc=%d)", player_manager_error());
|
||||||
g_dl_fail = 0;
|
g_dl_fail = 0;
|
||||||
|
|
||||||
|
printf("== P12: 切音质后重播同一曲(同 idx 在播时再 play)-> 换档重启,非卡「缓冲中」(§25)==\n");
|
||||||
|
usleep(300 * 1000);
|
||||||
|
g_eof = 0; g_reads_left = -1; g_cache_ok = 1; g_url_rc = NCM_OK; g_fee = 0;
|
||||||
|
g_part_size = 0; g_http_status = 200; g_416_first = 0; g_dl_delay_ms = 0; g_dl_fail = 0;
|
||||||
|
g_vip = NCM_VIP_NONE; g_writes = 0; g_downloads = 0; g_url_calls = 0; g_last_key[0] = 0;
|
||||||
|
/* 以 standard 档播放 song0(缓存命中,PLAYING) */
|
||||||
|
player_manager_set_quality(NCM_LEVEL_STANDARD, 10);
|
||||||
|
player_manager_play(0);
|
||||||
|
CHECK(wait_state(NCM_PC_PLAYING, 3000), "song0 playing at standard (cache hit)");
|
||||||
|
CHECK(g_writes > 0, "standard song actually emitting audio (writes=%d)", g_writes);
|
||||||
|
/* 切音质到 lossless,然后重播同一曲(同 idx=0 再次 play)。
|
||||||
|
* 新档位 = 新缓存条目(lookup 必 miss)→ 如实置 g_cache_ok=0 强制走下载(边下边播)。 */
|
||||||
|
player_manager_set_quality(NCM_LEVEL_LOSSLESS, 10);
|
||||||
|
g_cache_ok = 0;
|
||||||
|
player_manager_play(0);
|
||||||
|
/* §25 修前:同 idx 重播被解码主循环忽略(cur 不变、state 仍 BUFFER)→ 旧曲继续出声、
|
||||||
|
* 恒「缓冲中」,且旧曲收尾被外层当「自然播完」顺延下一首(新档位永不生效)。
|
||||||
|
* 修后:seq 变化即 break → 外层重跑 run_song(0) 换档下载 → 状态回到 PLAYING
|
||||||
|
* (UI 显示采样率/时间而非「缓冲中」)。 */
|
||||||
|
CHECK(wait_state(NCM_PC_PLAYING, 4000),
|
||||||
|
"re-tap same idx -> back to PLAYING (expect 换档开播,非卡 缓冲中)");
|
||||||
|
CHECK(g_downloads >= 1, "re-tap started new-quality download (downloads=%d)", g_downloads);
|
||||||
|
CHECK(strcmp(g_last_key, "1000_lossless") == 0,
|
||||||
|
"re-tap cache key=%s (expect 1000_lossless = 新档位)", g_last_key);
|
||||||
|
/* sim 账号非会员 → lossless 被 §20 钳顶到 EXHIGH;关键是 eff_level 已按新意图回填
|
||||||
|
* (UI 据此显示实际生效档,不再停 BUFFER/「缓冲中」)。 */
|
||||||
|
CHECK(player_manager_eff_level() == NCM_LEVEL_EXHIGH,
|
||||||
|
"re-tap eff_level=%d (expect EXHIGH=非会员钳顶的新档意图)", player_manager_eff_level());
|
||||||
|
g_eof = 1;
|
||||||
|
wait_state(NCM_PC_STOPPED, 3000);
|
||||||
|
|
||||||
usleep(200 * 1000);
|
usleep(200 * 1000);
|
||||||
player_manager_stop_thread();
|
player_manager_stop_thread();
|
||||||
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");
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user