@@ -3806,6 +3806,59 @@ object MG4Hardware {
38063806 private const val RUI_NEXT_UP = 11
38073807 private const val RUI_PREV_UP = 12
38083808
3809+ /* *
3810+ * S'assure que le helper audio A9 est joignable, et le récupère sinon.
3811+ *
3812+ * ⚠️ Sans ça, sa perte était DÉFINITIVE pour la durée du processus, et personne ne s'en
3813+ * apercevait :
3814+ * • [audioGetArg] et [audioSet3] mettent `sAudioHelper` à null dès que son binder meurt ;
3815+ * • [initAudio] refuse de refaire la liaison tant que `sAudioServiceConn` existe — or
3816+ * `onServiceDisconnected` ne l'efface pas ;
3817+ * • le helper est un binder de SECOND niveau, obtenu par une requête au CarAdapter : même
3818+ * quand ce dernier va bien, plus rien ne le redemande.
3819+ *
3820+ * Le volume basculait alors en silence sur `AudioManager`, jusqu'à la prochaine ouverture de
3821+ * l'application — la seule à appeler [initAudio]. Vu de l'utilisateur : un volume qui
3822+ * « marchait avant » et ne marche plus, sans rien avoir changé.
3823+ */
3824+ private fun assurerHelperAudio () {
3825+ if (! isA9Sound()) return
3826+ if (sAudioHelper?.isBinderAlive == true ) return
3827+ val ctx = sAppContext ? : return
3828+
3829+ // Le CarAdapter répond encore : il suffit de lui redemander le helper.
3830+ if (sCarAdapterBinder?.isBinderAlive == true ) {
3831+ AppLogger .i(VOL_TAG , " helper audio perdu — nouvelle requête au CarAdapter" )
3832+ tryGetAudioHelper()
3833+ if (sAudioHelper?.isBinderAlive == true ) return
3834+ }
3835+
3836+ // Le service lui-même est parti : on repart d'une liaison neuve. L'ancienne connexion
3837+ // est détachée d'abord, sinon on en accumulerait une par tentative.
3838+ AppLogger .i(VOL_TAG , " CarAdapter injoignable — nouvelle liaison audio" )
3839+ sAudioServiceConn?.let { conn ->
3840+ runCatching { ctx.applicationContext.unbindService(conn) }
3841+ }
3842+ sAudioServiceConn = null
3843+ sCarAdapterBinder = null
3844+ initAudio(ctx)
3845+
3846+ // La liaison est asynchrone. Sans cette attente, le premier appui échouerait et seul le
3847+ // suivant agirait — un raccourci qui « marche une fois sur deux » est pire qu'un
3848+ // raccourci qui ne marche pas, on ne sait pas quoi en conclure. L'appelant est déjà sur
3849+ // un contexte IO, l'attente ne coûte rien à l'écran.
3850+ val limite = SystemClock .uptimeMillis() + MEDIA_BIND_MS
3851+ while (SystemClock .uptimeMillis() < limite) {
3852+ if (sAudioHelper?.isBinderAlive == true ) {
3853+ AppLogger .i(VOL_TAG , " helper audio récupéré" )
3854+ return
3855+ }
3856+ try { Thread .sleep(40 ) } catch (_: InterruptedException ) {}
3857+ }
3858+ AppLogger .w(VOL_TAG , " helper audio toujours absent après ${MEDIA_BIND_MS } ms — " +
3859+ " le volume passera par AudioManager" )
3860+ }
3861+
38093862 /* * Envoie une commande à la pile de projection. Rend faux si le service ne répond pas. */
38103863 private fun ruiEnvoyer (valeur : Int ): Boolean {
38113864 val binder = serviceBinder(" " , RUI_PKG , RUI_CLS ) ? : return false
@@ -4382,6 +4435,7 @@ object MG4Hardware {
43824435 * est un réglage du véhicule.
43834436 */
43844437 fun mediaVolumeStep (delta : Int ): Boolean {
4438+ assurerHelperAudio()
43854439 val actuel = getMediaVolume()
43864440 val max = getMediaVolumeMax()
43874441 if (actuel >= 0 && max > 0 ) {
0 commit comments