Skip to content

Releases

v1.2.0

Choose a tag to compare

@doitian doitian released this 09 Oct 11:13
ad12fbb

Features

  • Add space shortcut for play/pause (9379aae)

Fixes

  • Keep volume controls together on narrow screens (#11)

Performance

  • Prefetch the next track's introduction and lyrics, pre-resolve its audio URL (#10)

v1.1.0

Choose a tag to compare

@doitian doitian released this 07 Oct 20:14
1620f46

Features

  • Push player state over an event stream instead of polling (#5)
  • panel: Scroll the queue to the song that starts playing (#4)
  • Fade play/pause and debounce seeks behind a silent hold (#3)

Fixes

  • Stream audio from the shell's HTTP origin so seeking works (#2)

v1.0.0

Choose a tag to compare

@doitian doitian released this 06 Oct 00:33
edd02bf

The first stable release: a NetEase Cloud Music player inside DeepSeek Harness, with an AI DJ that keeps the queue going and an agent that can drive it.

What 1.0 gives you: in-app playback at the quality you choose, QR sign-in, your NetEase likes and listening history, synced lyrics and song details with AI-written introductions, a queue that survives a restart, and a DJ that learns from what you like, skip and dislike, steered by a mood or a temporary boost. The player follows the Harness's light or dark theme.

Changes since 0.1.10:

The next song's details are ready when it starts. While Details is shown, the player fetches the next song's album, genre and tags in the background, so they appear the moment it begins instead of after a moment's loading. Nothing is fetched ahead in shuffle, or while Lyrics is shown.

The autoplay notice no longer flashes. Pressing Play used to show the "needs one interaction" notice for half a second before the music started. It now appears only when the browser actually refuses to play.

Notices don't move the page. When the autoplay notice does appear, it floats just below the header instead of pushing the cover and controls down, so nothing jumps under your pointer.

Updating needs a restart of DeepSeek Harness: part of the autoplay fix is in the audio engine, which the Harness loads once.

Verified on the release commit with npm run check, the six deterministic suites (219 tests) and the live integration suite against NetEase (43 tests); each commit since 0.1.10 also passes the deterministic suites on its own. The notice was checked in a browser against a stand-in host, at desktop and phone width.

v0.1.10

Choose a tag to compare

@doitian doitian released this 06 Oct 00:06
4e3b107

Song details beside the lyrics, a queue that survives a restart, and a player that matches DeepSeek Harness's light or dark theme.

Song details. The card under the transport controls now opens on Details: the song's album (edition, release date, label), genre, tags, language and tempo, awards, and the films or shows it was featured in. Switch to Lyrics for the synced lines; the player remembers your choice. Only the pane you're looking at is fetched and kept up to date.

AI introductions. Below the details, the DJ's model writes a short introduction to the song and its singer from what NetEase knows about them, in the player's language. It loads after everything else, so the song never waits for it. Click it to read NetEase's own text instead: a listener review and introductions to each singer and to the album. Without a model, NetEase's text shows directly.

The queue survives a restart. The queue and the current song are saved, and come back paused where you left off, so you can press play straight away instead of waiting for the DJ's first batch.

Clear the queue. A new Clear button in the queue header empties the queue except the song playing. Unlike removing songs one by one, it doesn't count them as dislikes. With the DJ on, it refills the queue right away.

Mood changes and boosts take effect sooner. After you change the mood or set a boost, the DJ's next batch replaces its own picks that haven't played yet, so the new direction starts right away. The song playing and songs you queued yourself stay.

Light and dark. The player follows DeepSeek Harness's theme, switching with it, and uses its colours, including a custom theme. Its scrollbars are now Harness's thin rounded ones, and a short window no longer shows two scrollbars on the left.

Verified on the release commit with npm run check, the six deterministic suites (214 tests) and the live integration suite against NetEase (43 tests). The panel was checked in a browser against a stand-in host, in light and dark; following the Harness's own theme switch is covered by tests and is best seen after updating.

v0.1.9

Choose a tag to compare

@doitian doitian released this 05 Oct 14:00
33fcb3c

A switch for your listening history, and icons that finally match.

Listening history switch. While you're signed in, the top of the player has a Listening history switch beside your account. Turn it off to keep your plays local, and back on to add them to your NetEase history again. Your choice is remembered, and it wins over scrobble in the plugin's config.

Consistent icons. Some controls used text symbols (♡ ✕ ▲ ▼ ▶) that each system draws in its own font — on Windows, some as colour emoji — next to icons drawn differently. Every icon on the page is now from one set, drawn the same way: a liked song fills its heart, the playing song and the play button use the same solid play icon, and the ✕ is the same wherever it removes or dislikes something.

Verified on the release commit with npm run check, the six deterministic suites (189 tests) and the live integration suite against NetEase (40 tests). The new look was checked in a browser against a stand-in host; how it sits in DeepSeek Harness's own theme is best seen after updating.

v0.1.8

Choose a tag to compare

@doitian doitian released this 05 Oct 13:50
b85fbc1

Songs you play here now show up in your NetEase listening history.

Listening history. Signed in, plays reach 最近播放 and 听歌排行 on every device, as plays from the web — the same way the music.163.com web player reports them. A song counts once you've listened past the skip window (30 seconds, or a quarter of a long song); one you skip sooner isn't recorded. A song you finish is reported with its full length, and one you leave after the window with the time you listened. To keep your plays local, set scrobble: false in the plugin's config. /music/health shows whether reporting is on and how the last report went; a failed report never affects playback.

Getting there meant reading the web player's own requests: it reports plays to a separate log host, a report carrying the desktop client's cookies is silently dropped, and the body has to match the web player's field for field. The details are in docs/DESIGN.md.

Verified on the release commit with npm run check, the six deterministic suites (187 tests) and the live integration suite against NetEase (39 tests). Reporting was verified live through the plugin's own player and client with a signed-in session: the play reached 最近播放 within 15 seconds.

v0.1.7

Choose a tag to compare

@doitian doitian released this 05 Oct 10:29
c959237

A redesigned player, and temporary boosts for when you want more — or fewer — songs like the one playing.

Boosts. Press ▲ beside the song title for more songs like it, or ▼ for fewer, without a lasting like or dislike. A boost lasts an hour and then expires on its own; active boosts are listed in the AI DJ card with their time left, and ✕ ends one early. While the DJ is on, ▲ also queues a few similar songs right after the current one, and ▼ takes the DJ's upcoming picks that are like it out of the queue — never songs you queued yourself. For a song NetEase knows no similar songs for (晴天 is one), a boost falls back to that song's artist. The agent can set boosts too, through music_dj, for 5 minutes to 12 hours.

A redesigned player. On a wide window the player now has two columns: what is playing on the left — a large cover, the song's controls beside its title, centred transport, and the lyrics — and what comes next on the right: one AI DJ card with an on/off switch, the mood brief, the last plan, your boosts and the model picker, above the queue. On a narrow window they stack. Liked and boosted states show as accent icons instead of solid blocks, and the transport icons no longer turn into colour emoji on Windows.

Fixes. A test that failed about one run in eight now checks what it means to, deterministically.

Verified on the release commit with npm run check, the five deterministic suites (176 tests) and the live integration suite against NetEase (38 tests). The new layout was checked in a browser against a stand-in host; how it looks inside DeepSeek Harness, including its dark theme, is best seen after updating.

v0.1.6

Choose a tag to compare

@doitian doitian released this 05 Oct 04:40
864d848

The AI DJ learns your taste from what you like and listen to, instead of from its own picks, and a dislike now takes a song out of the queue.

The DJ no longer teaches itself. Every song that played counted as one you enjoyed — including the ones the DJ picked — and the play history decided your favourite artists and where the DJ looked next. On one account, early batches from the charts made Chinese pop the "favourites", those seeded more of it, and the DJ ended up recommending almost only Chinese songs while the account's likes were mostly not.

  • Your likes shape your taste. The DJ reads your newest 200 NetEase likes; their artists count toward your favourites, and the model sees your liked songs by name. Before, it saw only likes that happened to be played through the plugin — 2 of 119 on that account.
  • Similar songs start from your likes too, not only from recent plays.
  • Signed in, your own sources get more room: more daily recommendations and likes, fewer chart and new-song slots.
  • A song the DJ picked counts only once you've heard it through, past the skip window. Songs you choose yourself count straight away.

Your existing plays predate this and still count for now; they fade out as you keep listening.

A dislike removes the song. The ✕ takes the song out of the queue, wherever it is; if it's playing, the next one starts. A disliked song you queue again on purpose stays, and its filled ✕ clears the dislike. Removing the playing song at the end of the queue now wraps to the start instead of going back a song.

Fixes. Playing a song through the player or the agent recorded it twice, doubling its weight; queueing a song through the agent recorded an extra play of whatever was already playing. Each play is now recorded once.

The README is rewritten for people using the plugin; the design notes and developer guide moved to docs/DESIGN.md.

Verified with npm run check and the five deterministic suites (164 tests) on the release commit, and the live integration suite against NetEase (36 tests) on the code it releases. The DJ's model calls are still tested against stubs only.

v0.1.5

Choose a tag to compare

@doitian doitian released this 05 Oct 01:04
40622be

The AI DJ curates from a much better pool, and learns from more of what you do.

Top-ups land in full. The DJ kept picking songs still waiting in the queue, which the queue then dropped as duplicates, so a refill planned for five could add two, or none, while reporting five. Queued songs no longer reach the pool, and the report counts what was actually added. A model that answers with more picks than asked for is held to the count, and the music_dj tool's count now applies to replan as documented.

Skips teach the DJ. Pressing next before 30 seconds, or before a quarter of the song when that is longer, records a skip. A song skipped once ranks lower; skipped twice, it leaves the pool like a dislike. A skipped play no longer counts toward an artist's affinity, so abandoning an artist's songs stops making them a favourite, and the model sees what you skip. Going back, a song ending or failing, a song that never started playing, and the next the dislike button presses are not skips.

A better candidate pool.

  • Similarity starts from songs you listened through, never from one you just disliked or skipped, and from your likes when the history is thin.
  • Signed in, personal FM (私人FM) joins the pool, and liked songs you have not heard in three days come back now and then.
  • A mood brief finds music through its best-loved playlists rather than a song search, which mostly matched titles. With a model, the brief is first rewritten into Chinese search terms, once per brief: "90s cantopop" searched as written found Notorious B.I.G. and Eminem; rewritten, it finds 张国荣, 刘德华 and 许冠杰. The terms used are shown as Brief searched as.
  • The four general charts are sampled together instead of 热歌榜 alone, and every source has a quota, so none can crowd the others out.

Fewer requests, more variety. Each source is cached for about as long as it stays the same — similarity for a day, charts for six hours, the daily recommendations for three — and every plan draws a random handful from it, so a cached list still gives a different set each time. For playlists you do not own, the DJ samples the whole list rather than the first 20 tracks NetEase details. The DJ's own requests are paced a random 0.25–0.75 s apart, so a plan looks like someone browsing rather than a burst; a first plan with a brief sends about 14 requests over 7 seconds, and the next about 2.

Heartbeat mode (心动模式) was tried and left out: it answers code 500 on the plain web API this plugin uses.

Verified on the release commit with npm run check, the five deterministic suites (154 tests), and the live integration suite against NetEase (34 tests). Personal FM and the liked-song lookup were checked against a signed-in session; the model's own rewriting of a brief has not yet been observed outside DSH.

v0.1.4

Choose a tag to compare

@doitian doitian released this 04 Oct 18:49
483ca0a

The list's ✕ removes a song again. It had been routed through the taste
endpoint so that a removal would also be recorded as a dislike, but that
endpoint only writes the level: the click changed the taste, said so, and left
the row in place. The removal now goes through the queue endpoint, which drops
the row and records the removal as a dislike of an unrated track in the same
request — the judgement that keeps the DJ from deriving the same song again.
Removing the row that is playing advances playback, as documented, and a refusal
on the taste half can no longer keep a row the click asked to remove.

The row's confirm names what the click did — "Removed" / "已移除" — instead of
calling the removal a dislike. A track that already carries a level keeps it, and
the transport row's ✕ still confirms a dislike.

Verified on the release commit with npm run check and the four deterministic
suites (119 tests), including two new panel tests: removing a rated row, and
removing the row that is playing.