CHHapticAdvancedPatternPlayer not working with GCController

Hello everyone,

I want send haptics to ps4 controller.

CHHapticPatternPlayer and CHHapticAdvancedPatternPlayer good work with iPhone.

On PS4 controller If I use CHHapticPatternPlayer all work good, but if I use CHHapticAdvancedPatternPlayer I get error. I want use CHHapticAdvancedPatternPlayer to use additional settings. I don't found any information how to fix it -

        CHHapticEngine.mm:624   -[CHHapticEngine finishInit:]_block_invoke: ERROR: Server connection broke with error 'Не удалось завершить операцию. (com.apple.CoreHaptics, ошибка -4811)'
The engine stopped because a system error occurred.
        AVHapticClient.mm:1228  -[AVHapticClient getSyncDelegateForMethod:errorHandler:]_block_invoke: ERROR: Sync XPC call for 'loadAndPrepareHapticSequenceFromEvents:reply:' (client ID 0x21) failed: Не удалось установить связь с приложением-помощником.
Не удалось создать или воспроизвести паттерн: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 5087 named com.apple.GameController.gamecontrollerd.haptics" UserInfo={NSDebugDescription=connection to service with pid 5087 named com.apple.GameController.gamecontrollerd.haptics}

My Haptic class -

import Foundation
import CoreHaptics
import GameController


protocol HapticsControllerDelegate: AnyObject {
    func didConnectController()
    func didDisconnectController()
    func enginePlayerStart(value: Bool)
}

final class HapticsControllerManager {
    
    static let shared = HapticsControllerManager()
    
    private var isSetup = false
    
    private var hapticEngine: CHHapticEngine?
    private var hapticPlayer: CHHapticAdvancedPatternPlayer?
    
    weak var delegate: HapticsControllerDelegate? {
        didSet {
            if delegate != nil {
                startObserving()
            }
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    private func startObserving() {
        
        guard !isSetup else { return }
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(controllerDidConnect),
            name: .GCControllerDidConnect,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(controllerDidDisconnect),
            name: .GCControllerDidDisconnect,
            object: nil
        )
        isSetup = true
    }
    
    @objc private func controllerDidConnect(notification: Notification) {
        delegate?.didConnectController()
        self.createAndStartHapticEngine()
    }
    
    @objc private func controllerDidDisconnect(notification: Notification) {
        delegate?.didDisconnectController()
        hapticEngine = nil
        hapticPlayer = nil
    }

    private func createAndStartHapticEngine() {

        guard let controller = GCController.controllers().first else {
            print("No controller connected")
            return
        }
        
        guard controller.haptics != nil else {
            print("Haptics not supported on this controller")
            return
        }
        
        hapticEngine = createEngine(for: controller, locality: .default)
        hapticEngine?.playsHapticsOnly = true
        
        do {
            try hapticEngine?.start()
        } catch {
            print("Не удалось запустить движок тактильной обратной связи: \(error)")
        }
    }
    
    private func createEngine(for controller: GCController, locality: GCHapticsLocality) -> CHHapticEngine? {
        
        guard let engine = controller.haptics?.createEngine(withLocality: locality) else {
            print("Failed to create engine.")
            return nil
        }
        
        print("Successfully created engine.")
        
        engine.stoppedHandler = { reason in
            print("The engine stopped because \(reason.message)")
        }
        
        engine.resetHandler = {
            print("The engine reset --> Restarting now!")
            do {
                try engine.start()
            } catch {
                print("Failed to restart the engine: \(error)")
            }
        }
        
        return engine
    }

    func startHapticFeedback(haptics: [CHHapticEvent]) {
      
        do {
            let pattern = try CHHapticPattern(events: haptics, parameters: [])
            hapticPlayer = try hapticEngine?.makeAdvancedPlayer(with: pattern)
            hapticPlayer?.loopEnabled = true
            
            try hapticPlayer?.start(atTime: 0)
            self.delegate?.enginePlayerStart(value: true)
        } catch {
            self.delegate?.enginePlayerStart(value: false)
            print("Не удалось создать или воспроизвести паттерн: \(error)")
        }
    }

    func stopHapticFeedback() {
        do {
            try hapticPlayer?.stop(atTime: 0)
            self.delegate?.enginePlayerStart(value: false)
        } catch {
            self.delegate?.enginePlayerStart(value: true)
            print("Не удалось остановить воспроизведение вибрации: \(error)")
        }
    }
}

extension CHHapticEngine.StoppedReason {
    
    var message: String {
        switch self {
        case .audioSessionInterrupt:
            return "the audio session was interrupted."
        case .applicationSuspended:
            return "the application was suspended."
        case .idleTimeout:
            return "an idle timeout occurred."
        case .systemError:
            return "a system error occurred."
        case .notifyWhenFinished:
            return "playback finished."
        case .engineDestroyed:
            return "the engine was destroyed."
        case .gameControllerDisconnect:
            return "the game controller disconnected."
        @unknown default:
            return "an unknown error occurred."
        }
    }
}

custom haptic events -

    static func changeVibrationPower(power: HapricPower) -> [CHHapticEvent] {
        let continuousEvent = CHHapticEvent(eventType: .hapticContinuous, parameters: [
            CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0),
            CHHapticEventParameter(parameterID: .hapticIntensity, value: power.value)
        ], relativeTime: 0, duration: 0.5)
        
        return [continuousEvent]
    }

On PS4 controller If I use CHHapticPatternPlayer all work good, but if I use CHHapticAdvancedPatternPlayer I get error. I want use CHHapticAdvancedPatternPlayer to use additional settings. I don't found any information how to fix it -

If you haven't already, please file a bug on this then post the the bug number back here. Once I've got the bug number, I'll see if I can determine what's going wrong.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

CHHapticAdvancedPatternPlayer not working with GCController
 
 
Q