So there is a solution. Its called foreground notification service. First I was doing wake lock but this is insufficient:
Now my code looks like this (sorry I use a lot of custom extensions)
    app.startService<LooperPlayNotificationService>()
    wakeLock.acquire()
So I keep app alive and working fine in background.
class LooperPlayNotificationService : Service() {
    companion object {
        val NOTIFICATIONS_CHANNEL = "${app.packageName} notifications"
    }
    override fun onBind(intent: Intent): IBinder? = null
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        start()
        return START_STICKY
    }
    override fun onCreate() {
        super.onCreate()
        start()
    }
    private val playButtonActionId = "play_button_action"
    private lateinit var playButtonAction: BroadcastReceiver
    private var started = false
    // https://stackoverflow.com/questions/6619143/start-sticky-foreground-android-service-goes-away-without-notice
    // There's a bug in 2.3 (not sure if it was fixed yet) where when a Service is killed and restarted,
    // its onStartCommand() will NOT be called again. Instead you're going to have to do any setting up in onCreate()
    private fun start() {
        if (started) return
        started = true
        startForeground(647823876, createNotification())
        playButtonAction = register(playButtonActionId) {
            main.looper?.player?.asStarted { it.stop() }
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        unregister(this.playButtonAction)
    }
    private fun createNotification() = Builder(this, NOTIFICATIONS_CHANNEL)
        .setSmallIcon(outline_all_inclusive_24)
        .setContentIntent(getActivity(this, 0, Intent<InstrumentsActivity>(this),
            FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
        .setPriority(PRIORITY_DEFAULT)
        .setAutoCancel(false).setOngoing(true)
        .addAction(ic_stop_circle_black_24dp, "Stop",
            getBroadcast(this, 0, Intent(playButtonActionId),
                FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
        .setContentText(getString(R.string.app_name))
        .setContentText(main.looper?.preset?.item?.value?.title?.value).build()
}
this is basically just a service, it has to be defined in manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <service
        android:name=".model.mode.looper.player.state.LooperPlayNotificationService"
        android:enabled="true"
        android:exported="true" />
And so on, there is bunch of examples about this matter, but overall it was not so trivial to implement due to various details you can see in code I posted.