Skip to content

Online Sources: Streaming Import and YouTube queue #418

Description

@thedavidweng

Problem Statement

I keep karaoke songs as local files in OpenKara. A lot of the music I already pay for or already watch lives on NetEase Cloud Music or as YouTube karaoke videos. I do not want to download those by hand, and I do not want YouTube treated as a file dump. I want to turn those origins on in Settings, sign in to NetEase when I need my playlists, paste a public YouTube link when the video already has accompaniment and lyrics, and have OpenKara own whatever actually lands in my library.

Solution

Settings grows an Online Sources section. I can enable YouTube as a Video Source and NetEase as a Streaming Source.

When YouTube is on, I paste a public video or playlist link. OpenKara queues those items and plays them on the public watch page in a WebView. They do not become library songs. Age-restricted or private items fail in the open.

When NetEase is on, I sign in the same way YesPlayMusic does (QR, phone, or email). I browse liked tracks, my Streaming Playlists, and search. I import a whole list or one track. OpenKara writes real library songs through the same import path I already use for files. A later import of the same Streaming Playlist updates the same Playlist. If the file is a different quality of the same listing, OpenKara asks me to keep or replace, like a file manager, and I can apply that choice to the rest of the batch. Grey songs stay visible and do not import. Overseas use works because NetEase requests send a China Client Address. There is no UNM and no second audio engine.

I can reveal a song file or its stem folder from the song menu. Those actions disable when the target is missing.

Seams

Test at these seams. Prefer them over new ones.

  1. Online Source registry (new, one dispatch). Enable flags and capability lists. Callers ask the registry, not NetEase or YouTube by name.
  2. Streaming Source port (new, one interface, many adapters). Sign-in, browse, search, and resolve a track to an importable file or an Import Refusal. NetEase is the first adapter.
  3. Existing song import and import workflow. Streaming Import ends here. Library Decision reuses the CDG pause-and-ask prompt, including Apply to Remaining.
  4. Existing playback session. A YouTube queue id plays through the same play/pause/seek/ended path. The session stops local audio before the watch-page WebView starts.

Do not add a per-brand import writer. Do not add a second playback coordinator for YouTube.

