From 1600df618aeb173bea393392d6b88e5df184fb3f Mon Sep 17 00:00:00 2001 From: Alexander <33609792+Alexander5015@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:51:19 -0600 Subject: [PATCH 1/5] Handle Now Playing fallback and availability notices --- boringNotch.xcodeproj/project.pbxproj | 4 + boringNotch/ContentView.swift | 82 ++- boringNotch/Localizable.xcstrings | 257 ++++++++- .../AppleMusicController.swift | 1 + .../MediaControllerProtocol.swift | 13 +- .../NowPlayingController.swift | 205 ++----- .../NowPlayingStreamSupport.swift | 427 ++++++++++++++ .../MediaControllers/SpotifyController.swift | 1 + .../YouTubeMusicController.swift | 14 +- boringNotch/boringNotchApp.swift | 7 - .../MusicControllerSelectionView.swift | 126 +++-- .../Settings/Views/MediaSettingsView.swift | 97 ++-- .../components/Webcam/WebcamView.swift | 8 +- boringNotch/helpers/MediaChecker.swift | 120 ++-- .../managers/AudioCaptureManager.swift | 5 +- boringNotch/managers/MusicManager.swift | 523 +++++++++++++++--- boringNotch/models/BoringViewModel.swift | 8 +- boringNotch/models/Constants.swift | 44 +- boringNotch/models/PlaybackState.swift | 6 +- .../Versions/A/MediaRemoteAdapter | Bin 331584 -> 331296 bytes .../MediaRemoteAdapterTestClient | Bin 127200 -> 128624 bytes mediaremote-adapter/mediaremote-adapter.pl | 19 +- 22 files changed, 1552 insertions(+), 415 deletions(-) create mode 100644 boringNotch/MediaControllers/NowPlayingStreamSupport.swift diff --git a/boringNotch.xcodeproj/project.pbxproj b/boringNotch.xcodeproj/project.pbxproj index 500c7cfe6..ae0e67bdc 100644 --- a/boringNotch.xcodeproj/project.pbxproj +++ b/boringNotch.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 1153BD982D9881F900979FB0 /* AppleScriptHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD972D9881F900979FB0 /* AppleScriptHelper.swift */; }; 1153BD9A2D98824300979FB0 /* SpotifyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD992D98824300979FB0 /* SpotifyController.swift */; }; 1153BD9C2D98853B00979FB0 /* NowPlayingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */; }; + A1B2C3D4E5F60718293A4B5D /* NowPlayingStreamSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5C /* NowPlayingStreamSupport.swift */; }; 1153BDA72D99B22200979FB0 /* YouTubeMusicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BDA62D99B22200979FB0 /* YouTubeMusicController.swift */; }; 115C12EC2ED3D003009754CA /* OpenNotchOSD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115C12EB2ED3D003009754CA /* OpenNotchOSD.swift */; }; 1160F8D82DD98230006FBB94 /* NotchShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160F8D72DD98230006FBB94 /* NotchShape.swift */; }; @@ -235,6 +236,7 @@ 1153BD972D9881F900979FB0 /* AppleScriptHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptHelper.swift; sourceTree = ""; }; 1153BD992D98824300979FB0 /* SpotifyController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyController.swift; sourceTree = ""; }; 1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingController.swift; sourceTree = ""; }; + A1B2C3D4E5F60718293A4B5C /* NowPlayingStreamSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingStreamSupport.swift; sourceTree = ""; }; 1153BDA62D99B22200979FB0 /* YouTubeMusicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeMusicController.swift; sourceTree = ""; }; 115C12EB2ED3D003009754CA /* OpenNotchOSD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenNotchOSD.swift; sourceTree = ""; }; 1160F8D72DD98230006FBB94 /* NotchShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchShape.swift; sourceTree = ""; }; @@ -486,6 +488,7 @@ 1153BD922D986E4300979FB0 /* AppleMusicController.swift */, 1153BD992D98824300979FB0 /* SpotifyController.swift */, 1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */, + A1B2C3D4E5F60718293A4B5C /* NowPlayingStreamSupport.swift */, ); path = MediaControllers; sourceTree = ""; @@ -1073,6 +1076,7 @@ 14D570CB2C5F4B2C0011E668 /* BatteryStatusViewModel.swift in Sources */, 9A0887322C7A693000C160EA /* TabButton.swift in Sources */, 1153BD9C2D98853B00979FB0 /* NowPlayingController.swift in Sources */, + A1B2C3D4E5F60718293A4B5D /* NowPlayingStreamSupport.swift in Sources */, 11985BEF2F37E48900F81585 /* OSDIconView.swift in Sources */, 1194E9402EACC652009C82D6 /* Color+AccentColor.swift in Sources */, B141C2412CA5F53F00AC8CC8 /* SparkleView.swift in Sources */, diff --git a/boringNotch/ContentView.swift b/boringNotch/ContentView.swift index 07896651c..a2dbb3203 100644 --- a/boringNotch/ContentView.swift +++ b/boringNotch/ContentView.swift @@ -88,7 +88,9 @@ struct ContentView: View { private var computedChinWidth: CGFloat { var chinWidth: CGFloat = vm.closedNotchSize.width - if coordinator.expandingView.type == .battery && coordinator.expandingView.show + if shouldDisplayNowPlayingFallbackNotice { + chinWidth = 330 + } else if coordinator.expandingView.type == .battery && coordinator.expandingView.show && vm.notchState == .closed && Defaults[.showPowerStatusNotifications] { chinWidth = 640 @@ -107,6 +109,21 @@ struct ContentView: View { return chinWidth } + private var shouldDisplayNowPlayingFallbackNotice: Bool { + guard musicManager.nowPlayingNotice != nil else { return false } + + let selectedScreen = NSScreen.screen(withUUID: coordinator.selectedScreenUUID) + let targetScreenUUID = selectedScreen?.displayUUID ?? NSScreen.main?.displayUUID + let currentScreen = vm.screenUUID.flatMap { NSScreen.screen(withUUID: $0) } + let isConnected = vm.screenUUID == nil || currentScreen != nil + let isTargetDisplay = vm.screenUUID == nil || vm.screenUUID == targetScreenUUID + + return isConnected + && isTargetDisplay + && vm.notchState == .closed + && !isNotchHeightZero + } + // If the closed notch height is 0 (any display/setting), display a 10pt nearly-invisible notch // instead of fully hiding it. This preserves layout while avoiding visual artifacts. private var isNotchHeightZero: Bool { vm.effectiveClosedNotchHeight == 0 } @@ -160,23 +177,23 @@ struct ContentView: View { handleHover(hovering) } .onTapGesture { - if vm.notchState == .closed { + if vm.notchState == .closed && !shouldDisplayNowPlayingFallbackNotice { doOpen() } } - .conditionalModifier(Defaults[.enableGestures]) { view in + .conditionalModifier(Defaults[.enableGestures] && !shouldDisplayNowPlayingFallbackNotice) { view in view .panGesture(direction: .down) { translation, phase in handleDownGesture(translation: translation, phase: phase) } } - .conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures]) { view in + .conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures] && !shouldDisplayNowPlayingFallbackNotice) { view in view .panGesture(direction: .up) { translation, phase in handleUpGesture(translation: translation, phase: phase) } } - .conditionalModifier(Defaults[.enableHorizontalMediaGestures] && Defaults[.enableGestures]) { view in + .conditionalModifier(Defaults[.enableHorizontalMediaGestures] && Defaults[.enableGestures] && !shouldDisplayNowPlayingFallbackNotice) { view in view .panGesture(direction: .left) { translation, phase in handleNextTrackGesture(translation: translation, phase: phase) @@ -300,7 +317,11 @@ struct ContentView: View { .padding(.top, 40) Spacer() } else { - if coordinator.expandingView.type == .battery && coordinator.expandingView.show + if shouldDisplayNowPlayingFallbackNotice, + let notice = musicManager.nowPlayingNotice { + nowPlayingFallbackNotice(notice) + .transition(.opacity.combined(with: .scale(scale: 0.96, anchor: .top))) + } else if coordinator.expandingView.type == .battery && coordinator.expandingView.show && vm.notchState == .closed && Defaults[.showPowerStatusNotifications] { HStack(spacing: 0) { @@ -427,6 +448,51 @@ struct ContentView: View { .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data], delegate: GeneralDropTargetDelegate(isTargeted: $dropInteraction.generalDropTargeting)) } + private func nowPlayingFallbackNotice(_ notice: NowPlayingFallbackNotice) -> some View { + HStack(spacing: 11) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(Color.orange) + .frame(width: 24, height: 24) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 2) { + Text(notice.title) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.white) + + Text(notice.subtitle) + .font(.caption) + .foregroundStyle(.white.opacity(0.62)) + } + .lineLimit(2) + + Spacer(minLength: 5) + } + .padding(.horizontal, 14) + .padding(.vertical, 9) + .frame(width: 330) + .frame(minHeight: 58) + .accessibilityElement(children: .combine) + .onAppear { + if musicManager.markNowPlayingNoticePresented(notice.id) { + announceNowPlayingFallbackNotice(notice) + } + } + } + + private func announceNowPlayingFallbackNotice(_ notice: NowPlayingFallbackNotice) { + let announcement = "\(String(localized: notice.title)). \(String(localized: notice.subtitle))." + NSAccessibility.post( + element: NSApplication.shared, + notification: .announcementRequested, + userInfo: [ + .announcement: announcement, + .priority: NSAccessibilityPriorityLevel.high.rawValue, + ] + ) + } + @ViewBuilder func BoringFaceAnimation() -> some View { HStack { @@ -552,7 +618,7 @@ struct ContentView: View { var dragDetector: some View { @Bindable var dropInteraction = vm.dropInteraction - if Defaults[.boringShelf] && vm.notchState == .closed { + if Defaults[.boringShelf] && vm.notchState == .closed && !shouldDisplayNowPlayingFallbackNotice { Color.clear .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(Rectangle()) @@ -591,6 +657,7 @@ struct ContentView: View { } guard vm.notchState == .closed, + !shouldDisplayNowPlayingFallbackNotice, !coordinator.shouldShowSneakPeek(on: vm.screenUUID), Defaults[.openNotchOnHover] else { return } @@ -601,6 +668,7 @@ struct ContentView: View { await MainActor.run { guard self.vm.notchState == .closed, self.isHovering, + !self.shouldDisplayNowPlayingFallbackNotice, !self.coordinator.shouldShowSneakPeek(on: self.vm.screenUUID) else { return } self.doOpen() diff --git a/boringNotch/Localizable.xcstrings b/boringNotch/Localizable.xcstrings index 21c0e7f6e..36e981080 100644 --- a/boringNotch/Localizable.xcstrings +++ b/boringNotch/Localizable.xcstrings @@ -3828,6 +3828,251 @@ } } }, + "Apple Music" : { + "comment" : "Apple Music service name.", + "shouldTranslate" : false, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Music" + } + } + } + }, + "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." : { + "comment" : "Generic recoverable Now Playing availability message.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." + } + } + } + }, + "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." : { + "comment" : "Now Playing availability message when a bundled component is missing.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." + } + } + } + }, + "Connects directly to the Apple Music app." : { + "comment" : "Onboarding description of the Apple Music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connects directly to the Apple Music app." + } + } + } + }, + "Connects directly to the Spotify app." : { + "comment" : "Onboarding description of the Spotify music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connects directly to the Spotify app." + } + } + } + }, + "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." : { + "comment" : "Media settings footer shown after the Now Playing runtime stream repeatedly stops.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." + } + } + } + }, + "Now Playing unavailable" : { + "comment" : "Title of the passive notice shown when the Now Playing media source cannot be used.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing unavailable" + } + } + } + }, + "Now Playing is not supported by this version of macOS. Choose another music source." : { + "comment" : "Now Playing availability message when required private macOS APIs are unavailable.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing is not supported by this version of macOS. Choose another music source." + } + } + } + }, + "Requires a third-party client with API plugin enabled." : { + "comment" : "Onboarding description of the YouTube Music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requires a third-party client with API plugin enabled." + } + } + } + }, + "Switched to %@" : { + "comment" : "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Switched to %@" + } + } + } + }, + "Spotify" : { + "comment" : "Spotify service name.", + "shouldTranslate" : false, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spotify" + } + } + } + }, + "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." : { + "comment" : "Now Playing availability message when the self-check receives no test data.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." + } + } + } + }, + "The Now Playing check took too long. Try again in a moment." : { + "comment" : "Now Playing availability message when the self-check times out.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Now Playing check took too long. Try again in a moment." + } + } + } + }, + "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." : { + "comment" : "Onboarding description of the universal macOS Now Playing music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." + } + } + } + }, + "Using %@ temporarily" : { + "comment" : "Temporary Now Playing fallback notice. The placeholder is the fallback music source name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ temporarily" + } + } + } + }, + "Using %@ temporarily. Your Now Playing preference is preserved." : { + "comment" : "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ temporarily. Your Now Playing preference is preserved." + } + } + } + }, + "YouTube Music" : { + "comment" : "YouTube Music service name.", + "shouldTranslate" : false, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube Music" + } + } + } + }, + "Camera Access Required" : { + "comment" : "Camera permission alert title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Camera Access Required" + } + } + } + }, + "Open Settings" : { + "comment" : "Button title that opens app or system settings", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Settings" + } + } + } + }, + "Open System Settings" : { + "comment" : "Button title that opens System Settings", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open System Settings" + } + } + } + }, + "Please allow camera access in System Settings." : { + "comment" : "Camera permission alert message", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please allow camera access in System Settings." + } + } + } + }, + "Please allow camera access in System Settings to use the mirror feature." : { + "comment" : "Mirror camera permission alert message", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please allow camera access in System Settings to use the mirror feature." + } + } + } + }, "Change media with horizontal gestures" : { "localizations" : { "de" : { @@ -4316,6 +4561,10 @@ } } }, + "Check Again" : { + "comment" : "A button that refreshes the availability of the 'Now Playing' media source.", + "isCommentAutoGenerated" : true + }, "Check for updates automatically" : { "localizations" : { "de" : { @@ -4486,6 +4735,10 @@ } } }, + "Checking Now Playing availability..." : { + "comment" : "A message displayed when the app is checking if Now Playing is available.", + "isCommentAutoGenerated" : true + }, "Choose a Music Source" : { "localizations" : { "cs" : { @@ -11525,6 +11778,7 @@ } }, "https://github.com/pear-devs/pear-desktop" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -27548,6 +27802,7 @@ } }, "YouTube Music requires this third-party app to be installed: " : { + "extractionState" : "stale", "localizations" : { "cs" : { "stringUnit" : { @@ -27667,4 +27922,4 @@ } }, "version" : "1.1" -} \ No newline at end of file +} diff --git a/boringNotch/MediaControllers/AppleMusicController.swift b/boringNotch/MediaControllers/AppleMusicController.swift index bd5903354..9b6a58de0 100644 --- a/boringNotch/MediaControllers/AppleMusicController.swift +++ b/boringNotch/MediaControllers/AppleMusicController.swift @@ -9,6 +9,7 @@ import Foundation import Combine import SwiftUI +@MainActor class AppleMusicController: MediaControllerProtocol { // MARK: - Properties @Published private var playbackState: PlaybackState = PlaybackState( diff --git a/boringNotch/MediaControllers/MediaControllerProtocol.swift b/boringNotch/MediaControllers/MediaControllerProtocol.swift index 423c0583b..83b4f2884 100644 --- a/boringNotch/MediaControllers/MediaControllerProtocol.swift +++ b/boringNotch/MediaControllers/MediaControllerProtocol.swift @@ -6,10 +6,10 @@ // import Foundation -import AppKit import Combine -protocol MediaControllerProtocol: ObservableObject { +@MainActor +protocol MediaControllerProtocol: AnyObject { var playbackStatePublisher: AnyPublisher { get } var supportsVolumeControl: Bool { get } var supportsFavorite: Bool { get } @@ -27,3 +27,12 @@ protocol MediaControllerProtocol: ObservableObject { func isActive() -> Bool func updatePlaybackInfo() async } + +@MainActor +protocol NowPlayingRuntimeControlling: MediaControllerProtocol { + var runtimeFailures: AsyncStream { get } + + func startRuntimeStream() + func restartRuntimeStream() + func stopRuntimeStream() +} diff --git a/boringNotch/MediaControllers/NowPlayingController.swift b/boringNotch/MediaControllers/NowPlayingController.swift index 739899078..3835e3037 100644 --- a/boringNotch/MediaControllers/NowPlayingController.swift +++ b/boringNotch/MediaControllers/NowPlayingController.swift @@ -9,7 +9,8 @@ import AppKit import Combine import Foundation -final class NowPlayingController: ObservableObject, MediaControllerProtocol { +@MainActor +final class NowPlayingController: NowPlayingRuntimeControlling { func updatePlaybackInfo() async { await fetchFavoriteStateIfSupported() } @@ -64,13 +65,18 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { private let MRMediaRemoteSetElapsedTimeFunction: @convention(c) (Double) -> Void private let MRMediaRemoteSetShuffleModeFunction: @convention(c) (Int) -> Void private let MRMediaRemoteSetRepeatModeFunction: @convention(c) (Int) -> Void + private let adapterScriptURL: URL + private let adapterFrameworkPath: String - private var process: Process? - private var pipeHandler: JSONLinesPipeHandler? - private var streamTask: Task? + let runtimeFailures: AsyncStream + private let runtimeFailureContinuation: AsyncStream.Continuation + + private var streamSession: NowPlayingStreamSession? // MARK: - Initialization - init?() { + init() throws { + let resources = try NowPlayingResources.load(requiresTestClient: false) + guard let bundle = CFBundleCreate( kCFAllocatorDefault, @@ -83,8 +89,9 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { bundle, "MRMediaRemoteSetShuffleMode" as CFString), let MRMediaRemoteSetRepeatModePointer = CFBundleGetFunctionPointerForName( bundle, "MRMediaRemoteSetRepeatMode" as CFString) - - else { return nil } + else { + throw NowPlayingSetupFailure.mediaRemoteUnavailable + } mediaRemoteBundle = bundle MRMediaRemoteSendCommandFunction = unsafeBitCast( @@ -95,27 +102,18 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { MRMediaRemoteSetShuffleModePointer, to: (@convention(c) (Int) -> Void).self) MRMediaRemoteSetRepeatModeFunction = unsafeBitCast( MRMediaRemoteSetRepeatModePointer, to: (@convention(c) (Int) -> Void).self) + adapterScriptURL = resources.adapterScriptURL + adapterFrameworkPath = resources.adapterFrameworkPath - Task { await setupNowPlayingObserver() } + let runtimeFailureChannel = AsyncStream.makeStream( + of: NowPlayingRuntimeFailure.self + ) + runtimeFailures = runtimeFailureChannel.stream + runtimeFailureContinuation = runtimeFailureChannel.continuation } deinit { - streamTask?.cancel() - - if let pipeHandler = self.pipeHandler { - Task { await pipeHandler.close() - } - } - - if let process = self.process { - if process.isRunning { - process.terminate() - process.waitUntilExit() - } - } - - self.process = nil - self.pipeHandler = nil + runtimeFailureContinuation.finish() } // MARK: - Protocol Implementation @@ -186,43 +184,38 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { playbackState.volume = clampedLevel } - // MARK: - Setup Methods - private func setupNowPlayingObserver() async { + // MARK: - Runtime Stream Lifecycle + func startRuntimeStream() { + guard streamSession == nil else { return } + let process = Process() - guard - let scriptURL = Bundle.main.url(forResource: "mediaremote-adapter", withExtension: "pl"), - let frameworkPath = Bundle.main.privateFrameworksPath?.appending("/MediaRemoteAdapter.framework") - else { - assertionFailure("Could not find mediaremote-adapter.pl script or framework path") - return - } - process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") - process.arguments = [scriptURL.path, frameworkPath, "stream"] - - let pipeHandler = JSONLinesPipeHandler() - process.standardOutput = await pipeHandler.getPipe() - - self.process = process - self.pipeHandler = pipeHandler - - do { - try process.run() - streamTask = Task { [weak self] in - await self?.processJSONStream() + process.arguments = [adapterScriptURL.path, adapterFrameworkPath, "stream"] + + let session = NowPlayingStreamSession( + process: process, + onUpdate: { [weak self] update in + await self?.handleAdapterUpdate(update) + }, + onFailure: { [weak self] failure in + guard let self else { return } + self.streamSession = nil + self.runtimeFailureContinuation.yield(failure) } - } catch { - assertionFailure("Failed to launch mediaremote-adapter.pl: \(error)") - } + ) + streamSession = session + session.start() } - // MARK: - Async Stream Processing - private func processJSONStream() async { - guard let pipeHandler = self.pipeHandler else { return } - - await pipeHandler.readJSONLines(as: NowPlayingUpdate.self) { [weak self] update in - await self?.handleAdapterUpdate(update) - } + func restartRuntimeStream() { + stopRuntimeStream() + startRuntimeStream() + } + + func stopRuntimeStream() { + let session = streamSession + streamSession = nil + session?.stop() } // MARK: - Update Methods @@ -351,103 +344,3 @@ private extension NowPlayingController { return preferred.normalizedBundleIdentifiers } } - -struct NowPlayingUpdate: Codable { - let payload: NowPlayingPayload - let diff: Bool? -} - -struct NowPlayingPayload: Codable { - let title: String? - let artist: String? - let album: String? - let duration: Double? - let elapsedTime: Double? - let shuffleMode: Int? - let repeatMode: Int? - let artworkData: String? - let timestamp: String? - let playbackRate: Double? - let playing: Bool? - let parentApplicationBundleIdentifier: String? - let bundleIdentifier: String? - let volume: Double? -} - -actor JSONLinesPipeHandler { - private let pipe: Pipe - private let fileHandle: FileHandle - private var buffer = "" - - init() { - self.pipe = Pipe() - self.fileHandle = pipe.fileHandleForReading - } - - func getPipe() -> Pipe { - return pipe - } - - func readJSONLines(as type: T.Type, onLine: @escaping (T) async -> Void) async { - do { - try await self.processLines(as: type) { decodedObject in - await onLine(decodedObject) - } - } catch { - print("Error processing JSON stream: \(error)") - } - } - - private func processLines(as type: T.Type, onLine: @escaping (T) async -> Void) async throws { - while true { - let data = try await readData() - guard !data.isEmpty else { break } - - if let chunk = String(data: data, encoding: .utf8) { - buffer.append(chunk) - - while let range = buffer.range(of: "\n") { - let line = String(buffer[..(_ line: String, as type: T.Type, onLine: @escaping (T) async -> Void) async { - guard let data = line.data(using: .utf8) else { - return - } - do { - let decodedObject = try JSONDecoder().decode(T.self, from: data) - await onLine(decodedObject) - } catch { - // Ignore lines that can't be decoded - } - } - - private func readData() async throws -> Data { - return try await withCheckedThrowingContinuation { continuation in - - fileHandle.readabilityHandler = { handle in - let data = handle.availableData - handle.readabilityHandler = nil - continuation.resume(returning: data) - } - } - } - - func close() async { - do { - fileHandle.readabilityHandler = nil - try fileHandle.close() - try pipe.fileHandleForWriting.close() - } catch { - print("Error closing pipe handler: \(error)") - } - } -} diff --git a/boringNotch/MediaControllers/NowPlayingStreamSupport.swift b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift new file mode 100644 index 000000000..9292b7de0 --- /dev/null +++ b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift @@ -0,0 +1,427 @@ +import Foundation + +enum ChildProcessTermination: Equatable, Sendable { + case exited(Int32) + case signaled(Int32) + case unknown(status: Int32) + + init(process: Process) { + switch process.terminationReason { + case .exit: + self = .exited(process.terminationStatus) + case .uncaughtSignal: + self = .signaled(process.terminationStatus) + @unknown default: + self = .unknown(status: process.terminationStatus) + } + } +} + +enum TimedProcessResult: Equatable, Sendable { + case terminated(ChildProcessTermination) + case timedOut +} + +enum TimedProcessRunner { + static func run( + _ process: Process, + timeout: Duration + ) async throws -> TimedProcessResult { + try Task.checkCancellation() + + let terminations = AsyncStream { continuation in + process.terminationHandler = { terminatedProcess in + continuation.yield(ChildProcessTermination(process: terminatedProcess)) + continuation.finish() + } + } + + do { + try process.run() + } catch { + process.terminationHandler = nil + throw error + } + + defer { process.terminationHandler = nil } + + return try await withTaskCancellationHandler { + let result = try await withThrowingTaskGroup(of: TimedProcessResult.self) { group in + group.addTask { + for await termination in terminations { + try Task.checkCancellation() + return .terminated(termination) + } + throw CancellationError() + } + group.addTask { + try await Task.sleep(for: timeout) + return .timedOut + } + + guard let firstResult = try await group.next() else { + throw CancellationError() + } + group.cancelAll() + return firstResult + } + + if result == .timedOut { + forceStop(process) + } + return result + } onCancel: { + forceStop(process) + } + } + + static func forceStop(_ process: Process) { + guard process.isRunning else { return } + _ = kill(process.processIdentifier, SIGKILL) + } +} + +enum NowPlayingSetupFailure: Error, Equatable, Sendable { + case missingAdapterScript + case missingAdapterFramework + case missingTestClient + case mediaRemoteUnavailable +} + +struct NowPlayingResources: Sendable { + let adapterScriptURL: URL + let adapterFrameworkPath: String + let testClientURL: URL? + + static func load( + from bundle: Bundle = .main, + requiresTestClient: Bool + ) throws -> Self { + let fileManager = FileManager.default + + guard let scriptURL = bundle.url( + forResource: "mediaremote-adapter", + withExtension: "pl" + ), fileManager.isReadableFile(atPath: scriptURL.path) else { + throw NowPlayingSetupFailure.missingAdapterScript + } + + guard let privateFrameworksPath = bundle.privateFrameworksPath else { + throw NowPlayingSetupFailure.missingAdapterFramework + } + let frameworkPath = privateFrameworksPath.appending("/MediaRemoteAdapter.framework") + let frameworkExecutable = frameworkPath.appending("/MediaRemoteAdapter") + guard fileManager.isExecutableFile(atPath: frameworkExecutable) else { + throw NowPlayingSetupFailure.missingAdapterFramework + } + + let testClientURL: URL? + if requiresTestClient { + guard let url = bundle.url( + forResource: "MediaRemoteAdapterTestClient", + withExtension: nil + ), fileManager.isExecutableFile(atPath: url.path) else { + throw NowPlayingSetupFailure.missingTestClient + } + testClientURL = url + } else { + testClientURL = nil + } + + return Self( + adapterScriptURL: scriptURL, + adapterFrameworkPath: frameworkPath, + testClientURL: testClientURL + ) + } +} + +enum NowPlayingProbeFailure: Equatable, Sendable { + case launchFailed + case timedOut + case helperPathMissing + case helperLaunchFailed + case helperSetupTimedOut + case noData + case wrapperFailed + case signaled(Int32) + case unexpectedExit(Int32) +} + +enum NowPlayingAvailability: Equatable, Sendable { + case unchecked + case available + case setupFailure(NowPlayingSetupFailure) + case temporaryFailure(NowPlayingProbeFailure) + case runtimeStreamFailed + + var requiresFallback: Bool { + switch self { + case .unchecked, .available: + false + case .setupFailure, .temporaryFailure, .runtimeStreamFailed: + true + } + } + + var isRecoverableFailure: Bool { + switch self { + case .temporaryFailure, .runtimeStreamFailed: + true + case .unchecked, .available, .setupFailure: + false + } + } + + var isSelectable: Bool { self == .available } + + var showsCheckAgainButton: Bool { isRecoverableFailure } + + var settingsMessage: LocalizedStringResource? { + switch self { + case .unchecked, .available: + nil + case .setupFailure(.mediaRemoteUnavailable): + LocalizedStringResource( + "Now Playing is not supported by this version of macOS. Choose another music source.", + comment: "Now Playing availability message when required private macOS APIs are unavailable." + ) + case .setupFailure: + LocalizedStringResource( + "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this.", + comment: "Now Playing availability message when a bundled component is missing." + ) + case .temporaryFailure(.timedOut): + LocalizedStringResource( + "The Now Playing check took too long. Try again in a moment.", + comment: "Now Playing availability message when the self-check times out." + ) + case .temporaryFailure(.noData): + LocalizedStringResource( + "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening.", + comment: "Now Playing availability message when the self-check receives no test data." + ) + case .temporaryFailure: + LocalizedStringResource( + "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening.", + comment: "Generic recoverable Now Playing availability message." + ) + case .runtimeStreamFailed: + LocalizedStringResource( + "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing.", + comment: "Media settings message after the Now Playing runtime stream repeatedly stops." + ) + } + } + + var shouldRetry: Bool { + if case .temporaryFailure = self { return true } + return false + } +} + +protocol NowPlayingAvailabilityChecking: Sendable { + func checkAvailability(maxAttempts: Int) async throws -> NowPlayingAvailability +} + +enum NowPlayingRuntimeFailure: Equatable, Sendable { + case helperLaunchFailed + case helperExited(ChildProcessTermination) + case streamEnded + case streamReadFailed + case malformedOutput +} + +enum NowPlayingRuntimeRecoveryAction: Equatable, Sendable { + case restart(after: Duration) + case fallback +} + +struct NowPlayingRuntimeRecoveryPolicy: Sendable { + static let retryDelay: Duration = .milliseconds(500) + static let rapidFailureWindow: Duration = .seconds(60) + + private(set) var previousFailure: ContinuousClock.Instant? + + mutating func action(at instant: ContinuousClock.Instant) -> NowPlayingRuntimeRecoveryAction { + defer { previousFailure = instant } + + guard let previousFailure else { + return .restart(after: Self.retryDelay) + } + + return previousFailure.duration(to: instant) < Self.rapidFailureWindow + ? .fallback + : .restart(after: Self.retryDelay) + } + + mutating func reset() { + previousFailure = nil + } +} + +actor JSONLinesPipeHandler { + nonisolated let outputPipe: Pipe + nonisolated let fileHandle: FileHandle + private var byteIterator: FileHandle.AsyncBytes.Iterator + private var consecutiveMalformedLines = 0 + + init(pipe: Pipe = Pipe()) { + outputPipe = pipe + fileHandle = pipe.fileHandleForReading + byteIterator = fileHandle.bytes.makeAsyncIterator() + } + + func readJSONLines( + as type: Value.Type, + onValue: @escaping @Sendable (Value) async -> Void + ) async -> NowPlayingRuntimeFailure { + var line = Data() + var iterator = byteIterator + + do { + while let byte = try await iterator.next() { + guard !Task.isCancelled else { return .streamEnded } + + guard byte == UInt8(ascii: "\n") else { + line.append(byte) + continue + } + + if line.last == UInt8(ascii: "\r") { + line.removeLast() + } + + guard !line.isEmpty, + let decoded = try? JSONDecoder().decode(Value.self, from: line) + else { + consecutiveMalformedLines += 1 + line.removeAll(keepingCapacity: true) + if consecutiveMalformedLines >= 3 { + return .malformedOutput + } + continue + } + + consecutiveMalformedLines = 0 + line.removeAll(keepingCapacity: true) + await onValue(decoded) + } + + return .streamEnded + } catch { + return Task.isCancelled ? .streamEnded : .streamReadFailed + } + } + + nonisolated func close() { + try? fileHandle.close() + try? outputPipe.fileHandleForWriting.close() + } +} + +@MainActor +final class NowPlayingStreamSession { + private let process: Process + private let reader: JSONLinesPipeHandler + private let onUpdate: @MainActor (NowPlayingUpdate) async -> Void + private let onFailure: @MainActor (NowPlayingRuntimeFailure) -> Void + + private var readTask: Task? + private var isStopped = false + private var didReportFailure = false + + init( + process: Process, + reader: JSONLinesPipeHandler = JSONLinesPipeHandler(), + onUpdate: @escaping @MainActor (NowPlayingUpdate) async -> Void, + onFailure: @escaping @MainActor (NowPlayingRuntimeFailure) -> Void + ) { + self.process = process + self.reader = reader + self.onUpdate = onUpdate + self.onFailure = onFailure + } + + func start() { + guard readTask == nil, !isStopped else { return } + + process.standardOutput = reader.outputPipe + process.terminationHandler = { [weak self] terminatedProcess in + let termination = ChildProcessTermination(process: terminatedProcess) + Task { @MainActor [weak self] in + self?.finish(with: .helperExited(termination)) + } + } + + do { + try process.run() + } catch { + process.terminationHandler = nil + finish(with: .helperLaunchFailed) + return + } + + let session = self + readTask = Task { @MainActor [reader, session] in + let failure = await reader.readJSONLines(as: NowPlayingUpdate.self) { update in + await session.receive(update) + } + session.finish(with: failure) + } + } + + func stop() { + guard !isStopped else { return } + cleanup() + } + + private func receive(_ update: NowPlayingUpdate) async { + guard !isStopped else { return } + await onUpdate(update) + } + + private func finish(with failure: NowPlayingRuntimeFailure) { + guard !isStopped, !didReportFailure else { return } + didReportFailure = true + cleanup() + onFailure(failure) + } + + private func cleanup() { + isStopped = true + process.terminationHandler = nil + readTask?.cancel() + readTask = nil + reader.close() + TimedProcessRunner.forceStop(process) + } + + deinit { + process.terminationHandler = nil + reader.close() + TimedProcessRunner.forceStop(process) + } +} + +struct NowPlayingUpdate: Codable, Sendable { + let payload: NowPlayingPayload + let diff: Bool? +} + +struct NowPlayingPayload: Codable, Sendable { + let title: String? + let artist: String? + let album: String? + let duration: Double? + let elapsedTime: Double? + let shuffleMode: Int? + let repeatMode: Int? + let artworkData: String? + let timestamp: String? + let playbackRate: Double? + let playing: Bool? + let parentApplicationBundleIdentifier: String? + let bundleIdentifier: String? + let volume: Double? +} diff --git a/boringNotch/MediaControllers/SpotifyController.swift b/boringNotch/MediaControllers/SpotifyController.swift index f1a2867c5..9bf5d38c7 100644 --- a/boringNotch/MediaControllers/SpotifyController.swift +++ b/boringNotch/MediaControllers/SpotifyController.swift @@ -9,6 +9,7 @@ import Foundation import Combine import SwiftUI +@MainActor class SpotifyController: MediaControllerProtocol { func setFavorite(_ favorite: Bool) async { //Placeholder diff --git a/boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicController.swift b/boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicController.swift index c5a3b87bd..ddfbce9ae 100644 --- a/boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicController.swift +++ b/boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicController.swift @@ -10,6 +10,7 @@ import Foundation import Combine import SwiftUI +@MainActor final class YouTubeMusicController: MediaControllerProtocol { // MARK: - Published Properties @Published var playbackState = PlaybackState( @@ -69,10 +70,15 @@ final class YouTubeMusicController: MediaControllerProtocol { deinit { artworkFetchTask?.cancel() - cancelReconnect(resetDelay: false) + reconnectTask?.cancel() appStateObserver?.cancel() - stopPeriodicUpdates() - disconnectClient(takeWebSocketClient()) + updateTimer?.invalidate() + + if let webSocketClient { + Task { + await webSocketClient.disconnect() + } + } } // MARK: - MediaControllerProtocol Implementation @@ -106,7 +112,7 @@ final class YouTubeMusicController: MediaControllerProtocol { func toggleShuffle() async { await sendCommand(endpoint: "/shuffle", method: "POST") } func toggleRepeat() async { await sendCommand(endpoint: "/switch-repeat", method: "POST") } - nonisolated func isActive() -> Bool { + func isActive() -> Bool { NSWorkspace.shared.runningApplications.contains { $0.bundleIdentifier == configuration.bundleIdentifier } diff --git a/boringNotch/boringNotchApp.swift b/boringNotch/boringNotchApp.swift index c3b78a6b6..489a9ee23 100644 --- a/boringNotch/boringNotchApp.swift +++ b/boringNotch/boringNotchApp.swift @@ -6,7 +6,6 @@ // import AVFoundation -import Combine import Defaults import KeyboardShortcuts import Sparkle @@ -470,12 +469,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { self.showOnboardingWindow() } playWelcomeSound() - } else if MusicManager.shared.isNowPlayingDeprecated - && Defaults[.mediaController] == .nowPlaying - { - DispatchQueue.main.async { - self.showOnboardingWindow(step: .musicPermission) - } } previousScreens = NSScreen.screens diff --git a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift index 9ae147ea5..9598d9c88 100644 --- a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift +++ b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift @@ -6,23 +6,13 @@ // import SwiftUI -import Defaults - +@MainActor struct MusicControllerSelectionView: View { let onContinue: () -> Void - @Default(.mediaController) var mediaController - - private var availableMediaControllers: [MediaControllerType] { - if MusicManager.shared.isNowPlayingDeprecated { - return MediaControllerType.allCases.filter { $0 != .nowPlaying } - } else { - return MediaControllerType.allCases - } - } - - @State private var selectedMediaController: MediaControllerType = Defaults[.mediaController] + @ObservedObject private var musicManager = MusicManager.shared + @State private var selectedMediaController = MusicManager.shared.preferredMediaController var body: some View { VStack(spacing: 20) { @@ -39,46 +29,96 @@ struct MusicControllerSelectionView: View { ScrollView { VStack(spacing: 12) { - ForEach(availableMediaControllers) { controller in - ControllerOptionView( - controller: controller, - isSelected: self.selectedMediaController == controller - ) - .onTapGesture { - self.selectedMediaController = controller + ForEach(MediaControllerType.allCases) { controller in + let isEnabled = controller != .nowPlaying + || ( + !musicManager.isCheckingNowPlayingAvailability + && musicManager.nowPlayingAvailability.isSelectable + ) + + Button { + selectedMediaController = controller + } label: { + ControllerOptionView( + controller: controller, + isSelected: selectedMediaController == controller, + isEnabled: isEnabled + ) + } + .buttonStyle(.plain) + .disabled(!isEnabled) + + if controller == .youtubeMusic, + let url = URL(string: "https://github.com/pear-devs/pear-desktop") { + Link("View on GitHub: pear-devs/pear-desktop", destination: url) + .font(.subheadline) } } } .padding() } - //Disable scroll if there are 4 or fewer to avoid unnecessary scroll behavior - .scrollDisabled(availableMediaControllers.count <= 4) + // Disable scroll if there are 4 or fewer to avoid unnecessary scroll behavior + .scrollDisabled(MediaControllerType.allCases.count <= 4) -// Spacer() + nowPlayingAvailabilityStatus Button("Continue", action: { - self.mediaController = self.selectedMediaController - NotificationCenter.default.post( - name: Notification.Name.mediaControllerChanged, - object: nil - ) + guard canContinue else { return } + musicManager.selectMediaController(selectedMediaController) onContinue() }) .buttonStyle(.borderedProminent) .controlSize(.large) + .disabled(!canContinue) .padding(.bottom, 24) } + .task { + musicManager.ensureNowPlayingAvailabilityChecked() + } .frame(maxWidth: .infinity, maxHeight: .infinity) .background( VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow) .ignoresSafeArea() ) } + + private var canContinue: Bool { + selectedMediaController != .nowPlaying + || ( + !musicManager.isCheckingNowPlayingAvailability + && musicManager.nowPlayingAvailability.isSelectable + ) + } + + @ViewBuilder + private var nowPlayingAvailabilityStatus: some View { + if musicManager.isCheckingNowPlayingAvailability { + Text("Checking Now Playing availability...") + .font(.caption) + .foregroundStyle(.secondary) + } else if let message = musicManager.nowPlayingAvailability.settingsMessage { + VStack(spacing: 8) { + Text(message) + .font(.caption) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + + if musicManager.nowPlayingAvailability.showsCheckAgainButton { + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() + } + .font(.caption) + } + } + .padding(.horizontal, 24) + } + } } struct ControllerOptionView: View { let controller: MediaControllerType let isSelected: Bool + let isEnabled: Bool var body: some View { HStack(spacing: 16) { @@ -92,15 +132,10 @@ struct ControllerOptionView: View { .font(.headline) .fontWeight(.semibold) - Text(controller.description) + Text(controller.descriptionResource) .font(.subheadline) .foregroundColor(.secondary) - if controller == .youtubeMusic, let url = URL(string: "https://github.com/pear-devs/pear-desktop") { - Link("View on GitHub: pear-devs/pear-desktop", destination: url) - .font(.subheadline) - .padding(.top, 2) - } } Spacer() @@ -115,21 +150,34 @@ struct ControllerOptionView: View { .stroke(isSelected ? Color.effectiveAccent : Color.secondary.opacity(0.3), lineWidth: 1.5) ) .contentShape(Rectangle()) + .opacity(isEnabled ? 1 : 0.5) } } extension MediaControllerType { - var description: String { + var descriptionResource: LocalizedStringResource { switch self { case .nowPlaying: - return "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." + LocalizedStringResource( + "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version.", + comment: "Onboarding description of the universal macOS Now Playing music source." + ) case .spotify: - return "Connects directly to the Spotify app." + LocalizedStringResource( + "Connects directly to the Spotify app.", + comment: "Onboarding description of the Spotify music source." + ) case .appleMusic: - return "Connects directly to the Apple Music app." + LocalizedStringResource( + "Connects directly to the Apple Music app.", + comment: "Onboarding description of the Apple Music source." + ) case .youtubeMusic: - return "Requires a third-party client with API plugin enabled." + LocalizedStringResource( + "Requires a third-party client with API plugin enabled.", + comment: "Onboarding description of the YouTube Music source." + ) } } } diff --git a/boringNotch/components/Settings/Views/MediaSettingsView.swift b/boringNotch/components/Settings/Views/MediaSettingsView.swift index 55410274d..c6c85e355 100644 --- a/boringNotch/components/Settings/Views/MediaSettingsView.swift +++ b/boringNotch/components/Settings/Views/MediaSettingsView.swift @@ -10,50 +10,34 @@ import SwiftUI struct Media: View { @Default(.waitInterval) var waitInterval - @Default(.mediaController) var mediaController @ObservedObject var coordinator = BoringViewCoordinator.shared @Default(.hideNotchOption) var hideNotchOption @Default(.enableSneakPeek) private var enableSneakPeek @Default(.sneakPeekStyles) var sneakPeekStyles @Default(.enableLyrics) var enableLyrics + @ObservedObject private var musicManager = MusicManager.shared var body: some View { Form { Section { - Picker("Music Source", selection: $mediaController) { - ForEach(availableMediaControllers) { controller in - Text(controller.localizedString).tag(controller) + Picker("Music Source", selection: mediaControllerSelection) { + ForEach(MediaControllerType.allCases) { controller in + Text(controller.localizedResource) + .tag(controller) + .disabled( + controller == .nowPlaying + && ( + musicManager.isCheckingNowPlayingAvailability + || !musicManager.nowPlayingAvailability.isSelectable + ) + ) } } - .onChange(of: mediaController) { _, _ in - NotificationCenter.default.post( - name: Notification.Name.mediaControllerChanged, - object: nil - ) - } } header: { Text("Media Source") } footer: { - if MusicManager.shared.isNowPlayingDeprecated { - HStack { - Text("YouTube Music requires this third-party app to be installed: ") - .foregroundStyle(.secondary) - .font(.caption) - Link( - "https://github.com/pear-devs/pear-desktop", - destination: URL(string: "https://github.com/pear-devs/pear-desktop")! - ) - .font(.caption) - .foregroundColor(.blue) // Ensures it's visibly a link - } - } else { - Text( - "'Now Playing' was the only option on previous versions and works with all media apps." - ) - .foregroundStyle(.secondary) - .font(.caption) - } + mediaSourceFooter } Section { @@ -124,14 +108,59 @@ struct Media: View { } .accentColor(.effectiveAccent) .navigationTitle("Media") + .task { + musicManager.ensureNowPlayingAvailabilityChecked() + } } - // Only show controller options that are available on this macOS version - private var availableMediaControllers: [MediaControllerType] { - if MusicManager.shared.isNowPlayingDeprecated { - return MediaControllerType.allCases.filter { $0 != .nowPlaying } + private var mediaControllerSelection: Binding { + Binding( + get: { musicManager.preferredMediaController }, + set: { selectedController in + guard selectedController != musicManager.preferredMediaController else { return } + musicManager.selectMediaController(selectedController) + } + ) + } + + @ViewBuilder + private var mediaSourceFooter: some View { + let availability = musicManager.nowPlayingAvailability + + if musicManager.isCheckingNowPlayingAvailability { + footerText("Checking Now Playing availability...") + } else if let message = availability.settingsMessage { + VStack(alignment: .leading, spacing: 6) { + footerText(message) + + if musicManager.preferredMediaController == .nowPlaying, + let effectiveController = musicManager.effectiveMediaController, + effectiveController != .nowPlaying { + footerText( + LocalizedStringResource( + "Using \(effectiveController.localizedString) temporarily. Your Now Playing preference is preserved.", + comment: "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source." + ) + ) + } + + if availability.showsCheckAgainButton { + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() + } + .font(.caption) + } + } } else { - return MediaControllerType.allCases + footerText( + "'Now Playing' was the only option on previous versions and works with all media apps." + ) } } + + private func footerText(_ text: LocalizedStringResource) -> some View { + Text(text) + .foregroundStyle(.secondary) + .font(.caption) + } } diff --git a/boringNotch/components/Webcam/WebcamView.swift b/boringNotch/components/Webcam/WebcamView.swift index e506b55fe..71900dad6 100644 --- a/boringNotch/components/Webcam/WebcamView.swift +++ b/boringNotch/components/Webcam/WebcamView.swift @@ -70,10 +70,10 @@ struct CameraPreviewView: View { case .denied, .restricted: DispatchQueue.main.async { let alert = NSAlert() - alert.messageText = "Camera Access Required" - alert.informativeText = "Please allow camera access in System Settings to use the mirror feature." - alert.addButton(withTitle: "Open System Settings") - alert.addButton(withTitle: "Cancel") + alert.messageText = NSLocalizedString("Camera Access Required", comment: "Camera permission alert title") + alert.informativeText = NSLocalizedString("Please allow camera access in System Settings to use the mirror feature.", comment: "Mirror camera permission alert message") + alert.addButton(withTitle: NSLocalizedString("Open System Settings", comment: "Button title that opens System Settings")) + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel button title")) if alert.runModal() == .alertFirstButtonReturn { if let settingsURL = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera") { diff --git a/boringNotch/helpers/MediaChecker.swift b/boringNotch/helpers/MediaChecker.swift index 50494e59a..fd3aa7d25 100644 --- a/boringNotch/helpers/MediaChecker.swift +++ b/boringNotch/helpers/MediaChecker.swift @@ -7,61 +7,85 @@ import Foundation -final class MediaChecker: Sendable { +struct MediaChecker: NowPlayingAvailabilityChecking { + func checkAvailability(maxAttempts: Int = 3) async throws -> NowPlayingAvailability { + let attempts = max(1, maxAttempts) - enum MediaCheckerError: Error { - case missingResources - case processExecutionFailed - case timeout + for attempt in 1...attempts { + try Task.checkCancellation() + let availability = try await runAvailabilityCheck() + guard availability.shouldRetry, attempt < attempts else { + return availability + } + + try await Task.sleep(for: .milliseconds(350 * attempt)) + } + + return .unchecked } - func checkDeprecationStatus() async throws -> Bool { - try await Task.detached(priority: .userInitiated) { - guard let scriptURL = Bundle.main.url(forResource: "mediaremote-adapter", withExtension: "pl"), - let nowPlayingTestClientPath = Bundle.main.url(forResource: "MediaRemoteAdapterTestClient", withExtension: nil)?.path, - let frameworkPath = Bundle.main.privateFrameworksPath?.appending("/MediaRemoteAdapter.framework") - else { - throw MediaCheckerError.missingResources - } + private func runAvailabilityCheck() async throws -> NowPlayingAvailability { + let resources: NowPlayingResources + do { + resources = try NowPlayingResources.load(requiresTestClient: true) + } catch let failure as NowPlayingSetupFailure { + return .setupFailure(failure) + } catch { + return .setupFailure(.missingAdapterScript) + } - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") - process.arguments = [scriptURL.path, frameworkPath, nowPlayingTestClientPath, "test"] + guard let testClientURL = resources.testClientURL else { + return .setupFailure(.missingTestClient) + } - do { - try process.run() - } catch { - throw MediaCheckerError.processExecutionFailed - } + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") + process.arguments = [ + resources.adapterScriptURL.path, + resources.adapterFrameworkPath, + testClientURL.path, + "test", + ] - // Timeout after 10 seconds - let didExit: Bool = try await withThrowingTaskGroup(of: Bool.self) { group in - group.addTask { - process.waitUntilExit() - return true - } - group.addTask { - try await Task.sleep(for: .seconds(10)) - if process.isRunning { - process.terminate() - } - return false // Timed out - } - for try await exited in group { - if exited { - group.cancelAll() - return true - } - } - throw MediaCheckerError.timeout - } + let result: TimedProcessResult + do { + result = try await TimedProcessRunner.run(process, timeout: .seconds(10)) + } catch is CancellationError { + throw CancellationError() + } catch { + return .temporaryFailure(.launchFailed) + } - if !didExit { - throw MediaCheckerError.timeout - } + switch result { + case .timedOut: + return .temporaryFailure(.timedOut) + case .terminated(let termination): + return Self.availability(for: termination) + } + } - let isDeprecated = process.terminationStatus == 1 - return isDeprecated - }.value + static func availability( + for termination: ChildProcessTermination + ) -> NowPlayingAvailability { + switch termination { + case .exited(0): + return .available + case .exited(1): + return .temporaryFailure(.helperPathMissing) + case .exited(2): + return .temporaryFailure(.helperLaunchFailed) + case .exited(3): + return .temporaryFailure(.helperSetupTimedOut) + case .exited(4): + return .temporaryFailure(.noData) + case .exited(64): + return .temporaryFailure(.wrapperFailed) + case .exited(let status): + return .temporaryFailure(.unexpectedExit(status)) + case .signaled(let signal): + return .temporaryFailure(.signaled(signal)) + case .unknown(let status): + return .temporaryFailure(.unexpectedExit(status)) + } } } diff --git a/boringNotch/managers/AudioCaptureManager.swift b/boringNotch/managers/AudioCaptureManager.swift index 52331efad..417ea38e9 100644 --- a/boringNotch/managers/AudioCaptureManager.swift +++ b/boringNotch/managers/AudioCaptureManager.swift @@ -108,7 +108,9 @@ final class AudioCaptureManager: ObservableObject { fftQueue.setSpecific(key: Self.fftQueueKey, value: ()) lifecycleQueue.setSpecific(key: Self.lifecycleQueueKey, value: ()) computeBandRanges(sampleRate: sampleRate) - observeState() + Task { @MainActor [weak self] in + self?.observeState() + } } deinit { @@ -154,6 +156,7 @@ final class AudioCaptureManager: ObservableObject { // MARK: - State observation + @MainActor private func observeState() { let music = MusicManager.shared let enabledPublisher = Defaults.publisher(.realtimeAudioWaveform) diff --git a/boringNotch/managers/MusicManager.swift b/boringNotch/managers/MusicManager.swift index 275dbdcab..fd1676f5e 100644 --- a/boringNotch/managers/MusicManager.swift +++ b/boringNotch/managers/MusicManager.swift @@ -14,26 +14,126 @@ let defaultImage: NSImage = .init( accessibilityDescription: "Album Art" )! -class MusicManager: ObservableObject { +@MainActor +protocol MediaControllerCreating { + func makeController(for type: MediaControllerType) throws -> any MediaControllerProtocol +} + +@MainActor +struct DefaultMediaControllerFactory: MediaControllerCreating { + func makeController(for type: MediaControllerType) throws -> any MediaControllerProtocol { + switch type { + case .nowPlaying: + return try NowPlayingController() + case .appleMusic: + return AppleMusicController() + case .spotify: + return SpotifyController() + case .youtubeMusic: + return YouTubeMusicController() + } + } +} + +@MainActor +protocol MediaControllerPreferenceStoring: AnyObject { + var preferredController: MediaControllerType { get set } + var didChooseController: Bool { get set } + var lastSupportedBundleIdentifier: String? { get set } + + func migrateIfNeeded() +} + +@MainActor +final class DefaultsMediaControllerPreferences: MediaControllerPreferenceStoring { + var preferredController: MediaControllerType { + get { Defaults[.mediaController] } + set { Defaults[.mediaController] = newValue } + } + + var didChooseController: Bool { + get { Defaults[.didChooseMediaController] } + set { Defaults[.didChooseMediaController] = newValue } + } + + var lastSupportedBundleIdentifier: String? { + get { Defaults[.lastSupportedNowPlayingBundleIdentifier] } + set { Defaults[.lastSupportedNowPlayingBundleIdentifier] = newValue } + } + + func migrateIfNeeded() { + guard !Defaults[.didMigrateMediaControllerChoice] else { return } + + let firstLaunch = UserDefaults.standard.object(forKey: "firstLaunch") as? Bool ?? true + let hasStoredController = UserDefaults.standard.object(forKey: "mediaController") != nil + didChooseController = didChooseController || hasStoredController || !firstLaunch + Defaults[.didMigrateMediaControllerChoice] = true + } +} + +struct NowPlayingFallbackNotice: Identifiable, Equatable { + let id = UUID() + let fallbackSource: MediaControllerType + let isTemporary: Bool + + var title: LocalizedStringResource { + LocalizedStringResource( + "Now Playing unavailable", + comment: "Title of the passive notice shown when the Now Playing media source cannot be used." + ) + } + + var subtitle: LocalizedStringResource { + if isTemporary { + LocalizedStringResource( + "Using \(fallbackSource.localizedString) temporarily", + comment: "Temporary Now Playing fallback notice. The placeholder is the fallback music source name." + ) + } else { + LocalizedStringResource( + "Switched to \(fallbackSource.localizedString)", + comment: "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name." + ) + } + } +} + +@MainActor +final class MusicManager: ObservableObject { // MARK: - Properties static let shared = MusicManager() - private var cancellables = Set() private var controllerCancellables = Set() private var debounceIdleTask: Task? - - // Helper to check if macOS has removed support for NowPlayingController - public private(set) var isNowPlayingDeprecated: Bool = false - private let mediaChecker = MediaChecker() + private var availabilityTask: Task? + private var runtimeFailureTask: Task? + private var runtimeRetryTask: Task? + private var noticeDismissalTask: Task? + private var runtimeRecoveryPolicy = NowPlayingRuntimeRecoveryPolicy() + private var availabilityRequestID: UUID? + private var isDestroyed = false + private var didShowNoticeForCurrentFailure = false + + private let preferences: any MediaControllerPreferenceStoring + private let availabilityChecker: any NowPlayingAvailabilityChecking + private let controllerFactory: any MediaControllerCreating + private let noticeDuration: Duration + + // Helper to check if macOS can use NowPlayingController + @Published private(set) var preferredMediaController: MediaControllerType + @Published private(set) var nowPlayingAvailability: NowPlayingAvailability = .unchecked + @Published private(set) var isCheckingNowPlayingAvailability = false + @Published private(set) var effectiveMediaController: MediaControllerType? + @Published private(set) var nowPlayingNotice: NowPlayingFallbackNotice? // Active controller private var activeController: (any MediaControllerProtocol)? // Published properties for UI - @Published var songTitle: String = "I'm Handsome" - @Published var artistName: String = "Me" + @Published var songTitle: String = "" + @Published var artistName: String = "" @Published var albumArt: NSImage = defaultImage @Published var isPlaying = false - @Published var album: String = "Self Love" + @Published var album: String = "" @Published var isPlayerIdle: Bool = true @Published var animations: BoringAnimations = .init() @Published var avgColor: NSColor = .white @@ -47,7 +147,7 @@ class MusicManager: ObservableObject { @Published var repeatMode: RepeatMode = .off @Published var volume: Double = 0.5 @Published var volumeControlSupported: Bool = true - @ObservedObject var coordinator = BoringViewCoordinator.shared + private let coordinator = BoringViewCoordinator.shared @Published var usingAppIconForArtwork: Bool = false @Published var canFavoriteTrack: Bool = false @@ -61,9 +161,9 @@ class MusicManager: ObservableObject { private var artworkData: Data? = nil // Store last values at the time artwork was changed - private var lastArtworkTitle: String = "I'm Handsome" - private var lastArtworkArtist: String = "Me" - private var lastArtworkAlbum: String = "Self Love" + private var lastArtworkTitle: String = "" + private var lastArtworkArtist: String = "" + private var lastArtworkAlbum: String = "" private var lastArtworkBundleIdentifier: String? = nil @Published var isFlipping: Bool = false @@ -73,118 +173,365 @@ class MusicManager: ObservableObject { private var transitionWorkItem: DispatchWorkItem? // MARK: - Initialization - init() { - // Listen for changes to the default controller preference - NotificationCenter.default.publisher(for: Notification.Name.mediaControllerChanged) - .sink { [weak self] _ in - self?.setActiveControllerBasedOnPreference() - } - .store(in: &cancellables) - - // Initialize deprecation check asynchronously - Task { @MainActor in - do { - self.isNowPlayingDeprecated = try await self.mediaChecker.checkDeprecationStatus() - print("Deprecation check completed: \(self.isNowPlayingDeprecated)") - } catch { - print("Failed to check deprecation status: \(error). Defaulting to false.") - self.isNowPlayingDeprecated = false - } - - // Initialize the active controller after deprecation check - self.setActiveControllerBasedOnPreference() + convenience init() { + self.init( + preferences: DefaultsMediaControllerPreferences(), + availabilityChecker: MediaChecker(), + controllerFactory: DefaultMediaControllerFactory() + ) + } + + init( + preferences: any MediaControllerPreferenceStoring, + availabilityChecker: any NowPlayingAvailabilityChecking, + controllerFactory: any MediaControllerCreating, + noticeDuration: Duration = .seconds(6) + ) { + self.preferences = preferences + self.availabilityChecker = availabilityChecker + self.controllerFactory = controllerFactory + self.noticeDuration = noticeDuration + + preferences.migrateIfNeeded() + preferredMediaController = preferences.preferredController + + if preferredMediaController == .nowPlaying { + activateFallback(showNotice: false) + ensureNowPlayingAvailabilityChecked() + } else { + activateControllerIfNeeded(preferredMediaController) } } deinit { - destroy() + debounceIdleTask?.cancel() + availabilityTask?.cancel() + runtimeFailureTask?.cancel() + runtimeRetryTask?.cancel() + noticeDismissalTask?.cancel() + controllerCancellables.removeAll() + flipWorkItem?.cancel() + transitionWorkItem?.cancel() } - - public func destroy() { + + func destroy() { + guard !isDestroyed else { return } + isDestroyed = true + availabilityRequestID = nil + debounceIdleTask?.cancel() - cancellables.removeAll() + availabilityTask?.cancel() + runtimeFailureTask?.cancel() + runtimeRetryTask?.cancel() + noticeDismissalTask?.cancel() + availabilityTask = nil + runtimeFailureTask = nil + runtimeRetryTask = nil + noticeDismissalTask = nil + isCheckingNowPlayingAvailability = false controllerCancellables.removeAll() flipWorkItem?.cancel() transitionWorkItem?.cancel() + (activeController as? any NowPlayingRuntimeControlling)?.stopRuntimeStream() - // Release active controller activeController = nil + effectiveMediaController = nil + nowPlayingNotice = nil } - // MARK: - Setup Methods - private func createController(for type: MediaControllerType) -> (any MediaControllerProtocol)? { - // Cleanup previous controller - if activeController != nil { - controllerCancellables.removeAll() - activeController = nil + func selectMediaController(_ type: MediaControllerType) { + guard !isDestroyed else { return } + if case .setupFailure = nowPlayingAvailability, type == .nowPlaying { + return } - let newController: (any MediaControllerProtocol)? + preferences.preferredController = type + preferences.didChooseController = true + preferredMediaController = type + runtimeRecoveryPolicy.reset() + clearNotice(resetEpisode: true) - switch type { - case .nowPlaying: - // Only create NowPlayingController if not deprecated on this macOS version - if !self.isNowPlayingDeprecated { - newController = NowPlayingController() + if type == .nowPlaying { + if nowPlayingAvailability == .available { + activateControllerIfNeeded(.nowPlaying) } else { - return nil + activateFallback(showNotice: false) + refreshNowPlayingAvailability() } - case .appleMusic: - newController = AppleMusicController() - case .spotify: - newController = SpotifyController() - case .youtubeMusic: - newController = YouTubeMusicController() + } else { + activateControllerIfNeeded(type) } + } - // Set up state observation for the new controller - if let controller = newController { - controller.playbackStatePublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] state in - guard let self = self, - self.activeController === controller else { return } - self.updateFromPlaybackState(state) - } - .store(in: &controllerCancellables) + private func resolvedNowPlayingFallback() -> MediaControllerType { + guard let bundleIdentifier = preferences.lastSupportedBundleIdentifier, + let controller = MediaControllerType(nowPlayingBundleIdentifier: bundleIdentifier) + else { + return .appleMusic } - return newController + return controller } - private func setActiveControllerBasedOnPreference() { - let preferredType = Defaults[.mediaController] - print("Preferred Media Controller: \(preferredType)") + func ensureNowPlayingAvailabilityChecked() { + guard nowPlayingAvailability == .unchecked else { return } + startAvailabilityCheck() + } + + func refreshNowPlayingAvailability() { + startAvailabilityCheck() + } + + private func startAvailabilityCheck() { + guard !isDestroyed, availabilityTask == nil else { return } - // If NowPlaying is deprecated but that's the preference, use Apple Music instead - let controllerType = (self.isNowPlayingDeprecated && preferredType == .nowPlaying) - ? .appleMusic - : preferredType + let requestID = UUID() + let checker = availabilityChecker + availabilityRequestID = requestID + isCheckingNowPlayingAvailability = true - if let controller = createController(for: controllerType) { - setActiveController(controller) - } else if controllerType != .appleMusic, let fallbackController = createController(for: .appleMusic) { - // Fallback to Apple Music if preferred controller couldn't be created - setActiveController(fallbackController) + availabilityTask = Task { @MainActor [weak self, checker] in + do { + let availability = try await checker.checkAvailability(maxAttempts: 3) + self?.finishAvailabilityCheck(availability, requestID: requestID) + } catch is CancellationError { + self?.finishCancelledAvailabilityCheck(requestID: requestID) + } catch { + self?.finishAvailabilityCheck( + .temporaryFailure(.launchFailed), + requestID: requestID + ) + } } } - private func setActiveController(_ controller: any MediaControllerProtocol) { - // Cancel any existing flip animation - flipWorkItem?.cancel() + private func finishCancelledAvailabilityCheck(requestID: UUID) { + guard availabilityRequestID == requestID else { return } + availabilityTask = nil + availabilityRequestID = nil + isCheckingNowPlayingAvailability = false + } + + private func finishAvailabilityCheck( + _ availability: NowPlayingAvailability, + requestID: UUID + ) { + guard !isDestroyed, availabilityRequestID == requestID else { return } + + availabilityTask = nil + availabilityRequestID = nil + isCheckingNowPlayingAvailability = false + nowPlayingAvailability = availability + + if availability == .available { + runtimeRecoveryPolicy.reset() + clearNotice(resetEpisode: true) + if preferredMediaController == .nowPlaying { + activateControllerIfNeeded(.nowPlaying) + } + } else if availability.requiresFallback, + preferredMediaController == .nowPlaying { + activateFallback( + for: availability, + showNotice: preferences.didChooseController + ) + } + } + + private func activateFallback( + for availability: NowPlayingAvailability? = nil, + showNotice: Bool + ) { + let fallbackController = resolvedNowPlayingFallback() + let isTemporary = availability?.isRecoverableFailure ?? true + + if let availability, availability.requiresFallback, !isTemporary { + preferences.preferredController = fallbackController + preferredMediaController = fallbackController + } + + if showNotice, availability != nil { + requestNotice( + fallbackController: fallbackController, + isTemporary: isTemporary + ) + } + + activateControllerIfNeeded(fallbackController) + } + + private func requestNotice( + fallbackController: MediaControllerType, + isTemporary: Bool + ) { + guard !didShowNoticeForCurrentFailure else { return } + didShowNoticeForCurrentFailure = true + + let notice = NowPlayingFallbackNotice( + fallbackSource: fallbackController, + isTemporary: isTemporary + ) + nowPlayingNotice = notice + } + + @discardableResult + func markNowPlayingNoticePresented(_ noticeID: UUID) -> Bool { + guard nowPlayingNotice?.id == noticeID, + noticeDismissalTask == nil + else { + return false + } + + noticeDismissalTask = Task { @MainActor [weak self] in + guard let self else { return } + do { + try await Task.sleep(for: self.noticeDuration) + } catch { + return + } + guard self.nowPlayingNotice?.id == noticeID else { return } + self.nowPlayingNotice = nil + self.noticeDismissalTask = nil + } + return true + } + + private func clearNotice(resetEpisode: Bool) { + noticeDismissalTask?.cancel() + noticeDismissalTask = nil + nowPlayingNotice = nil + if resetEpisode { + didShowNoticeForCurrentFailure = false + } + } + + private func activateControllerIfNeeded(_ type: MediaControllerType) { + guard !isDestroyed, + activeController == nil || effectiveMediaController != type + else { + return + } + + do { + let controller = try controllerFactory.makeController(for: type) + activateController(controller, type: type) + } catch let failure as NowPlayingSetupFailure where type == .nowPlaying { + let availability = NowPlayingAvailability.setupFailure(failure) + nowPlayingAvailability = availability + activateFallback( + for: availability, + showNotice: preferences.didChooseController + ) + } catch { + guard type != .appleMusic else { return } + do { + let controller = try controllerFactory.makeController(for: .appleMusic) + activateController(controller, type: .appleMusic) + } catch { + return + } + } + } + + private func activateController( + _ controller: any MediaControllerProtocol, + type: MediaControllerType + ) { + runtimeFailureTask?.cancel() + runtimeRetryTask?.cancel() + runtimeFailureTask = nil + runtimeRetryTask = nil + (activeController as? any NowPlayingRuntimeControlling)?.stopRuntimeStream() + controllerCancellables.removeAll() - // Set new active controller + flipWorkItem?.cancel() activeController = controller - - self.canFavoriteTrack = controller.supportsFavorite + effectiveMediaController = type + canFavoriteTrack = controller.supportsFavorite + + controller.playbackStatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self, controller] state in + guard let self, + self.activeController === controller, + state.lastUpdated != .distantPast + else { + return + } + self.updateFromPlaybackState(state) + } + .store(in: &controllerCancellables) + + if let runtimeController = controller as? any NowPlayingRuntimeControlling { + runtimeFailureTask = Task { @MainActor [weak self, runtimeController] in + for await failure in runtimeController.runtimeFailures { + guard let self else { return } + self.handleRuntimeFailure(failure, from: runtimeController) + } + } + } - // Get current state from active controller forceUpdate() + (controller as? any NowPlayingRuntimeControlling)?.startRuntimeStream() + } + + private func handleRuntimeFailure( + _ failure: NowPlayingRuntimeFailure, + from controller: any NowPlayingRuntimeControlling + ) { + guard activeController === controller, + effectiveMediaController == .nowPlaying, + preferredMediaController == .nowPlaying + else { + return + } + + print("Now Playing runtime stream failed: \(failure)") + + switch runtimeRecoveryPolicy.action(at: .now) { + case .restart(let delay): + runtimeRetryTask?.cancel() + runtimeRetryTask = Task { @MainActor [weak self, controller] in + do { + try await Task.sleep(for: delay) + } catch { + return + } + + guard let self, + !self.isDestroyed, + self.preferredMediaController == .nowPlaying, + self.effectiveMediaController == .nowPlaying, + self.activeController === controller + else { + return + } + + self.runtimeRetryTask = nil + controller.restartRuntimeStream() + } + + case .fallback: + runtimeRetryTask?.cancel() + runtimeRetryTask = nil + nowPlayingAvailability = .runtimeStreamFailed + activateFallback( + for: .runtimeStreamFailed, + showNotice: preferences.didChooseController + ) + } } // MARK: - Update Methods - @MainActor private func updateFromPlaybackState(_ state: PlaybackState) { + guard state.lastUpdated != .distantPast else { return } + + if effectiveMediaController == .nowPlaying, + MediaControllerType(nowPlayingBundleIdentifier: state.bundleIdentifier) != nil, + preferences.lastSupportedBundleIdentifier != state.bundleIdentifier { + preferences.lastSupportedBundleIdentifier = state.bundleIdentifier + } + // Check for playback state changes (playing/paused) if state.isPlaying != self.isPlaying { NSLog("Playback state changed: \(state.isPlaying ? "Playing" : "Paused")") diff --git a/boringNotch/models/BoringViewModel.swift b/boringNotch/models/BoringViewModel.swift index 77d5366d8..cb4f2e6d2 100644 --- a/boringNotch/models/BoringViewModel.swift +++ b/boringNotch/models/BoringViewModel.swift @@ -142,10 +142,10 @@ class BoringViewModel: NSObject, ObservableObject { NSApp.activate(ignoringOtherApps: true) let alert = NSAlert() - alert.messageText = "Camera Access Required" - alert.informativeText = "Please allow camera access in System Settings." - alert.addButton(withTitle: "Open Settings") - alert.addButton(withTitle: "Cancel") + alert.messageText = NSLocalizedString("Camera Access Required", comment: "Camera permission alert title") + alert.informativeText = NSLocalizedString("Please allow camera access in System Settings.", comment: "Camera permission alert message") + alert.addButton(withTitle: NSLocalizedString("Open Settings", comment: "Button title that opens app or system settings")) + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel button title")) if alert.runModal() == .alertFirstButtonReturn { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera") { diff --git a/boringNotch/models/Constants.swift b/boringNotch/models/Constants.swift index fc2847421..580708f9a 100644 --- a/boringNotch/models/Constants.swift +++ b/boringNotch/models/Constants.swift @@ -102,9 +102,6 @@ struct AppLanguage: RawRepresentable, Hashable, Identifiable, Defaults.Serializa // Define notification names at file scope extension Notification.Name { - // MARK: - Media - static let mediaControllerChanged = Notification.Name("mediaControllerChanged") - // MARK: - Display static let selectedScreenChanged = Notification.Name("SelectedScreenChanged") static let notchHeightChanged = Notification.Name("NotchHeightChanged") @@ -133,18 +130,35 @@ enum MediaControllerType: String, CaseIterable, Identifiable, Defaults.Serializa var id: String { self.rawValue } - var localizedString: String { + init?(nowPlayingBundleIdentifier bundleIdentifier: String) { + switch bundleIdentifier { + case "com.apple.Music": + self = .appleMusic + case "com.spotify.client": + self = .spotify + case YouTubeMusicConfiguration.default.bundleIdentifier: + self = .youtubeMusic + default: + return nil + } + } + + var localizedResource: LocalizedStringResource { switch self { case .nowPlaying: - return NSLocalizedString("Now Playing", comment: "") + "Now Playing" case .appleMusic: - return "Apple Music" + "Apple Music" case .spotify: - return "Spotify" + "Spotify" case .youtubeMusic: - return "YouTube Music" + "YouTube Music" } } + + var localizedString: String { + String(localized: localizedResource) + } } // Sneak peek styles for selection in settings @@ -329,6 +343,12 @@ extension Defaults.Keys { // MARK: Media Controller static let mediaController = Key("mediaController", default: defaultMediaController) + static let didChooseMediaController = Key("didChooseMediaController", default: false) + static let didMigrateMediaControllerChoice = Key("didMigrateMediaControllerChoice", default: false) + static let lastSupportedNowPlayingBundleIdentifier = Key( + "lastSupportedNowPlayingBundleIdentifier", + default: nil + ) // MARK: Advanced Settings static let useCustomAccentColor = Key("useCustomAccentColor", default: false) @@ -339,13 +359,9 @@ extension Defaults.Keys { // Normalize scroll/gesture direction so when macOS "Natural scrolling" is disabled, it doesn't invert gestures static let normalizeGestureDirection = Key("normalizeGestureDirection", default: true) - // Helper to determine the default media controller based on NowPlaying deprecation status + // Keep the default stable. Runtime availability is handled by MusicManager. static var defaultMediaController: MediaControllerType { - if MusicManager.shared.isNowPlayingDeprecated { - return .appleMusic - } else { - return .nowPlaying - } + .nowPlaying } static let didClearLegacyURLCacheV1 = Key("didClearLegacyURLCache_v1", default: false) diff --git a/boringNotch/models/PlaybackState.swift b/boringNotch/models/PlaybackState.swift index 7ad4c47f0..59781b7ba 100644 --- a/boringNotch/models/PlaybackState.swift +++ b/boringNotch/models/PlaybackState.swift @@ -17,9 +17,9 @@ struct PlaybackState { var bundleIdentifier: String var audioCaptureBundleIdentifiers: [String] = [] var isPlaying: Bool = false - var title: String = "I'm Handsome" - var artist: String = "Me" - var album: String = "Self Love" + var title: String = "" + var artist: String = "" + var album: String = "" var currentTime: Double = 0 var duration: Double = 0 var playbackRate: Double = 1 diff --git a/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter b/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter index b717eb00e579a8127660406d2925b8948ee51b58..ff51776c9a089b5fa05974840c15859b8c3736b1 100755 GIT binary patch literal 331296 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 z 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 "durationMicros" "elapsedTime" -> "elapsedTimeMicros" "elapsedTimeNow" -> "elapsedTimeNowMicros" "timestamp" -> "timestampEpochMicros" (converted to epoch time) + --no-artwork: Omits "artworkData" and "artworkMimeType" from the payload. + Useful for consumers that do not render artwork, since this avoids + emitting several hundred kilobytes of base64 data per update. --human-readable, -h: Makes values human-readable. Use only for debugging. The JSON output is pretty-printed and the following keys are adapted: "artworkData" -> Binary data is truncated to a shorter representation @@ -81,7 +84,8 @@ () sub fail { my ($error) = @_; print STDERR "$error\n"; - exit 1; + # Keep wrapper failures disjoint from adapter_test's documented 0...4 statuses. + exit 64; } fail "Framework path not provided" unless @ARGV >= 1; @@ -125,7 +129,7 @@ sub parse_options { my $i = $start_index; while ($i <= $#ARGV) { my $arg = $ARGV[$i]; - if ($arg =~ /^--([a-z\\-]+)(?:=(.*))?$/) { + if ($arg =~ /^--([a-z:\.\\-]+)(?:=(.*))?$/) { my $key = $1; my $value = defined $2 ? $2 : undef; $arg_map{$key} = $value; @@ -196,9 +200,15 @@ sub set_env_option_value { elsif ($key eq "micros") { set_env_option($options, $key); } + elsif ($key eq "no-artwork") { + set_env_option($options, $key); + } elsif ($key eq "human-readable" || $key eq "h") { set_env_option($options, "human-readable"); } + elsif ($key eq "experimental-peculiar-debounce:com.tidal.desktop") { + set_env_option_value($options, $key); + } else { fail "Unrecognized option '$key'"; } @@ -211,6 +221,9 @@ sub set_env_option_value { if ($key eq "micros") { set_env_option($options, $key); } + elsif ($key eq "no-artwork") { + set_env_option($options, $key); + } elsif ($key eq "human-readable" || $key eq "h") { set_env_option($options, "human-readable"); } From 3c8936e1b214c3cead2ae97743d67ee8ead512ed Mon Sep 17 00:00:00 2001 From: Alexander <33609792+Alexander5015@users.noreply.github.com> Date: Fri, 21 Aug 2026 19:36:21 -0600 Subject: [PATCH 2/5] Improve Now Playing reliability messages and localization updates --- boringNotch/Localizable.xcstrings | 506 +++++++++++++++--------------- 1 file changed, 255 insertions(+), 251 deletions(-) diff --git a/boringNotch/Localizable.xcstrings b/boringNotch/Localizable.xcstrings index 36e981080..a599a6357 100644 --- a/boringNotch/Localizable.xcstrings +++ b/boringNotch/Localizable.xcstrings @@ -145,9 +145,6 @@ } } }, - "%@x" : { - "comment" : "Animation speed multiplier." - }, "%@" : { "localizations" : { "en" : { @@ -164,6 +161,9 @@ } } }, + "%@x" : { + "comment" : "Animation speed multiplier." + }, "%lld" : { "localizations" : { "en" : { @@ -1850,6 +1850,18 @@ } } }, + "Apple Music" : { + "comment" : "Apple Music service name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Music" + } + } + }, + "shouldTranslate" : false + }, "Auto-scroll to next event" : { "localizations" : { "de" : { @@ -3106,6 +3118,28 @@ } } }, + "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." : { + "comment" : "Generic recoverable Now Playing availability message.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." + } + } + } + }, + "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." : { + "comment" : "Now Playing availability message when a bundled component is missing.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." + } + } + } + }, "boring.notch" : { "localizations" : { "cs" : { @@ -3709,8 +3743,19 @@ "Camera" : { "comment" : "A label for the camera picker." }, + "Camera Access Required" : { + "comment" : "Camera permission alert title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Camera Access Required" + } + } + } + }, "Cancel" : { - "extractionState" : "stale", + "comment" : "Cancel button title", "localizations" : { "cs" : { "stringUnit" : { @@ -3828,251 +3873,6 @@ } } }, - "Apple Music" : { - "comment" : "Apple Music service name.", - "shouldTranslate" : false, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apple Music" - } - } - } - }, - "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." : { - "comment" : "Generic recoverable Now Playing availability message.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." - } - } - } - }, - "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." : { - "comment" : "Now Playing availability message when a bundled component is missing.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." - } - } - } - }, - "Connects directly to the Apple Music app." : { - "comment" : "Onboarding description of the Apple Music source.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connects directly to the Apple Music app." - } - } - } - }, - "Connects directly to the Spotify app." : { - "comment" : "Onboarding description of the Spotify music source.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connects directly to the Spotify app." - } - } - } - }, - "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." : { - "comment" : "Media settings footer shown after the Now Playing runtime stream repeatedly stops.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." - } - } - } - }, - "Now Playing unavailable" : { - "comment" : "Title of the passive notice shown when the Now Playing media source cannot be used.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now Playing unavailable" - } - } - } - }, - "Now Playing is not supported by this version of macOS. Choose another music source." : { - "comment" : "Now Playing availability message when required private macOS APIs are unavailable.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now Playing is not supported by this version of macOS. Choose another music source." - } - } - } - }, - "Requires a third-party client with API plugin enabled." : { - "comment" : "Onboarding description of the YouTube Music source.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Requires a third-party client with API plugin enabled." - } - } - } - }, - "Switched to %@" : { - "comment" : "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Switched to %@" - } - } - } - }, - "Spotify" : { - "comment" : "Spotify service name.", - "shouldTranslate" : false, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Spotify" - } - } - } - }, - "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." : { - "comment" : "Now Playing availability message when the self-check receives no test data.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." - } - } - } - }, - "The Now Playing check took too long. Try again in a moment." : { - "comment" : "Now Playing availability message when the self-check times out.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The Now Playing check took too long. Try again in a moment." - } - } - } - }, - "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." : { - "comment" : "Onboarding description of the universal macOS Now Playing music source.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." - } - } - } - }, - "Using %@ temporarily" : { - "comment" : "Temporary Now Playing fallback notice. The placeholder is the fallback music source name.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Using %@ temporarily" - } - } - } - }, - "Using %@ temporarily. Your Now Playing preference is preserved." : { - "comment" : "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Using %@ temporarily. Your Now Playing preference is preserved." - } - } - } - }, - "YouTube Music" : { - "comment" : "YouTube Music service name.", - "shouldTranslate" : false, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "YouTube Music" - } - } - } - }, - "Camera Access Required" : { - "comment" : "Camera permission alert title", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Camera Access Required" - } - } - } - }, - "Open Settings" : { - "comment" : "Button title that opens app or system settings", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Settings" - } - } - } - }, - "Open System Settings" : { - "comment" : "Button title that opens System Settings", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open System Settings" - } - } - } - }, - "Please allow camera access in System Settings." : { - "comment" : "Camera permission alert message", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please allow camera access in System Settings." - } - } - } - }, - "Please allow camera access in System Settings to use the mirror feature." : { - "comment" : "Mirror camera permission alert message", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please allow camera access in System Settings to use the mirror feature." - } - } - } - }, "Change media with horizontal gestures" : { "localizations" : { "de" : { @@ -6114,6 +5914,28 @@ } } }, + "Connects directly to the Apple Music app." : { + "comment" : "Onboarding description of the Apple Music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connects directly to the Apple Music app." + } + } + } + }, + "Connects directly to the Spotify app." : { + "comment" : "Onboarding description of the Spotify music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connects directly to the Spotify app." + } + } + } + }, "Continue" : { "localizations" : { "cs" : { @@ -12924,6 +12746,17 @@ } } }, + "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." : { + "comment" : "Now Playing availability message when the self-check receives no test data.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." + } + } + } + }, "Made with 🫶🏻 by not so boring not.people" : { "localizations" : { "cs" : { @@ -16416,6 +16249,39 @@ } } }, + "Now Playing is not supported by this version of macOS. Choose another music source." : { + "comment" : "Now Playing availability message when required private macOS APIs are unavailable.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing is not supported by this version of macOS. Choose another music source." + } + } + } + }, + "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." : { + "comment" : "Media settings message after the Now Playing runtime stream repeatedly stops.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." + } + } + } + }, + "Now Playing unavailable" : { + "comment" : "Title of the passive notice shown when the Now Playing media source cannot be used.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing unavailable" + } + } + } + }, "Open Calendar Settings" : { "localizations" : { "cs" : { @@ -16862,6 +16728,17 @@ } } }, + "Open Settings" : { + "comment" : "Button title that opens app or system settings", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Settings" + } + } + } + }, "Open shelf by default if items are present" : { "localizations" : { "de" : { @@ -16974,6 +16851,17 @@ } } }, + "Open System Settings" : { + "comment" : "Button title that opens System Settings", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open System Settings" + } + } + } + }, "Option (⌥) Key Behavior" : { "localizations" : { "cs" : { @@ -17759,6 +17647,28 @@ } } }, + "Please allow camera access in System Settings to use the mirror feature." : { + "comment" : "Mirror camera permission alert message", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please allow camera access in System Settings to use the mirror feature." + } + } + } + }, + "Please allow camera access in System Settings." : { + "comment" : "Camera permission alert message", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please allow camera access in System Settings." + } + } + } + }, "Plugged In" : { "localizations" : { "cs" : { @@ -19327,6 +19237,17 @@ } } }, + "Requires a third-party client with API plugin enabled." : { + "comment" : "Onboarding description of the YouTube Music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requires a third-party client with API plugin enabled." + } + } + } + }, "Requires macOS 14.2 or later. Update macOS to enable real-time audio waveform." : { "localizations" : { "de" : { @@ -21944,6 +21865,10 @@ } } }, + "Show charging wattage" : { + "comment" : "A checkbox to show the wattage of the charging port.", + "isCommentAutoGenerated" : true + }, "Show cool face animation while inactive" : { "localizations" : { "de" : { @@ -24361,6 +24286,18 @@ } } }, + "Spotify" : { + "comment" : "Spotify service name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spotify" + } + } + }, + "shouldTranslate" : false + }, "Square" : { "localizations" : { "cs" : { @@ -24598,6 +24535,17 @@ } } }, + "Switched to %@" : { + "comment" : "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Switched to %@" + } + } + } + }, "System" : { "localizations" : { "de" : { @@ -24888,7 +24836,7 @@ } }, "System Setting" : { - "comment" : "Week starts on: follow the macOS system setting (shown with the resolved day in parentheses)", + "comment" : "Week starts on: follow the macOS system setting", "localizations" : { "de" : { "stringUnit" : { @@ -24928,6 +24876,17 @@ } } }, + "The Now Playing check took too long. Try again in a moment." : { + "comment" : "Now Playing availability message when the self-check times out.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Now Playing check took too long. Try again in a moment." + } + } + } + }, "Time to Full Charge: %@" : { "localizations" : { "de" : { @@ -26389,6 +26348,28 @@ } } }, + "Using %@ temporarily" : { + "comment" : "Temporary Now Playing fallback notice. The placeholder is the fallback music source name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ temporarily" + } + } + } + }, + "Using %@ temporarily. Your Now Playing preference is preserved." : { + "comment" : "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ temporarily. Your Now Playing preference is preserved." + } + } + } + }, "Using System Accent" : { "localizations" : { "de" : { @@ -27465,6 +27446,17 @@ } } }, + "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." : { + "comment" : "Onboarding description of the universal macOS Now Playing music source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version." + } + } + } + }, "You can now enjoy the app. If you want to tweak things further, you can always visit the settings." : { "localizations" : { "de" : { @@ -27801,6 +27793,18 @@ } } }, + "YouTube Music" : { + "comment" : "YouTube Music service name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube Music" + } + } + }, + "shouldTranslate" : false + }, "YouTube Music requires this third-party app to be installed: " : { "extractionState" : "stale", "localizations" : { @@ -27922,4 +27926,4 @@ } }, "version" : "1.1" -} +} \ No newline at end of file From db2ca2eed402e028167ddd2b0299b04db6472e99 Mon Sep 17 00:00:00 2001 From: Alexander <33609792+Alexander5015@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:21:39 -0600 Subject: [PATCH 3/5] Simplify Now Playing reliability flow --- boringNotch/ContentView.swift | 5 +- boringNotch/Localizable.xcstrings | 68 +--- .../MediaControllerProtocol.swift | 3 +- .../NowPlayingController.swift | 78 ++-- .../NowPlayingStreamSupport.swift | 250 +++--------- .../MusicControllerSelectionView.swift | 24 +- .../Settings/Views/MediaSettingsView.swift | 15 +- boringNotch/helpers/MediaChecker.swift | 81 +--- boringNotch/managers/MusicManager.swift | 383 ++++++------------ 9 files changed, 240 insertions(+), 667 deletions(-) diff --git a/boringNotch/ContentView.swift b/boringNotch/ContentView.swift index a2dbb3203..8d0e75b8e 100644 --- a/boringNotch/ContentView.swift +++ b/boringNotch/ContentView.swift @@ -43,6 +43,7 @@ struct ContentView: View { private let extendedHoverPadding: CGFloat = 30 private let zeroHeightHoverPadding: CGFloat = 10 + private let nowPlayingFallbackNoticeWidth: CGFloat = 330 // MARK: - Corner Radius Scaling private var cornerRadiusScaleFactor: CGFloat? { @@ -89,7 +90,7 @@ struct ContentView: View { var chinWidth: CGFloat = vm.closedNotchSize.width if shouldDisplayNowPlayingFallbackNotice { - chinWidth = 330 + chinWidth = nowPlayingFallbackNoticeWidth } else if coordinator.expandingView.type == .battery && coordinator.expandingView.show && vm.notchState == .closed && Defaults[.showPowerStatusNotifications] { @@ -471,7 +472,7 @@ struct ContentView: View { } .padding(.horizontal, 14) .padding(.vertical, 9) - .frame(width: 330) + .frame(width: nowPlayingFallbackNoticeWidth) .frame(minHeight: 58) .accessibilityElement(children: .combine) .onAppear { diff --git a/boringNotch/Localizable.xcstrings b/boringNotch/Localizable.xcstrings index a599a6357..7a7855690 100644 --- a/boringNotch/Localizable.xcstrings +++ b/boringNotch/Localizable.xcstrings @@ -3129,17 +3129,6 @@ } } }, - "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." : { - "comment" : "Now Playing availability message when a bundled component is missing.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this." - } - } - } - }, "boring.notch" : { "localizations" : { "cs" : { @@ -12746,17 +12735,6 @@ } } }, - "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." : { - "comment" : "Now Playing availability message when the self-check receives no test data.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening." - } - } - } - }, "Made with 🫶🏻 by not so boring not.people" : { "localizations" : { "cs" : { @@ -16249,28 +16227,6 @@ } } }, - "Now Playing is not supported by this version of macOS. Choose another music source." : { - "comment" : "Now Playing availability message when required private macOS APIs are unavailable.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now Playing is not supported by this version of macOS. Choose another music source." - } - } - } - }, - "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." : { - "comment" : "Media settings message after the Now Playing runtime stream repeatedly stops.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing." - } - } - } - }, "Now Playing unavailable" : { "comment" : "Title of the passive notice shown when the Now Playing media source cannot be used.", "localizations" : { @@ -24535,17 +24491,6 @@ } } }, - "Switched to %@" : { - "comment" : "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Switched to %@" - } - } - } - }, "System" : { "localizations" : { "de" : { @@ -24876,17 +24821,6 @@ } } }, - "The Now Playing check took too long. Try again in a moment." : { - "comment" : "Now Playing availability message when the self-check times out.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The Now Playing check took too long. Try again in a moment." - } - } - } - }, "Time to Full Charge: %@" : { "localizations" : { "de" : { @@ -27926,4 +27860,4 @@ } }, "version" : "1.1" -} \ No newline at end of file +} diff --git a/boringNotch/MediaControllers/MediaControllerProtocol.swift b/boringNotch/MediaControllers/MediaControllerProtocol.swift index 83b4f2884..b7258419c 100644 --- a/boringNotch/MediaControllers/MediaControllerProtocol.swift +++ b/boringNotch/MediaControllers/MediaControllerProtocol.swift @@ -30,9 +30,8 @@ protocol MediaControllerProtocol: AnyObject { @MainActor protocol NowPlayingRuntimeControlling: MediaControllerProtocol { - var runtimeFailures: AsyncStream { get } + var runtimeFailures: AsyncStream { get } func startRuntimeStream() - func restartRuntimeStream() func stopRuntimeStream() } diff --git a/boringNotch/MediaControllers/NowPlayingController.swift b/boringNotch/MediaControllers/NowPlayingController.swift index 3835e3037..fca18afc0 100644 --- a/boringNotch/MediaControllers/NowPlayingController.swift +++ b/boringNotch/MediaControllers/NowPlayingController.swift @@ -56,9 +56,6 @@ final class NowPlayingController: NowPlayingRuntimeControlling { await updatePlaybackInfo() } - private var lastMusicItem: - (title: String, artist: String, album: String, duration: TimeInterval, artworkData: Data?)? - // MARK: - Media Remote Functions private let mediaRemoteBundle: CFBundle private let MRMediaRemoteSendCommandFunction: @convention(c) (Int, AnyObject?) -> Void @@ -68,14 +65,14 @@ final class NowPlayingController: NowPlayingRuntimeControlling { private let adapterScriptURL: URL private let adapterFrameworkPath: String - let runtimeFailures: AsyncStream - private let runtimeFailureContinuation: AsyncStream.Continuation + let runtimeFailures: AsyncStream + private let runtimeFailureContinuation: AsyncStream.Continuation private var streamSession: NowPlayingStreamSession? // MARK: - Initialization init() throws { - let resources = try NowPlayingResources.load(requiresTestClient: false) + let resources = try NowPlayingResources.load() guard let bundle = CFBundleCreate( @@ -90,7 +87,7 @@ final class NowPlayingController: NowPlayingRuntimeControlling { let MRMediaRemoteSetRepeatModePointer = CFBundleGetFunctionPointerForName( bundle, "MRMediaRemoteSetRepeatMode" as CFString) else { - throw NowPlayingSetupFailure.mediaRemoteUnavailable + throw NowPlayingError.unavailable } mediaRemoteBundle = bundle @@ -105,14 +102,17 @@ final class NowPlayingController: NowPlayingRuntimeControlling { adapterScriptURL = resources.adapterScriptURL adapterFrameworkPath = resources.adapterFrameworkPath - let runtimeFailureChannel = AsyncStream.makeStream( - of: NowPlayingRuntimeFailure.self - ) + let runtimeFailureChannel = AsyncStream.makeStream(of: Void.self) runtimeFailures = runtimeFailureChannel.stream runtimeFailureContinuation = runtimeFailureChannel.continuation } deinit { + if let streamSession { + Task { @MainActor in + streamSession.stop() + } + } runtimeFailureContinuation.finish() } @@ -197,21 +197,16 @@ final class NowPlayingController: NowPlayingRuntimeControlling { onUpdate: { [weak self] update in await self?.handleAdapterUpdate(update) }, - onFailure: { [weak self] failure in + onFailure: { [weak self] in guard let self else { return } self.streamSession = nil - self.runtimeFailureContinuation.yield(failure) + self.runtimeFailureContinuation.yield() } ) streamSession = session session.start() } - func restartRuntimeStream() { - stopRuntimeStream() - startRuntimeStream() - } - func stopRuntimeStream() { let session = streamSession streamSession = nil @@ -305,34 +300,29 @@ final class NowPlayingController: NowPlayingRuntimeControlling { self.playbackState = newPlaybackState - // Fetch favorite state for supported apps asynchronously - // await fetchFavoriteStateIfSupported() } - - private func fetchFavoriteStateIfSupported() async { - let bundleID = playbackState.bundleIdentifier - - if bundleID == "com.apple.Music" { - let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.Music") - guard !runningApps.isEmpty else { return } - - let script = """ - tell application "Music" - try - return favorited of current track - on error - return false - end try - end tell - """ - if let result = try? await AppleScriptHelper.execute(script) { - var updated = self.playbackState - updated.isFavorite = result.booleanValue - self.playbackState = updated - } - } - } - + + private func fetchFavoriteStateIfSupported() async { + guard playbackState.bundleIdentifier == "com.apple.Music" else { return } + + let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.Music") + guard !runningApps.isEmpty else { return } + + let script = """ + tell application "Music" + try + return favorited of current track + on error + return false + end try + end tell + """ + if let result = try? await AppleScriptHelper.execute(script) { + var updated = playbackState + updated.isFavorite = result.booleanValue + playbackState = updated + } + } } private extension NowPlayingController { diff --git a/boringNotch/MediaControllers/NowPlayingStreamSupport.swift b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift index 9292b7de0..67eecd53d 100644 --- a/boringNotch/MediaControllers/NowPlayingStreamSupport.swift +++ b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift @@ -1,37 +1,17 @@ import Foundation -enum ChildProcessTermination: Equatable, Sendable { - case exited(Int32) - case signaled(Int32) - case unknown(status: Int32) - - init(process: Process) { - switch process.terminationReason { - case .exit: - self = .exited(process.terminationStatus) - case .uncaughtSignal: - self = .signaled(process.terminationStatus) - @unknown default: - self = .unknown(status: process.terminationStatus) - } - } -} - -enum TimedProcessResult: Equatable, Sendable { - case terminated(ChildProcessTermination) - case timedOut -} - enum TimedProcessRunner { - static func run( + static func exitsSuccessfully( _ process: Process, timeout: Duration - ) async throws -> TimedProcessResult { + ) async throws -> Bool { try Task.checkCancellation() - let terminations = AsyncStream { continuation in - process.terminationHandler = { terminatedProcess in - continuation.yield(ChildProcessTermination(process: terminatedProcess)) + let terminations = AsyncStream { continuation in + process.terminationHandler = { process in + continuation.yield( + process.terminationReason == .exit && process.terminationStatus == 0 + ) continuation.finish() } } @@ -46,86 +26,68 @@ enum TimedProcessRunner { defer { process.terminationHandler = nil } return try await withTaskCancellationHandler { - let result = try await withThrowingTaskGroup(of: TimedProcessResult.self) { group in + let succeeded = await withTaskGroup(of: Bool.self) { group in group.addTask { - for await termination in terminations { - try Task.checkCancellation() - return .terminated(termination) + for await succeeded in terminations { + return succeeded } - throw CancellationError() + return false } group.addTask { - try await Task.sleep(for: timeout) - return .timedOut + try? await Task.sleep(for: timeout) + return false } - guard let firstResult = try await group.next() else { - throw CancellationError() - } + let result = await group.next() ?? false group.cancelAll() - return firstResult + return result } - if result == .timedOut { - forceStop(process) - } - return result + try Task.checkCancellation() + stop(process) + return succeeded } onCancel: { - forceStop(process) + stop(process) } } - static func forceStop(_ process: Process) { + static func stop(_ process: Process) { guard process.isRunning else { return } _ = kill(process.processIdentifier, SIGKILL) } } -enum NowPlayingSetupFailure: Error, Equatable, Sendable { - case missingAdapterScript - case missingAdapterFramework - case missingTestClient - case mediaRemoteUnavailable -} +enum NowPlayingError: Error, Sendable { case unavailable } struct NowPlayingResources: Sendable { let adapterScriptURL: URL let adapterFrameworkPath: String - let testClientURL: URL? + let testClientURL: URL - static func load( - from bundle: Bundle = .main, - requiresTestClient: Bool - ) throws -> Self { + static func load(from bundle: Bundle = .main) throws -> Self { let fileManager = FileManager.default guard let scriptURL = bundle.url( forResource: "mediaremote-adapter", withExtension: "pl" ), fileManager.isReadableFile(atPath: scriptURL.path) else { - throw NowPlayingSetupFailure.missingAdapterScript + throw NowPlayingError.unavailable } guard let privateFrameworksPath = bundle.privateFrameworksPath else { - throw NowPlayingSetupFailure.missingAdapterFramework + throw NowPlayingError.unavailable } let frameworkPath = privateFrameworksPath.appending("/MediaRemoteAdapter.framework") let frameworkExecutable = frameworkPath.appending("/MediaRemoteAdapter") guard fileManager.isExecutableFile(atPath: frameworkExecutable) else { - throw NowPlayingSetupFailure.missingAdapterFramework + throw NowPlayingError.unavailable } - let testClientURL: URL? - if requiresTestClient { - guard let url = bundle.url( - forResource: "MediaRemoteAdapterTestClient", - withExtension: nil - ), fileManager.isExecutableFile(atPath: url.path) else { - throw NowPlayingSetupFailure.missingTestClient - } - testClientURL = url - } else { - testClientURL = nil + guard let testClientURL = bundle.url( + forResource: "MediaRemoteAdapterTestClient", + withExtension: nil + ), fileManager.isExecutableFile(atPath: testClientURL.path) else { + throw NowPlayingError.unavailable } return Self( @@ -136,127 +98,24 @@ struct NowPlayingResources: Sendable { } } -enum NowPlayingProbeFailure: Equatable, Sendable { - case launchFailed - case timedOut - case helperPathMissing - case helperLaunchFailed - case helperSetupTimedOut - case noData - case wrapperFailed - case signaled(Int32) - case unexpectedExit(Int32) -} - enum NowPlayingAvailability: Equatable, Sendable { case unchecked + case checking case available - case setupFailure(NowPlayingSetupFailure) - case temporaryFailure(NowPlayingProbeFailure) - case runtimeStreamFailed - - var requiresFallback: Bool { - switch self { - case .unchecked, .available: - false - case .setupFailure, .temporaryFailure, .runtimeStreamFailed: - true - } - } - - var isRecoverableFailure: Bool { - switch self { - case .temporaryFailure, .runtimeStreamFailed: - true - case .unchecked, .available, .setupFailure: - false - } - } + case unavailable var isSelectable: Bool { self == .available } - var showsCheckAgainButton: Bool { isRecoverableFailure } - var settingsMessage: LocalizedStringResource? { switch self { - case .unchecked, .available: + case .unchecked, .checking, .available: nil - case .setupFailure(.mediaRemoteUnavailable): - LocalizedStringResource( - "Now Playing is not supported by this version of macOS. Choose another music source.", - comment: "Now Playing availability message when required private macOS APIs are unavailable." - ) - case .setupFailure: - LocalizedStringResource( - "Boring Notch is missing a required Now Playing component. Reinstalling the app should fix this.", - comment: "Now Playing availability message when a bundled component is missing." - ) - case .temporaryFailure(.timedOut): - LocalizedStringResource( - "The Now Playing check took too long. Try again in a moment.", - comment: "Now Playing availability message when the self-check times out." - ) - case .temporaryFailure(.noData): - LocalizedStringResource( - "macOS did not return data to the Now Playing self-check. Try again, or reopen Boring Notch if it keeps happening.", - comment: "Now Playing availability message when the self-check receives no test data." - ) - case .temporaryFailure: + case .unavailable: LocalizedStringResource( "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening.", comment: "Generic recoverable Now Playing availability message." ) - case .runtimeStreamFailed: - LocalizedStringResource( - "Now Playing stopped responding. Boring Notch switched music sources temporarily. Check again to restore Now Playing.", - comment: "Media settings message after the Now Playing runtime stream repeatedly stops." - ) - } - } - - var shouldRetry: Bool { - if case .temporaryFailure = self { return true } - return false - } -} - -protocol NowPlayingAvailabilityChecking: Sendable { - func checkAvailability(maxAttempts: Int) async throws -> NowPlayingAvailability -} - -enum NowPlayingRuntimeFailure: Equatable, Sendable { - case helperLaunchFailed - case helperExited(ChildProcessTermination) - case streamEnded - case streamReadFailed - case malformedOutput -} - -enum NowPlayingRuntimeRecoveryAction: Equatable, Sendable { - case restart(after: Duration) - case fallback -} - -struct NowPlayingRuntimeRecoveryPolicy: Sendable { - static let retryDelay: Duration = .milliseconds(500) - static let rapidFailureWindow: Duration = .seconds(60) - - private(set) var previousFailure: ContinuousClock.Instant? - - mutating func action(at instant: ContinuousClock.Instant) -> NowPlayingRuntimeRecoveryAction { - defer { previousFailure = instant } - - guard let previousFailure else { - return .restart(after: Self.retryDelay) } - - return previousFailure.duration(to: instant) < Self.rapidFailureWindow - ? .fallback - : .restart(after: Self.retryDelay) - } - - mutating func reset() { - previousFailure = nil } } @@ -275,13 +134,13 @@ actor JSONLinesPipeHandler { func readJSONLines( as type: Value.Type, onValue: @escaping @Sendable (Value) async -> Void - ) async -> NowPlayingRuntimeFailure { + ) async { var line = Data() var iterator = byteIterator do { while let byte = try await iterator.next() { - guard !Task.isCancelled else { return .streamEnded } + guard !Task.isCancelled else { return } guard byte == UInt8(ascii: "\n") else { line.append(byte) @@ -298,7 +157,7 @@ actor JSONLinesPipeHandler { consecutiveMalformedLines += 1 line.removeAll(keepingCapacity: true) if consecutiveMalformedLines >= 3 { - return .malformedOutput + return } continue } @@ -307,11 +166,7 @@ actor JSONLinesPipeHandler { line.removeAll(keepingCapacity: true) await onValue(decoded) } - - return .streamEnded - } catch { - return Task.isCancelled ? .streamEnded : .streamReadFailed - } + } catch {} } nonisolated func close() { @@ -325,17 +180,16 @@ final class NowPlayingStreamSession { private let process: Process private let reader: JSONLinesPipeHandler private let onUpdate: @MainActor (NowPlayingUpdate) async -> Void - private let onFailure: @MainActor (NowPlayingRuntimeFailure) -> Void + private let onFailure: @MainActor () -> Void private var readTask: Task? private var isStopped = false - private var didReportFailure = false init( process: Process, reader: JSONLinesPipeHandler = JSONLinesPipeHandler(), onUpdate: @escaping @MainActor (NowPlayingUpdate) async -> Void, - onFailure: @escaping @MainActor (NowPlayingRuntimeFailure) -> Void + onFailure: @escaping @MainActor () -> Void ) { self.process = process self.reader = reader @@ -347,10 +201,9 @@ final class NowPlayingStreamSession { guard readTask == nil, !isStopped else { return } process.standardOutput = reader.outputPipe - process.terminationHandler = { [weak self] terminatedProcess in - let termination = ChildProcessTermination(process: terminatedProcess) + process.terminationHandler = { [weak self] _ in Task { @MainActor [weak self] in - self?.finish(with: .helperExited(termination)) + self?.finish() } } @@ -358,16 +211,16 @@ final class NowPlayingStreamSession { try process.run() } catch { process.terminationHandler = nil - finish(with: .helperLaunchFailed) + finish() return } let session = self readTask = Task { @MainActor [reader, session] in - let failure = await reader.readJSONLines(as: NowPlayingUpdate.self) { update in + await reader.readJSONLines(as: NowPlayingUpdate.self) { update in await session.receive(update) } - session.finish(with: failure) + session.finish() } } @@ -381,11 +234,10 @@ final class NowPlayingStreamSession { await onUpdate(update) } - private func finish(with failure: NowPlayingRuntimeFailure) { - guard !isStopped, !didReportFailure else { return } - didReportFailure = true + private func finish() { + guard !isStopped else { return } cleanup() - onFailure(failure) + onFailure() } private func cleanup() { @@ -394,13 +246,13 @@ final class NowPlayingStreamSession { readTask?.cancel() readTask = nil reader.close() - TimedProcessRunner.forceStop(process) + TimedProcessRunner.stop(process) } deinit { process.terminationHandler = nil reader.close() - TimedProcessRunner.forceStop(process) + TimedProcessRunner.stop(process) } } diff --git a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift index 9598d9c88..1fb54bba5 100644 --- a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift +++ b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift @@ -31,10 +31,7 @@ struct MusicControllerSelectionView: View { VStack(spacing: 12) { ForEach(MediaControllerType.allCases) { controller in let isEnabled = controller != .nowPlaying - || ( - !musicManager.isCheckingNowPlayingAvailability - && musicManager.nowPlayingAvailability.isSelectable - ) + || isNowPlayingSelectionEnabled Button { selectedMediaController = controller @@ -84,15 +81,16 @@ struct MusicControllerSelectionView: View { private var canContinue: Bool { selectedMediaController != .nowPlaying - || ( - !musicManager.isCheckingNowPlayingAvailability - && musicManager.nowPlayingAvailability.isSelectable - ) + || isNowPlayingSelectionEnabled + } + + private var isNowPlayingSelectionEnabled: Bool { + musicManager.nowPlayingAvailability.isSelectable } @ViewBuilder private var nowPlayingAvailabilityStatus: some View { - if musicManager.isCheckingNowPlayingAvailability { + if musicManager.nowPlayingAvailability == .checking { Text("Checking Now Playing availability...") .font(.caption) .foregroundStyle(.secondary) @@ -103,12 +101,10 @@ struct MusicControllerSelectionView: View { .foregroundStyle(.secondary) .multilineTextAlignment(.center) - if musicManager.nowPlayingAvailability.showsCheckAgainButton { - Button("Check Again") { - musicManager.refreshNowPlayingAvailability() - } - .font(.caption) + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() } + .font(.caption) } .padding(.horizontal, 24) } diff --git a/boringNotch/components/Settings/Views/MediaSettingsView.swift b/boringNotch/components/Settings/Views/MediaSettingsView.swift index c6c85e355..53585baf9 100644 --- a/boringNotch/components/Settings/Views/MediaSettingsView.swift +++ b/boringNotch/components/Settings/Views/MediaSettingsView.swift @@ -27,10 +27,7 @@ struct Media: View { .tag(controller) .disabled( controller == .nowPlaying - && ( - musicManager.isCheckingNowPlayingAvailability - || !musicManager.nowPlayingAvailability.isSelectable - ) + && !musicManager.nowPlayingAvailability.isSelectable ) } } @@ -127,7 +124,7 @@ struct Media: View { private var mediaSourceFooter: some View { let availability = musicManager.nowPlayingAvailability - if musicManager.isCheckingNowPlayingAvailability { + if availability == .checking { footerText("Checking Now Playing availability...") } else if let message = availability.settingsMessage { VStack(alignment: .leading, spacing: 6) { @@ -144,12 +141,10 @@ struct Media: View { ) } - if availability.showsCheckAgainButton { - Button("Check Again") { - musicManager.refreshNowPlayingAvailability() - } - .font(.caption) + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() } + .font(.caption) } } else { footerText( diff --git a/boringNotch/helpers/MediaChecker.swift b/boringNotch/helpers/MediaChecker.swift index fd3aa7d25..d1144774d 100644 --- a/boringNotch/helpers/MediaChecker.swift +++ b/boringNotch/helpers/MediaChecker.swift @@ -7,85 +7,44 @@ import Foundation -struct MediaChecker: NowPlayingAvailabilityChecking { +struct MediaChecker: Sendable { func checkAvailability(maxAttempts: Int = 3) async throws -> NowPlayingAvailability { let attempts = max(1, maxAttempts) for attempt in 1...attempts { try Task.checkCancellation() - let availability = try await runAvailabilityCheck() - guard availability.shouldRetry, attempt < attempts else { - return availability + do { + if try await runAvailabilityCheck() { + return .available + } + } catch is CancellationError { + throw CancellationError() + } catch { + // A failed launch can be as transient as a failed probe, so retry it too. } - try await Task.sleep(for: .milliseconds(350 * attempt)) + if attempt < attempts { + try await Task.sleep(for: .milliseconds(350 * attempt)) + } } - return .unchecked + return .unavailable } - private func runAvailabilityCheck() async throws -> NowPlayingAvailability { - let resources: NowPlayingResources - do { - resources = try NowPlayingResources.load(requiresTestClient: true) - } catch let failure as NowPlayingSetupFailure { - return .setupFailure(failure) - } catch { - return .setupFailure(.missingAdapterScript) - } - - guard let testClientURL = resources.testClientURL else { - return .setupFailure(.missingTestClient) - } - + private func runAvailabilityCheck() async throws -> Bool { + let resources = try NowPlayingResources.load() let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") process.arguments = [ resources.adapterScriptURL.path, resources.adapterFrameworkPath, - testClientURL.path, + resources.testClientURL.path, "test", ] - let result: TimedProcessResult - do { - result = try await TimedProcessRunner.run(process, timeout: .seconds(10)) - } catch is CancellationError { - throw CancellationError() - } catch { - return .temporaryFailure(.launchFailed) - } - - switch result { - case .timedOut: - return .temporaryFailure(.timedOut) - case .terminated(let termination): - return Self.availability(for: termination) - } - } - - static func availability( - for termination: ChildProcessTermination - ) -> NowPlayingAvailability { - switch termination { - case .exited(0): - return .available - case .exited(1): - return .temporaryFailure(.helperPathMissing) - case .exited(2): - return .temporaryFailure(.helperLaunchFailed) - case .exited(3): - return .temporaryFailure(.helperSetupTimedOut) - case .exited(4): - return .temporaryFailure(.noData) - case .exited(64): - return .temporaryFailure(.wrapperFailed) - case .exited(let status): - return .temporaryFailure(.unexpectedExit(status)) - case .signaled(let signal): - return .temporaryFailure(.signaled(signal)) - case .unknown(let status): - return .temporaryFailure(.unexpectedExit(status)) - } + return try await TimedProcessRunner.exitsSuccessfully( + process, + timeout: .seconds(10) + ) } } diff --git a/boringNotch/managers/MusicManager.swift b/boringNotch/managers/MusicManager.swift index fd1676f5e..2b3c2d138 100644 --- a/boringNotch/managers/MusicManager.swift +++ b/boringNotch/managers/MusicManager.swift @@ -14,67 +14,9 @@ let defaultImage: NSImage = .init( accessibilityDescription: "Album Art" )! -@MainActor -protocol MediaControllerCreating { - func makeController(for type: MediaControllerType) throws -> any MediaControllerProtocol -} - -@MainActor -struct DefaultMediaControllerFactory: MediaControllerCreating { - func makeController(for type: MediaControllerType) throws -> any MediaControllerProtocol { - switch type { - case .nowPlaying: - return try NowPlayingController() - case .appleMusic: - return AppleMusicController() - case .spotify: - return SpotifyController() - case .youtubeMusic: - return YouTubeMusicController() - } - } -} - -@MainActor -protocol MediaControllerPreferenceStoring: AnyObject { - var preferredController: MediaControllerType { get set } - var didChooseController: Bool { get set } - var lastSupportedBundleIdentifier: String? { get set } - - func migrateIfNeeded() -} - -@MainActor -final class DefaultsMediaControllerPreferences: MediaControllerPreferenceStoring { - var preferredController: MediaControllerType { - get { Defaults[.mediaController] } - set { Defaults[.mediaController] = newValue } - } - - var didChooseController: Bool { - get { Defaults[.didChooseMediaController] } - set { Defaults[.didChooseMediaController] = newValue } - } - - var lastSupportedBundleIdentifier: String? { - get { Defaults[.lastSupportedNowPlayingBundleIdentifier] } - set { Defaults[.lastSupportedNowPlayingBundleIdentifier] = newValue } - } - - func migrateIfNeeded() { - guard !Defaults[.didMigrateMediaControllerChoice] else { return } - - let firstLaunch = UserDefaults.standard.object(forKey: "firstLaunch") as? Bool ?? true - let hasStoredController = UserDefaults.standard.object(forKey: "mediaController") != nil - didChooseController = didChooseController || hasStoredController || !firstLaunch - Defaults[.didMigrateMediaControllerChoice] = true - } -} - struct NowPlayingFallbackNotice: Identifiable, Equatable { let id = UUID() let fallbackSource: MediaControllerType - let isTemporary: Bool var title: LocalizedStringResource { LocalizedStringResource( @@ -84,17 +26,10 @@ struct NowPlayingFallbackNotice: Identifiable, Equatable { } var subtitle: LocalizedStringResource { - if isTemporary { - LocalizedStringResource( - "Using \(fallbackSource.localizedString) temporarily", - comment: "Temporary Now Playing fallback notice. The placeholder is the fallback music source name." - ) - } else { - LocalizedStringResource( - "Switched to \(fallbackSource.localizedString)", - comment: "Now Playing fallback notice for a persistent source change. The placeholder is the new music source name." - ) - } + LocalizedStringResource( + "Using \(fallbackSource.localizedString) temporarily", + comment: "Temporary Now Playing fallback notice. The placeholder is the fallback music source name." + ) } } @@ -102,26 +37,19 @@ struct NowPlayingFallbackNotice: Identifiable, Equatable { final class MusicManager: ObservableObject { // MARK: - Properties static let shared = MusicManager() + private static let noticeDuration: Duration = .seconds(6) + private var controllerCancellables = Set() private var debounceIdleTask: Task? private var availabilityTask: Task? private var runtimeFailureTask: Task? - private var runtimeRetryTask: Task? private var noticeDismissalTask: Task? - private var runtimeRecoveryPolicy = NowPlayingRuntimeRecoveryPolicy() - private var availabilityRequestID: UUID? private var isDestroyed = false private var didShowNoticeForCurrentFailure = false - private let preferences: any MediaControllerPreferenceStoring - private let availabilityChecker: any NowPlayingAvailabilityChecking - private let controllerFactory: any MediaControllerCreating - private let noticeDuration: Duration - // Helper to check if macOS can use NowPlayingController @Published private(set) var preferredMediaController: MediaControllerType @Published private(set) var nowPlayingAvailability: NowPlayingAvailability = .unchecked - @Published private(set) var isCheckingNowPlayingAvailability = false @Published private(set) var effectiveMediaController: MediaControllerType? @Published private(set) var nowPlayingNotice: NowPlayingFallbackNotice? @@ -173,27 +101,9 @@ final class MusicManager: ObservableObject { private var transitionWorkItem: DispatchWorkItem? // MARK: - Initialization - convenience init() { - self.init( - preferences: DefaultsMediaControllerPreferences(), - availabilityChecker: MediaChecker(), - controllerFactory: DefaultMediaControllerFactory() - ) - } - - init( - preferences: any MediaControllerPreferenceStoring, - availabilityChecker: any NowPlayingAvailabilityChecking, - controllerFactory: any MediaControllerCreating, - noticeDuration: Duration = .seconds(6) - ) { - self.preferences = preferences - self.availabilityChecker = availabilityChecker - self.controllerFactory = controllerFactory - self.noticeDuration = noticeDuration - - preferences.migrateIfNeeded() - preferredMediaController = preferences.preferredController + init() { + Self.migrateMediaControllerPreferenceIfNeeded() + preferredMediaController = Defaults[.mediaController] if preferredMediaController == .nowPlaying { activateFallback(showNotice: false) @@ -203,11 +113,21 @@ final class MusicManager: ObservableObject { } } + private static func migrateMediaControllerPreferenceIfNeeded() { + guard !Defaults[.didMigrateMediaControllerChoice] else { return } + + let firstLaunch = UserDefaults.standard.object(forKey: "firstLaunch") as? Bool ?? true + let hasStoredController = UserDefaults.standard.object(forKey: "mediaController") != nil + Defaults[.didChooseMediaController] = Defaults[.didChooseMediaController] + || hasStoredController + || !firstLaunch + Defaults[.didMigrateMediaControllerChoice] = true + } + deinit { debounceIdleTask?.cancel() availabilityTask?.cancel() runtimeFailureTask?.cancel() - runtimeRetryTask?.cancel() noticeDismissalTask?.cancel() controllerCancellables.removeAll() flipWorkItem?.cancel() @@ -217,18 +137,14 @@ final class MusicManager: ObservableObject { func destroy() { guard !isDestroyed else { return } isDestroyed = true - availabilityRequestID = nil debounceIdleTask?.cancel() availabilityTask?.cancel() runtimeFailureTask?.cancel() - runtimeRetryTask?.cancel() noticeDismissalTask?.cancel() availabilityTask = nil runtimeFailureTask = nil - runtimeRetryTask = nil noticeDismissalTask = nil - isCheckingNowPlayingAvailability = false controllerCancellables.removeAll() flipWorkItem?.cancel() transitionWorkItem?.cancel() @@ -241,15 +157,11 @@ final class MusicManager: ObservableObject { func selectMediaController(_ type: MediaControllerType) { guard !isDestroyed else { return } - if case .setupFailure = nowPlayingAvailability, type == .nowPlaying { - return - } - preferences.preferredController = type - preferences.didChooseController = true + Defaults[.mediaController] = type + Defaults[.didChooseMediaController] = true preferredMediaController = type - runtimeRecoveryPolicy.reset() - clearNotice(resetEpisode: true) + clearNotice() if type == .nowPlaying { if nowPlayingAvailability == .available { @@ -264,7 +176,7 @@ final class MusicManager: ObservableObject { } private func resolvedNowPlayingFallback() -> MediaControllerType { - guard let bundleIdentifier = preferences.lastSupportedBundleIdentifier, + guard let bundleIdentifier = Defaults[.lastSupportedNowPlayingBundleIdentifier], let controller = MediaControllerType(nowPlayingBundleIdentifier: bundleIdentifier) else { return .appleMusic @@ -285,93 +197,47 @@ final class MusicManager: ObservableObject { private func startAvailabilityCheck() { guard !isDestroyed, availabilityTask == nil else { return } - let requestID = UUID() - let checker = availabilityChecker - availabilityRequestID = requestID - isCheckingNowPlayingAvailability = true - - availabilityTask = Task { @MainActor [weak self, checker] in + nowPlayingAvailability = .checking + availabilityTask = Task { @MainActor [weak self] in + let availability: NowPlayingAvailability do { - let availability = try await checker.checkAvailability(maxAttempts: 3) - self?.finishAvailabilityCheck(availability, requestID: requestID) + availability = try await MediaChecker().checkAvailability(maxAttempts: 3) } catch is CancellationError { - self?.finishCancelledAvailabilityCheck(requestID: requestID) + return } catch { - self?.finishAvailabilityCheck( - .temporaryFailure(.launchFailed), - requestID: requestID - ) + availability = .unavailable } - } - } - private func finishCancelledAvailabilityCheck(requestID: UUID) { - guard availabilityRequestID == requestID else { return } - availabilityTask = nil - availabilityRequestID = nil - isCheckingNowPlayingAvailability = false - } + guard let self, !self.isDestroyed else { return } + self.availabilityTask = nil + self.nowPlayingAvailability = availability - private func finishAvailabilityCheck( - _ availability: NowPlayingAvailability, - requestID: UUID - ) { - guard !isDestroyed, availabilityRequestID == requestID else { return } - - availabilityTask = nil - availabilityRequestID = nil - isCheckingNowPlayingAvailability = false - nowPlayingAvailability = availability - - if availability == .available { - runtimeRecoveryPolicy.reset() - clearNotice(resetEpisode: true) - if preferredMediaController == .nowPlaying { - activateControllerIfNeeded(.nowPlaying) + if availability == .available { + self.clearNotice() + if self.preferredMediaController == .nowPlaying { + self.activateControllerIfNeeded(.nowPlaying) + } + } else if self.preferredMediaController == .nowPlaying { + self.activateFallback(showNotice: Defaults[.didChooseMediaController]) } - } else if availability.requiresFallback, - preferredMediaController == .nowPlaying { - activateFallback( - for: availability, - showNotice: preferences.didChooseController - ) } } - private func activateFallback( - for availability: NowPlayingAvailability? = nil, - showNotice: Bool - ) { + private func activateFallback(showNotice: Bool) { let fallbackController = resolvedNowPlayingFallback() - let isTemporary = availability?.isRecoverableFailure ?? true - if let availability, availability.requiresFallback, !isTemporary { - preferences.preferredController = fallbackController - preferredMediaController = fallbackController - } - - if showNotice, availability != nil { - requestNotice( - fallbackController: fallbackController, - isTemporary: isTemporary - ) + if showNotice { + requestNotice(fallbackController: fallbackController) } activateControllerIfNeeded(fallbackController) } - private func requestNotice( - fallbackController: MediaControllerType, - isTemporary: Bool - ) { + private func requestNotice(fallbackController: MediaControllerType) { guard !didShowNoticeForCurrentFailure else { return } didShowNoticeForCurrentFailure = true - let notice = NowPlayingFallbackNotice( - fallbackSource: fallbackController, - isTemporary: isTemporary - ) - nowPlayingNotice = notice + nowPlayingNotice = NowPlayingFallbackNotice(fallbackSource: fallbackController) } @discardableResult @@ -385,7 +251,7 @@ final class MusicManager: ObservableObject { noticeDismissalTask = Task { @MainActor [weak self] in guard let self else { return } do { - try await Task.sleep(for: self.noticeDuration) + try await Task.sleep(for: Self.noticeDuration) } catch { return } @@ -396,13 +262,11 @@ final class MusicManager: ObservableObject { return true } - private func clearNotice(resetEpisode: Bool) { + private func clearNotice() { noticeDismissalTask?.cancel() noticeDismissalTask = nil nowPlayingNotice = nil - if resetEpisode { - didShowNoticeForCurrentFailure = false - } + didShowNoticeForCurrentFailure = false } private func activateControllerIfNeeded(_ type: MediaControllerType) { @@ -413,23 +277,32 @@ final class MusicManager: ObservableObject { } do { - let controller = try controllerFactory.makeController(for: type) + let controller = try makeController(for: type) activateController(controller, type: type) - } catch let failure as NowPlayingSetupFailure where type == .nowPlaying { - let availability = NowPlayingAvailability.setupFailure(failure) - nowPlayingAvailability = availability - activateFallback( - for: availability, - showNotice: preferences.didChooseController - ) } catch { - guard type != .appleMusic else { return } - do { - let controller = try controllerFactory.makeController(for: .appleMusic) - activateController(controller, type: .appleMusic) - } catch { + if type == .nowPlaying { + nowPlayingAvailability = .unavailable + activateFallback(showNotice: Defaults[.didChooseMediaController]) return } + + guard type != .appleMusic else { return } + activateController(AppleMusicController(), type: .appleMusic) + } + } + + private func makeController( + for type: MediaControllerType + ) throws -> any MediaControllerProtocol { + switch type { + case .nowPlaying: + try NowPlayingController() + case .appleMusic: + AppleMusicController() + case .spotify: + SpotifyController() + case .youtubeMusic: + YouTubeMusicController() } } @@ -437,17 +310,21 @@ final class MusicManager: ObservableObject { _ controller: any MediaControllerProtocol, type: MediaControllerType ) { + let isReplacingController = activeController != nil + runtimeFailureTask?.cancel() - runtimeRetryTask?.cancel() runtimeFailureTask = nil - runtimeRetryTask = nil (activeController as? any NowPlayingRuntimeControlling)?.stopRuntimeStream() controllerCancellables.removeAll() flipWorkItem?.cancel() + if isReplacingController { + resetPublishedPlaybackState() + } activeController = controller effectiveMediaController = type canFavoriteTrack = controller.supportsFavorite + volumeControlSupported = controller.supportsVolumeControl controller.playbackStatePublisher .receive(on: DispatchQueue.main) @@ -464,9 +341,9 @@ final class MusicManager: ObservableObject { if let runtimeController = controller as? any NowPlayingRuntimeControlling { runtimeFailureTask = Task { @MainActor [weak self, runtimeController] in - for await failure in runtimeController.runtimeFailures { + for await _ in runtimeController.runtimeFailures { guard let self else { return } - self.handleRuntimeFailure(failure, from: runtimeController) + self.handleRuntimeFailure(from: runtimeController) } } } @@ -475,10 +352,38 @@ final class MusicManager: ObservableObject { (controller as? any NowPlayingRuntimeControlling)?.startRuntimeStream() } - private func handleRuntimeFailure( - _ failure: NowPlayingRuntimeFailure, - from controller: any NowPlayingRuntimeControlling - ) { + private func resetPublishedPlaybackState() { + debounceIdleTask?.cancel() + debounceIdleTask = nil + + songTitle = "" + artistName = "" + album = "" + albumArt = defaultImage + isPlaying = false + isPlayerIdle = true + avgColor = .white + bundleIdentifier = nil + audioCaptureBundleIdentifiers = [] + songDuration = 0 + elapsedTime = 0 + timestampDate = Date() + playbackRate = 1 + isShuffled = false + repeatMode = .off + volume = 0.5 + usingAppIconForArtwork = false + isFavoriteTrack = false + + artworkData = nil + lastArtworkTitle = "" + lastArtworkArtist = "" + lastArtworkAlbum = "" + lastArtworkBundleIdentifier = nil + lyricsService.clearLyrics() + } + + private func handleRuntimeFailure(from controller: any NowPlayingRuntimeControlling) { guard activeController === controller, effectiveMediaController == .nowPlaying, preferredMediaController == .nowPlaying @@ -486,40 +391,9 @@ final class MusicManager: ObservableObject { return } - print("Now Playing runtime stream failed: \(failure)") - - switch runtimeRecoveryPolicy.action(at: .now) { - case .restart(let delay): - runtimeRetryTask?.cancel() - runtimeRetryTask = Task { @MainActor [weak self, controller] in - do { - try await Task.sleep(for: delay) - } catch { - return - } - - guard let self, - !self.isDestroyed, - self.preferredMediaController == .nowPlaying, - self.effectiveMediaController == .nowPlaying, - self.activeController === controller - else { - return - } - - self.runtimeRetryTask = nil - controller.restartRuntimeStream() - } - - case .fallback: - runtimeRetryTask?.cancel() - runtimeRetryTask = nil - nowPlayingAvailability = .runtimeStreamFailed - activateFallback( - for: .runtimeStreamFailed, - showNotice: preferences.didChooseController - ) - } + NSLog("Now Playing runtime stream failed; switching to fallback") + nowPlayingAvailability = .unavailable + activateFallback(showNotice: Defaults[.didChooseMediaController]) } // MARK: - Update Methods @@ -528,8 +402,8 @@ final class MusicManager: ObservableObject { if effectiveMediaController == .nowPlaying, MediaControllerType(nowPlayingBundleIdentifier: state.bundleIdentifier) != nil, - preferences.lastSupportedBundleIdentifier != state.bundleIdentifier { - preferences.lastSupportedBundleIdentifier = state.bundleIdentifier + Defaults[.lastSupportedNowPlayingBundleIdentifier] != state.bundleIdentifier { + Defaults[.lastSupportedNowPlayingBundleIdentifier] = state.bundleIdentifier } // Check for playback state changes (playing/paused) @@ -656,33 +530,6 @@ final class MusicManager: ObservableObject { setFavorite(!isFavoriteTrack) } - @MainActor - private func toggleAppleMusicFavorite() async { - let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.Music") - guard !runningApps.isEmpty else { return } - - let script = """ - tell application \"Music\" - if it is running then - try - set loved of current track to (not loved of current track) - return loved of current track - on error - return false - end try - else - return false - end if - end tell - """ - - if let result = try? await AppleScriptHelper.execute(script) { - let loved = result.booleanValue - self.isFavoriteTrack = loved - self.forceUpdate() - } - } - func setFavorite(_ favorite: Bool) { guard canFavoriteTrack else { return } guard let controller = activeController else { return } From b60a8a544ffc9be4f475e2c5ea2086c374a08c39 Mon Sep 17 00:00:00 2001 From: Alexander <33609792+Alexander5015@users.noreply.github.com> Date: Sat, 29 Aug 2026 11:15:05 -0600 Subject: [PATCH 4/5] Prevent stale artwork after controller changes --- boringNotch/managers/MusicManager.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/boringNotch/managers/MusicManager.swift b/boringNotch/managers/MusicManager.swift index 2b3c2d138..d6d88eae3 100644 --- a/boringNotch/managers/MusicManager.swift +++ b/boringNotch/managers/MusicManager.swift @@ -582,8 +582,9 @@ final class MusicManager: ObservableObject { if let artworkImage = NSImage(data: artworkData) { DispatchQueue.main.async { [weak self] in - self?.usingAppIconForArtwork = false - self?.updateAlbumArt(newAlbumArt: artworkImage) + guard let self, self.artworkData == artworkData else { return } + self.usingAppIconForArtwork = false + self.updateAlbumArt(newAlbumArt: artworkImage) } } } From 8dca1b36d0786ad61688bbdea25d61721ca069da Mon Sep 17 00:00:00 2001 From: Alexander <33609792+Alexander5015@users.noreply.github.com> Date: Sat, 29 Aug 2026 11:20:35 -0600 Subject: [PATCH 5/5] Categorize Now Playing failures and recovery --- boringNotch/Localizable.xcstrings | 78 ++++++++++- .../NowPlayingStreamSupport.swift | 36 +++++- .../MusicControllerSelectionView.swift | 8 +- .../Settings/Views/MediaSettingsView.swift | 27 ++-- boringNotch/helpers/MediaChecker.swift | 14 +- boringNotch/managers/MusicManager.swift | 121 ++++++++++++++---- .../NowPlayingAvailabilityTests.swift | 39 ++++++ 7 files changed, 272 insertions(+), 51 deletions(-) create mode 100644 boringNotchTests/NowPlayingAvailabilityTests.swift diff --git a/boringNotch/Localizable.xcstrings b/boringNotch/Localizable.xcstrings index 7a7855690..ccafcaa23 100644 --- a/boringNotch/Localizable.xcstrings +++ b/boringNotch/Localizable.xcstrings @@ -3118,13 +3118,46 @@ } } }, - "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." : { - "comment" : "Generic recoverable Now Playing availability message.", + "Boring Notch could not verify Now Playing. Try again, or reopen the app if it keeps happening." : { + "comment" : "Recoverable Now Playing probe failure message.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening." + "value" : "Boring Notch could not verify Now Playing. Try again, or reopen the app if it keeps happening." + } + } + } + }, + "Boring Notch lost its Now Playing connection. Reconnecting automatically..." : { + "comment" : "Now Playing runtime failure message shown before an automatic recovery attempt.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch lost its Now Playing connection. Reconnecting automatically..." + } + } + } + }, + "Boring Notch's Now Playing components are unavailable. Reopen or reinstall the app." : { + "comment" : "Now Playing setup failure message shown when required bundled components cannot be used.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Boring Notch's Now Playing components are unavailable. Reopen or reinstall the app." + } + } + } + }, + "Could not verify Now Playing" : { + "comment" : "Title of the passive notice shown when the Now Playing availability probe fails.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Could not verify Now Playing" } } } @@ -16227,13 +16260,24 @@ } } }, - "Now Playing unavailable" : { - "comment" : "Title of the passive notice shown when the Now Playing media source cannot be used.", + "Now Playing components unavailable" : { + "comment" : "Title of the passive notice shown when required Now Playing components cannot be used.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Now Playing unavailable" + "value" : "Now Playing components unavailable" + } + } + } + }, + "Now Playing connection lost" : { + "comment" : "Title of the passive notice shown when an active Now Playing stream fails.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Now Playing connection lost" } } } @@ -26282,6 +26326,28 @@ } } }, + "Using %@ instead" : { + "comment" : "Now Playing setup failure notice. The placeholder is the fallback music source name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ instead" + } + } + } + }, + "Using %@ instead. Your Now Playing preference is preserved." : { + "comment" : "Media settings footer for a non-recoverable Now Playing setup failure. The placeholder is the active fallback source.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using %@ instead. Your Now Playing preference is preserved." + } + } + } + }, "Using %@ temporarily" : { "comment" : "Temporary Now Playing fallback notice. The placeholder is the fallback music source name.", "localizations" : { diff --git a/boringNotch/MediaControllers/NowPlayingStreamSupport.swift b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift index 67eecd53d..0b9e17382 100644 --- a/boringNotch/MediaControllers/NowPlayingStreamSupport.swift +++ b/boringNotch/MediaControllers/NowPlayingStreamSupport.swift @@ -98,22 +98,50 @@ struct NowPlayingResources: Sendable { } } +enum NowPlayingFailure: Equatable, Sendable { + case setup + case probe + case runtime +} + enum NowPlayingAvailability: Equatable, Sendable { case unchecked case checking case available - case unavailable + case unavailable(NowPlayingFailure) var isSelectable: Bool { self == .available } + var failure: NowPlayingFailure? { + guard case let .unavailable(failure) = self else { return nil } + return failure + } + + var offersManualRetry: Bool { failure == .probe } + + var usesTemporaryFallback: Bool { + guard let failure else { return false } + return failure != .setup + } + var settingsMessage: LocalizedStringResource? { switch self { case .unchecked, .checking, .available: nil - case .unavailable: + case .unavailable(.setup): + LocalizedStringResource( + "Boring Notch's Now Playing components are unavailable. Reopen or reinstall the app.", + comment: "Now Playing setup failure message shown when required bundled components cannot be used." + ) + case .unavailable(.probe): + LocalizedStringResource( + "Boring Notch could not verify Now Playing. Try again, or reopen the app if it keeps happening.", + comment: "Recoverable Now Playing probe failure message." + ) + case .unavailable(.runtime): LocalizedStringResource( - "Boring Notch could not check Now Playing. Try again, or reopen the app if it keeps happening.", - comment: "Generic recoverable Now Playing availability message." + "Boring Notch lost its Now Playing connection. Reconnecting automatically...", + comment: "Now Playing runtime failure message shown before an automatic recovery attempt." ) } } diff --git a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift index 1fb54bba5..29dd15e92 100644 --- a/boringNotch/components/Onboarding/MusicControllerSelectionView.swift +++ b/boringNotch/components/Onboarding/MusicControllerSelectionView.swift @@ -101,10 +101,12 @@ struct MusicControllerSelectionView: View { .foregroundStyle(.secondary) .multilineTextAlignment(.center) - Button("Check Again") { - musicManager.refreshNowPlayingAvailability() + if musicManager.nowPlayingAvailability.offersManualRetry { + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() + } + .font(.caption) } - .font(.caption) } .padding(.horizontal, 24) } diff --git a/boringNotch/components/Settings/Views/MediaSettingsView.swift b/boringNotch/components/Settings/Views/MediaSettingsView.swift index 53585baf9..3ba4674c7 100644 --- a/boringNotch/components/Settings/Views/MediaSettingsView.swift +++ b/boringNotch/components/Settings/Views/MediaSettingsView.swift @@ -133,18 +133,29 @@ struct Media: View { if musicManager.preferredMediaController == .nowPlaying, let effectiveController = musicManager.effectiveMediaController, effectiveController != .nowPlaying { - footerText( - LocalizedStringResource( - "Using \(effectiveController.localizedString) temporarily. Your Now Playing preference is preserved.", - comment: "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source." + if availability.usesTemporaryFallback { + footerText( + LocalizedStringResource( + "Using \(effectiveController.localizedString) temporarily. Your Now Playing preference is preserved.", + comment: "Media settings footer for a temporary Now Playing fallback. The placeholder is the active fallback source." + ) + ) + } else { + footerText( + LocalizedStringResource( + "Using \(effectiveController.localizedString) instead. Your Now Playing preference is preserved.", + comment: "Media settings footer for a non-recoverable Now Playing setup failure. The placeholder is the active fallback source." + ) ) - ) + } } - Button("Check Again") { - musicManager.refreshNowPlayingAvailability() + if availability.offersManualRetry { + Button("Check Again") { + musicManager.refreshNowPlayingAvailability() + } + .font(.caption) } - .font(.caption) } } else { footerText( diff --git a/boringNotch/helpers/MediaChecker.swift b/boringNotch/helpers/MediaChecker.swift index d1144774d..ae8d644b8 100644 --- a/boringNotch/helpers/MediaChecker.swift +++ b/boringNotch/helpers/MediaChecker.swift @@ -10,11 +10,18 @@ import Foundation struct MediaChecker: Sendable { func checkAvailability(maxAttempts: Int = 3) async throws -> NowPlayingAvailability { let attempts = max(1, maxAttempts) + let resources: NowPlayingResources + + do { + resources = try NowPlayingResources.load() + } catch { + return .unavailable(.setup) + } for attempt in 1...attempts { try Task.checkCancellation() do { - if try await runAvailabilityCheck() { + if try await runAvailabilityCheck(resources: resources) { return .available } } catch is CancellationError { @@ -28,11 +35,10 @@ struct MediaChecker: Sendable { } } - return .unavailable + return .unavailable(.probe) } - private func runAvailabilityCheck() async throws -> Bool { - let resources = try NowPlayingResources.load() + private func runAvailabilityCheck(resources: NowPlayingResources) async throws -> Bool { let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") process.arguments = [ diff --git a/boringNotch/managers/MusicManager.swift b/boringNotch/managers/MusicManager.swift index d6d88eae3..e32446267 100644 --- a/boringNotch/managers/MusicManager.swift +++ b/boringNotch/managers/MusicManager.swift @@ -17,19 +17,40 @@ let defaultImage: NSImage = .init( struct NowPlayingFallbackNotice: Identifiable, Equatable { let id = UUID() let fallbackSource: MediaControllerType + let failure: NowPlayingFailure var title: LocalizedStringResource { - LocalizedStringResource( - "Now Playing unavailable", - comment: "Title of the passive notice shown when the Now Playing media source cannot be used." - ) + switch failure { + case .setup: + LocalizedStringResource( + "Now Playing components unavailable", + comment: "Title of the passive notice shown when required Now Playing components cannot be used." + ) + case .probe: + LocalizedStringResource( + "Could not verify Now Playing", + comment: "Title of the passive notice shown when the Now Playing availability probe fails." + ) + case .runtime: + LocalizedStringResource( + "Now Playing connection lost", + comment: "Title of the passive notice shown when an active Now Playing stream fails." + ) + } } var subtitle: LocalizedStringResource { - LocalizedStringResource( - "Using \(fallbackSource.localizedString) temporarily", - comment: "Temporary Now Playing fallback notice. The placeholder is the fallback music source name." - ) + if failure == .setup { + LocalizedStringResource( + "Using \(fallbackSource.localizedString) instead", + comment: "Now Playing setup failure notice. The placeholder is the fallback music source name." + ) + } else { + LocalizedStringResource( + "Using \(fallbackSource.localizedString) temporarily", + comment: "Temporary Now Playing fallback notice. The placeholder is the fallback music source name." + ) + } } } @@ -38,14 +59,16 @@ final class MusicManager: ObservableObject { // MARK: - Properties static let shared = MusicManager() private static let noticeDuration: Duration = .seconds(6) + private static let runtimeRecoveryDelay: Duration = .seconds(1) private var controllerCancellables = Set() private var debounceIdleTask: Task? private var availabilityTask: Task? private var runtimeFailureTask: Task? + private var runtimeRecoveryTask: Task? private var noticeDismissalTask: Task? private var isDestroyed = false - private var didShowNoticeForCurrentFailure = false + private var lastNoticedFailure: NowPlayingFailure? // Helper to check if macOS can use NowPlayingController @Published private(set) var preferredMediaController: MediaControllerType @@ -106,7 +129,7 @@ final class MusicManager: ObservableObject { preferredMediaController = Defaults[.mediaController] if preferredMediaController == .nowPlaying { - activateFallback(showNotice: false) + activateFallback() ensureNowPlayingAvailabilityChecked() } else { activateControllerIfNeeded(preferredMediaController) @@ -128,6 +151,7 @@ final class MusicManager: ObservableObject { debounceIdleTask?.cancel() availabilityTask?.cancel() runtimeFailureTask?.cancel() + runtimeRecoveryTask?.cancel() noticeDismissalTask?.cancel() controllerCancellables.removeAll() flipWorkItem?.cancel() @@ -141,9 +165,11 @@ final class MusicManager: ObservableObject { debounceIdleTask?.cancel() availabilityTask?.cancel() runtimeFailureTask?.cancel() + runtimeRecoveryTask?.cancel() noticeDismissalTask?.cancel() availabilityTask = nil runtimeFailureTask = nil + runtimeRecoveryTask = nil noticeDismissalTask = nil controllerCancellables.removeAll() flipWorkItem?.cancel() @@ -161,13 +187,14 @@ final class MusicManager: ObservableObject { Defaults[.mediaController] = type Defaults[.didChooseMediaController] = true preferredMediaController = type + cancelRuntimeRecovery() clearNotice() if type == .nowPlaying { if nowPlayingAvailability == .available { activateControllerIfNeeded(.nowPlaying) } else { - activateFallback(showNotice: false) + activateFallback() refreshNowPlayingAvailability() } } else { @@ -191,6 +218,7 @@ final class MusicManager: ObservableObject { } func refreshNowPlayingAvailability() { + cancelRuntimeRecovery() startAvailabilityCheck() } @@ -205,7 +233,7 @@ final class MusicManager: ObservableObject { } catch is CancellationError { return } catch { - availability = .unavailable + availability = .unavailable(.probe) } guard let self, !self.isDestroyed else { return } @@ -217,27 +245,37 @@ final class MusicManager: ObservableObject { if self.preferredMediaController == .nowPlaying { self.activateControllerIfNeeded(.nowPlaying) } - } else if self.preferredMediaController == .nowPlaying { - self.activateFallback(showNotice: Defaults[.didChooseMediaController]) + } else if self.preferredMediaController == .nowPlaying, + let failure = availability.failure { + let noticeFailure = Defaults[.didChooseMediaController] ? failure : nil + self.activateFallback(noticeFailure: noticeFailure) } } } - private func activateFallback(showNotice: Bool) { + private func activateFallback(noticeFailure: NowPlayingFailure? = nil) { let fallbackController = resolvedNowPlayingFallback() - if showNotice { - requestNotice(fallbackController: fallbackController) + if let noticeFailure { + requestNotice(fallbackController: fallbackController, failure: noticeFailure) } activateControllerIfNeeded(fallbackController) } - private func requestNotice(fallbackController: MediaControllerType) { - guard !didShowNoticeForCurrentFailure else { return } - didShowNoticeForCurrentFailure = true + private func requestNotice( + fallbackController: MediaControllerType, + failure: NowPlayingFailure + ) { + guard lastNoticedFailure != failure else { return } + lastNoticedFailure = failure - nowPlayingNotice = NowPlayingFallbackNotice(fallbackSource: fallbackController) + noticeDismissalTask?.cancel() + noticeDismissalTask = nil + nowPlayingNotice = NowPlayingFallbackNotice( + fallbackSource: fallbackController, + failure: failure + ) } @discardableResult @@ -266,7 +304,7 @@ final class MusicManager: ObservableObject { noticeDismissalTask?.cancel() noticeDismissalTask = nil nowPlayingNotice = nil - didShowNoticeForCurrentFailure = false + lastNoticedFailure = nil } private func activateControllerIfNeeded(_ type: MediaControllerType) { @@ -281,8 +319,10 @@ final class MusicManager: ObservableObject { activateController(controller, type: type) } catch { if type == .nowPlaying { - nowPlayingAvailability = .unavailable - activateFallback(showNotice: Defaults[.didChooseMediaController]) + let failure = NowPlayingFailure.setup + nowPlayingAvailability = .unavailable(failure) + let noticeFailure = Defaults[.didChooseMediaController] ? failure : nil + activateFallback(noticeFailure: noticeFailure) return } @@ -392,8 +432,37 @@ final class MusicManager: ObservableObject { } NSLog("Now Playing runtime stream failed; switching to fallback") - nowPlayingAvailability = .unavailable - activateFallback(showNotice: Defaults[.didChooseMediaController]) + let failure = NowPlayingFailure.runtime + nowPlayingAvailability = .unavailable(failure) + let noticeFailure = Defaults[.didChooseMediaController] ? failure : nil + activateFallback(noticeFailure: noticeFailure) + scheduleRuntimeRecovery() + } + + private func scheduleRuntimeRecovery() { + cancelRuntimeRecovery() + runtimeRecoveryTask = Task { @MainActor [weak self] in + do { + try await Task.sleep(for: Self.runtimeRecoveryDelay) + } catch { + return + } + + guard let self, + !self.isDestroyed, + self.preferredMediaController == .nowPlaying + else { + return + } + + self.runtimeRecoveryTask = nil + self.startAvailabilityCheck() + } + } + + private func cancelRuntimeRecovery() { + runtimeRecoveryTask?.cancel() + runtimeRecoveryTask = nil } // MARK: - Update Methods diff --git a/boringNotchTests/NowPlayingAvailabilityTests.swift b/boringNotchTests/NowPlayingAvailabilityTests.swift new file mode 100644 index 000000000..1af665244 --- /dev/null +++ b/boringNotchTests/NowPlayingAvailabilityTests.swift @@ -0,0 +1,39 @@ +// +// NowPlayingAvailabilityTests.swift +// boringNotchTests +// + +import XCTest + +@testable import boringNotch + +final class NowPlayingAvailabilityTests: XCTestCase { + func testAvailableIsSelectable() { + XCTAssertTrue(NowPlayingAvailability.available.isSelectable) + XCTAssertFalse(NowPlayingAvailability.available.usesTemporaryFallback) + } + + func testSetupFailureIsNotRetriable() { + let availability = NowPlayingAvailability.unavailable(.setup) + + XCTAssertEqual(availability.failure, .setup) + XCTAssertFalse(availability.offersManualRetry) + XCTAssertFalse(availability.usesTemporaryFallback) + } + + func testProbeFailureOffersManualRetry() { + let availability = NowPlayingAvailability.unavailable(.probe) + + XCTAssertEqual(availability.failure, .probe) + XCTAssertTrue(availability.offersManualRetry) + XCTAssertTrue(availability.usesTemporaryFallback) + } + + func testRuntimeFailureDoesNotOfferManualRetry() { + let availability = NowPlayingAvailability.unavailable(.runtime) + + XCTAssertEqual(availability.failure, .runtime) + XCTAssertFalse(availability.offersManualRetry) + XCTAssertTrue(availability.usesTemporaryFallback) + } +}