Compare commits

..

3 Commits

Author SHA1 Message Date
ad9281b609 graceful shutdown 2023-10-20 16:34:09 -04:00
33a0298735 scene 1 2 2023-10-20 15:39:22 -04:00
ba5e7aa7f8 pan 2023-10-20 15:31:37 -04:00
6 changed files with 144 additions and 79 deletions

View File

@@ -7,10 +7,11 @@ import org.freedesktop.dbus.interfaces.DBusInterface
import org.freedesktop.dbus.interfaces.DBusSigHandler import org.freedesktop.dbus.interfaces.DBusSigHandler
import org.freedesktop.dbus.messages.DBusSignal import org.freedesktop.dbus.messages.DBusSignal
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.Queue import java.util.concurrent.BlockingQueue
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread import kotlin.concurrent.thread
class DBus(private val q: Queue<Op>) { class DBus(private val q: BlockingQueue<Op>) {
private val thread = thread( private val thread = thread(
start = true, start = true,
@@ -27,7 +28,11 @@ class DBus(private val q: Queue<Op>) {
log.info("signal: ${signal.op}") log.info("signal: ${signal.op}")
val op = Op.valueOf(signal.op) val op = Op.valueOf(signal.op)
log.info("op: ${op}") log.info("op: ${op}")
q.offer(op) try {
q.offer(op, 1, TimeUnit.SECONDS)
} catch (e:InterruptedException) {
log.debug("queue offer interrupted")
}
} }
while (true) { while (true) {
try { try {

View File

@@ -1,5 +0,0 @@
package net.eksb.obsdc
fun interface Handler {
fun handle(op:Op)
}

View File

@@ -8,6 +8,6 @@ object Main {
fun main(args: Array<String>) { fun main(args: Array<String>) {
val q:BlockingQueue<Op> = LinkedBlockingQueue() val q:BlockingQueue<Op> = LinkedBlockingQueue()
DBus(q) // forks daemon thread DBus(q) // forks daemon thread
ObsCommunity(q).run() // blocks Obs(q).run() // blocks
} }
} }

View File

@@ -0,0 +1,129 @@
package net.eksb.obsdc
import io.obswebsocket.community.client.OBSRemoteController
import io.obswebsocket.community.client.message.event.ui.StudioModeStateChangedEvent
import io.obswebsocket.community.client.model.Scene
import io.obswebsocket.community.client.model.SceneItem.Transform
import io.obswebsocket.community.client.model.SceneItem.Transform.TransformBuilder
import org.slf4j.LoggerFactory
import java.util.concurrent.BlockingQueue
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
// protocol docs: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
class Obs(private val q:BlockingQueue<Op>): AutoCloseable {
private val panAmount = 50F
private val controller = OBSRemoteController.builder()
.host("localhost")
.port(4455)
.password("R3tRkVXhFofJ2wRF") // TODO put this in a file
.autoConnect(false)
.connectionTimeout(3)
.lifecycle()
.onReady(::ready)
.onClose { code -> log.error("closed:${code}")}
.onControllerError { e -> log.error("controller error", e ) }
.onCommunicatorError { e -> log.error("comm error", e ) }
.onDisconnect { log.info("disconnected") }
.and()
.registerEventListener(StudioModeStateChangedEvent::class.java) {
log.info("studio mode state change: ${it}")
}
.build()
private var alive = true
init {
Runtime.getRuntime().addShutdownHook(thread(start=false) {
log.info("shutdown")
alive = false
controller.stop()
})
}
fun run() {
controller.connect()
}
override fun close() {
controller.disconnect()
}
private fun ready() {
log.info("ready")
while(alive) {
val op: Op? = q.poll(1, TimeUnit.SECONDS) // blocks
if (op != null) {
log.info("op: ${op}")
op(op)
break
}
}
}
private fun op(op:Op) {
when(op) {
Op.SCENE_1 -> scene { scenes -> scenes.firstOrNull() }
Op.SCENE_2 -> scene { scenes -> scenes.asSequence().drop(1).firstOrNull() }
Op.STUDIO_TRANSITION -> {
controller.triggerStudioModeTransition { response ->
// This does not get called?
log.info("Response successful: ${response.isSuccessful}")
ready()
}
}
Op.PAN_UP -> pan { old -> positionY(old.positionY - panAmount ) }
Op.PAN_DOWN -> pan { old -> positionY(old.positionY + panAmount ) }
Op.PAN_LEFT -> pan { old -> positionX(old.positionX - panAmount ) }
Op.PAN_RIGHT -> pan { old -> positionX(old.positionX + panAmount ) }
Op.TODO -> {
log.info("OP=TODO")
ready()
}
}
}
private fun scene(selector:(List<Scene>)->Scene?) {
controller.getSceneList { response ->
val scene = selector(response.scenes.sortedBy(Scene::getSceneIndex).reversed())
log.info("select scene ${scene?.sceneName} index:${scene?.sceneIndex}")
if (scene != null) {
controller.setCurrentProgramScene(scene.sceneName) { response ->
ready()
}
} else {
ready()
}
}
}
private fun pan(block:TransformBuilder.(Transform)->TransformBuilder) {
controller.getCurrentProgramScene { response ->
val sceneName = response.currentProgramSceneName
log.info("scene name: ${sceneName}")
controller.getSceneItemList(sceneName) { response ->
val item = response.sceneItems.last()
val itemId = item.sceneItemId
log.info("last item id: ${itemId}")
controller.getSceneItemTransform(sceneName, itemId) { response ->
val transform = response.sceneItemTransform
log.info("position: ${transform.positionX} x ${transform.positionY}")
val newTransform = block(Transform.builder(), transform).build()
controller.setSceneItemTransform(sceneName, itemId, newTransform) { response ->
log.info("transform successful: ${response.isSuccessful}")
// Have to set the current scene to take effect if in studio mode.
controller.setCurrentProgramScene(sceneName) { response ->
ready()
}
}
}
}
}
}
companion object {
private val log = LoggerFactory.getLogger(Obs::class.java)
}
}

View File

@@ -1,69 +0,0 @@
package net.eksb.obsdc
import io.obswebsocket.community.client.OBSRemoteController
import io.obswebsocket.community.client.message.event.ui.StudioModeStateChangedEvent
import org.slf4j.LoggerFactory
import java.util.concurrent.BlockingQueue
// protocol docs: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
class ObsCommunity(private val q:BlockingQueue<Op>): AutoCloseable {
private val controller = OBSRemoteController.builder()
.host("localhost")
.port(4455)
.password("R3tRkVXhFofJ2wRF") // TODO put this in a file
.autoConnect(false)
.connectionTimeout(3)
.lifecycle()
.onReady(::ready)
.onClose { code -> log.error("closed:${code}")}
.onControllerError { e -> log.error("controller error", e ) }
.onCommunicatorError { e -> log.error("comm error", e ) }
.onDisconnect { log.info("disconnected") }
.and()
.registerEventListener(StudioModeStateChangedEvent::class.java) {
log.info("studio mode state change: ${it}")
}
.build()
fun run() {
controller.connect()
}
override fun close() {
controller.disconnect()
}
private fun ready() {
log.info("ready")
val op: Op = q.take() // blocks
log.info("op: ${op}")
op(op)
}
private fun op(op:Op) {
when(op) {
Op.SCENE_TEST -> {
controller.setCurrentProgramScene("test") { response ->
log.info("set scene response: ${response.isSuccessful}")
ready()
}
}
Op.STUDIO_TRANSITION -> {
controller.triggerStudioModeTransition { response ->
// This does not get called?
log.info("Response successful: ${response.isSuccessful}")
ready()
}
}
Op.TODO -> {
log.info("OP=TODO")
ready()
}
}
}
companion object {
private val log = LoggerFactory.getLogger(ObsCommunity::class.java)
}
}

View File

@@ -2,7 +2,12 @@ package net.eksb.obsdc
enum class Op { enum class Op {
STUDIO_TRANSITION, STUDIO_TRANSITION,
SCENE_TEST, SCENE_1,
SCENE_2,
PAN_UP,
PAN_DOWN,
PAN_LEFT,
PAN_RIGHT,
TODO, TODO,
; ;
} }