User Stories

  1. As a singer, I want Online Sources off by default, so a new install stays local-first.
  2. As a singer, I want a Settings switch for YouTube, so I only see paste-link when I asked for it.
  3. As a singer, I want a Settings switch for NetEase, so I only see NetEase sign-in when I asked for it.
  4. As a singer, I want those switches to survive restart, so I do not re-enable them every night.
  5. As a singer, I want turning a source off to hide its UI and reject its commands, so a disabled origin cannot act.
  6. As a singer, I want turning NetEase off to keep my Streaming Credentials, so I do not scan the QR again just to hide the panel.
  7. As a singer, I want a Sign out action on NetEase, so I can drop the session on this machine.
  8. As a singer, I want NetEase QR sign-in first, so I do not type a password unless I choose to.
  9. As a singer, I want phone-and-password sign-in, so I can use the same path as YesPlayMusic.
  10. As a singer, I want email-and-password sign-in, so I can use an email account.
  11. As a singer, I want OpenKara to forget the password after the login request, so my password never sits in the keychain.
  12. As a singer, I want only MUSIC_U and __csrf stored as Streaming Credentials, so the next launch is still signed in.
  13. As a singer, I want Streaming Credentials kept apart from Repository Credentials, so a Drive token cannot mix with a NetEase cookie.
  14. As a singer, I want a clear signed-in name after login, so I know which NetEase account is active.
  15. As a singer, I want expired NetEase sessions to send me back to sign-in, so a 301 or risk-control reply is not shown as "no rights".
  16. As a singer overseas, I want NetEase requests to send a China Client Address, so songs that play in China also resolve for me.
  17. As a singer, I do not want a Real-IP field in Settings, so I am not asked to invent an address.
  18. As a singer, I want to open liked tracks after sign-in, so I can import what I already hearted.
  19. As a singer, I want to list my NetEase Streaming Playlists, so I can pick which nights to import.
  20. As a singer, I want to search NetEase by keyword, so I can import one song without a playlist.
  21. As a singer, I want a Streaming Playlist to show title and artist even when the track is an Import Refusal, so I know why a grey song will not come in.
  22. As a singer, I want an Import Refusal to refuse download, so OpenKara never fetches a replacement from another platform.
  23. As a singer, I want selected Import Refusals on the failure list, so a batch import tells me what it skipped.
  24. As a singer, I want to import one visible track, so I do not have to take the whole list.
  25. As a singer, I want to import a whole Streaming Playlist, so a rehearsal set lands in one action.
  26. As a singer, I want a successful import to create a normal library song, so stems, lyrics, and remote publish work as they do today.
  27. As a singer, I want a whole-list import to create or update an OpenKara Playlist, so the set is singable from the sidebar.
  28. As a singer, I want that Playlist to stay mine after import, so a later change on NetEase does not rewrite my list by itself.
  29. As a singer, I want a second import of the same Streaming Playlist to find the same Playlist via a Playlist Origin Stamp, so I do not get a duplicate list.
  30. As a singer, I want that second import to add only missing tracks, so I do not re-download songs I already kept.
  31. As a singer, I want NetEase deletions on the remote list to leave my Playlist songs in place, so OpenKara remains the owner.
  32. As a singer, I want the same NetEase track at 192k and 320k to count as one library song, so quality is not a second title.
  33. As a singer, I want a live cut and a studio cut with different NetEase ids to stay two songs, so versions do not collapse.
  34. As a singer, I want title and artist ignored for identity, so two different recordings with the same name do not merge.
  35. As a singer, I want the same file hash to import as a no-op, so a true duplicate does not ask me anything.
  36. As a singer, I want a different file for the same Streaming Track Identity to open a Library Decision, so I choose Keep or Replace.
  37. As a singer, I want that decision to show title, artist, album, format, bit rate, duration, and size, so I can tell 192k from 320k without a hash.
  38. As a singer, I want that decision not to show the file hash, so the dialog stays human.
  39. As a singer, I want Keep Library Song to leave the current file, so my stems and mix stay put.
  40. As a singer, I want Replace Library Song to swap the audio and keep lyrics and playlist membership, so I do not rebuild the set.
  41. As a singer, I want Replace Library Song to drop old stems, so I do not sing against the wrong mix.
  42. As a singer, I want Apply to Remaining on that dialog, so an eight-conflict playlist is not eight separate chores.
  43. As a singer, I want Apply to Remaining to cover only later conflicts in this import, so a choice I already made does not change.
  44. As a singer, I want closing the dialog to cancel the rest of the import, so I can stop mid-batch.
  45. As a singer, I want CDG pairing to use the same Library Decision surface, so I learn one blocking dialog.
  46. As a singer, I want a failure list after a mixed batch, so I see refusals and cancelled items apart from successes.
  47. As a singer, I want Reveal Song File on the song menu, so I can find the imported audio in the system file manager.
  48. As a singer, I want Reveal Stems on the song menu, so I can open that song's stem folder.
  49. As a singer, I want those items disabled when the file or folder is missing, so I still know the action exists.
  50. As a singer, I want those items hidden on a YouTube queue item, so a video without a library file does not pretend to have one.
  51. As a singer, I want YouTube paste-link when YouTube is on, so I can queue a karaoke video in one step.
  52. As a singer, I want a public watch link to become one queue item, so I can play it next or now.
  53. As a singer, I want a public playlist link to expand into many queue items, so a karaoke list plays in order.
  54. As a singer, I want YouTube items out of my saved Playlists, so a restart does not try to publish a video as a file.
  55. As a singer, I want YouTube items to leave the queue on quit, so they follow the existing queue rule.
  56. As a singer, I want the public watch page in a WebView, so baked-in lyrics and accompaniment show on the stage.
  57. As a singer, I want OpenKara not to call YouTube /player stream URLs, so guest playback does not die as UNPLAYABLE.
  58. As a singer, I want no Google sign-in this version, so I do not hand OpenKara a Google session to paste a public link.
  59. As a singer, I want age-restricted or private YouTube items to fail with a plain reason, so I am not stuck on a silent stage.
  60. As a singer, I want play, pause, seek, volume, and next to drive the YouTube WebView, so the existing transport keys still work.
  61. As a singer, I want a finished YouTube item to advance the queue, so a mixed local-and-video night keeps moving.
  62. As a singer, I want local audio to stop before a YouTube item starts, so two songs never play at once.
  63. As a singer, I want only one YouTube player, so the audience window and the host do not double the sound.
  64. As a singer, I want the audience window to own that player when it is open, so the room sees the karaoke video.
  65. As a singer, I want stem sliders, EQ, crossfade, waveform, and lyrics fetch ignored on a YouTube item, so the chrome matches a video, not a file.
  66. As a singer, I want AirPlay lyrics output to refuse a YouTube item, so the TV is not sent an empty HLS lyrics stream.
  67. As a host, I want NetEase import and YouTube queue to share the same Queue and play bar, so the night has one control surface.
  68. As a future contributor, I want a second Streaming Source to implement the same port, so Kugou does not copy the import writer.
  69. As a future contributor, I want UNM engines rejected, so Apache-2.0 stays intact and grey songs stay refusals.

