From 270a7870feb6fb808e18227fe92c446956ae4516 Mon Sep 17 00:00:00 2001 From: Gianni Carlo Date: Sat, 27 Jun 2026 15:51:51 -0500 Subject: [PATCH 1/4] Add new settings for showing player on launch --- BookPlayer.xcodeproj/project.pbxproj | 4 ++ BookPlayer/Base.lproj/Localizable.strings | 3 ++ .../Library/ItemList/LibraryRootView.swift | 4 ++ BookPlayer/Services/CarPlayManager.swift | 26 ++++++++++++ .../SettingsPlayerControlsView.swift | 1 + .../StartupPlayerSectionView.swift | 41 +++++++++++++++++++ BookPlayer/ar.lproj/Localizable.strings | 3 ++ BookPlayer/ca.lproj/Localizable.strings | 3 ++ BookPlayer/cs.lproj/Localizable.strings | 3 ++ BookPlayer/da.lproj/Localizable.strings | 3 ++ BookPlayer/de.lproj/Localizable.strings | 3 ++ BookPlayer/el.lproj/Localizable.strings | 3 ++ BookPlayer/en.lproj/Localizable.strings | 3 ++ BookPlayer/es.lproj/Localizable.strings | 3 ++ BookPlayer/fi.lproj/Localizable.strings | 3 ++ BookPlayer/fr.lproj/Localizable.strings | 3 ++ BookPlayer/hu.lproj/Localizable.strings | 3 ++ BookPlayer/it.lproj/Localizable.strings | 3 ++ BookPlayer/ja.lproj/Localizable.strings | 3 ++ BookPlayer/nb.lproj/Localizable.strings | 3 ++ BookPlayer/nl.lproj/Localizable.strings | 3 ++ BookPlayer/pl.lproj/Localizable.strings | 3 ++ BookPlayer/pt-BR.lproj/Localizable.strings | 3 ++ BookPlayer/pt-PT.lproj/Localizable.strings | 3 ++ BookPlayer/ro.lproj/Localizable.strings | 3 ++ BookPlayer/ru.lproj/Localizable.strings | 3 ++ BookPlayer/sk-SK.lproj/Localizable.strings | 3 ++ BookPlayer/sv.lproj/Localizable.strings | 3 ++ BookPlayer/tr.lproj/Localizable.strings | 3 ++ BookPlayer/uk.lproj/Localizable.strings | 3 ++ BookPlayer/zh-Hans.lproj/Localizable.strings | 3 ++ Shared/Constants.swift | 2 + 32 files changed, 156 insertions(+) create mode 100644 BookPlayer/Settings/Sections/PlayerControls/StartupPlayerSectionView.swift diff --git a/BookPlayer.xcodeproj/project.pbxproj b/BookPlayer.xcodeproj/project.pbxproj index 4b166193f..4f07bd5b1 100644 --- a/BookPlayer.xcodeproj/project.pbxproj +++ b/BookPlayer.xcodeproj/project.pbxproj @@ -353,6 +353,7 @@ 631908AA2E369BDB009249C1 /* ProgressSeekingSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908A92E369BDA009249C1 /* ProgressSeekingSectionView.swift */; }; 631908AC2E369E31009249C1 /* GlobalSpeedSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908AB2E369E31009249C1 /* GlobalSpeedSectionView.swift */; }; 631908AE2E369EDC009249C1 /* BoostVolumeSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908AD2E369EDC009249C1 /* BoostVolumeSectionView.swift */; }; + 631908F02E369EDC009249C1 /* StartupPlayerSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908F12E369EDC009249C1 /* StartupPlayerSectionView.swift */; }; 631908B02E369FFA009249C1 /* AutoSleepTimerSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908AF2E369FFA009249C1 /* AutoSleepTimerSectionView.swift */; }; 631908B22E36A1AB009249C1 /* SmartRewindSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908B12E36A1AB009249C1 /* SmartRewindSectionView.swift */; }; 631908B42E36A26C009249C1 /* SkipIntervalsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631908B32E36A26C009249C1 /* SkipIntervalsSectionView.swift */; }; @@ -1329,6 +1330,7 @@ 631908A92E369BDA009249C1 /* ProgressSeekingSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressSeekingSectionView.swift; sourceTree = ""; }; 631908AB2E369E31009249C1 /* GlobalSpeedSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSpeedSectionView.swift; sourceTree = ""; }; 631908AD2E369EDC009249C1 /* BoostVolumeSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoostVolumeSectionView.swift; sourceTree = ""; }; + 631908F12E369EDC009249C1 /* StartupPlayerSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupPlayerSectionView.swift; sourceTree = ""; }; 631908AF2E369FFA009249C1 /* AutoSleepTimerSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSleepTimerSectionView.swift; sourceTree = ""; }; 631908B12E36A1AB009249C1 /* SmartRewindSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartRewindSectionView.swift; sourceTree = ""; }; 631908B32E36A26C009249C1 /* SkipIntervalsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkipIntervalsSectionView.swift; sourceTree = ""; }; @@ -2683,6 +2685,7 @@ 631908B12E36A1AB009249C1 /* SmartRewindSectionView.swift */, 631908AF2E369FFA009249C1 /* AutoSleepTimerSectionView.swift */, 631908AD2E369EDC009249C1 /* BoostVolumeSectionView.swift */, + 631908F12E369EDC009249C1 /* StartupPlayerSectionView.swift */, 631908AB2E369E31009249C1 /* GlobalSpeedSectionView.swift */, 631908A92E369BDA009249C1 /* ProgressSeekingSectionView.swift */, 631908A72E3697DC009249C1 /* ListOptionsSectionView.swift */, @@ -4599,6 +4602,7 @@ 4151A6DD26E4A13A00E49DBE /* MainCoordinator.swift in Sources */, 634BA54C2C0C21AF0015314D /* SecondOnboardingCoordinator.swift in Sources */, 631908AE2E369EDC009249C1 /* BoostVolumeSectionView.swift in Sources */, + 631908F02E369EDC009249C1 /* StartupPlayerSectionView.swift in Sources */, 63C6C2E62B5029BC00FFE0D8 /* SettingsAutolockView.swift in Sources */, 4124122826D19A8700B099DB /* StorageViewModel.swift in Sources */, 4158387926EB8D8800F4A12B /* LoadingViewController.swift in Sources */, diff --git a/BookPlayer/Base.lproj/Localizable.strings b/BookPlayer/Base.lproj/Localizable.strings index 6f6f3ab23..1c45f74fb 100644 --- a/BookPlayer/Base.lproj/Localizable.strings +++ b/BookPlayer/Base.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Doubles the volume.\nUse with caution and care for your hearing."; "settings_globalspeed_title" = "Global Speed Control"; "settings_globalspeed_description" = "Set speed across all books."; +"settings_openplayer_launch_title" = "Open Player on Launch"; +"settings_carplay_showplayer_title" = "Show Player on CarPlay Connect"; +"settings_startupplayer_description" = "Open the player screen for the last played book when launching the app or connecting to CarPlay."; "settings_autolock_title" = "Disable Autolock"; "settings_autolock_description" = "Prevent the device from locking when on the Player screen."; "settings_siri_lastplayed_title" = "Last played book"; diff --git a/BookPlayer/Library/ItemList/LibraryRootView.swift b/BookPlayer/Library/ItemList/LibraryRootView.swift index 9f7179b20..045ad034e 100644 --- a/BookPlayer/Library/ItemList/LibraryRootView.swift +++ b/BookPlayer/Library/ItemList/LibraryRootView.swift @@ -165,6 +165,10 @@ struct LibraryRootView: View { UserDefaults.standard.removeObject(forKey: Constants.UserDefaults.showPlayer) playerState.showPlayer = true } + + if UserDefaults.standard.bool(forKey: Constants.UserDefaults.openPlayerOnAppLaunch) { + playerState.showPlayer = true + } } catch BPPlayerError.fileMissing { // Silent preload: if the last-played file is missing on disk, // swallow the error. The user will see the proper alert if/when diff --git a/BookPlayer/Services/CarPlayManager.swift b/BookPlayer/Services/CarPlayManager.swift index 690f51ad9..ef17967d4 100644 --- a/BookPlayer/Services/CarPlayManager.swift +++ b/BookPlayer/Services/CarPlayManager.swift @@ -23,6 +23,10 @@ class CarPlayManager: NSObject { private var disposeBag = Set() /// Reference for updating boost volume title let boostVolumeItem = CPListItem(text: "", detailText: nil) + /// One-shot flag: when CarPlay connects with no loaded book yet, surface the + /// player on the next `.bookReady` (e.g. a resume-on-connect shortcut). Honors + /// the `carPlayShowPlayerOnConnect` setting without auto-loading a book ourselves. + private var shouldShowPlayerOnConnect = false override init() { super.init() @@ -39,12 +43,29 @@ class CarPlayManager: NSObject { self.setupNowPlayingTemplate() self.setRootTemplate() self.initializeDataIfNeeded() + + if UserDefaults.standard.bool(forKey: Constants.UserDefaults.carPlayShowPlayerOnConnect) { + if AppServices.shared.coreServices?.playerManager.currentItem != nil { + self.pushNowPlayingTemplate() + } else { + /// Nothing loaded yet; let an imminent `.bookReady` (e.g. resume-on-connect shortcut) surface the player + self.shouldShowPlayerOnConnect = true + } + } } func disconnect() { self.interfaceController = nil self.recentTemplate = nil self.libraryTemplate = nil + self.shouldShowPlayerOnConnect = false + } + + /// Push the shared Now Playing template, avoiding a duplicate push if it's already on top + @MainActor + private func pushNowPlayingTemplate() { + guard interfaceController?.topTemplate != CPNowPlayingTemplate.shared else { return } + interfaceController?.pushTemplate(CPNowPlayingTemplate.shared, animated: true, completion: nil) } @MainActor @@ -125,6 +146,11 @@ class CarPlayManager: NSObject { self.reloadRecentItems() self.setupNowPlayingTemplate() + + if self.shouldShowPlayerOnConnect { + self.shouldShowPlayerOnConnect = false + self.pushNowPlayingTemplate() + } }) .store(in: &disposeBag) diff --git a/BookPlayer/Settings/Sections/PlayerControls/SettingsPlayerControlsView.swift b/BookPlayer/Settings/Sections/PlayerControls/SettingsPlayerControlsView.swift index 4411915e1..cf2fa1741 100644 --- a/BookPlayer/Settings/Sections/PlayerControls/SettingsPlayerControlsView.swift +++ b/BookPlayer/Settings/Sections/PlayerControls/SettingsPlayerControlsView.swift @@ -21,6 +21,7 @@ struct SettingsPlayerControlsView: View { ProgressSeekingSectionView() ListOptionsSectionView() ProgressLabelsSectionView() + StartupPlayerSectionView() } .environmentObject(theme) .scrollContentBackground(.hidden) diff --git a/BookPlayer/Settings/Sections/PlayerControls/StartupPlayerSectionView.swift b/BookPlayer/Settings/Sections/PlayerControls/StartupPlayerSectionView.swift new file mode 100644 index 000000000..97e070f78 --- /dev/null +++ b/BookPlayer/Settings/Sections/PlayerControls/StartupPlayerSectionView.swift @@ -0,0 +1,41 @@ +// +// StartupPlayerSectionView.swift +// BookPlayer +// +// Created by Gianni Carlo on 27/6/26. +// Copyright © 2026 BookPlayer LLC. All rights reserved. +// + +import BookPlayerKit +import SwiftUI + +struct StartupPlayerSectionView: View { + @AppStorage(Constants.UserDefaults.openPlayerOnAppLaunch) var openPlayerOnAppLaunch: Bool = false + @AppStorage(Constants.UserDefaults.carPlayShowPlayerOnConnect) var carPlayShowPlayerOnConnect: Bool = false + + @EnvironmentObject var theme: ThemeViewModel + + var body: some View { + ThemedSection { + Toggle(isOn: $openPlayerOnAppLaunch) { + Text("settings_openplayer_launch_title") + .bpFont(.body) + } + Toggle(isOn: $carPlayShowPlayerOnConnect) { + Text("settings_carplay_showplayer_title") + .bpFont(.body) + } + } footer: { + Text("settings_startupplayer_description") + .bpFont(.caption) + .foregroundStyle(theme.secondaryColor) + } + } +} + +#Preview { + Form { + StartupPlayerSectionView() + } + .environmentObject(ThemeViewModel()) +} diff --git a/BookPlayer/ar.lproj/Localizable.strings b/BookPlayer/ar.lproj/Localizable.strings index 630115fbf..fafc5b91f 100644 --- a/BookPlayer/ar.lproj/Localizable.strings +++ b/BookPlayer/ar.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "ضاعف مستوى الصوت.\nاستخدم بحذر وعناية."; "settings_globalspeed_title" = "التحكم بالسرعة الافتراضية لجميع الكتب"; "settings_globalspeed_description" = "تعيين السرعة على جميع الكتب."; +"settings_openplayer_launch_title" = "فتح المشغل عند بدء التشغيل"; +"settings_carplay_showplayer_title" = "عرض المشغل عند الاتصال بـ CarPlay"; +"settings_startupplayer_description" = "فتح شاشة المشغل لآخر كتاب تم تشغيله عند فتح التطبيق أو الاتصال بـ CarPlay."; "settings_autolock_title" = "تعطيل القفل التلقائي"; "settings_autolock_description" = "منع قفل الجهاز عند فتح شاشة المشغل"; "settings_siri_lastplayed_title" = "آخر كتاب تم تشغيله"; diff --git a/BookPlayer/ca.lproj/Localizable.strings b/BookPlayer/ca.lproj/Localizable.strings index 687533225..21bbec2bd 100644 --- a/BookPlayer/ca.lproj/Localizable.strings +++ b/BookPlayer/ca.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dobla el volum.\nFeu servir amb precaució i cura de la vostra audició."; "settings_globalspeed_title" = "Control global de velocitat"; "settings_globalspeed_description" = "Estableix velocitat en tots els llibres."; +"settings_openplayer_launch_title" = "Obre el reproductor en iniciar"; +"settings_carplay_showplayer_title" = "Mostra el reproductor en connectar CarPlay"; +"settings_startupplayer_description" = "Obre la pantalla del reproductor amb l'últim llibre escoltat en iniciar l'aplicació o connectar-te a CarPlay."; "settings_autolock_title" = "Desactiva el bloqueig automàtic"; "settings_autolock_description" = "Eviteu que el dispositiu es bloquegi quan estigui a la pantalla del reproductor."; "settings_siri_lastplayed_title" = "Últim llibre reproduït"; diff --git a/BookPlayer/cs.lproj/Localizable.strings b/BookPlayer/cs.lproj/Localizable.strings index 39e311aa3..0da0d972a 100644 --- a/BookPlayer/cs.lproj/Localizable.strings +++ b/BookPlayer/cs.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Zdvojnásobí hlasitost přehrávání.\nPoužívejte opatrně a chraňete svůj sluch."; "settings_globalspeed_title" = "Nastavení rychlosti přehrávání"; "settings_globalspeed_description" = "Nastavit rychlost přehrávání pro všechny audioknihy."; +"settings_openplayer_launch_title" = "Otevřít přehrávač při spuštění"; +"settings_carplay_showplayer_title" = "Zobrazit přehrávač při připojení CarPlay"; +"settings_startupplayer_description" = "Otevřít obrazovku přehrávače s naposledy přehrávanou knihou při spuštění aplikace nebo připojení k CarPlay."; "settings_autolock_title" = "Zakázat automatické uzamčení"; "settings_autolock_description" = "Zabránit uzamčení zařízení při zobrazení Přehrávače."; "settings_siri_lastplayed_title" = "Naposledy přehrávaná audiokniha"; diff --git a/BookPlayer/da.lproj/Localizable.strings b/BookPlayer/da.lproj/Localizable.strings index 0767a0201..dca122ac9 100644 --- a/BookPlayer/da.lproj/Localizable.strings +++ b/BookPlayer/da.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Fordobler lydstyrken. \nAnvend med forsigtighed og omtanke for din hørelse."; "settings_globalspeed_title" = "Global hastighedskontrol"; "settings_globalspeed_description" = "Indstil hastighed på tværs af alle bøger."; +"settings_openplayer_launch_title" = "Åbn afspilleren ved start"; +"settings_carplay_showplayer_title" = "Vis afspilleren ved CarPlay-forbindelse"; +"settings_startupplayer_description" = "Åbn afspillerskærmen med den senest afspillede bog, når appen startes, eller der oprettes forbindelse til CarPlay."; "settings_autolock_title" = "Deaktiver autolås"; "settings_autolock_description" = "Undgå at enheden låser når afspilleren er i brug."; "settings_siri_lastplayed_title" = "Senest afspillede bog"; diff --git a/BookPlayer/de.lproj/Localizable.strings b/BookPlayer/de.lproj/Localizable.strings index 9f86986a8..5f8de52f4 100644 --- a/BookPlayer/de.lproj/Localizable.strings +++ b/BookPlayer/de.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Verdoppelt die Lautstärke. Bitte nur mit Vorsicht verwenden. Dein Gehör ist unersetzlich!"; "settings_globalspeed_title" = "Abspielgeschwindigkeit übergreifend festlegen"; "settings_globalspeed_description" = "Jedes Buch wird mit der gleichen Geschwindigkeit abgespielt."; +"settings_openplayer_launch_title" = "Player beim Start öffnen"; +"settings_carplay_showplayer_title" = "Player bei CarPlay-Verbindung anzeigen"; +"settings_startupplayer_description" = "Öffnet den Player mit dem zuletzt gehörten Buch, wenn die App gestartet oder eine Verbindung zu CarPlay hergestellt wird."; "settings_autolock_title" = "Bildschirmsperre verhindern"; "settings_autolock_description" = "Verhindern, dass das Gerät gesperrt wird, wenn der Wiedergabebildschirm aktiv ist."; "settings_siri_lastplayed_title" = "Zuletzt gespieltes Buch fortsetzen"; diff --git a/BookPlayer/el.lproj/Localizable.strings b/BookPlayer/el.lproj/Localizable.strings index 249261094..8c0ddd303 100644 --- a/BookPlayer/el.lproj/Localizable.strings +++ b/BookPlayer/el.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Διπλασιάζει την ένταση.\nΧρησιμοποιήστε με προσοχή και φροντίστε την ακοή σας."; "settings_globalspeed_title" = "Παγκόσμιος έλεγχος ταχύτητας"; "settings_globalspeed_description" = "Ρυθμίστε την ταχύτητα σε όλα τα βιβλία."; +"settings_openplayer_launch_title" = "Άνοιγμα αναπαραγωγής κατά την εκκίνηση"; +"settings_carplay_showplayer_title" = "Εμφάνιση αναπαραγωγής κατά τη σύνδεση με CarPlay"; +"settings_startupplayer_description" = "Ανοίξτε την οθόνη αναπαραγωγής με το τελευταίο βιβλίο που ακούσατε κατά την εκκίνηση της εφαρμογής ή τη σύνδεση με το CarPlay."; "settings_autolock_title" = "Απενεργοποιήστε το αυτόματο κλείδωμα"; "settings_autolock_description" = "Αποτρέψτε το κλείδωμα της συσκευής όταν βρίσκεται στην οθόνη του προγράμματος αναπαραγωγής."; "settings_siri_lastplayed_title" = "Το βιβλίο που παίχτηκε τελευταία"; diff --git a/BookPlayer/en.lproj/Localizable.strings b/BookPlayer/en.lproj/Localizable.strings index 2c3133c40..f91a5e458 100644 --- a/BookPlayer/en.lproj/Localizable.strings +++ b/BookPlayer/en.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Doubles the volume.\nUse with caution and care for your hearing."; "settings_globalspeed_title" = "Global Speed Control"; "settings_globalspeed_description" = "Set speed across all books."; +"settings_openplayer_launch_title" = "Open Player on Launch"; +"settings_carplay_showplayer_title" = "Show Player on CarPlay Connect"; +"settings_startupplayer_description" = "Open the player screen for the last played book when launching the app or connecting to CarPlay."; "settings_autolock_title" = "Disable Autolock"; "settings_autolock_description" = "Prevent the device from locking when on the Player screen."; "settings_siri_lastplayed_title" = "Last played book"; diff --git a/BookPlayer/es.lproj/Localizable.strings b/BookPlayer/es.lproj/Localizable.strings index 14c5d3342..840314895 100644 --- a/BookPlayer/es.lproj/Localizable.strings +++ b/BookPlayer/es.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dobla el volumen.\nUsar con precaución y cuida de tu oído."; "settings_globalspeed_title" = "Velocidad Global"; "settings_globalspeed_description" = "Establece la velocidad para todos los libros."; +"settings_openplayer_launch_title" = "Abrir el reproductor al iniciar"; +"settings_carplay_showplayer_title" = "Mostrar el reproductor al conectar CarPlay"; +"settings_startupplayer_description" = "Abre la pantalla del reproductor con el último libro reproducido al iniciar la aplicación o conectarte a CarPlay."; "settings_autolock_title" = "Desactivar el bloqueo automático"; "settings_autolock_description" = "Evita que el dispositivo se bloquee cuando está en la pantalla del Reproductor."; "settings_siri_lastplayed_title" = "Último libro escuchado"; diff --git a/BookPlayer/fi.lproj/Localizable.strings b/BookPlayer/fi.lproj/Localizable.strings index a37a9a0ce..75b424ba2 100644 --- a/BookPlayer/fi.lproj/Localizable.strings +++ b/BookPlayer/fi.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Kaksinkertaistaa äänenvoimakkuuden.\nKäytä varoen ja huolehdi kuulostasi."; "settings_globalspeed_title" = "Yleinen nopeudenhallinta"; "settings_globalspeed_description" = "Aseta nopeus kaikille kirjoille."; +"settings_openplayer_launch_title" = "Avaa soitin käynnistettäessä"; +"settings_carplay_showplayer_title" = "Näytä soitin CarPlay-yhteyden yhteydessä"; +"settings_startupplayer_description" = "Avaa soittimen viimeksi kuunnellulla kirjalla, kun sovellus käynnistetään tai yhdistetään CarPlayhin."; "settings_autolock_title" = "Poista automaattinen lukitus käytöstä"; "settings_autolock_description" = "Estä laitteen lukittuminen Soitin-näyssä"; "settings_siri_lastplayed_title" = "Viimeksi kuunneltu kirja"; diff --git a/BookPlayer/fr.lproj/Localizable.strings b/BookPlayer/fr.lproj/Localizable.strings index 32895ac6a..e11be4d97 100644 --- a/BookPlayer/fr.lproj/Localizable.strings +++ b/BookPlayer/fr.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Double le volume.\nUtilisez avec prudence et faites attention à votre audition."; "settings_globalspeed_title" = "Contrôle de la vitesse globale"; "settings_globalspeed_description" = "Applique la vitesse sur tous les livres."; +"settings_openplayer_launch_title" = "Ouvrir le lecteur au démarrage"; +"settings_carplay_showplayer_title" = "Afficher le lecteur lors de la connexion à CarPlay"; +"settings_startupplayer_description" = "Ouvre l'écran du lecteur avec le dernier livre écouté au lancement de l'application ou lors de la connexion à CarPlay."; "settings_autolock_title" = "Désactiver le verrouillage auto."; "settings_autolock_description" = "Empêche l'appareil de se verrouiller en étant sur l'écran du Lecteur."; "settings_siri_lastplayed_title" = "Dernier livre lu"; diff --git a/BookPlayer/hu.lproj/Localizable.strings b/BookPlayer/hu.lproj/Localizable.strings index fc9ffb7e3..152859ab7 100644 --- a/BookPlayer/hu.lproj/Localizable.strings +++ b/BookPlayer/hu.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Megduplázza a hangerőt.\nÓvatosan és körültekintően használja hallásának védelme érdekében"; "settings_globalspeed_title" = "Globális sebességszabályozás"; "settings_globalspeed_description" = "Sebesség beállítása az összes könyvben"; +"settings_openplayer_launch_title" = "Lejátszó megnyitása indításkor"; +"settings_carplay_showplayer_title" = "Lejátszó megjelenítése CarPlay csatlakozáskor"; +"settings_startupplayer_description" = "Megnyitja a lejátszó képernyőt az utoljára hallgatott könyvvel az alkalmazás indításakor vagy a CarPlay csatlakoztatásakor."; "settings_autolock_title" = "Automatikus zárolás tiltása"; "settings_autolock_description" = "A készülék a lejátszó képernyőn nem záródik le"; "settings_siri_lastplayed_title" = "A legutóbb lejátszott könyv"; diff --git a/BookPlayer/it.lproj/Localizable.strings b/BookPlayer/it.lproj/Localizable.strings index 3e09bcceb..c6b8e725a 100644 --- a/BookPlayer/it.lproj/Localizable.strings +++ b/BookPlayer/it.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Raddoppia il volume. \nUsa con cautela e fai attenzione al tuo udito."; "settings_globalspeed_title" = "Controllo globale della velocità "; "settings_globalspeed_description" = "Imposta velocità per tutti i libri."; +"settings_openplayer_launch_title" = "Apri il player all'avvio"; +"settings_carplay_showplayer_title" = "Mostra il player alla connessione CarPlay"; +"settings_startupplayer_description" = "Apre la schermata del player con l'ultimo libro ascoltato all'avvio dell'app o alla connessione a CarPlay."; "settings_autolock_title" = "Disattiva il blocco automatico"; "settings_autolock_description" = "Impedisci il blocco del dispositivo quando ci si trova sulla schermata del lettore."; "settings_siri_lastplayed_title" = "Ultimo libro riprodotto"; diff --git a/BookPlayer/ja.lproj/Localizable.strings b/BookPlayer/ja.lproj/Localizable.strings index 9c058d0ea..63a663147 100644 --- a/BookPlayer/ja.lproj/Localizable.strings +++ b/BookPlayer/ja.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "音量を2倍にします。 \n聴覚に配慮し、注意して使用してください。"; "settings_globalspeed_title" = "再生速度の共通設定"; "settings_globalspeed_description" = "すべてのブックに速度を設定します。"; +"settings_openplayer_launch_title" = "起動時にプレーヤーを開く"; +"settings_carplay_showplayer_title" = "CarPlay接続時にプレーヤーを表示"; +"settings_startupplayer_description" = "アプリの起動時やCarPlayへの接続時に、最後に再生したブックのプレーヤー画面を開きます。"; "settings_autolock_title" = "自動ロックを無効にする"; "settings_autolock_description" = "プレーヤー画面上でデバイスがロックされないようにします。"; "settings_siri_lastplayed_title" = "前回再生していたブック"; diff --git a/BookPlayer/nb.lproj/Localizable.strings b/BookPlayer/nb.lproj/Localizable.strings index 8a9a2d678..550a4d99c 100644 --- a/BookPlayer/nb.lproj/Localizable.strings +++ b/BookPlayer/nb.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dobler volumet.\nBruk med forsiktighet og ta vare på hørselen din."; "settings_globalspeed_title" = "Global hastighetskontroll"; "settings_globalspeed_description" = "Angi hastighet for alle bøker."; +"settings_openplayer_launch_title" = "Åpne spilleren ved oppstart"; +"settings_carplay_showplayer_title" = "Vis spilleren ved CarPlay-tilkobling"; +"settings_startupplayer_description" = "Åpne spillerskjermen med den sist spilte boken når appen startes eller kobles til CarPlay."; "settings_autolock_title" = "Deaktiver autolås"; "settings_autolock_description" = "Hindre at enheten låses når den viser spillerskjermen."; "settings_siri_lastplayed_title" = "Sist spilte bok"; diff --git a/BookPlayer/nl.lproj/Localizable.strings b/BookPlayer/nl.lproj/Localizable.strings index 1df720729..45f1ad044 100644 --- a/BookPlayer/nl.lproj/Localizable.strings +++ b/BookPlayer/nl.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Verdubbelt het volume. \n Wees voorzichtig en zorg voor uw gehoor."; "settings_globalspeed_title" = "Globale snelheidsregeling"; "settings_globalspeed_description" = "Stel snelheid in voor alle boeken."; +"settings_openplayer_launch_title" = "Speler openen bij opstarten"; +"settings_carplay_showplayer_title" = "Speler tonen bij CarPlay-verbinding"; +"settings_startupplayer_description" = "Open het spelerscherm met het laatst afgespeelde boek bij het starten van de app of het verbinden met CarPlay."; "settings_autolock_title" = "Autolock uitschakelen"; "settings_autolock_description" = "Voorkom dat het apparaat vergrendelt wanneer het zich op het spelerscherm bevindt."; "settings_siri_lastplayed_title" = "Laatst afgespeelde boek"; diff --git a/BookPlayer/pl.lproj/Localizable.strings b/BookPlayer/pl.lproj/Localizable.strings index 49acf5e3e..f616d54cb 100644 --- a/BookPlayer/pl.lproj/Localizable.strings +++ b/BookPlayer/pl.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Podwaja głośność. Używaj z rozwagą i troską o swój słuch."; "settings_globalspeed_title" = "Globalna Kontrola Prędkości"; "settings_globalspeed_description" = "Ustaw prędkość dla wszystkich książek."; +"settings_openplayer_launch_title" = "Otwórz odtwarzacz przy uruchomieniu"; +"settings_carplay_showplayer_title" = "Pokaż odtwarzacz po połączeniu z CarPlay"; +"settings_startupplayer_description" = "Otwórz ekran odtwarzacza z ostatnio odtwarzaną książką po uruchomieniu aplikacji lub połączeniu z CarPlay."; "settings_autolock_title" = "Wyłącz Autolock"; "settings_autolock_description" = "Zapobiegaj blokowaniu urządzenia na ekranie odtwarzacza."; "settings_siri_lastplayed_title" = "Ostatnio odtwarzana książka"; diff --git a/BookPlayer/pt-BR.lproj/Localizable.strings b/BookPlayer/pt-BR.lproj/Localizable.strings index 373fec95a..328de67c0 100644 --- a/BookPlayer/pt-BR.lproj/Localizable.strings +++ b/BookPlayer/pt-BR.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dobra o volume.\nUse com cautela e cuidado com sua audição."; "settings_globalspeed_title" = "Controle de Velocidade Global"; "settings_globalspeed_description" = "Defina a velocidade em todos os livros."; +"settings_openplayer_launch_title" = "Abrir o player ao iniciar"; +"settings_carplay_showplayer_title" = "Mostrar o player ao conectar ao CarPlay"; +"settings_startupplayer_description" = "Abre a tela do player com o último livro reproduzido ao iniciar o aplicativo ou conectar ao CarPlay."; "settings_autolock_title" = "Desativar Bloqueio Automático"; "settings_autolock_description" = "Evita o bloqueio do dispositivo quando estiver na tela de reprodução."; "settings_siri_lastplayed_title" = "Último livro reproduzido"; diff --git a/BookPlayer/pt-PT.lproj/Localizable.strings b/BookPlayer/pt-PT.lproj/Localizable.strings index d95878cfc..e356974fc 100644 --- a/BookPlayer/pt-PT.lproj/Localizable.strings +++ b/BookPlayer/pt-PT.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Duplicar o volume.\nUse com precaução e tenha cuidado com sua audição."; "settings_globalspeed_title" = "Controlo da Velocidade Global"; "settings_globalspeed_description" = "Definição da velocidade para todos os livros."; +"settings_openplayer_launch_title" = "Abrir o leitor ao iniciar"; +"settings_carplay_showplayer_title" = "Mostrar o leitor ao ligar ao CarPlay"; +"settings_startupplayer_description" = "Abre o ecrã do leitor com o último livro reproduzido ao iniciar a aplicação ou ao ligar ao CarPlay."; "settings_autolock_title" = "Desativar Bloqueio Automático"; "settings_autolock_description" = "Prevenir o bloqueio do dispositivo quando estiver no ecrã de reprodução."; "settings_siri_lastplayed_title" = "Último livro reproduzido"; diff --git a/BookPlayer/ro.lproj/Localizable.strings b/BookPlayer/ro.lproj/Localizable.strings index 0dec0ea52..f1adef2f8 100644 --- a/BookPlayer/ro.lproj/Localizable.strings +++ b/BookPlayer/ro.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dublează volumul.\nUtilizați cu prudență și grijă pentru auzul dvs."; "settings_globalspeed_title" = "Controlul global al vitezei"; "settings_globalspeed_description" = "Setați viteza în toate cărțile."; +"settings_openplayer_launch_title" = "Deschide playerul la pornire"; +"settings_carplay_showplayer_title" = "Afișează playerul la conectarea CarPlay"; +"settings_startupplayer_description" = "Deschide ecranul playerului cu ultima carte redată la pornirea aplicației sau la conectarea la CarPlay."; "settings_autolock_title" = "Dezactivează Autolock"; "settings_autolock_description" = "Împiedicați blocarea dispozitivului pe ecranul Playerului."; "settings_siri_lastplayed_title" = "Ultima carte redată"; diff --git a/BookPlayer/ru.lproj/Localizable.strings b/BookPlayer/ru.lproj/Localizable.strings index 1273407a1..992e0d96c 100644 --- a/BookPlayer/ru.lproj/Localizable.strings +++ b/BookPlayer/ru.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Удваивает громкость. \nИспользуйте с осторожностью и заботой о вашем слухе."; "settings_globalspeed_title" = "Глобальное управление скоростью"; "settings_globalspeed_description" = "Установите скорость для всех книг."; +"settings_openplayer_launch_title" = "Открывать плеер при запуске"; +"settings_carplay_showplayer_title" = "Показывать плеер при подключении CarPlay"; +"settings_startupplayer_description" = "Открывать экран плеера с последней прослушанной книгой при запуске приложения или подключении к CarPlay."; "settings_autolock_title" = "Отключить автоблокировку"; "settings_autolock_description" = "Запретить блокировку устройства на экране плеера."; "settings_siri_lastplayed_title" = "Последняя воспроизведенная книга"; diff --git a/BookPlayer/sk-SK.lproj/Localizable.strings b/BookPlayer/sk-SK.lproj/Localizable.strings index f7f883f55..25a907d12 100644 --- a/BookPlayer/sk-SK.lproj/Localizable.strings +++ b/BookPlayer/sk-SK.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Zdvojnásobí hlasitosť prehrávania.\nPoužívajte opatrne a chráňte si svoj sluch."; "settings_globalspeed_title" = "Globálne ovládanie rýchlosti"; "settings_globalspeed_description" = "Nastaví rýchlosť prehrávania pre všetky knihy."; +"settings_openplayer_launch_title" = "Otvoriť prehrávač pri spustení"; +"settings_carplay_showplayer_title" = "Zobraziť prehrávač pri pripojení CarPlay"; +"settings_startupplayer_description" = "Otvorí obrazovku prehrávača s naposledy prehrávanou knihou pri spustení aplikácie alebo pripojení k CarPlay."; "settings_autolock_title" = "Zakázať automatické uzamknutie"; "settings_autolock_description" = "Zabráni uzamknutiu zariadenia pri zobrazení Prehrávača."; "settings_siri_lastplayed_title" = "Naposledy prehrávaná kniha"; diff --git a/BookPlayer/sv.lproj/Localizable.strings b/BookPlayer/sv.lproj/Localizable.strings index d4a7c9192..98230b63a 100644 --- a/BookPlayer/sv.lproj/Localizable.strings +++ b/BookPlayer/sv.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Dubblar volymen. \nAnvänd med försiktighet och omsorg för din hörsel."; "settings_globalspeed_title" = "Global Uppspelningshastighet"; "settings_globalspeed_description" = "Ange uppspelningshastighet för alla böcker."; +"settings_openplayer_launch_title" = "Öppna spelaren vid start"; +"settings_carplay_showplayer_title" = "Visa spelaren vid CarPlay-anslutning"; +"settings_startupplayer_description" = "Öppna spelarskärmen med den senast spelade boken när appen startas eller ansluts till CarPlay."; "settings_autolock_title" = "Avaktivera Automatisk Låsning"; "settings_autolock_description" = "Förhindra att enheten låses när du är på spelarens skärmsida."; "settings_siri_lastplayed_title" = "Senast spelad bok"; diff --git a/BookPlayer/tr.lproj/Localizable.strings b/BookPlayer/tr.lproj/Localizable.strings index 088e91fff..6bec77ee3 100644 --- a/BookPlayer/tr.lproj/Localizable.strings +++ b/BookPlayer/tr.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Sesi iki katına çıkarır.\nİşitme duyunuz için dikkat edin ve dikkatli kullanın."; "settings_globalspeed_title" = "Evrensel Hız Kontrolü"; "settings_globalspeed_description" = "Tüm kitaplarda hızı ayarlayın."; +"settings_openplayer_launch_title" = "Başlangıçta oynatıcıyı aç"; +"settings_carplay_showplayer_title" = "CarPlay bağlantısında oynatıcıyı göster"; +"settings_startupplayer_description" = "Uygulamayı başlatırken veya CarPlay'e bağlanırken son çalınan kitabın oynatıcı ekranını açar."; "settings_autolock_title" = "Otomatik Kilidi Devre Dışı Bırak"; "settings_autolock_description" = "Oynatıcı ekranındayken cihazın kilitlenmesini önleyin."; "settings_siri_lastplayed_title" = "En son oynatılan kitap"; diff --git a/BookPlayer/uk.lproj/Localizable.strings b/BookPlayer/uk.lproj/Localizable.strings index 20175eb07..c14f02a90 100644 --- a/BookPlayer/uk.lproj/Localizable.strings +++ b/BookPlayer/uk.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "Подвоює гучність. \n Використовуйте обережно та дбайте про свій слух."; "settings_globalspeed_title" = "Глобальний контроль швидкості"; "settings_globalspeed_description" = "Встановити швидкість для всіх книг."; +"settings_openplayer_launch_title" = "Відкривати програвач під час запуску"; +"settings_carplay_showplayer_title" = "Показувати програвач під час підключення CarPlay"; +"settings_startupplayer_description" = "Відкривати екран програвача з останньою прослуханою книгою під час запуску застосунку або підключення до CarPlay."; "settings_autolock_title" = "Вимкнути автоблокування"; "settings_autolock_description" = "Запобігати блокуванню пристрою при відкритому програвачі."; "settings_siri_lastplayed_title" = "Остання відтворена книга"; diff --git a/BookPlayer/zh-Hans.lproj/Localizable.strings b/BookPlayer/zh-Hans.lproj/Localizable.strings index c219f5425..c00e0c6c2 100644 --- a/BookPlayer/zh-Hans.lproj/Localizable.strings +++ b/BookPlayer/zh-Hans.lproj/Localizable.strings @@ -9,6 +9,9 @@ "settings_boostvolume_description" = "将音量加倍。 \n请谨慎使用并避免影响听力。"; "settings_globalspeed_title" = "全局播放速度"; "settings_globalspeed_description" = "在所有有声书中使用同一播放速度。"; +"settings_openplayer_launch_title" = "启动时打开播放器"; +"settings_carplay_showplayer_title" = "连接 CarPlay 时显示播放器"; +"settings_startupplayer_description" = "在启动应用或连接 CarPlay 时,打开上次播放有声书的播放器界面。"; "settings_autolock_title" = "禁用自动锁定"; "settings_autolock_description" = "播放时防止锁定。"; "settings_siri_lastplayed_title" = "最后播放的书"; diff --git a/Shared/Constants.swift b/Shared/Constants.swift index 661be37e2..f479e60c0 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -39,6 +39,8 @@ public enum Constants { public static let autolockDisabled = "userSettingsDisableAutolock" public static let autolockDisabledOnlyWhenPowered = "userSettingsAutolockOnlyWhenPowered" public static let playerListPrefersBookmarks = "userSettingsPlayerListPrefersBookmarks" + public static let carPlayShowPlayerOnConnect = "userSettingsCarPlayShowPlayerOnConnect" + public static let openPlayerOnAppLaunch = "userSettingsOpenPlayerOnAppLaunch" public static let storageFilesSortOrder = "userSettingsStorageFilesSortOrder" public static let customSleepTimerDuration = "userSettingsCustomSleepTimerDuration" public static let autoTimerEnabled = "userSettingsAutoTimerEnabled" From b430473bc9eda52b53780f9a1754083a59a3f347 Mon Sep 17 00:00:00 2001 From: Gianni Carlo Date: Sun, 28 Jun 2026 22:50:29 -0500 Subject: [PATCH 2/4] Fix CarPlay on cold launch --- BookPlayer/Base.lproj/Localizable.strings | 2 +- .../Library/ItemList/LibraryRootView.swift | 11 +-- BookPlayer/Player/PlayerManager.swift | 43 ++++++++++++ BookPlayer/Services/CarPlayManager.swift | 70 ++++++++++++++++--- BookPlayer/ar.lproj/Localizable.strings | 2 +- BookPlayer/ca.lproj/Localizable.strings | 2 +- BookPlayer/cs.lproj/Localizable.strings | 2 +- BookPlayer/da.lproj/Localizable.strings | 2 +- BookPlayer/de.lproj/Localizable.strings | 2 +- BookPlayer/el.lproj/Localizable.strings | 2 +- BookPlayer/en.lproj/Localizable.strings | 2 +- BookPlayer/es.lproj/Localizable.strings | 2 +- BookPlayer/fi.lproj/Localizable.strings | 2 +- BookPlayer/fr.lproj/Localizable.strings | 2 +- BookPlayer/hu.lproj/Localizable.strings | 2 +- BookPlayer/it.lproj/Localizable.strings | 2 +- BookPlayer/ja.lproj/Localizable.strings | 2 +- BookPlayer/nb.lproj/Localizable.strings | 2 +- BookPlayer/nl.lproj/Localizable.strings | 2 +- BookPlayer/pl.lproj/Localizable.strings | 2 +- BookPlayer/pt-BR.lproj/Localizable.strings | 2 +- BookPlayer/pt-PT.lproj/Localizable.strings | 2 +- BookPlayer/ro.lproj/Localizable.strings | 2 +- BookPlayer/ru.lproj/Localizable.strings | 2 +- BookPlayer/sk-SK.lproj/Localizable.strings | 2 +- BookPlayer/sv.lproj/Localizable.strings | 2 +- BookPlayer/tr.lproj/Localizable.strings | 2 +- BookPlayer/uk.lproj/Localizable.strings | 2 +- BookPlayer/zh-Hans.lproj/Localizable.strings | 2 +- 29 files changed, 135 insertions(+), 41 deletions(-) diff --git a/BookPlayer/Base.lproj/Localizable.strings b/BookPlayer/Base.lproj/Localizable.strings index 1c45f74fb..9da2f799c 100644 --- a/BookPlayer/Base.lproj/Localizable.strings +++ b/BookPlayer/Base.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Set speed across all books."; "settings_openplayer_launch_title" = "Open Player on Launch"; "settings_carplay_showplayer_title" = "Show Player on CarPlay Connect"; -"settings_startupplayer_description" = "Open the player screen for the last played book when launching the app or connecting to CarPlay."; +"settings_startupplayer_description" = "Open the player for the last played book when launching the app or connecting to CarPlay. On CarPlay, this interrupts audio playing in other apps."; "settings_autolock_title" = "Disable Autolock"; "settings_autolock_description" = "Prevent the device from locking when on the Player screen."; "settings_siri_lastplayed_title" = "Last played book"; diff --git a/BookPlayer/Library/ItemList/LibraryRootView.swift b/BookPlayer/Library/ItemList/LibraryRootView.swift index 045ad034e..81b4b9369 100644 --- a/BookPlayer/Library/ItemList/LibraryRootView.swift +++ b/BookPlayer/Library/ItemList/LibraryRootView.swift @@ -134,6 +134,13 @@ struct LibraryRootView: View { func handleLibraryLoaded() async { await loadLastBookIfNeeded() + /// Open the player on launch if enabled and a book is loaded — checked here (not only inside + /// `loadLastBookIfNeeded`) so it still fires when the book was already loaded by another scene + /// (e.g. CarPlay) and the `currentItem == nil` guard there returns early. + if UserDefaults.standard.bool(forKey: Constants.UserDefaults.openPlayerOnAppLaunch), + playerManager.currentItem != nil { + playerState.showPlayer = true + } importManager.notifyPendingFiles() showSecondOnboarding() @@ -165,10 +172,6 @@ struct LibraryRootView: View { UserDefaults.standard.removeObject(forKey: Constants.UserDefaults.showPlayer) playerState.showPlayer = true } - - if UserDefaults.standard.bool(forKey: Constants.UserDefaults.openPlayerOnAppLaunch) { - playerState.showPlayer = true - } } catch BPPlayerError.fileMissing { // Silent preload: if the last-played file is missing on disk, // swallow the error. The user will see the proper alert if/when diff --git a/BookPlayer/Player/PlayerManager.swift b/BookPlayer/Player/PlayerManager.swift index 33aa2aae1..4a867cfaf 100755 --- a/BookPlayer/Player/PlayerManager.swift +++ b/BookPlayer/Player/PlayerManager.swift @@ -32,6 +32,8 @@ final class PlayerManager: NSObject, PlayerManagerProtocol, ObservableObject { private var timeControlSubscription: AnyCancellable? private var playableChapterSubscription: AnyCancellable? private var isPlayingSubscription: AnyCancellable? + /// Tracks the brief muted play used to claim Now Playing on CarPlay connect, so we can pause once it starts + private var nowPlayingClaimSubscription: AnyCancellable? private var periodicTimeObserver: Any? private var disposeBag = Set() /// Flag determining if it should resume playback after finishing up loading an item @@ -885,6 +887,47 @@ extension PlayerManager { play(autoPlayed: false) } + /// Take over the system Now Playing slot for CarPlay. iOS only designates the Now Playing app from + /// one that *actually plays* (a third-party app can't fake `playbackState` — that's an Apple-private + /// entitlement), so we briefly play **muted**, then pause the instant playback starts — landing on + /// our book paused, now owning Now Playing, with no audible blip. Skips if nothing is loaded or we're + /// already playing (we'd own it). A timeout fallback guarantees we unmute even if playback never starts. + /// + /// Note: this *will* interrupt audio another app is actively playing. There's no reliable way to detect + /// that beforehand here — `isOtherAudioPlaying` / `secondaryAudioShouldBeSilencedHint` only update once + /// our own session is active (the very act we're trying to avoid), and read stale (false) on a CarPlay + /// background launch. So we accept the takeover as a known side effect. Returns whether attempted. + @MainActor + @discardableResult + func claimNowPlayingThenPause() -> Bool { + guard currentItem != nil, !isPlaying else { return false } + + audioPlayer.isMuted = true + nowPlayingClaimSubscription = timeControlPassthroughPublisher + .filter { $0 == .playing } + .first() + .timeout(.seconds(3), scheduler: RunLoop.main) + .sink( + receiveCompletion: { [weak self] _ in + /// Fires on success (after pause) or on timeout — always restore audio so we can't get stuck muted + self?.audioPlayer.isMuted = false + self?.nowPlayingClaimSubscription = nil + }, + receiveValue: { [weak self] _ in + self?.pause() + /// Re-publish the cover: its async load can complete *before* we become the Now Playing app + /// (so that push is ignored), and nothing re-pushes it afterward — refresh it now that we own + /// the slot, otherwise CarPlay shows the blank artwork placeholder. + if let chapter = self?.currentItem?.currentChapter { + self?.setNowPlayingArtwork(chapter: chapter) + } + } + ) + + play() + return true + } + /// Persist a marker so the next successful activation can report whether — and how — /// the audio session recovered. Beta builds only. private func markAudioSessionFailure(_ error: NSError) { diff --git a/BookPlayer/Services/CarPlayManager.swift b/BookPlayer/Services/CarPlayManager.swift index ef17967d4..162316d22 100644 --- a/BookPlayer/Services/CarPlayManager.swift +++ b/BookPlayer/Services/CarPlayManager.swift @@ -40,17 +40,61 @@ class CarPlayManager: NSObject { func connect(_ interfaceController: CPInterfaceController) { self.interfaceController = interfaceController self.interfaceController?.delegate = self + /// Reset connect-scoped state so a re-connect without a paired disconnect doesn't act on a stale flag + self.shouldShowPlayerOnConnect = false self.setupNowPlayingTemplate() - self.setRootTemplate() + /// On a cold launch, `initializeDataIfNeeded()` runs and rebuilds the root template in its + /// completion — which tears down anything we push now. So when init is pending, defer presenting + /// the player to that rebuild (see `initializeDataIfNeeded`); otherwise present once the root here + /// is committed (pushing synchronously, before CarPlay sets the root, races and gets dropped). + let willInitializeData = WindowHelper.activeWindow == nil + self.setRootTemplate { [weak self] _, _ in + if !willInitializeData { + self?.showPlayerOnConnectIfNeeded() + } + } self.initializeDataIfNeeded() + } - if UserDefaults.standard.bool(forKey: Constants.UserDefaults.carPlayShowPlayerOnConnect) { - if AppServices.shared.coreServices?.playerManager.currentItem != nil { - self.pushNowPlayingTemplate() - } else { - /// Nothing loaded yet; let an imminent `.bookReady` (e.g. resume-on-connect shortcut) surface the player - self.shouldShowPlayerOnConnect = true - } + /// On connect, jump to Now Playing if a book is already loaded; otherwise arm a one-shot + /// so the next `.bookReady` (e.g. a resume-on-connect shortcut) surfaces it. + @MainActor + private func showPlayerOnConnectIfNeeded() { + guard UserDefaults.standard.bool(forKey: Constants.UserDefaults.carPlayShowPlayerOnConnect) else { + return + } + + if let playerManager = AppServices.shared.coreServices?.playerManager, + playerManager.currentItem != nil { + /// Take over the system Now Playing slot (brief muted play) so the pushed screen shows our book + /// instead of a blank placeholder. + playerManager.claimNowPlayingThenPause() + pushNowPlayingTemplate() + } else { + /// Nothing loaded yet; arm so the next `.bookReady` presents the player, and on a cold (killed) + /// launch — where no main-app window restored the last book — load it so that `.bookReady` fires. + shouldShowPlayerOnConnect = true + loadLastPlayedBookIfAvailable() + } + } + + /// On a cold (killed) CarPlay launch there's no main-app window to run the last-book restore, so + /// `currentItem` is nil. Load the last played book (paused) so `.bookReady` fires and the armed + /// `shouldShowPlayerOnConnect` flag presents the player. Stays on the tabs if there's no last book. + @MainActor + private func loadLastPlayedBookIfAvailable() { + Task { @MainActor in + guard + let coreServices = AppServices.shared.coreServices, + coreServices.playerManager.currentItem == nil, + let lastItem = coreServices.libraryService.getLibraryLastItem() + else { return } + + try? await coreServices.playerLoaderService.loadPlayer( + lastItem.relativePath, + autoplay: false, + recordAsLastBook: false + ) } } @@ -77,7 +121,10 @@ class CarPlayManager: NSObject { let dataInitializerCoordinator = DataInitializerCoordinator(alertPresenter: self) dataInitializerCoordinator.onFinish = { [weak self] in - self?.setRootTemplate() + self?.setRootTemplate { [weak self] _, _ in + /// Present the player now that the post-init root template is in place (deferred from connect) + self?.showPlayerOnConnectIfNeeded() + } if let coreServices = AppServices.shared.coreServices { coreServices.watchService.startSession() let listRefreshService = ListSyncRefreshService( @@ -149,6 +196,7 @@ class CarPlayManager: NSObject { if self.shouldShowPlayerOnConnect { self.shouldShowPlayerOnConnect = false + AppServices.shared.coreServices?.playerManager.claimNowPlayingThenPause() self.pushNowPlayingTemplate() } }) @@ -263,7 +311,7 @@ class CarPlayManager: NSObject { } /// Setup root Tab bar template with the Recent and Library tabs - func setRootTemplate() { + func setRootTemplate(completion: ((Bool, Error?) -> Void)? = nil) { let recentTemplate = CPListTemplate(title: "recent_title".localized, sections: []) self.recentTemplate = recentTemplate recentTemplate.tabTitle = "recent_title".localized @@ -274,7 +322,7 @@ class CarPlayManager: NSObject { libraryTemplate.tabImage = UIImage(systemName: "books.vertical") let tabTemplate = CPTabBarTemplate(templates: [recentTemplate, libraryTemplate]) tabTemplate.delegate = self - self.interfaceController?.setRootTemplate(tabTemplate, animated: false, completion: nil) + self.interfaceController?.setRootTemplate(tabTemplate, animated: false, completion: completion) } /// Reload content for the root library template diff --git a/BookPlayer/ar.lproj/Localizable.strings b/BookPlayer/ar.lproj/Localizable.strings index fafc5b91f..0e9829e76 100644 --- a/BookPlayer/ar.lproj/Localizable.strings +++ b/BookPlayer/ar.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "تعيين السرعة على جميع الكتب."; "settings_openplayer_launch_title" = "فتح المشغل عند بدء التشغيل"; "settings_carplay_showplayer_title" = "عرض المشغل عند الاتصال بـ CarPlay"; -"settings_startupplayer_description" = "فتح شاشة المشغل لآخر كتاب تم تشغيله عند فتح التطبيق أو الاتصال بـ CarPlay."; +"settings_startupplayer_description" = "فتح المشغل لآخر كتاب تم تشغيله عند فتح التطبيق أو الاتصال بـ CarPlay. في CarPlay، يؤدي هذا إلى إيقاف الصوت قيد التشغيل في التطبيقات الأخرى."; "settings_autolock_title" = "تعطيل القفل التلقائي"; "settings_autolock_description" = "منع قفل الجهاز عند فتح شاشة المشغل"; "settings_siri_lastplayed_title" = "آخر كتاب تم تشغيله"; diff --git a/BookPlayer/ca.lproj/Localizable.strings b/BookPlayer/ca.lproj/Localizable.strings index 21bbec2bd..7f3fcca9f 100644 --- a/BookPlayer/ca.lproj/Localizable.strings +++ b/BookPlayer/ca.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Estableix velocitat en tots els llibres."; "settings_openplayer_launch_title" = "Obre el reproductor en iniciar"; "settings_carplay_showplayer_title" = "Mostra el reproductor en connectar CarPlay"; -"settings_startupplayer_description" = "Obre la pantalla del reproductor amb l'últim llibre escoltat en iniciar l'aplicació o connectar-te a CarPlay."; +"settings_startupplayer_description" = "Obre el reproductor amb l'últim llibre escoltat en iniciar l'aplicació o connectar-te a CarPlay. A CarPlay, això interromp l'àudio que s'estigui reproduint en altres aplicacions."; "settings_autolock_title" = "Desactiva el bloqueig automàtic"; "settings_autolock_description" = "Eviteu que el dispositiu es bloquegi quan estigui a la pantalla del reproductor."; "settings_siri_lastplayed_title" = "Últim llibre reproduït"; diff --git a/BookPlayer/cs.lproj/Localizable.strings b/BookPlayer/cs.lproj/Localizable.strings index 0da0d972a..f816161d9 100644 --- a/BookPlayer/cs.lproj/Localizable.strings +++ b/BookPlayer/cs.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Nastavit rychlost přehrávání pro všechny audioknihy."; "settings_openplayer_launch_title" = "Otevřít přehrávač při spuštění"; "settings_carplay_showplayer_title" = "Zobrazit přehrávač při připojení CarPlay"; -"settings_startupplayer_description" = "Otevřít obrazovku přehrávače s naposledy přehrávanou knihou při spuštění aplikace nebo připojení k CarPlay."; +"settings_startupplayer_description" = "Otevřít přehrávač s naposledy přehrávanou knihou při spuštění aplikace nebo připojení k CarPlay. V CarPlay to přeruší zvuk přehrávaný v jiných aplikacích."; "settings_autolock_title" = "Zakázat automatické uzamčení"; "settings_autolock_description" = "Zabránit uzamčení zařízení při zobrazení Přehrávače."; "settings_siri_lastplayed_title" = "Naposledy přehrávaná audiokniha"; diff --git a/BookPlayer/da.lproj/Localizable.strings b/BookPlayer/da.lproj/Localizable.strings index dca122ac9..62a91bac8 100644 --- a/BookPlayer/da.lproj/Localizable.strings +++ b/BookPlayer/da.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Indstil hastighed på tværs af alle bøger."; "settings_openplayer_launch_title" = "Åbn afspilleren ved start"; "settings_carplay_showplayer_title" = "Vis afspilleren ved CarPlay-forbindelse"; -"settings_startupplayer_description" = "Åbn afspillerskærmen med den senest afspillede bog, når appen startes, eller der oprettes forbindelse til CarPlay."; +"settings_startupplayer_description" = "Åbn afspilleren med den senest afspillede bog, når appen startes, eller der oprettes forbindelse til CarPlay. I CarPlay afbryder dette lyd, der afspilles i andre apps."; "settings_autolock_title" = "Deaktiver autolås"; "settings_autolock_description" = "Undgå at enheden låser når afspilleren er i brug."; "settings_siri_lastplayed_title" = "Senest afspillede bog"; diff --git a/BookPlayer/de.lproj/Localizable.strings b/BookPlayer/de.lproj/Localizable.strings index 5f8de52f4..5c24bae0c 100644 --- a/BookPlayer/de.lproj/Localizable.strings +++ b/BookPlayer/de.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Jedes Buch wird mit der gleichen Geschwindigkeit abgespielt."; "settings_openplayer_launch_title" = "Player beim Start öffnen"; "settings_carplay_showplayer_title" = "Player bei CarPlay-Verbindung anzeigen"; -"settings_startupplayer_description" = "Öffnet den Player mit dem zuletzt gehörten Buch, wenn die App gestartet oder eine Verbindung zu CarPlay hergestellt wird."; +"settings_startupplayer_description" = "Öffnet den Player mit dem zuletzt gehörten Buch, wenn die App gestartet oder eine Verbindung zu CarPlay hergestellt wird. Bei CarPlay unterbricht dies die Audiowiedergabe anderer Apps."; "settings_autolock_title" = "Bildschirmsperre verhindern"; "settings_autolock_description" = "Verhindern, dass das Gerät gesperrt wird, wenn der Wiedergabebildschirm aktiv ist."; "settings_siri_lastplayed_title" = "Zuletzt gespieltes Buch fortsetzen"; diff --git a/BookPlayer/el.lproj/Localizable.strings b/BookPlayer/el.lproj/Localizable.strings index 8c0ddd303..564a125d8 100644 --- a/BookPlayer/el.lproj/Localizable.strings +++ b/BookPlayer/el.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Ρυθμίστε την ταχύτητα σε όλα τα βιβλία."; "settings_openplayer_launch_title" = "Άνοιγμα αναπαραγωγής κατά την εκκίνηση"; "settings_carplay_showplayer_title" = "Εμφάνιση αναπαραγωγής κατά τη σύνδεση με CarPlay"; -"settings_startupplayer_description" = "Ανοίξτε την οθόνη αναπαραγωγής με το τελευταίο βιβλίο που ακούσατε κατά την εκκίνηση της εφαρμογής ή τη σύνδεση με το CarPlay."; +"settings_startupplayer_description" = "Ανοίξτε την αναπαραγωγή με το τελευταίο βιβλίο που ακούσατε κατά την εκκίνηση της εφαρμογής ή τη σύνδεση με το CarPlay. Στο CarPlay, αυτό διακόπτει τον ήχο που αναπαράγεται σε άλλες εφαρμογές."; "settings_autolock_title" = "Απενεργοποιήστε το αυτόματο κλείδωμα"; "settings_autolock_description" = "Αποτρέψτε το κλείδωμα της συσκευής όταν βρίσκεται στην οθόνη του προγράμματος αναπαραγωγής."; "settings_siri_lastplayed_title" = "Το βιβλίο που παίχτηκε τελευταία"; diff --git a/BookPlayer/en.lproj/Localizable.strings b/BookPlayer/en.lproj/Localizable.strings index f91a5e458..7f79b0a42 100644 --- a/BookPlayer/en.lproj/Localizable.strings +++ b/BookPlayer/en.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Set speed across all books."; "settings_openplayer_launch_title" = "Open Player on Launch"; "settings_carplay_showplayer_title" = "Show Player on CarPlay Connect"; -"settings_startupplayer_description" = "Open the player screen for the last played book when launching the app or connecting to CarPlay."; +"settings_startupplayer_description" = "Open the player for the last played book when launching the app or connecting to CarPlay. On CarPlay, this interrupts audio playing in other apps."; "settings_autolock_title" = "Disable Autolock"; "settings_autolock_description" = "Prevent the device from locking when on the Player screen."; "settings_siri_lastplayed_title" = "Last played book"; diff --git a/BookPlayer/es.lproj/Localizable.strings b/BookPlayer/es.lproj/Localizable.strings index 840314895..708ded493 100644 --- a/BookPlayer/es.lproj/Localizable.strings +++ b/BookPlayer/es.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Establece la velocidad para todos los libros."; "settings_openplayer_launch_title" = "Abrir el reproductor al iniciar"; "settings_carplay_showplayer_title" = "Mostrar el reproductor al conectar CarPlay"; -"settings_startupplayer_description" = "Abre la pantalla del reproductor con el último libro reproducido al iniciar la aplicación o conectarte a CarPlay."; +"settings_startupplayer_description" = "Abre el reproductor con el último libro reproducido al iniciar la aplicación o conectarte a CarPlay. En CarPlay, esto interrumpe el audio que se esté reproduciendo en otras aplicaciones."; "settings_autolock_title" = "Desactivar el bloqueo automático"; "settings_autolock_description" = "Evita que el dispositivo se bloquee cuando está en la pantalla del Reproductor."; "settings_siri_lastplayed_title" = "Último libro escuchado"; diff --git a/BookPlayer/fi.lproj/Localizable.strings b/BookPlayer/fi.lproj/Localizable.strings index 75b424ba2..153d4b6cc 100644 --- a/BookPlayer/fi.lproj/Localizable.strings +++ b/BookPlayer/fi.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Aseta nopeus kaikille kirjoille."; "settings_openplayer_launch_title" = "Avaa soitin käynnistettäessä"; "settings_carplay_showplayer_title" = "Näytä soitin CarPlay-yhteyden yhteydessä"; -"settings_startupplayer_description" = "Avaa soittimen viimeksi kuunnellulla kirjalla, kun sovellus käynnistetään tai yhdistetään CarPlayhin."; +"settings_startupplayer_description" = "Avaa soittimen viimeksi kuunnellulla kirjalla, kun sovellus käynnistetään tai yhdistetään CarPlayhin. CarPlayssa tämä keskeyttää muissa sovelluksissa toistettavan äänen."; "settings_autolock_title" = "Poista automaattinen lukitus käytöstä"; "settings_autolock_description" = "Estä laitteen lukittuminen Soitin-näyssä"; "settings_siri_lastplayed_title" = "Viimeksi kuunneltu kirja"; diff --git a/BookPlayer/fr.lproj/Localizable.strings b/BookPlayer/fr.lproj/Localizable.strings index e11be4d97..12bd58ae4 100644 --- a/BookPlayer/fr.lproj/Localizable.strings +++ b/BookPlayer/fr.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Applique la vitesse sur tous les livres."; "settings_openplayer_launch_title" = "Ouvrir le lecteur au démarrage"; "settings_carplay_showplayer_title" = "Afficher le lecteur lors de la connexion à CarPlay"; -"settings_startupplayer_description" = "Ouvre l'écran du lecteur avec le dernier livre écouté au lancement de l'application ou lors de la connexion à CarPlay."; +"settings_startupplayer_description" = "Ouvre le lecteur avec le dernier livre écouté au lancement de l'application ou lors de la connexion à CarPlay. Sur CarPlay, cela interrompt l'audio en cours de lecture dans d'autres applications."; "settings_autolock_title" = "Désactiver le verrouillage auto."; "settings_autolock_description" = "Empêche l'appareil de se verrouiller en étant sur l'écran du Lecteur."; "settings_siri_lastplayed_title" = "Dernier livre lu"; diff --git a/BookPlayer/hu.lproj/Localizable.strings b/BookPlayer/hu.lproj/Localizable.strings index 152859ab7..7a7ac1934 100644 --- a/BookPlayer/hu.lproj/Localizable.strings +++ b/BookPlayer/hu.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Sebesség beállítása az összes könyvben"; "settings_openplayer_launch_title" = "Lejátszó megnyitása indításkor"; "settings_carplay_showplayer_title" = "Lejátszó megjelenítése CarPlay csatlakozáskor"; -"settings_startupplayer_description" = "Megnyitja a lejátszó képernyőt az utoljára hallgatott könyvvel az alkalmazás indításakor vagy a CarPlay csatlakoztatásakor."; +"settings_startupplayer_description" = "Megnyitja a lejátszót az utoljára hallgatott könyvvel az alkalmazás indításakor vagy a CarPlay csatlakoztatásakor. CarPlay esetén ez megszakítja a más alkalmazásokban lejátszott hangot."; "settings_autolock_title" = "Automatikus zárolás tiltása"; "settings_autolock_description" = "A készülék a lejátszó képernyőn nem záródik le"; "settings_siri_lastplayed_title" = "A legutóbb lejátszott könyv"; diff --git a/BookPlayer/it.lproj/Localizable.strings b/BookPlayer/it.lproj/Localizable.strings index c6b8e725a..a97611751 100644 --- a/BookPlayer/it.lproj/Localizable.strings +++ b/BookPlayer/it.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Imposta velocità per tutti i libri."; "settings_openplayer_launch_title" = "Apri il player all'avvio"; "settings_carplay_showplayer_title" = "Mostra il player alla connessione CarPlay"; -"settings_startupplayer_description" = "Apre la schermata del player con l'ultimo libro ascoltato all'avvio dell'app o alla connessione a CarPlay."; +"settings_startupplayer_description" = "Apre il player con l'ultimo libro ascoltato all'avvio dell'app o alla connessione a CarPlay. Su CarPlay, questo interrompe l'audio in riproduzione in altre app."; "settings_autolock_title" = "Disattiva il blocco automatico"; "settings_autolock_description" = "Impedisci il blocco del dispositivo quando ci si trova sulla schermata del lettore."; "settings_siri_lastplayed_title" = "Ultimo libro riprodotto"; diff --git a/BookPlayer/ja.lproj/Localizable.strings b/BookPlayer/ja.lproj/Localizable.strings index 63a663147..d9983bde6 100644 --- a/BookPlayer/ja.lproj/Localizable.strings +++ b/BookPlayer/ja.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "すべてのブックに速度を設定します。"; "settings_openplayer_launch_title" = "起動時にプレーヤーを開く"; "settings_carplay_showplayer_title" = "CarPlay接続時にプレーヤーを表示"; -"settings_startupplayer_description" = "アプリの起動時やCarPlayへの接続時に、最後に再生したブックのプレーヤー画面を開きます。"; +"settings_startupplayer_description" = "アプリの起動時やCarPlayへの接続時に、最後に再生したブックのプレーヤーを開きます。CarPlayでは、他のアプリで再生中のオーディオを中断します。"; "settings_autolock_title" = "自動ロックを無効にする"; "settings_autolock_description" = "プレーヤー画面上でデバイスがロックされないようにします。"; "settings_siri_lastplayed_title" = "前回再生していたブック"; diff --git a/BookPlayer/nb.lproj/Localizable.strings b/BookPlayer/nb.lproj/Localizable.strings index 550a4d99c..9d93c03f6 100644 --- a/BookPlayer/nb.lproj/Localizable.strings +++ b/BookPlayer/nb.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Angi hastighet for alle bøker."; "settings_openplayer_launch_title" = "Åpne spilleren ved oppstart"; "settings_carplay_showplayer_title" = "Vis spilleren ved CarPlay-tilkobling"; -"settings_startupplayer_description" = "Åpne spillerskjermen med den sist spilte boken når appen startes eller kobles til CarPlay."; +"settings_startupplayer_description" = "Åpne spilleren med den sist spilte boken når appen startes eller kobles til CarPlay. På CarPlay avbryter dette lyd som spilles av i andre apper."; "settings_autolock_title" = "Deaktiver autolås"; "settings_autolock_description" = "Hindre at enheten låses når den viser spillerskjermen."; "settings_siri_lastplayed_title" = "Sist spilte bok"; diff --git a/BookPlayer/nl.lproj/Localizable.strings b/BookPlayer/nl.lproj/Localizable.strings index 45f1ad044..89f34bedd 100644 --- a/BookPlayer/nl.lproj/Localizable.strings +++ b/BookPlayer/nl.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Stel snelheid in voor alle boeken."; "settings_openplayer_launch_title" = "Speler openen bij opstarten"; "settings_carplay_showplayer_title" = "Speler tonen bij CarPlay-verbinding"; -"settings_startupplayer_description" = "Open het spelerscherm met het laatst afgespeelde boek bij het starten van de app of het verbinden met CarPlay."; +"settings_startupplayer_description" = "Open de speler met het laatst afgespeelde boek bij het starten van de app of het verbinden met CarPlay. Op CarPlay onderbreekt dit audio die in andere apps wordt afgespeeld."; "settings_autolock_title" = "Autolock uitschakelen"; "settings_autolock_description" = "Voorkom dat het apparaat vergrendelt wanneer het zich op het spelerscherm bevindt."; "settings_siri_lastplayed_title" = "Laatst afgespeelde boek"; diff --git a/BookPlayer/pl.lproj/Localizable.strings b/BookPlayer/pl.lproj/Localizable.strings index f616d54cb..097a04092 100644 --- a/BookPlayer/pl.lproj/Localizable.strings +++ b/BookPlayer/pl.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Ustaw prędkość dla wszystkich książek."; "settings_openplayer_launch_title" = "Otwórz odtwarzacz przy uruchomieniu"; "settings_carplay_showplayer_title" = "Pokaż odtwarzacz po połączeniu z CarPlay"; -"settings_startupplayer_description" = "Otwórz ekran odtwarzacza z ostatnio odtwarzaną książką po uruchomieniu aplikacji lub połączeniu z CarPlay."; +"settings_startupplayer_description" = "Otwórz odtwarzacz z ostatnio odtwarzaną książką po uruchomieniu aplikacji lub połączeniu z CarPlay. W CarPlay przerywa to dźwięk odtwarzany w innych aplikacjach."; "settings_autolock_title" = "Wyłącz Autolock"; "settings_autolock_description" = "Zapobiegaj blokowaniu urządzenia na ekranie odtwarzacza."; "settings_siri_lastplayed_title" = "Ostatnio odtwarzana książka"; diff --git a/BookPlayer/pt-BR.lproj/Localizable.strings b/BookPlayer/pt-BR.lproj/Localizable.strings index 328de67c0..426b30b50 100644 --- a/BookPlayer/pt-BR.lproj/Localizable.strings +++ b/BookPlayer/pt-BR.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Defina a velocidade em todos os livros."; "settings_openplayer_launch_title" = "Abrir o player ao iniciar"; "settings_carplay_showplayer_title" = "Mostrar o player ao conectar ao CarPlay"; -"settings_startupplayer_description" = "Abre a tela do player com o último livro reproduzido ao iniciar o aplicativo ou conectar ao CarPlay."; +"settings_startupplayer_description" = "Abre o player com o último livro reproduzido ao iniciar o aplicativo ou conectar ao CarPlay. No CarPlay, isso interrompe o áudio que estiver sendo reproduzido em outros aplicativos."; "settings_autolock_title" = "Desativar Bloqueio Automático"; "settings_autolock_description" = "Evita o bloqueio do dispositivo quando estiver na tela de reprodução."; "settings_siri_lastplayed_title" = "Último livro reproduzido"; diff --git a/BookPlayer/pt-PT.lproj/Localizable.strings b/BookPlayer/pt-PT.lproj/Localizable.strings index e356974fc..158d3f8f9 100644 --- a/BookPlayer/pt-PT.lproj/Localizable.strings +++ b/BookPlayer/pt-PT.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Definição da velocidade para todos os livros."; "settings_openplayer_launch_title" = "Abrir o leitor ao iniciar"; "settings_carplay_showplayer_title" = "Mostrar o leitor ao ligar ao CarPlay"; -"settings_startupplayer_description" = "Abre o ecrã do leitor com o último livro reproduzido ao iniciar a aplicação ou ao ligar ao CarPlay."; +"settings_startupplayer_description" = "Abre o leitor com o último livro reproduzido ao iniciar a aplicação ou ao ligar ao CarPlay. No CarPlay, isto interrompe o áudio que esteja a ser reproduzido noutras aplicações."; "settings_autolock_title" = "Desativar Bloqueio Automático"; "settings_autolock_description" = "Prevenir o bloqueio do dispositivo quando estiver no ecrã de reprodução."; "settings_siri_lastplayed_title" = "Último livro reproduzido"; diff --git a/BookPlayer/ro.lproj/Localizable.strings b/BookPlayer/ro.lproj/Localizable.strings index f1adef2f8..9a5cc02cf 100644 --- a/BookPlayer/ro.lproj/Localizable.strings +++ b/BookPlayer/ro.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Setați viteza în toate cărțile."; "settings_openplayer_launch_title" = "Deschide playerul la pornire"; "settings_carplay_showplayer_title" = "Afișează playerul la conectarea CarPlay"; -"settings_startupplayer_description" = "Deschide ecranul playerului cu ultima carte redată la pornirea aplicației sau la conectarea la CarPlay."; +"settings_startupplayer_description" = "Deschide playerul cu ultima carte redată la pornirea aplicației sau la conectarea la CarPlay. Pe CarPlay, acest lucru întrerupe sunetul redat în alte aplicații."; "settings_autolock_title" = "Dezactivează Autolock"; "settings_autolock_description" = "Împiedicați blocarea dispozitivului pe ecranul Playerului."; "settings_siri_lastplayed_title" = "Ultima carte redată"; diff --git a/BookPlayer/ru.lproj/Localizable.strings b/BookPlayer/ru.lproj/Localizable.strings index 992e0d96c..34f74bf69 100644 --- a/BookPlayer/ru.lproj/Localizable.strings +++ b/BookPlayer/ru.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Установите скорость для всех книг."; "settings_openplayer_launch_title" = "Открывать плеер при запуске"; "settings_carplay_showplayer_title" = "Показывать плеер при подключении CarPlay"; -"settings_startupplayer_description" = "Открывать экран плеера с последней прослушанной книгой при запуске приложения или подключении к CarPlay."; +"settings_startupplayer_description" = "Открывать плеер с последней прослушанной книгой при запуске приложения или подключении к CarPlay. В CarPlay это прерывает звук, воспроизводимый в других приложениях."; "settings_autolock_title" = "Отключить автоблокировку"; "settings_autolock_description" = "Запретить блокировку устройства на экране плеера."; "settings_siri_lastplayed_title" = "Последняя воспроизведенная книга"; diff --git a/BookPlayer/sk-SK.lproj/Localizable.strings b/BookPlayer/sk-SK.lproj/Localizable.strings index 25a907d12..0f48e96df 100644 --- a/BookPlayer/sk-SK.lproj/Localizable.strings +++ b/BookPlayer/sk-SK.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Nastaví rýchlosť prehrávania pre všetky knihy."; "settings_openplayer_launch_title" = "Otvoriť prehrávač pri spustení"; "settings_carplay_showplayer_title" = "Zobraziť prehrávač pri pripojení CarPlay"; -"settings_startupplayer_description" = "Otvorí obrazovku prehrávača s naposledy prehrávanou knihou pri spustení aplikácie alebo pripojení k CarPlay."; +"settings_startupplayer_description" = "Otvorí prehrávač s naposledy prehrávanou knihou pri spustení aplikácie alebo pripojení k CarPlay. V CarPlay to preruší zvuk prehrávaný v iných aplikáciách."; "settings_autolock_title" = "Zakázať automatické uzamknutie"; "settings_autolock_description" = "Zabráni uzamknutiu zariadenia pri zobrazení Prehrávača."; "settings_siri_lastplayed_title" = "Naposledy prehrávaná kniha"; diff --git a/BookPlayer/sv.lproj/Localizable.strings b/BookPlayer/sv.lproj/Localizable.strings index 98230b63a..d534e6807 100644 --- a/BookPlayer/sv.lproj/Localizable.strings +++ b/BookPlayer/sv.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Ange uppspelningshastighet för alla böcker."; "settings_openplayer_launch_title" = "Öppna spelaren vid start"; "settings_carplay_showplayer_title" = "Visa spelaren vid CarPlay-anslutning"; -"settings_startupplayer_description" = "Öppna spelarskärmen med den senast spelade boken när appen startas eller ansluts till CarPlay."; +"settings_startupplayer_description" = "Öppna spelaren med den senast spelade boken när appen startas eller ansluts till CarPlay. På CarPlay avbryter detta ljud som spelas upp i andra appar."; "settings_autolock_title" = "Avaktivera Automatisk Låsning"; "settings_autolock_description" = "Förhindra att enheten låses när du är på spelarens skärmsida."; "settings_siri_lastplayed_title" = "Senast spelad bok"; diff --git a/BookPlayer/tr.lproj/Localizable.strings b/BookPlayer/tr.lproj/Localizable.strings index 6bec77ee3..3b8294002 100644 --- a/BookPlayer/tr.lproj/Localizable.strings +++ b/BookPlayer/tr.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Tüm kitaplarda hızı ayarlayın."; "settings_openplayer_launch_title" = "Başlangıçta oynatıcıyı aç"; "settings_carplay_showplayer_title" = "CarPlay bağlantısında oynatıcıyı göster"; -"settings_startupplayer_description" = "Uygulamayı başlatırken veya CarPlay'e bağlanırken son çalınan kitabın oynatıcı ekranını açar."; +"settings_startupplayer_description" = "Uygulamayı başlatırken veya CarPlay'e bağlanırken son çalınan kitabın oynatıcısını açar. CarPlay'de bu, diğer uygulamalarda çalan sesi kesintiye uğratır."; "settings_autolock_title" = "Otomatik Kilidi Devre Dışı Bırak"; "settings_autolock_description" = "Oynatıcı ekranındayken cihazın kilitlenmesini önleyin."; "settings_siri_lastplayed_title" = "En son oynatılan kitap"; diff --git a/BookPlayer/uk.lproj/Localizable.strings b/BookPlayer/uk.lproj/Localizable.strings index c14f02a90..271e2bec3 100644 --- a/BookPlayer/uk.lproj/Localizable.strings +++ b/BookPlayer/uk.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "Встановити швидкість для всіх книг."; "settings_openplayer_launch_title" = "Відкривати програвач під час запуску"; "settings_carplay_showplayer_title" = "Показувати програвач під час підключення CarPlay"; -"settings_startupplayer_description" = "Відкривати екран програвача з останньою прослуханою книгою під час запуску застосунку або підключення до CarPlay."; +"settings_startupplayer_description" = "Відкривати програвач з останньою прослуханою книгою під час запуску застосунку або підключення до CarPlay. У CarPlay це перериває звук, що відтворюється в інших застосунках."; "settings_autolock_title" = "Вимкнути автоблокування"; "settings_autolock_description" = "Запобігати блокуванню пристрою при відкритому програвачі."; "settings_siri_lastplayed_title" = "Остання відтворена книга"; diff --git a/BookPlayer/zh-Hans.lproj/Localizable.strings b/BookPlayer/zh-Hans.lproj/Localizable.strings index c00e0c6c2..cccf351c3 100644 --- a/BookPlayer/zh-Hans.lproj/Localizable.strings +++ b/BookPlayer/zh-Hans.lproj/Localizable.strings @@ -11,7 +11,7 @@ "settings_globalspeed_description" = "在所有有声书中使用同一播放速度。"; "settings_openplayer_launch_title" = "启动时打开播放器"; "settings_carplay_showplayer_title" = "连接 CarPlay 时显示播放器"; -"settings_startupplayer_description" = "在启动应用或连接 CarPlay 时,打开上次播放有声书的播放器界面。"; +"settings_startupplayer_description" = "在启动应用或连接 CarPlay 时,打开上次播放有声书的播放器。在 CarPlay 上,这会中断其他应用正在播放的音频。"; "settings_autolock_title" = "禁用自动锁定"; "settings_autolock_description" = "播放时防止锁定。"; "settings_siri_lastplayed_title" = "最后播放的书"; From 7b05a1e2ca696ee88484f7e5bb2a5938f39c79cd Mon Sep 17 00:00:00 2001 From: Gianni Carlo Date: Sun, 28 Jun 2026 23:10:51 -0500 Subject: [PATCH 3/4] Address feedback --- .../Library/ItemList/LibraryRootView.swift | 7 ++-- BookPlayer/Player/PlayerManager.swift | 40 +++++++++++++------ BookPlayer/Services/CarPlayManager.swift | 17 +++++--- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/BookPlayer/Library/ItemList/LibraryRootView.swift b/BookPlayer/Library/ItemList/LibraryRootView.swift index 81b4b9369..06f808da9 100644 --- a/BookPlayer/Library/ItemList/LibraryRootView.swift +++ b/BookPlayer/Library/ItemList/LibraryRootView.swift @@ -134,9 +134,10 @@ struct LibraryRootView: View { func handleLibraryLoaded() async { await loadLastBookIfNeeded() - /// Open the player on launch if enabled and a book is loaded — checked here (not only inside - /// `loadLastBookIfNeeded`) so it still fires when the book was already loaded by another scene - /// (e.g. CarPlay) and the `currentItem == nil` guard there returns early. + /// Open the player on launch when enabled and a book is loaded. Checked here, after the load above, + /// so it covers both a plain cold launch (where `loadLastBookIfNeeded` just loaded the last book) + /// and the case where the book was already loaded by another scene (e.g. CarPlay) — which makes the + /// `currentItem == nil` guard inside `loadLastBookIfNeeded` return early. if UserDefaults.standard.bool(forKey: Constants.UserDefaults.openPlayerOnAppLaunch), playerManager.currentItem != nil { playerState.showPlayer = true diff --git a/BookPlayer/Player/PlayerManager.swift b/BookPlayer/Player/PlayerManager.swift index 4a867cfaf..7209fc2a8 100755 --- a/BookPlayer/Player/PlayerManager.swift +++ b/BookPlayer/Player/PlayerManager.swift @@ -896,31 +896,45 @@ extension PlayerManager { /// Note: this *will* interrupt audio another app is actively playing. There's no reliable way to detect /// that beforehand here — `isOtherAudioPlaying` / `secondaryAudioShouldBeSilencedHint` only update once /// our own session is active (the very act we're trying to avoid), and read stale (false) on a CarPlay - /// background launch. So we accept the takeover as a known side effect. Returns whether attempted. + /// background launch. So we accept the takeover as a known side effect. + /// + /// Accepted edge: if the user taps play during the brief muted window, our `.first()` captures that + /// `.playing` and pauses it — they'd tap again. Distinguishing their play from ours on the shared + /// player isn't reliable, and the window is short, so we accept it. Returns whether attempted. @MainActor @discardableResult func claimNowPlayingThenPause() -> Bool { - guard currentItem != nil, !isPlaying else { return false } + /// `nowPlayingClaimSubscription == nil` prevents re-entrancy: a second call would otherwise cancel + /// the in-flight subscription before it restores `isMuted`. + guard currentItem != nil, !isPlaying, nowPlayingClaimSubscription == nil else { return false } audioPlayer.isMuted = true nowPlayingClaimSubscription = timeControlPassthroughPublisher .filter { $0 == .playing } .first() - .timeout(.seconds(3), scheduler: RunLoop.main) + // DispatchQueue (not RunLoop) so the timeout still fires while the run loop is in tracking mode + // (e.g. CarPlay scrolling). + .timeout(.seconds(3), scheduler: DispatchQueue.main) .sink( receiveCompletion: { [weak self] _ in - /// Fires on success (after pause) or on timeout — always restore audio so we can't get stuck muted - self?.audioPlayer.isMuted = false - self?.nowPlayingClaimSubscription = nil + guard let self else { return } + self.nowPlayingClaimSubscription = nil + /// Pause regardless of how we completed: on success this is the intended pause; on **timeout** + /// it cancels the still-pending `play()` (which re-checks `Task.isCancelled` after the async + /// `prepareForPlayback`), so a slow/streaming load can't end up playing aloud and un-paused. + self.pause() + /// Unmute only after the pause settles — `timeControlStatus` reaches `.paused` asynchronously, + /// so unmuting on the same tick can leak a few ms of audio (same reason `bindPauseObserver` + /// delays). A timeout fallback still guarantees we never stay muted. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in + self?.audioPlayer.isMuted = false + } }, receiveValue: { [weak self] _ in - self?.pause() - /// Re-publish the cover: its async load can complete *before* we become the Now Playing app - /// (so that push is ignored), and nothing re-pushes it afterward — refresh it now that we own - /// the slot, otherwise CarPlay shows the blank artwork placeholder. - if let chapter = self?.currentItem?.currentChapter { - self?.setNowPlayingArtwork(chapter: chapter) - } + /// We actually became the Now Playing app — re-publish the cover, whose async load can finish + /// before we own the slot (so its push is ignored) and is never re-pushed otherwise. + guard let self, let chapter = self.currentItem?.currentChapter else { return } + self.setNowPlayingArtwork(chapter: chapter) } ) diff --git a/BookPlayer/Services/CarPlayManager.swift b/BookPlayer/Services/CarPlayManager.swift index 162316d22..2e6f470b5 100644 --- a/BookPlayer/Services/CarPlayManager.swift +++ b/BookPlayer/Services/CarPlayManager.swift @@ -90,11 +90,18 @@ class CarPlayManager: NSObject { let lastItem = coreServices.libraryService.getLibraryLastItem() else { return } - try? await coreServices.playerLoaderService.loadPlayer( - lastItem.relativePath, - autoplay: false, - recordAsLastBook: false - ) + do { + try await coreServices.playerLoaderService.loadPlayer( + lastItem.relativePath, + autoplay: false, + recordAsLastBook: false + ) + } catch { + /// The preload failed, so `.bookReady` won't fire — disarm so we don't stay armed waiting to + /// present. Stay silent (no alert): this is an automatic preload, and the user gets the proper + /// error if/when they tap the book themselves (same as the main app's cold-launch restore). + shouldShowPlayerOnConnect = false + } } } From 8b31068f2b7470ff37395e35c86f949b6e8fb0c6 Mon Sep 17 00:00:00 2001 From: Gianni Carlo Date: Sun, 28 Jun 2026 23:29:46 -0500 Subject: [PATCH 4/4] Address more feedback --- BookPlayer/Player/PlayerManager.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/BookPlayer/Player/PlayerManager.swift b/BookPlayer/Player/PlayerManager.swift index 7209fc2a8..e2fd5daa8 100755 --- a/BookPlayer/Player/PlayerManager.swift +++ b/BookPlayer/Player/PlayerManager.swift @@ -904,8 +904,9 @@ extension PlayerManager { @MainActor @discardableResult func claimNowPlayingThenPause() -> Bool { - /// `nowPlayingClaimSubscription == nil` prevents re-entrancy: a second call would otherwise cancel - /// the in-flight subscription before it restores `isMuted`. + /// `nowPlayingClaimSubscription == nil` gates re-entrancy: a claim stays "in flight" (subscription + /// non-nil) until its unmute completes below, so a second blip can't start mid-claim and get unmuted + /// by this one's timer. guard currentItem != nil, !isPlaying, nowPlayingClaimSubscription == nil else { return false } audioPlayer.isMuted = true @@ -918,16 +919,18 @@ extension PlayerManager { .sink( receiveCompletion: { [weak self] _ in guard let self else { return } - self.nowPlayingClaimSubscription = nil /// Pause regardless of how we completed: on success this is the intended pause; on **timeout** /// it cancels the still-pending `play()` (which re-checks `Task.isCancelled` after the async /// `prepareForPlayback`), so a slow/streaming load can't end up playing aloud and un-paused. self.pause() /// Unmute only after the pause settles — `timeControlStatus` reaches `.paused` asynchronously, /// so unmuting on the same tick can leak a few ms of audio (same reason `bindPauseObserver` - /// delays). A timeout fallback still guarantees we never stay muted. + /// delays). Clear the subscription here (not earlier), so the re-entrancy guard keeps blocking + /// a new claim until we're fully done and this timer can't unmute someone else's blip. DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in - self?.audioPlayer.isMuted = false + guard let self else { return } + self.audioPlayer.isMuted = false + self.nowPlayingClaimSubscription = nil } }, receiveValue: { [weak self] _ in