Skip to content

[player]:Improve track transitions with same-instance mpv playback and next-track preloading - #1033

Open
shinnkka wants to merge 22 commits into
feeluown:masterfrom
shinnkka:smooth_transition
Open

[player]:Improve track transitions with same-instance mpv playback and next-track preloading#1033
shinnkka wants to merge 22 commits into
feeluown:masterfrom
shinnkka:smooth_transition

Conversation

@shinnkka

@shinnkka shinnkka commented Apr 28, 2026

Copy link
Copy Markdown
Contributor
  1. Keep the same mpv instance alive across track transitions instead of stopping and restarting playback for every song.
  2. Start preloading the next track when the current one has PLAYLIST_PRELOAD_THRESHOLD_SECONDS seconds or less remaining.5s by default. Edit config to change.
  3. Prefer mpv’s internal playlist or same-instance sequential playback for directly playable local audio.
  4. For network streams, resolve a directly playable URL ahead of time and reuse the preloaded media and metadata during handoff.
  5. enable MPV_PREFETCH_PLAYLIST by default

@shinnkka

Copy link
Copy Markdown
Contributor Author

relate #1029

@shinnkka
shinnkka marked this pull request as draft April 29, 2026 09:48
@shinnkka
shinnkka force-pushed the smooth_transition branch from 97b2267 to 79f2dbc Compare April 29, 2026 11:23
@shinnkka
shinnkka marked this pull request as ready for review April 29, 2026 14:11
Comment thread feeluown/player/playlist.py Outdated
Comment thread feeluown/app/config.py Outdated
Comment thread feeluown/player/playlist.py Outdated
@shinnkka
shinnkka marked this pull request as draft May 18, 2026 06:15
@shinnkka
shinnkka marked this pull request as ready for review May 18, 2026 08:08
@shinnkka

Copy link
Copy Markdown
Contributor Author

原来是mpris那里的bug,我还以为搞坏了什么。。。

@cosven

cosven commented May 26, 2026

Copy link
Copy Markdown
Member

这个 PR 改动比较基础,等哪天空一点,好好看看~

@shinnkka
shinnkka force-pushed the smooth_transition branch from 1d57c6d to 65ed2de Compare June 17, 2026 00:02
Comment thread feeluown/player/mpvplayer.py Outdated
Comment thread feeluown/player/mpvplayer.py Outdated
Comment thread feeluown/player/mpvplayer.py Outdated
Comment thread feeluown/player/mpvplayer.py Outdated
Comment thread feeluown/player/mpvplayer.py Outdated
Comment thread feeluown/player/mpvplayer.py Outdated
@cosven

cosven commented Jul 1, 2026

Copy link
Copy Markdown
Member

中文

我重新从最底层的 MpvPlayer 接口看了一遍这个 PR。整体方向是可以继续探索的,但现在的实现还没有把 mpv 内部 playlist 纳入 FeelUOwn 的状态机里,所以会有几个基础问题需要先解决。

1. mpv playlist 自动切换应该成为状态流转的来源

现在 PR 的思路是:提前把下一首 append 到 mpv playlist,然后仍然依赖旧的 media_finished -> Playlist.next() -> player.play(preloaded_media) 路径来同步 current_song/current_media

这个状态来源不够一致。mpv 已经可能在内部自动进入下一项,但 FeelUOwn 仍然在事后补状态。实际测试中,mpv 自动切到 queued item 时,media_loaded 会先触发,但 player.current_media 仍然是上一首,media_changed 反而在后面。这打破了现有语义:media_changed 应该表示媒体源已切换但尚未加载完成,media_loaded 才表示开始播放。

更合理的路径应该是让 mpv 的 playlist-pos / START_FILE 驱动这次切换:

mpv playlist-pos / START_FILE
  -> MpvPlayer 找到对应 queued media
  -> emit media_about_to_changed(old_media, new_media)
  -> update _current_media / _current_metadata
  -> emit media_changed(new_media)
  -> later FILE_LOADED -> emit media_loaded/media_loaded_v2

这样 MpvPlayer 才是 mpv playlist 状态的 owner,信号顺序也能保持稳定。

2. 不应该从 media/metadata 反推 song

MpvPlayer 不应该认识 SongModel,也不应该靠 media.url 反查 song。URL 可能重复、可能是 fallback 结果、也可能因为重定向/临时 URL/headers/proxy/decryption key 而不能唯一表示 song。

建议在 queue 时生成一个明确的 queued id:

queued_id = player.queue_media(media, metadata=metadata)

MpvPlayer 维护:

queued_id -> media/metadata/mpv playlist item

PlaylistPreloadManager 维护:

queued_id -> song

当 mpv 通过 playlist-pos / START_FILE 激活 queued item 时,MpvPlayer 可以发一个明确事件:

queued_media_activated.emit(queued_id, media, metadata)

然后 Playlistqueued_id 同步 current_song

3. http_headers / http_proxy 不能用全局 option 设置

现在 queue_media() 里调用 _set_http_headers() / _set_http_proxy() 会影响当前正在播放的 media,因为这些是 mpv 全局 option。

我确认了 loadfile(..., options) 对正常播放支持 per-file options,例如 http-header-fieldshttp-proxy 可以分别作用到不同文件。但这里还有一个关键限制:当 --prefetch-playlist=yes 打开时,mpv 的预取阶段并不能可靠使用下一项的 per-file options。

本地 mpv 0.41.0 测试结果:

prefetch_playlist=False:
one.m4a -> origin, X-Fuo: one
two.m4a -> proxy,  X-Fuo: two