Implementation Decisions

  • Follow ADR 0031, 0032, 0033, and 0034. Use CONTEXT.md terms only.
  • Add an Online Source registry with persisted enable flags for YouTube and NetEase. Default both off.
  • Add one Streaming Source port: identity, credential status, sign-in (QR poll, phone, email), sign-out, liked tracks, Streaming Playlists, search, and resolve-to-file-or-refusal.
  • Implement one NetEase adapter on that port. Use the unofficial NetEase API client already proven in YesPlayMusic (ncm-api-rs or equivalent). Always set China Client Address (real_ip / random_cn_ip). Do not link UNM crates.
  • Store Streaming Credentials in the OS keychain under a streaming-specific service name. Persist MUSIC_U and __csrf only. Never persist password or md5_password.
  • Add Streaming Track Identity and Playlist Origin Stamp to the library schema without replacing songs.hash as the file identity. Map (source, remote track id) to the current song. Map (source, remote playlist id) to one Playlist.
  • Streaming Import writes a temp file, then calls the existing song import. Create or update a Playlist only after successful imports in that batch.
  • Import Conflict is a Library Decision. Reuse the CDG pause-and-ask workflow. Show metadata, never hash. Support Keep, Replace, Apply to Remaining, and cancel.
  • Replace keeps lyrics and playlist rows, writes the new file, retargets the identity map, and deletes stale stems and waveform cache for the old hash.
  • Import Refusal is not a Library Decision. List it in batch failures.
  • Add Reveal Song File and Reveal Stems to the library song menu. Use the system “show in folder” action. Disable when the path is missing. Hide on Video Source queue items.
  • Add one Video Source port: parse watch and playlist URLs, resolve public playlist entries, return queue items with title, channel, duration, thumbnail.
  • Queue items for YouTube use a yt: id prefix. Playback session treats that prefix as the video transport. Do not put those ids in playlist_songs.
  • Play YouTube by loading the public watch page in one WebView. Do not request /player. Do not persist Google cookies.
  • Playback arbiter: starting YouTube stops the local audio engine and cancels preload; starting a library song tears down the YouTube WebView.
  • Audience window, when open, hosts the single YouTube WebView. The host shows metadata and transport controls only.
  • Public IPC for enable flags, NetEase session, browse, import, YouTube resolve, reveal-in-folder, and Library Decision prompts belongs in a new catalog contract, updated in the same change as the commands.
  • Product surfaces: Settings (Online Sources), NetEase browse, YouTube paste-link, Library Decision, song menu. Match interaction, language, and security profiles. Put required evidence on the PR.

Testing Decisions

Good tests observe the ports and the existing import and playback sessions. They do not assert NetEase wire crypto, cookie strings, or WebView HTML.

  • Registry: disabled source rejects browse and import; enabled source reports capabilities; flags persist across a reload of settings.
  • Streaming Source fake adapter: QR/password success stores credentials and not the password; sign-out clears credentials; disable does not; resolve returns a file the import path accepts; empty URL and trial clips are Import Refusals; geo-empty URL with China Client Address set is not required to be faked as success.
  • Streaming Import on the existing import workflow: first playlist import creates a Playlist with an origin stamp; second import of the same remote id adds only new tracks; remote removal does not delete local rows; same hash is silent; same identity different bytes pauses on Library Decision; Keep leaves the file; Replace retargets identity and drops stems; Apply to Remaining applies to later conflicts only; cancel stops the batch.
  • Video Source fake resolver: watch URL → one queue item; playlist URL → many items; private or age-gated resolve fails with a typed error; play of yt: does not call song decode.
  • Playback session: play(yt:…) does not invoke local play; play(hash) after YouTube clears the video transport; ended on YouTube dequeues the next id.
  • Reveal commands: missing file returns a disabled/unavailable result; existing relative media and stem folder resolve to absolute paths.
  • Prior art: import-workflow and CDG prompt tests, PlaybackSession tests, remote auth keychain tests, settings persistence tests.

Out of Scope

  • UNM, ytdl, Bilibili fetch, grey-song unlock, or any second audio engine
  • Google sign-in, SAPISIDHASH, age-gate unlock, private YouTube lists
  • YouTube download or YouTube items in saved Playlists
  • Kugou, QQ Music, Spotify, or Scrobbler implementations
  • Match Search across signed-in sources
  • AirPlay of YouTube video
  • A standing library-manager page
  • Password or phone number persistence
  • A user-facing Real-IP setting
  • Changing songs.hash as the primary key of local files

Further Notes

  • Glossary: CONTEXT.md. Decisions: ADR 0031–0034.
  • YesPlayMusic is the reference for NetEase sign-in, cookie session, /song/url refusal vs empty URL, and China Client Address. Kaset is the reference for signed-out watch-page playback and the ban on guest /player URLs.
  • Incremental delivery can ship registry and Settings first, then NetEase sign-in and import, then Library Decision, then YouTube resolve and WebView play. Each slice must still honor these seams.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions