From 5829b6d83586983de510f31356b330db82adb1e9 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sun, 5 Oct 2025 23:41:09 +0530 Subject: [PATCH] Still compiling - but NowPlaying detection is fucked --- .../NowPlayingController.swift | 233 +++++++++++++----- 1 file changed, 172 insertions(+), 61 deletions(-) diff --git a/DynamicIsland/MediaControllers/NowPlayingController.swift b/DynamicIsland/MediaControllers/NowPlayingController.swift index 69b754e..f357908 100644 --- a/DynamicIsland/MediaControllers/NowPlayingController.swift +++ b/DynamicIsland/MediaControllers/NowPlayingController.swift @@ -11,20 +11,19 @@ import Foundation final class NowPlayingController: ObservableObject, MediaControllerProtocol { // Stub for now to conform with ControllerProtocol - func updatePlaybackInfo() {} + func updatePlaybackInfo() async {} // MARK: - Properties @Published private(set) var playbackState: PlaybackState = .init( bundleIdentifier: "com.apple.Music" ) - private var isResourcesAvailable = false var playbackStatePublisher: AnyPublisher { $playbackState.eraseToAnyPublisher() } var isWorking: Bool { - return isResourcesAvailable + return process != nil && process?.isRunning == true } private var lastMusicItem: (title: String, artist: String, album: String, duration: TimeInterval, artworkData: Data?)? @@ -37,8 +36,8 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { private let MRMediaRemoteSetRepeatModeFunction: @convention(c) (Int) -> Void private var process: Process? - private var pipe: Pipe? - private var buffer = "" + private var pipeHandler: JSONLinesPipeHandler? + private var streamTask: Task? // MARK: - Initialization init?() { @@ -67,11 +66,17 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { MRMediaRemoteSetRepeatModeFunction = unsafeBitCast( MRMediaRemoteSetRepeatModePointer, to: (@convention(c) (Int) -> Void).self) - setupNowPlayingObserver() - updatePlaybackInfo() + Task { await setupNowPlayingObserver() } } deinit { + streamTask?.cancel() + + if let pipeHandler = self.pipeHandler { + Task { await pipeHandler.close() + } + } + if let process = self.process { if process.isRunning { process.terminate() @@ -79,13 +84,8 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { } } - if let pipe = self.pipe { - pipe.fileHandleForReading.closeFile() - pipe.fileHandleForWriting.closeFile() - } - self.process = nil - self.pipe = nil + self.pipeHandler = nil } // MARK: - Protocol Implementation @@ -119,7 +119,7 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { func toggleShuffle() async { // MRMediaRemoteSendCommandFunction(6, nil) - MRMediaRemoteSetShuffleModeFunction(playbackState.isShuffled ? 3 : 1) + MRMediaRemoteSetShuffleModeFunction(playbackState.isShuffled ? 1 : 3) playbackState.isShuffled.toggle() } @@ -131,78 +131,86 @@ final class NowPlayingController: ObservableObject, MediaControllerProtocol { } // MARK: - Setup Methods - private func setupNowPlayingObserver() { + private func setupNowPlayingObserver() async { let process = Process() guard - let scriptURL = Bundle.main.url(forResource: "mediaremote-adapter", withExtension: "pl") + let scriptURL = Bundle.main.url(forResource: "mediaremote-adapter", withExtension: "pl"), + let frameworkPath = Bundle.main.privateFrameworksPath?.appending("/MediaRemoteAdapter.framework") else { - print("⚠️ Could not find mediaremote-adapter.pl script - NowPlaying controller disabled") - isResourcesAvailable = false + assertionFailure("Could not find mediaremote-adapter.pl script or framework path") return } - let bundlePath = Bundle.main.bundlePath - let frameworkPath = bundlePath + "/Contents/Frameworks/MediaRemoteAdapter.framework" - - isResourcesAvailable = true process.executableURL = URL(fileURLWithPath: "/usr/bin/perl") process.arguments = [scriptURL.path, frameworkPath, "stream"] - let pipe = Pipe() - process.standardOutput = pipe + + let pipeHandler = JSONLinesPipeHandler() + process.standardOutput = await pipeHandler.getPipe() + self.process = process - self.pipe = pipe - - pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in - let data = handle.availableData - guard !data.isEmpty else { return } - if let chunk = String(data: data, encoding: .utf8) { - self?.buffer.append(chunk) - while let range = self?.buffer.range(of: "\n") { - let line = String(self?.buffer[.. 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)") + } + } +}