简介与背景
当前 FM/Radio 模式的歌曲推荐算法较为基础。Radio.fetch_songs_func(feeluown/player/radio.py:18)存在两个已知问题:
- 排序粗糙:获取到的相似歌曲没有按相似度排序(代码中有 TODO: "sort the songs by similarity")
- 无反馈机制:用户跳过/删除歌曲的行为没有被用来调整后续推荐
现有代码中的策略描述:
- similar songs (most providers have this functionality)
- a random song in the same album or similar album
- popular songs sang by the same artist or similar artist
此外,FM._on_songs_fetched(feeluown/player/fm.py:110)在获取不到足够歌曲时直接退出 FM 模式,体验不够友好。
同时,项目已具备 AI 基础设施(feeluown/ai/copilot.py,LangChain/OpenAI 兼容),可以作为增强推荐的底层能力。
方案概述
Phase 1: 基础改进(无 AI)
1.1 用户反馈信号
在 Playlist 中新增用户行为事件:
# feeluown/player/playlist.py
self.song_skipped = Signal() # (song, duration_played)
self.song_liked = Signal() # (song,)
self.song_disliked = Signal() # (song,)
触发时机:
- skip: 用户在播放 < 30% 时点击 next
- like: 用户点击收藏按钮
- dislike: 用户从播放列表中删除当前歌曲(已有 remove 逻辑)
1.2 反馈存储
新增 FeedbackStore(建议 feeluown/player/feedback.py):
class FeedbackStore:
"""基于本地 SQLite 的轻量反馈存储"""
def record_skip(self, song, duration_played)
def record_like(self, song)
def record_dislike(self, song)
def get_song_score(self, song) -> float # -1.0 ~ 1.0
def get_similar_song_weight(self, song, candidate) -> float
存储位置:~/.feeluown/feedback.db(SQLite),表结构:
CREATE TABLE feedback (
song_uri TEXT PRIMARY KEY,
skip_count INTEGER DEFAULT 0,
like_count INTEGER DEFAULT 0,
dislike_count INTEGER DEFAULT 0,
total_play_duration REAL DEFAULT 0,
last_played_at TIMESTAMP
);
CREATE TABLE song_similarity (
song_uri TEXT,
candidate_uri TEXT,
score REAL,
PRIMARY KEY (song_uri, candidate_uri)
);
1.3 改进 Radio 排序
修改 Radio.fetch_songs_func(feeluown/player/radio.py:18):
def fetch_songs_func(self, number):
valid_songs = []
while len(valid_songs) < number:
if not self._stack:
break
song = self._stack.popleft()
if song not in self._app.playlist.list():
continue
provider = self._app.library.get(song.source)
if not isinstance(provider, SupportsSongSimilar):
continue
songs = provider.song_list_similar(song)
for candidate in songs:
if candidate not in self._songs_set:
valid_songs.append(candidate)
self._similarity_cache[(song, candidate)] = 1.0
# 新增:按反馈分数排序
scored = []
for song in valid_songs:
score = self._feedback_store.get_song_score(song)
scored.append((score, song))
scored.sort(key=lambda x: x[0], reverse=True)
for _, song in scored[:number]:
self._stack.append(song)
self._songs_set.add(song)
return [s for _, s in scored[:number]]
1.4 FM 模式降级而非退出
修改 FM._on_songs_fetched(feeluown/player/fm.py:110):
def _on_songs_fetched(self, future):
try:
songs = future.result()
except asyncio.CancelledError:
logger.exception("fm-fetch-songs task is cancelled")
except ProviderIOError:
logger.exception("fm-fetch-songs io error")
else:
if len(songs) < self._minimum_per_fetch:
# 改进:不立即退出,而是从播放历史中随机补充
fallback_songs = self._get_fallback_songs(self._minimum_per_fetch)
if fallback_songs:
self._feed_playlist(fallback_songs)
else:
self._app.show_msg(t("track-radio-not-enough"))
self.deactivate()
else:
self._feed_playlist(songs)
finally:
self._is_fetching_songs = False
Phase 2: AI 增强(可选,后续迭代)
利用 feeluown/ai/copilot.py 的 LangChain 集成,基于用户反馈历史生成更精准的推荐:
- 分析用户喜欢的歌曲的 artist/genre/era 模式
- 使用 embedding 模型计算歌曲语义相似度(而非仅依赖 provider 的 similar 接口)
- 在用户跳过特定歌曲时,反向学习排除该特征
此阶段依赖 Phase 1 的反馈数据积累,建议作为后续独立 PR。
不在本次范围内
- 跨 provider 的相似度整合(Phase 1 仅使用当前 provider 的 similar 接口)
- 社交功能(共享 radio 偏好)
- 实时学习模型(使用本地统计而非在线学习)
参考
feeluown/player/radio.py:18 — Radio.fetch_songs_func(含 TODO)
feeluown/player/radio.py:49 — TODO: "sort the songs by similarity"
feeluown/player/fm.py:83 — _on_playlist_eof_reached
feeluown/player/fm.py:110 — _on_songs_fetched(降级退出逻辑)
feeluown/ai/copilot.py — AI 基础设施
feeluown/player/recently_played.py — 播放历史数据
简介与背景
当前 FM/Radio 模式的歌曲推荐算法较为基础。
Radio.fetch_songs_func(feeluown/player/radio.py:18)存在两个已知问题:现有代码中的策略描述:
此外,
FM._on_songs_fetched(feeluown/player/fm.py:110)在获取不到足够歌曲时直接退出 FM 模式,体验不够友好。同时,项目已具备 AI 基础设施(
feeluown/ai/copilot.py,LangChain/OpenAI 兼容),可以作为增强推荐的底层能力。方案概述
Phase 1: 基础改进(无 AI)
1.1 用户反馈信号
在
Playlist中新增用户行为事件:触发时机:
1.2 反馈存储
新增
FeedbackStore(建议feeluown/player/feedback.py):存储位置:
~/.feeluown/feedback.db(SQLite),表结构:1.3 改进 Radio 排序
修改
Radio.fetch_songs_func(feeluown/player/radio.py:18):1.4 FM 模式降级而非退出
修改
FM._on_songs_fetched(feeluown/player/fm.py:110):Phase 2: AI 增强(可选,后续迭代)
利用
feeluown/ai/copilot.py的 LangChain 集成,基于用户反馈历史生成更精准的推荐:此阶段依赖 Phase 1 的反馈数据积累,建议作为后续独立 PR。
不在本次范围内
参考
feeluown/player/radio.py:18— Radio.fetch_songs_func(含 TODO)feeluown/player/radio.py:49— TODO: "sort the songs by similarity"feeluown/player/fm.py:83—_on_playlist_eof_reachedfeeluown/player/fm.py:110—_on_songs_fetched(降级退出逻辑)feeluown/ai/copilot.py— AI 基础设施feeluown/player/recently_played.py— 播放历史数据