macos support + a pile of playback and player fixes#33
macos support + a pile of playback and player fixes#33yuvrajangadsingh wants to merge 50 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.
This reverts commit c1d7793.
The side card was top-heavy for lyric-less tracks: art, controls, a lone "No lyrics found." and a dead column under it. Fill that space with Up Next instead, the same QueueBody the queue toggle renders, minus its close button, under a one-line no-lyrics caption. Lyrics take the slot back automatically on tracks that have them.
The queue speaks for itself; the caption was dead weight over it.
Two attacks on the same complaint (a song that ends minutes before its file does): - the clean-audio hunt now fires for ANY queued kind, not just video rows, extended/looped re-uploads surface as ordinary song rows too. It goes through findCleanAudioAlternate, which verifies the title, cross-checks artists, and only swaps to a meaningfully shorter album version (>60s shorter for song rows; near-equal allowed for true video rows). No duration data means no swap. - long-outro auto-advance: when the synced lyrics' last sung line sits more than 2 minutes before the end of the file, advance to the next track 60 seconds after the vocals end. Normal instrumental outros (under 2 min of tail) keep playing; seeking into the tail disables the skip for that track. Covers the cases the hunt can't: YT's own canonical entry sometimes IS the extended cut (No Guidance 8:41), and per-track video-source selections persist from older sessions.
- a synced record beats a plain one no matter which lrclib endpoint returned it. lrclib keeps duplicate rows per song (artist credited alone vs with features) and the exact-match endpoint kept hitting the plain-only duplicate - version qualifiers (remix/live) now outrank duration closeness, so a remix upload doesnt get the originals timings just because the two edits are a second apart - timestamps rescale when the upload is a sped-up/slowed edit of the record the timings were cut for. a constant offset can never fix a tempo change - one endpoint erroring no longer throws away the other ones hit
- wkwebview reads yt-dlps un-remuxed dash m4a headers at exactly 2x the real length (no ffmpeg shipped, so no fixup; chromium reads the sample tables and never sees this). clamp against the tracks listed duration, fetch the length from the tracks own /next row when a queue entry arrives without one, and re-clamp when it lands - a guard advances when a doubled file runs past its real end, and long dead outros auto-advance using synced lyrics as the reference - the media element is a <video> element now so the source toggle can carry actual video (wired up in the player commits) - hunt gates tightened so the clean-audio search cant swap a track to a sped-up bootleg - yt-dlp gets a client chain (android_vr,ios) so videos drm-blocked on one client resolve on the other instead of 403ing and skipping - media-remote listener registrations catch and log their failures
the source toggle only ever swapped which audio edit streamed - there was no video surface anywhere. an explicit switch now requests the real mv file (?video=1, cached side by side with the audio) and the fullscreen player shows it in place of the artwork. seeded records stay audio-only: only a user switch opts a track in, so video-native queue rows dont silently start pulling full videos
- one player column on the left (art, meta, seek with remaining time, transport, persistent volume slider), lyrics fill the rest of the stage. without lyrics the same column centers alone - big bold lyric lines with a visible window: past lines dim, upcoming lines melt continuously into the panes bottom mask, stanza gaps come from the lrc break markers, the first line cues during the intro - blur is not in the line transition list anymore - animating filter re-rasterizes every line each frame and starved the scroll, which read as stutter on every line change - artwork crossfades on track change, and the itunes hi-res upgrade only applies if it resolves before the art settles. art, backdrop and accent never change mid-track - accent legibility: near-greyscale covers clamp to white, dark accents lift in hsl (lightness up, saturation floored) so the fill reads on top of its own cover instead of red-on-red - thumbless seek rail butt-joins its two segments so there is no notch - the plain-lyrics fallback gets the same large treatment so unsynced tracks dont look broken
the windowed top edge fades from pure black so the menu bar boundary disappears instead of showing a seam, and the traffic-light inset collapses in native fullscreen where the lights auto-hide (the reserved space read as a dead gap before the first button)
|
@yuvrajangadsingh Wow, great work! I'll look through what's been done here and check which approach will be the easiest to maintain. In the meantime, could you send a screenshot of how you handled the navigation and window title bar on macOS? Given that the close/minimize buttons are on the left there and are slightly different in size. |
fb757f3 to
a3f4c8d
Compare
|
I'm also currently working on a large number of fixes specifically for the player, track list display, and so on, and I need to check how all of this will work together so that we don't end up doing double work in the future. |
a3f4c8d to
077b7f4
Compare
|
thanks! heres the titlebar handling. on mac i dont recreate the window controls at all, the OS draws its own traffic lights: native decorations with titleBarStyle overlay and a hidden title via a tauri.macos.conf.json platform overlay, so sizes and positions are always exactly what macos renders. the windows-style min/max/close cluster simply doesnt render on mac, and the nav cluster pads itself past the lights so nothing overlaps. in native fullscreen macos auto-hides the lights, so the padding collapses with them, otherwise you get a dead gap before the first button. windows behavior is untouched. windowed (native lights, nav clear of them): native fullscreen (lights auto hidden, nav goes flush): on the double work: makes sense. beyond the port my branch already carries a bunch of player-side work (apple music style fullscreen with synced lyrics, a family of playback duration fixes, real video playback on the source toggle, media keys via mpremotecommandcenter). tell me which areas you have in flight for the player and track list and i will stay out of them or rebase around you. and since #27 is open too, one structural thought, zero pressure: if you would rather keep this repo windows-first, i am happy to run a dedicated mac repo under any name you like, full credit to this project as the origin, and every fix that also helps windows comes back here as small focused prs. the duration clamp, the lyrics record picking and the yt-dlp client chain are already cross platform wins. and if you would rather merge mac into main, i am in for that too. whichever is easier for you to maintain. |
upstream 0.3.1 removed the navigator.mediaSession action handlers when souvlaki took over on windows, but on mac the system now playing seat routes through wkwebviews media session, so pause from the widget or the media keys poked the element directly (ui kept showing playing) and next/prev did nothing at all. restore the handlers mac-only, and sync external pause/play from the element back into the store so no seat owner can ever desync the ui again.
the itunes lookup trusted the first search hit, which ranks by popularity and often belongs to a different release, so a track could wear another albums art (portrait of you showing the saroor cover). the tracks album now rides into the lookup, results are filtered by collection name, and a known album with no matching result keeps the yt thumbnail instead of lying. album goes into the cache key too and the key version bumps to flush old wrong entries.
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. |


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.