2026-03-28 13:49:47 -07:00
|
|
|
name: NavidromePlayer
|
|
|
|
|
options:
|
2026-04-08 15:19:26 -07:00
|
|
|
bundleIdPrefix: ca.dallasgroot.navidromeplayer
|
2026-03-28 13:49:47 -07:00
|
|
|
deploymentTarget:
|
2026-04-08 15:34:14 -07:00
|
|
|
iOS: "26.0"
|
|
|
|
|
watchOS: "26.0"
|
|
|
|
|
xcodeVersion: "26.0"
|
2026-03-28 13:49:47 -07:00
|
|
|
groupSortPosition: top
|
|
|
|
|
createIntermediateGroups: true
|
|
|
|
|
|
2026-04-04 06:58:58 -07:00
|
|
|
packages:
|
|
|
|
|
ZIPFoundation:
|
|
|
|
|
url: https://github.com/weichsel/ZIPFoundation
|
|
|
|
|
from: "0.9.19"
|
Performance audit, Now Playing widget, crossfade stability, cover art embedding, DJ profile bulk cache
PERFORMANCE AUDIT
- Removed 16 dead SubsonicClient methods (~117 lines)
- Added NSCache memory tier to LibraryCache, AlbumCoverStore,
ArtistCoverStore, RadioCoverStore
- Replaced weak polynomial hash with FNV-1a 64-bit in ImageCache
- Split PlaybackStateStore into save() (full queue) and savePosition()
(time only)
- Reused single SubsonicClient in OfflineManager instead of
per-download allocation
- Added periodic ImageCache disk trim every 50 writes
- Changed AudioPreFetcher to fuzzy offline match
(isSongAvailableOffline)
- Removed dead code: hasCompanionLibrary, downloadedSongIds, isActive,
CachedImageLoader.task
- Fixed thread safety: inline JSONEncoder/JSONDecoder in LibraryCache
(no shared instances)
WIDGET EXTENSION (new target: NavidromeWidget)
- v2 glassmorphism design: blurred album art background + frosted
glass panel
- Waveform scrubber: 40-bar Canvas with tap-to-seek (20 segments via
SeekToIntent)
- Color-adaptive theming: CIAreaAverage dominant color extraction with
HSB contrast adjustment
- Transport controls: previous/play-pause/next with interactive
AppIntents
- Up Next footer with crossfade countdown from Smart DJ profiles
- Large widget: 3-item queue list with numbered rows
- Small/Medium/Large sizes matching design mockups
- App Group communication via WidgetSharedState (UserDefaults)
- Darwin notification observer for widget→app commands
- Foreground command pickup for suspended app recovery
- Idempotency guards on all widget commands
CROSSFADE & PLAYBACK FIXES
- Fixed dual audio on single-song queue: guard nextSong.id ==
currentSong?.id in prepareNextForCrossfade
- Fixed crossfade play path never calling pushWidgetState (returned
before reaching it)
- Fixed crossfade needsNextTrack callback missing queue persistence +
widget push
- Fixed toggleShuffle queue not persisted after PlaybackStateStore
split
- Added nowPlayingSyncTimer restart on foreground (Lock Screen seek
bar drift)
- Added AVPlayer currentTime/duration sync in resumeVisTimers before
vis timer restart
(fixes waveform distortion after background — confirmed by Apple
Forums + SoundCloud engineering)
COVER ART PIPELINE
- Fixed pushWidgetState cover art size mismatch (300→600 to match
fetchAndSetArtwork)
- Added custom cover art key differentiation ("custom_" prefix forces
re-blur)
- Changed server art lookup from memoryOnlyImage to cachedImage
(memory+disk fallback)
- Added POST /library/cover-art-by-path endpoint (was missing — iOS
fallback hit 404)
- Added navidrome_id fallback on existing cover art endpoint
- Added embed_cover_art_in_file/embed_cover_art_in_directory: mutagen
writes cover art
directly into FLAC/MP3/M4A/OGG metadata tags so Navidrome serves
updated art
- All three upload paths (by-id, by-path, upload-tracks) now embed +
trigger_scan
COMPANION API FIXES
- Fixed _create_task recursion (was calling itself instead of
asyncio.create_task)
- Fixed navidrome_db NameError on /library/conflicts endpoint
- Reduced WebSocket connect/disconnect logging (only first-client and
all-disconnected)
SMART DJ PROFILE PREFETCH
- New endpoint: GET /smart-dj/profiles/export (bulk JSON, gzip
automatic)
- SmartDJCache.loadBulkCache() reads single file on launch (instant)
- SmartDJCache.bulkImport() writes all profiles in one atomic file
- CompanionAPIService.fetchAllProfiles() fetches entire profile set in
one request
- Wired into NavidromePlayerApp.task after server connect
- SmartCrossfadeManager unchanged — already reads from SmartDJCache
first
WEBSOCKET NOISE REDUCTION
- iOS: silent reconnect retries, only log milestones (#1, #5, every
20th)
- iOS: log "reconnected after N attempts" on success, silent initial
connect
- Python: only log first client connect and all-clients-disconnected
Files: 13 modified, 8 new (including companion-api/main.py)
2026-04-12 19:24:22 -07:00
|
|
|
ConfettiSwiftUI:
|
|
|
|
|
url: https://github.com/simibac/ConfettiSwiftUI
|
|
|
|
|
from: "3.0.0"
|
2026-04-04 06:58:58 -07:00
|
|
|
|
2026-03-28 13:49:47 -07:00
|
|
|
settings:
|
|
|
|
|
base:
|
|
|
|
|
SWIFT_VERSION: "5.9"
|
|
|
|
|
MARKETING_VERSION: "1.0.0"
|
2026-04-30 23:13:40 -07:00
|
|
|
CURRENT_PROJECT_VERSION: "15"
|
2026-03-28 13:49:47 -07:00
|
|
|
DEAD_CODE_STRIPPING: true
|
|
|
|
|
ENABLE_USER_SCRIPT_SANDBOXING: true
|
|
|
|
|
DEVELOPMENT_TEAM: E9C9AGS9K6
|
|
|
|
|
|
|
|
|
|
targets:
|
|
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
# iOS App
|
|
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
NavidromePlayer:
|
|
|
|
|
type: application
|
|
|
|
|
platform: iOS
|
2026-04-08 15:34:14 -07:00
|
|
|
deploymentTarget: "26.0"
|
2026-03-28 13:49:47 -07:00
|
|
|
sources:
|
|
|
|
|
- path: iOS
|
|
|
|
|
- path: Shared
|
|
|
|
|
resources:
|
|
|
|
|
- path: iOS/Resources
|
|
|
|
|
settings:
|
|
|
|
|
base:
|
2026-04-08 15:34:14 -07:00
|
|
|
PRODUCT_BUNDLE_IDENTIFIER: ca.dallasgroot.navidromeplayer.app
|
2026-03-28 13:49:47 -07:00
|
|
|
INFOPLIST_FILE: iOS/Resources/Info.plist
|
|
|
|
|
CODE_SIGN_ENTITLEMENTS: iOS/Resources/NavidromePlayer.entitlements
|
|
|
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
|
|
|
|
TARGETED_DEVICE_FAMILY: "1,2"
|
2026-04-08 15:34:14 -07:00
|
|
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: true
|
2026-03-28 13:49:47 -07:00
|
|
|
dependencies:
|
|
|
|
|
- target: NavidromeWatch
|
2026-04-10 07:00:30 -07:00
|
|
|
embed: true
|
2026-04-12 13:39:13 -07:00
|
|
|
- target: NavidromeWidget
|
|
|
|
|
embed: true
|
2026-04-04 06:58:58 -07:00
|
|
|
- package: ZIPFoundation
|
Performance audit, Now Playing widget, crossfade stability, cover art embedding, DJ profile bulk cache
PERFORMANCE AUDIT
- Removed 16 dead SubsonicClient methods (~117 lines)
- Added NSCache memory tier to LibraryCache, AlbumCoverStore,
ArtistCoverStore, RadioCoverStore
- Replaced weak polynomial hash with FNV-1a 64-bit in ImageCache
- Split PlaybackStateStore into save() (full queue) and savePosition()
(time only)
- Reused single SubsonicClient in OfflineManager instead of
per-download allocation
- Added periodic ImageCache disk trim every 50 writes
- Changed AudioPreFetcher to fuzzy offline match
(isSongAvailableOffline)
- Removed dead code: hasCompanionLibrary, downloadedSongIds, isActive,
CachedImageLoader.task
- Fixed thread safety: inline JSONEncoder/JSONDecoder in LibraryCache
(no shared instances)
WIDGET EXTENSION (new target: NavidromeWidget)
- v2 glassmorphism design: blurred album art background + frosted
glass panel
- Waveform scrubber: 40-bar Canvas with tap-to-seek (20 segments via
SeekToIntent)
- Color-adaptive theming: CIAreaAverage dominant color extraction with
HSB contrast adjustment
- Transport controls: previous/play-pause/next with interactive
AppIntents
- Up Next footer with crossfade countdown from Smart DJ profiles
- Large widget: 3-item queue list with numbered rows
- Small/Medium/Large sizes matching design mockups
- App Group communication via WidgetSharedState (UserDefaults)
- Darwin notification observer for widget→app commands
- Foreground command pickup for suspended app recovery
- Idempotency guards on all widget commands
CROSSFADE & PLAYBACK FIXES
- Fixed dual audio on single-song queue: guard nextSong.id ==
currentSong?.id in prepareNextForCrossfade
- Fixed crossfade play path never calling pushWidgetState (returned
before reaching it)
- Fixed crossfade needsNextTrack callback missing queue persistence +
widget push
- Fixed toggleShuffle queue not persisted after PlaybackStateStore
split
- Added nowPlayingSyncTimer restart on foreground (Lock Screen seek
bar drift)
- Added AVPlayer currentTime/duration sync in resumeVisTimers before
vis timer restart
(fixes waveform distortion after background — confirmed by Apple
Forums + SoundCloud engineering)
COVER ART PIPELINE
- Fixed pushWidgetState cover art size mismatch (300→600 to match
fetchAndSetArtwork)
- Added custom cover art key differentiation ("custom_" prefix forces
re-blur)
- Changed server art lookup from memoryOnlyImage to cachedImage
(memory+disk fallback)
- Added POST /library/cover-art-by-path endpoint (was missing — iOS
fallback hit 404)
- Added navidrome_id fallback on existing cover art endpoint
- Added embed_cover_art_in_file/embed_cover_art_in_directory: mutagen
writes cover art
directly into FLAC/MP3/M4A/OGG metadata tags so Navidrome serves
updated art
- All three upload paths (by-id, by-path, upload-tracks) now embed +
trigger_scan
COMPANION API FIXES
- Fixed _create_task recursion (was calling itself instead of
asyncio.create_task)
- Fixed navidrome_db NameError on /library/conflicts endpoint
- Reduced WebSocket connect/disconnect logging (only first-client and
all-disconnected)
SMART DJ PROFILE PREFETCH
- New endpoint: GET /smart-dj/profiles/export (bulk JSON, gzip
automatic)
- SmartDJCache.loadBulkCache() reads single file on launch (instant)
- SmartDJCache.bulkImport() writes all profiles in one atomic file
- CompanionAPIService.fetchAllProfiles() fetches entire profile set in
one request
- Wired into NavidromePlayerApp.task after server connect
- SmartCrossfadeManager unchanged — already reads from SmartDJCache
first
WEBSOCKET NOISE REDUCTION
- iOS: silent reconnect retries, only log milestones (#1, #5, every
20th)
- iOS: log "reconnected after N attempts" on success, silent initial
connect
- Python: only log first client connect and all-clients-disconnected
Files: 13 modified, 8 new (including companion-api/main.py)
2026-04-12 19:24:22 -07:00
|
|
|
- package: ConfettiSwiftUI
|
2026-03-28 13:49:47 -07:00
|
|
|
|
|
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
# watchOS App
|
|
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
NavidromeWatch:
|
|
|
|
|
type: application
|
|
|
|
|
platform: watchOS
|
2026-04-08 15:34:14 -07:00
|
|
|
deploymentTarget: "26.0"
|
2026-03-28 13:49:47 -07:00
|
|
|
sources:
|
|
|
|
|
- path: watchOS
|
|
|
|
|
- path: Shared
|
|
|
|
|
resources:
|
|
|
|
|
- path: watchOS/Resources
|
|
|
|
|
settings:
|
|
|
|
|
base:
|
2026-04-08 15:34:14 -07:00
|
|
|
PRODUCT_BUNDLE_IDENTIFIER: ca.dallasgroot.navidromeplayer.app.watchkitapp
|
2026-03-28 13:49:47 -07:00
|
|
|
INFOPLIST_FILE: watchOS/Resources/Info.plist
|
|
|
|
|
CODE_SIGN_ENTITLEMENTS: watchOS/Resources/NavidromeWatch.entitlements
|
|
|
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
2026-04-08 15:34:14 -07:00
|
|
|
WATCHOS_DEPLOYMENT_TARGET: "26.0"
|
2026-04-10 07:00:30 -07:00
|
|
|
SKIP_INSTALL: true
|
2026-03-28 13:49:47 -07:00
|
|
|
|
2026-04-12 13:39:13 -07:00
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
# Widget Extension
|
|
|
|
|
# ─────────────────────────────────────
|
|
|
|
|
NavidromeWidget:
|
2026-04-12 13:52:10 -07:00
|
|
|
type: app-extension
|
2026-04-12 13:39:13 -07:00
|
|
|
platform: iOS
|
|
|
|
|
deploymentTarget: "17.0"
|
|
|
|
|
sources:
|
|
|
|
|
- path: Widget
|
|
|
|
|
excludes:
|
|
|
|
|
- "*.entitlements"
|
2026-04-12 13:52:10 -07:00
|
|
|
- "Info.plist"
|
2026-04-12 13:39:13 -07:00
|
|
|
- path: Shared/Storage/WidgetSharedState.swift
|
|
|
|
|
settings:
|
|
|
|
|
base:
|
|
|
|
|
PRODUCT_BUNDLE_IDENTIFIER: ca.dallasgroot.navidromeplayer.app.widget
|
|
|
|
|
PRODUCT_NAME: NavidromeWidget
|
2026-04-12 13:52:10 -07:00
|
|
|
INFOPLIST_FILE: Widget/Info.plist
|
2026-04-12 13:39:13 -07:00
|
|
|
CODE_SIGN_ENTITLEMENTS: Widget/NavidromeWidget.entitlements
|
|
|
|
|
SWIFT_EMIT_LOC_STRINGS: "YES"
|
|
|
|
|
SKIP_INSTALL: true
|
|
|
|
|
|
2026-03-28 13:49:47 -07:00
|
|
|
schemes:
|
|
|
|
|
NavidromePlayer:
|
|
|
|
|
build:
|
|
|
|
|
targets:
|
|
|
|
|
NavidromePlayer: all
|
2026-04-08 15:34:14 -07:00
|
|
|
NavidromeWatch: all
|
2026-04-12 13:39:13 -07:00
|
|
|
NavidromeWidget: all
|
2026-04-08 15:34:14 -07:00
|
|
|
run:
|
|
|
|
|
config: Debug
|
|
|
|
|
test:
|
|
|
|
|
config: Debug
|
|
|
|
|
profile:
|
|
|
|
|
config: Release
|
|
|
|
|
analyze:
|
|
|
|
|
config: Debug
|
2026-03-28 13:49:47 -07:00
|
|
|
archive:
|
|
|
|
|
config: Release
|