macos support + a pile of playback and player fixes - #33
macos support + a pile of playback and player fixes#33yuvrajangadsingh wants to merge 68 commits into
Conversation
the login window hardcodes a chrome-on-windows ua. that matches reality under webview2, but on macos the engine is wkwebview, which fingerprints as webkit. google's consumer account check flags the mismatch and blocks sign-in with "this browser or app may not be secure". send a safari ua on macos so the claimed browser matches the actual engine; other platforms keep the existing ua.
one stalled provider (hung request that never resolves) kept its query in isLoading forever, so the panel showed "Loading lyrics..." until you changed tracks. give each provider an 8s budget via AbortSignal.timeout (with an AbortController fallback for older webkit) threaded through every fetch in its chain. musixmatch/genius swallow their own errors into null, so on an aborted run they now rethrow instead — a timeout gets retried by react-query rather than cached as "no lyrics" for an hour. all three queries settle now, so the panel lands on the existing "No lyrics found." state instead of spinning.
macos trackpad momentum scrolling outruns the virtualizer at 8 rows of overscan. flicking through a long playlist shows white gaps before rows mount. 16 keeps rows painted through the fastest fling at the cost of a few extra mounted rows.
lrclib and musixmatch match by a text search, so a track with no real synced lyrics could pull back a totally different song's words. wire the existing hitMatches check into both providers and tighten match.ts so a single shared artist name (or a loose token overlap) no longer counts as a match. jaccard + meaningfulContains + a collab-aware heuristic decide it now.
webkit (wkwebview) on macos only decodes a narrow codec set, so the old bestaudio pick could hand it an opus/webm track it refused to play. add a mac audio ladder that prefers aac-in-mp4 and falls back through opus/webm to a progressive muxed mp4 so audio always plays. also add a separate video stream variant (progressive h264 mp4, itag 18 floor) cached under `<id>.video.mp4` so the same track can hold both an audio-only and a music-video download side by side. the stream handler sniffs the container and serves video/mp4 vs audio/* by magic bytes. non-macos keeps its original webm-first selection.
…nter the active line flipped 0.72s ahead of the vocal, which read as the highlight racing the singer. drop the lookahead to 0.2s so it lands on the beat. also raise the resting position from 0.36 to 0.45 of the viewport so the active line sits closer to center with a bit more upcoming text still visible.
some tracks stream a different edit than the lyric timings were cut to, so the highlight runs a fixed amount ahead of or behind the vocal. add a +/- 0.25s nudge that floats over the lyric column, shows the current offset like "+0.75s", and resets on click. the offset applies to both the active-line math and the click-to-seek target, and persists per videoId in localStorage.
the expand button on the player card opens a full-window now-playing view: the cover blown up and blurred as an ambient backdrop, the sharp art in front, and synced lyrics beside it. esc or the chevron closes it. when a track has no lyrics the empty right pane and the "no lyrics found" line looked broken, so drop them and center the art on its own instead. the seek fill, play button, and active shuffle/repeat pull a vibrant accent from the cover art. a client-side canvas read taints on the cors-less art cdns, so a small rust command fetches the bytes and picks a saturated dominant color inside a legible lightness band, falling back to brand red for near-monochrome covers.
the progressive yt-dlp stream reports its duration late, so the store duration sat at 0 for the first seconds of a track. that collapsed the seek bar's max to 1, which pinned the played fill as a stray red dot at the far left and left the total time showing 0:00. fall back to the browse metadata duration until the element reports its own, so the bar scales right and the total reads correctly from the start.
when the selected source is the music video and the webview can't decode that stream (media_err_decode / media_err_src_not_supported), the player showed a raw error banner and skipped the track. instead drop that track's source back to audio once and let the resolver retry with the song stream, which every track has. the selected-source check stops it looping: once we're on audio a repeat failure falls through to the normal error handling.
toggling to the music video ran a fuzzy search and returned the first result that wasn't the current id, which for a video-native track (one that already is a music video) was a completely different clip. thread the song/video kind onto queue tracks. a video-native track now stays on its own id in both modes since its stream carries the audio too, so it never searches. for genuine song<->video pairs, read the counterpart id straight from innertube's /next wrapper (playlistPanelVideoWrapperRenderer.counterpart + musicVideoType) and seed the source toggle with it, so switching lands on the real other version (same song, grouped by yt) rather than a search. song-native tracks with no exposed counterpart keep the search as a fallback.
a tauri wkwebview only bridges navigator.mediaSession to windows smtc, so macos control center, the touch bar, media keys and airpods showed "not playing" and couldn't drive playback. add a native mediaplayer.framework bridge (objc2): push title, artist, album, duration, elapsed and play state into mpnowplayinginfocenter, and register mpremotecommandcenter handlers (play, pause, toggle, next, prev, seek) that emit tauri events the playback store already listens for. the frontend invokes set_now_playing on track change, play/pause and seek. all cfg(macos), a no-op elsewhere. artwork is left out for now: the only artwork api this crate build exposes is the block-based initWithBoundsSize:requestHandler:, whose return-pointer ownership can't be validated without running on device, and a wrong guess risks a crash when control center renders the art. text, times and the transport commands work without it.
# Conflicts: # src/components/layout/lyrics-view.tsx
# Conflicts: # src-tauri/Cargo.lock # src-tauri/src/lib.rs
- tauri.macos.conf.json: native decorations with an overlay title bar (hidden title, traffic lights at 14,12) instead of the Windows-style custom frame; per-platform so Windows keeps decorations:false. - Same file fixes macOS bundling: base config targets ["nsis"] which is Windows-only, so tauri build compiled the binary but never regenerated YTubic.app — installs silently shipped stale builds. targets ["app"] plus createUpdaterArtifacts:false (updater signing needs the private key, which local dev builds don't have). - top-bar: hide the custom min/max/close cells on macOS and start the nav cluster clear of the traffic lights. - capabilities: allow set-fullscreen (immersive player) and internal-toggle-maximize (drag-region double-click zoom).
…res cover fix
- yt-dlp: player_client tv,android_vr -> android_vr. YouTube is running
a DRM experiment on the tv client (yt-dlp #12563), so every uncached
download 403'd; android_vr downloads clean.
- accent extraction: walk an ordered candidate list (local iTunes cover
first, then every thumbnail largest->smallest) instead of a single
fragile URL, and fall back to a neutral grey rather than brand red
when art has no vibrant color or the fetch fails. Shared hook drives
both the compact player and the fullscreen view.
- image fetcher: follow same-kind redirects with per-hop validation,
send browser-like headers, and allow the app's own loopback cover
server, keeping the SSRF allowlist for everything else.
- iTunes artwork: the 100000x100000-999 URL trick now returns HTTP 400
(Apple dropped it); request 3000x3000bb and bump the cover cache key
so stale dead URLs re-resolve.
- fullscreen player: ambient backdrop + accent share the candidate
list with an onError fallback, native macOS fullscreen while open.
- audio engine: auto-hunt the clean audio ("song") version for
music-video uploads that YouTube didn't pair with a counterpart.
…t is unknown Video uploads often carry no artist metadata. hitMatches deliberately relaxed to title-only in that case, so an exact-title hit for a different song sailed through: a 6:59 Jokhay track showed the lyrics of a ~4-minute English song that happens to be called Bittersweet. - match: durationMatches helper (both durations known, within ±4s). - lrclib/musixmatch: when the request has no artist, keep only hits whose duration vouches for the match (musixmatch now receives the track duration and reads track_length off search hits). - genius: search results carry no durations, so artist-less requests are unverifiable there — skip instead of guessing. - sources: bump lyric query keys to v2 so persisted wrong-song entries re-resolve under the new gating. - audio-engine: skip the auto audio-hunt for artist-less tracks — a bare-title search could swap playback itself to the wrong song, which is worse than wrong lyrics. The manual Song/Video switch still works. Verified against live LRCLIB: "Bittersweet" with no artist at 419s — old path picks a wrong 231s song, new path returns no lyrics.
The backdrop <img> was keyed by URL, so every change — track switches and the mid-track thumbnail -> iTunes-cover upgrade — unmounted it and dropped the view to the black scrim for a frame, which read as a blink. Use the same two-slot 700ms opacity cross-fade the app-wide BackgroundCover already uses; failed loads still advance the caller's candidate list.
Stricter follow-up to the duration-vouch gate: with no artist metadata there is nothing to verify a match against, so don't query the providers at all, the panel goes straight to "No lyrics found." and the fullscreen view centers the art instead of reserving a lyrics pane. Provider-level duration gates stay as a second layer for direct callers. Keys bumped again so persisted artist-less entries drop out.
Fresh indie releases often exist on no lyric DB at all (correctly showing "No lyrics found." now that wrong-song matches are gated). Give that dead end one door: a link that opens the default browser with a quoted title + artist lyrics search.
5151ccf to
faa9d2b
Compare
|
heads up, the branch is rebuilt on your current main since the old merge went stale after the history rewrite, pr is mergeable again. also pushed a week of fixes from daily driving it: lyric timing for padded and sped up uploads, playlist pages were gluing ytm suggestions onto the real tracks, search history on the empty search page, and cleaner artist lines for tracks played from cards. |
Add native macOS window chrome, Keychain-backed cookie encryption, platform-aware login and media integration, and a universal Intel/Apple Silicon release build. Document feature ideas gathered from the fork network. Incorporates macOS work and ideas from PR #27 and PR #33. Co-authored-by: Enoch Fabiyi <ayomikun204@gmail.com> Co-authored-by: Yuvraj Angad Singh <yuvrajangad.s@gmail.com>
…band the notch-band gradient starts from solid black over 128px so the menu-bar strip melts into bright ambient backdrops instead of showing a hard seam. every app scroller pins its x axis (overscroll-behavior-x + overflow-x clip): trackpad diagonal scrolls were elastic-bouncing whole panes sideways in wkwebview even with overflow hidden.
local builds ride a personal branch; accepting an upstream release would replace them wholesale. the automatic startup check now runs only when VITE_ENABLE_UPDATE_CHECK=1, which the release workflow sets, so official builds keep updating while local builds never nag. the manual menu check always works.
the manual song/video toggle took the first search result of the opposite kind on faith, which played the 15:54 bollywood dilbar for a 2:49 remix. candidates now need a real title match (qualifier-aware, so a remix is not 'exact' for the original), artist overlap when both sides name artists, and a duration inside the plausible counterpart window; no match shows 'no version found' instead of someone else's video. the persisted pair cache is dropped once via a store version bump because pairs cached by the old resolver would have kept winning over the gates.
macos showed two now-playing rows: a blank 'ytubic' entry from the native mpnowplayinginfocenter path and the real track from webkit's automatic session for the playing element. webkit publishes its session regardless, so the webview side (navigator.mediaSession metadata with artwork, position state, action handlers) is now the single owner and the native bridge is no longer initialized - which also ends double-fired transport presses. action handlers register individually so an unsupported action on some webkit build cannot abort the rest.
…eams at the truth carry-seek: switching song/video keeps the playhead (token-invalidated, queue-identity-checked, companion gated on reaching the target frame). doubled-duration streams: avfoundation reads yt-dlp's un-remuxed dash m4a headers at 2x the real length; correctedDuration already displayed the listed truth but playback ran past it into the phantom silent half. ontimeupdate now advances at the corrected end, re-arming whenever the playhead returns before it so repeat-one and replays of the same source end correctly every pass, and the element rewinds first so a stopped queue never resumes into silence.
when the companion tops out below the cap, rows above its real height disappear instead of presenting choices that silently fail, and the check marks what is actually playing rather than the unreachable cap. the tops-out footer stays.
the fork inherited upstream's windows readme, so the repo read as a windows project with a download-for-windows button. lead with what this actually is: the macos port, what it adds on top, mac install with the gatekeeper step, and upstream credited at the top and in credits.
someone landing here should know what this is and have it installed before they hit anything technical. wordmark and nav up top, install second with the gatekeeper step inline instead of buried in an faq, troubleshooting folded into details blocks, prose instead of bullet soup. dropped genius from the lyrics list, it has been dead upstream for weeks.
|
closing this one. you shipped first-class macos support in 0.4.0, which covers the platform side this branch originally existed for, and the branch has drifted well past its original scope since (68 commits now, mostly player and lyrics work that isn't mac specific). i'll keep the fork going for the extras. if you ever want any of the individual pieces upstream, say the word and i'll open focused prs instead of one big branch — the ones i think stand alone are the video mode with the quality picker, the lyrics record matching (picks the right record instead of the closest, auto-aligns padded intros), and the apple music style fullscreen. thanks for taking the time to look through it. |
hey, i ported ytubic to macos and ended up fixing a bunch of playback and player things along the way. everything below is runtime verified on a real mac (apple silicon, macos 15) over several days of daily listening. i dont have a windows machine, so windows is NOT tested, happy to adjust anything that looks off for win.
mac port
playback correctness
lyrics
player
merge notes
your 0.3.1 is merged in: retry-nonce stream retries and the media-control listener are intact, the cache sweeper unions both stream variants (.video.mp4) with the new .meta.json sidecars, and discord/lastfm ride along untouched.
happy to split any of this into smaller prs if thats easier to review.