prefetch_playlist=True:
one.m4a -> origin, X-Fuo: one
two.m4a -> origin, X-Fuo: one

也就是说,第二首预取没有走自己的 proxy,也用了第一首的 header。

短期建议:如果 Media 带有 http_headers / http_proxy / decryption_key,不要进入 mpv prefetch playlist,回退到普通切歌路径。长期如果要完整支持这类 media,可能需要 mpv hook on_load + file-local-options/<option>,在 mpv 真正打开某个 playlist item 之前设置该 item 自己的网络参数。

4. 底层接口可以收窄

MpvPlayer.queue_media() 建议先只接受 Media,去掉模糊兼容逻辑,比如 Media(media)_queued_sources 也不太需要单独维护,mpv 已经有 playlist_filenames,重复状态会带来同步问题。另外,video=False 当前没有实际生效,如果保留这个参数,需要作为 per-file option 处理;否则应先移除。

我的建议是:先把 MpvPlayer 这一层的状态机和接口边界设计清楚,再接回 PreloadManager / Playlist。否则上层再怎么补逻辑,也会被底层 mpv playlist 的自动切换和网络 option 作用域问题影响。


English

I reviewed this PR again from the lowest-level MpvPlayer interface. The general direction is worth exploring, but the current implementation does not yet make mpv's internal playlist part of FeelUOwn's state machine. A few foundational issues should be addressed first.

1. mpv playlist auto-advance should drive the state transition

The current implementation appends the next track to mpv's playlist, but still relies on the old path:

media_finished -> Playlist.next() -> player.play(preloaded_media)

to synchronize current_song/current_media.

This means there are two different sources of truth. mpv may already auto-advance to the next playlist item internally, while FeelUOwn updates its state afterwards. In a local test, when mpv auto-advanced to the queued item, media_loaded was emitted while player.current_media was still the previous media, and media_changed came later. This breaks the existing signal semantics: media_changed should mean the media source has changed but is not loaded yet, and media_loaded should come afterwards.

A better path would be to let mpv's playlist-pos / START_FILE drive this transition:

mpv playlist-pos / START_FILE
  -> MpvPlayer finds the activated queued media
  -> emit media_about_to_changed(old_media, new_media)
  -> update _current_media / _current_metadata
  -> emit media_changed(new_media)
  -> later FILE_LOADED -> emit media_loaded/media_loaded_v2

This makes MpvPlayer the owner of mpv playlist state and keeps the signal ordering stable.

2. We should not infer song from media/metadata

MpvPlayer should not know about SongModel, and it should not infer a song from media.url. URLs can be duplicated, can represent fallback media, and may not be stable because of redirects, temporary URLs, headers, proxies, or decryption keys.

I suggest generating an explicit queued id when queueing media:

queued_id = player.queue_media(media, metadata=metadata)

MpvPlayer owns:

queued_id -> media/metadata/mpv playlist item

Playlist or PreloadManager owns:

queued_id -> song

When mpv activates the queued item via playlist-pos / START_FILE, MpvPlayer can emit:

queued_media_activated.emit(queued_id, media, metadata)

Then Playlist can use queued_id to synchronize current_song.

3. http_headers / http_proxy must not be set as global options

Currently queue_media() calls _set_http_headers() / _set_http_proxy(). These set mpv global options, so they may affect the media currently being played.

I confirmed that loadfile(..., options) supports per-file options during normal playback. For example, http-header-fields and http-proxy can be applied to different files separately. However, there is an important limitation: with --prefetch-playlist=yes, mpv's prefetch stage does not reliably use the next item's per-file options.

Local test with mpv 0.41.0:

prefetch_playlist=False:
one.m4a -> origin, X-Fuo: one
two.m4a -> proxy,  X-Fuo: two

prefetch_playlist=True:
one.m4a -> origin, X-Fuo: one
two.m4a -> origin, X-Fuo: one

So the second item was prefetched without its own proxy and with the first item's header.

Short-term recommendation: if a Media has http_headers / http_proxy / decryption_key, do not put it into mpv's prefetch playlist. Fall back to the regular transition path. Long-term, full support for this probably requires mpv's on_load hook plus file-local-options/<option>, so the correct network options can be applied before mpv opens each playlist item.

4. The low-level API can be narrower

MpvPlayer.queue_media() should probably accept only Media for now and avoid broad compatibility logic such as Media(media). _queued_sources also seems unnecessary because mpv already exposes playlist_filenames; duplicating this state can create synchronization problems. Also, video=False currently does not actually affect queued playback. If the parameter stays, it should be applied as a per-file option; otherwise it should be removed for now.

My suggestion is to first clarify the MpvPlayer state machine and low-level API boundary, then connect it back to PreloadManager / Playlist. Otherwise, upper-layer logic will keep fighting mpv's internal playlist auto-advance and option scoping behavior.

@cosven

cosven commented Jul 1, 2026

Copy link
Copy Markdown
Member

上面是我和 AI 在本地进行了一些探索,让 AI 评论上去的。

经过和 AI 的分析讨论,我觉得要上 prefetch-playlist 这个功能,有几个核心 block 点

  1. 不同的 media 可能有不同的 http_headers / http_proxy 属性,但是 mpv 的 playlist 不支持为不同的 item 指定不同的 http 属性。这是一个硬性 block 点。对应 AI 说的第 3 点。
  2. 状态机的流转(对应 AI 说的第 1 点)。AI 说的比较具体的,不复述了。

  1. AI 说的第 2 点也是个问题。不过是可以通过详细规划方案来解决的。
  2. AI 说的第 4 点可以先忽略

@shinnkka

shinnkka commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

cc @cosven

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants