diff --git a/cmd/mxl-player/main.go b/cmd/mxl-player/main.go index 465b526..f6bdd06 100644 --- a/cmd/mxl-player/main.go +++ b/cmd/mxl-player/main.go @@ -99,6 +99,8 @@ func main() { // timelapse audio: 5fbec3b1-1b0f-417d-9059-8b94a47197ec // f1 video: 5fbec3b1-1b0f-417d-9059-8b94a47197ef // f1 audio: 5fbec3b1-1b0f-417d-9059-8b94a47197eb + // sync video: 2618979d-76a5-45e0-83cb-0f192978d1cd + // sync audio: 9d2a041b-01cf-4ee4-bffa-188fe093c99b var args appArgs flagSet := pflag.NewFlagSet(APP_NAME, pflag.ContinueOnError) flagSet.SortFlags = false @@ -200,6 +202,19 @@ func main() { // ImGui init gui := imgui.New() defer gui.Destroy() + fontConfig := cimgui.NewFontConfig() + font := gui.IO().Fonts().AddFontFromFileTTFV( + "/home/itten/Downloads/JetBrainsMono/JetBrainsMonoNLNerdFontMono-Regular.ttf", + 18, + fontConfig, + nil, + ) + fontConfig.Destroy() + if font == nil || font.CData == nil { + log.Fatal("failed to load ImGui font") + } + gui.IO().SetFontDefault(font) + // sdl keys handler sdl.StartTextInput(windowHandler) defer sdl.StopTextInput(windowHandler) // fin on ImGui init @@ -421,6 +436,12 @@ func main() { ) lastFrame = time.Now() + // ImGui + var ( + settingWindowWidth float32 = 700 + settingsWindowState bool = true + ) + for running { frameStart := time.Now() var event [128]byte @@ -447,6 +468,8 @@ func main() { resized = true case sdl.KeyF1: showStats = !showStats + case sdl.KeyF2: + settingsWindowState = !settingsWindowState } } gui.ProcessEvent(&event) @@ -523,8 +546,6 @@ func main() { // end of stats if r != nil { gui.BeginFrame(time.Since(lastFrame), int32(r.Extent().Width), int32(r.Extent().Height)) - // test widget - // cimgui.Begin("Test") if showStats { cimgui.SetNextWindowPos(cimgui.Vec2{X: 10, Y: 10}) cimgui.SetNextWindowSize(cimgui.Vec2{X: 200, Y: 200}) @@ -540,13 +561,9 @@ func main() { displayedVideoHeight, )) } - cimgui.Text("\nPress F1 to hide stats") - cimgui.Text("Q or Esc to quit") - cimgui.Text("F for fullscreen") cimgui.End() } - cimgui.Begin("Connection") - + // settings & info window snapshot, hasSnapshot := player.Controller.Snapshot() videoConfigured := videoStr != "" audioConfigured := audioStr != "" @@ -556,147 +573,176 @@ func main() { videoConfigured = snapshot.Desired.Video.IsConfigured() audioConfigured = snapshot.Desired.Audio.IsConfigured() syncRequested = snapshot.Desired.SyncRequested - cimgui.Text(fmt.Sprintf( - "Topology: %s (generation %d)", - snapshot.Plan.Topology, - snapshot.Generation, - )) - } else { - cimgui.Text("Topology: starting") } - cimgui.Separator() - cimgui.InputTextWithHint("Video domain", "/dev/shm/mxl", &videoDomainStr, 0, nil) - cimgui.InputTextWithHint("Video UUID", "", &videoStr, 0, nil) - cimgui.InputTextWithHint("Audio domain", "/dev/shm/mxl", &audioDomainStr, 0, nil) - cimgui.InputTextWithHint("Audio UUID", "", &audioStr, 0, nil) - if cimgui.Button("Apply feeds") { - doReconnect() - } - cimgui.SameLine() - if videoActive || audioActive { - if cimgui.Button("Stop all") { - enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopAll}) - } - } - if (videoConfigured && !videoActive) || (audioConfigured && !audioActive) { - if videoActive || audioActive { + drawSettingsContents := func() { + var collapsingHeaderFlags cimgui.TreeNodeFlags = cimgui.TreeNodeFlagsDefaultOpen + drawFeedsSections := func() { + cimgui.SeparatorText("Video") + cimgui.InputTextWithHint("Video domain", "/dev/shm/mxl", &videoDomainStr, 0, nil) + cimgui.InputTextWithHint("Video UUID", "", &videoStr, 0, nil) + if videoActive { + cimgui.SameLine() + if cimgui.Button("Stop##video") { + videoActive = false + enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopVideo}) + } + } + if !videoActive && videoConfigured { + cimgui.SameLine() + if cimgui.Button("Resume##video") { + videoActive = true + enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeVideo}) + } + } + if videoConfigured { + cimgui.SameLine() + if cimgui.Button("Remove##video") { + videoActive = false + videoStr = "" + enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveVideo}) + } + } + cimgui.SeparatorText("Audio") + cimgui.InputTextWithHint("Audio domain", "/dev/shm/mxl", &audioDomainStr, 0, nil) + cimgui.InputTextWithHint("Audio UUID", "", &audioStr, 0, nil) + if audioActive { + cimgui.SameLine() + if cimgui.Button("Stop##audio") { + audioActive = false + enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopAudio}) + } + } + + if !audioActive && audioConfigured { + cimgui.SameLine() + if cimgui.Button("Resume##audio") { + audioActive = true + enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAudio}) + } + } + if audioConfigured { + cimgui.SameLine() + if cimgui.Button("Remove##audio") { + audioActive = false + audioStr = "" + enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveAudio}) + } + } + cimgui.SeparatorText("Controls") + if cimgui.Button("Apply feeds") { + doReconnect() + } cimgui.SameLine() - } - if cimgui.Button("Resume all") { - enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAll}) - } - } - - if cimgui.Checkbox("Synchronize", &syncRequested) { - kind := playback.CommandDisableSync - if syncRequested { - kind = playback.CommandEnableSync - } - enqueueCommand(playback.SessionCommand{Kind: kind}) - } - if hasSnapshot && syncRequested && snapshot.Plan.Topology != playback.TopologySynchronized { - switch { - case !videoConfigured || !audioConfigured: - cimgui.TextWrapped("Sync requested: waiting for both feeds to be configured.") - case !videoActive || !audioActive: - cimgui.TextWrapped("Sync requested: waiting for both feeds to be active.") - case snapshot.Desired.Video.Domain != snapshot.Desired.Audio.Domain: - cimgui.TextWrapped("Sync requested, but native MXL sync requires matching domains. Playing independently.") - default: - cimgui.TextWrapped("Sync requested but currently unavailable. Playing independently.") - } - } - - cimgui.Separator() - cimgui.Text("Video") - if videoActive { - if cimgui.Button("Stop video") { - videoActive = false - enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopVideo}) - } - } - if !videoActive && videoConfigured { - cimgui.SameLine() - if cimgui.Button("Resume video") { - videoActive = true - enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeVideo}) - } - } - if videoConfigured { - cimgui.SameLine() - if cimgui.Button("Remove video") { - videoActive = false - videoStr = "" - enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveVideo}) - } - } - if videoActive { - cimgui.Text("Video desired: active") - } else if videoConfigured { - cimgui.Text("Video desired: stopped") - } else { - cimgui.Text("Video desired: not configured") - } - - cimgui.Separator() - cimgui.Text("Audio") - if audioActive { - if cimgui.Button("Stop audio") { - audioActive = false - enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopAudio}) - } - } - - if !audioActive && audioConfigured { - cimgui.SameLine() - if cimgui.Button("Resume audio") { - audioActive = true - enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAudio}) - } - } - - if audioConfigured { - cimgui.SameLine() - if cimgui.Button("Remove audio") { - audioActive = false - audioStr = "" - enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveAudio}) - } - } - - if audioActive { - cimgui.Text("Audio desired: active") - } else if audioConfigured { - cimgui.Text("Audio desired: stopped") - } else { - cimgui.Text("Audio desired: not configured") - } - - cimgui.Separator() - cimgui.Text("Current playback") - if hasSnapshot { - switch snapshot.Plan.Topology { - case playback.TopologySynchronized: - drawUnitStatus("Synchronized group", playback.UnitSync) - case playback.TopologyIndependent: - if snapshot.Plan.Video.Active { - drawUnitStatus("Video", playback.UnitVideo) + if videoActive || audioActive { + if cimgui.Button("Stop all") { + enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopAll}) + } } - if snapshot.Plan.Audio.Active { - drawUnitStatus("Audio", playback.UnitAudio) + if (videoConfigured && !videoActive) || (audioConfigured && !audioActive) { + if videoActive || audioActive { + cimgui.SameLine() + } + if cimgui.Button("Resume all") { + enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAll}) + } } - case playback.TopologyIdle: - cimgui.Text("No active feeds") + cimgui.SameLine() + if cimgui.Checkbox("Synchronize", &syncRequested) { + kind := playback.CommandDisableSync + if syncRequested { + kind = playback.CommandEnableSync + } + enqueueCommand(playback.SessionCommand{Kind: kind}) + } + cimgui.SeparatorText("Feeds stats") + cimgui.Checkbox("Show stats", &showStats) + } + if cimgui.CollapsingHeaderTreeNodeFlagsV("Feeds", collapsingHeaderFlags) { + drawFeedsSections() + } + + drawHotkeysSection := func() { + cimgui.Text("F1 - show/hide stats") + cimgui.Text("F2 - show/hide settings") + cimgui.Text("F - toggle fullscreen") + cimgui.Text("Q or Esc - quit") + } + if cimgui.CollapsingHeaderTreeNodeFlagsV("Hotkeys", collapsingHeaderFlags) { + drawHotkeysSection() + } + + drawDebugSection := func() { + if hasSnapshot { + cimgui.Text(fmt.Sprintf( + "Topology: %s (generation %d)", + snapshot.Plan.Topology, + snapshot.Generation, + )) + } else { + cimgui.Text("Topology: starting") + } + if hasSnapshot && syncRequested && snapshot.Plan.Topology != playback.TopologySynchronized { + switch { + case !videoConfigured || !audioConfigured: + cimgui.TextWrapped("Sync requested: waiting for both feeds to be configured.") + case !videoActive || !audioActive: + cimgui.TextWrapped("Sync requested: waiting for both feeds to be active.") + case snapshot.Desired.Video.Domain != snapshot.Desired.Audio.Domain: + cimgui.TextWrapped("Sync requested, but native MXL sync requires matching domains. Playing independently.") + default: + cimgui.TextWrapped("Sync requested but currently unavailable. Playing independently.") + } + } + cimgui.Separator() + cimgui.Text("Current playback") + if hasSnapshot { + switch snapshot.Plan.Topology { + case playback.TopologySynchronized: + drawUnitStatus("Synchronized group", playback.UnitSync) + case playback.TopologyIndependent: + if snapshot.Plan.Video.Active { + drawUnitStatus("Video", playback.UnitVideo) + } + if snapshot.Plan.Audio.Active { + drawUnitStatus("Audio", playback.UnitAudio) + } + case playback.TopologyIdle: + cimgui.Text("No active feeds") + } + } else { + cimgui.Text("Playback controller is starting") + } + cimgui.Separator() + if videoActive { + cimgui.Text("Video desired: active") + } else if videoConfigured { + cimgui.Text("Video desired: stopped") + } else { + cimgui.Text("Video desired: not configured") + } + if audioActive { + cimgui.Text("Audio desired: active") + } else if audioConfigured { + cimgui.Text("Audio desired: stopped") + } else { + cimgui.Text("Audio desired: not configured") + } + + } + if cimgui.CollapsingHeaderTreeNodeFlagsV("Debug Info", collapsingHeaderFlags) { + drawDebugSection() } - } else { - cimgui.Text("Playback controller is starting") } - cimgui.Separator() - cimgui.Checkbox("Show stats", &showStats) - - cimgui.End() + if settingsWindowState { + cimgui.SetNextWindowPos(cimgui.Vec2{X: float32(r.Extent().Width) - settingWindowWidth, Y: 0}) + cimgui.SetNextWindowSize(cimgui.Vec2{X: settingWindowWidth, Y: float32(r.Extent().Height)}) + if cimgui.BeginV("Settings & Info", &settingsWindowState, cimgui.WindowFlagsNone) { + drawSettingsContents() + } + cimgui.End() + } gui.EndFrame() lastFrame = time.Now() // end of test widget diff --git a/imgui.ini b/imgui.ini index 883190a..f8dd632 100644 --- a/imgui.ini +++ b/imgui.ini @@ -1,6 +1,6 @@ [Window][Debug##Default] -Pos=60,60 -Size=400,400 +Pos=519,181 +Size=400,398 Collapsed=0 [Window][Test] @@ -14,7 +14,27 @@ Size=200,200 Collapsed=0 [Window][Connection] -Pos=322,130 -Size=661,444 +Pos=42,264 +Size=605,416 +Collapsed=0 + +[Window][Test slider] +Pos=1370,0 +Size=550,1080 +Collapsed=0 + +[Window][Settings] +Pos=730,0 +Size=550,720 +Collapsed=0 + +[Window][Settings & Info] +Pos=1220,0 +Size=700,1080 +Collapsed=0 + +[Window][Seetings & Info] +Pos=1220,0 +Size=700,1080 Collapsed=0 diff --git a/internal/sdl/sdl.go b/internal/sdl/sdl.go index 6e50ba1..b8cb2e4 100644 --- a/internal/sdl/sdl.go +++ b/internal/sdl/sdl.go @@ -32,6 +32,7 @@ const ( KeyF uint32 = 0x66 KeyQ uint32 = 0x71 KeyF1 uint32 = 0x4000003A + KeyF2 uint32 = 0x4000003B InitAudio uint32 = 0x00000010 AudioDeviceDefaultPlayback uint32 = 0xFFFFFFFF diff --git a/mxl-gst-scripts/loop-feed3.sh b/mxl-gst-scripts/loop-feed3.sh new file mode 100755 index 0000000..a1e2b65 --- /dev/null +++ b/mxl-gst-scripts/loop-feed3.sh @@ -0,0 +1,12 @@ +#!/bin/bash +VIDEO_ID="2618979d-76a5-45e0-83cb-0f192978d1cd" +AUDIO_ID="9d2a041b-01cf-4ee4-bffa-188fe093c99b" +VIDEO_URI=$1 +if [[ -z "${VIDEO_URI}" ]] then + VIDEO_URI="${HOME}/Videos/test-vid/motogp.ts" +fi +export GST_PLUGIN_PATH="${HOME}/.gst-plugin:${GST_PLUGIN_PATH}" +mxl-gst-looping-filesrc -d /dev/shm/mxl -i "${VIDEO_URI}" --video-id "${VIDEO_ID}" --audio-id "${AUDIO_ID}" 2>/dev/null +# sleep 5 +# kill -9 $(pidof "mxl-gst-looping-filesrc") +# echo -e "\ngst-looping-filesrc killed"