From 478526185a0f9c45a604375b723dca5934faccb0 Mon Sep 17 00:00:00 2001
From: Kono Tyran <HAklowner@gmail.com>
Date: Tue, 19 Sep 2023 11:54:16 -0700
Subject: [PATCH] split stream updates from layout updates. gave update staff
 button. auto refresh layouts on obs.

---
 pom.xml                                       |  2 +-
 .../com/alttprleague/LeagueRestreamTool.java  | 63 ++++++++++++++-----
 .../com/alttprleague/LeagueRestreamTool.jfd   | 11 +++-
 src/main/java/com/alttprleague/OBSClient.kt   | 19 ++++--
 src/main/resources/html/FourPlayer.html       |  9 ++-
 src/main/resources/html/TwoPlayer.html        | 11 +++-
 6 files changed, 90 insertions(+), 25 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9bc67e5..c9a948e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>com.alttprleague</groupId>
     <artifactId>restreamtool</artifactId>
-    <version>3.1</version>
+    <version>3.3</version>
 
     <build>
         <plugins>
diff --git a/src/main/java/com/alttprleague/LeagueRestreamTool.java b/src/main/java/com/alttprleague/LeagueRestreamTool.java
index b5a66c4..adaf1a7 100644
--- a/src/main/java/com/alttprleague/LeagueRestreamTool.java
+++ b/src/main/java/com/alttprleague/LeagueRestreamTool.java
@@ -71,7 +71,7 @@ public class LeagueRestreamTool {
 
     public static void main(String[] args) throws IOException {
         var restreamTool = new LeagueRestreamTool();
-        window = new JFrame("League Restream Tool V3.1");
+        window = new JFrame("League Restream Tool V3.3");
         URL iconURL = LeagueRestreamTool.class.getResource("/LeagueLogo.png");
         window.setIconImage(ImageIO.read(iconURL));
         window.setContentPane(restreamTool.pMain);
@@ -248,6 +248,10 @@ private Process startLink(String streamFrom, int port, Console console, boolean
     }
 
     private void fetchChannel(String channelName) {
+        fetchChannel(channelName, true);
+    }
+
+    private void fetchChannel(String channelName, boolean updateStreams) {
         try {
             var request = HttpRequest.newBuilder()
                     .uri(URI.create("https://alttprleague.com/api/restream/?channel="+ channelName))
@@ -256,7 +260,7 @@ private void fetchChannel(String channelName) {
 
             httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                     .thenApply(HttpResponse::body)
-                    .thenAccept(this::ProcessWebsiteResponse);
+                    .thenAccept((r) -> ProcessWebsiteResponse(r, updateStreams));
         }
         catch (Exception e) {
             consoleTopLeft.appendError("Error while fetching restream data from League website.");
@@ -264,20 +268,34 @@ private void fetchChannel(String channelName) {
         }
     }
 
-    private void ProcessWebsiteResponse(String json) {
-        try {
+    private void ProcessWebsiteResponse(String json, boolean updateStreams) {
             var gson = new Gson();
-            var channel = gson.fromJson(json, Channel.class);
-            if(!channel.error.isBlank()) {
+            channel = gson.fromJson(json, Channel.class);
+            if (!channel.error.isBlank()) {
                 consoleTopLeft.appendError(channel.error);
                 return;
             }
-            if(channel.episode == null) {
-                consoleTopLeft.appendError("No episode loaded on Restreamer Dashboard for "+channel.twitch_name+".");
+            if (channel.episode == null) {
+                consoleTopLeft.appendError("No episode loaded on Restreamer Dashboard for " + channel.twitch_name + ".");
                 return;
             }
 
-            this.channel = channel;
+            updateLayouts();
+            if(updateStreams)
+                updateStreams();
+    }
+
+    private void updateStreams() {
+        startStreamLink(Screen.TOP_LEFT,channel.episode.players[0].streaming_from);
+        startStreamLink(Screen.TOP_RIGHT,channel.episode.players[1].streaming_from);
+        if(channel.episode.players.length == 4) {
+            startStreamLink(Screen.BOTTOM_LEFT,channel.episode.players[2].streaming_from);
+            startStreamLink(Screen.BOTTOM_RIGHT,channel.episode.players[3].streaming_from);
+        }
+    }
+
+    private void updateLayouts() {
+
             btnStreamKey.setEnabled(true);
             obsRelay.setStreamKey(channel.stream_key);
 
@@ -305,7 +323,6 @@ private void ProcessWebsiteResponse(String json) {
             }
 
             // LEFT TEAM PLAYER 1
-            startStreamLink(Screen.TOP_LEFT,channel.episode.players[0].streaming_from);
             preRaceTemplate.getElementById("p1_logo").attr("src",channel.episode.players[0].logo_url);
             //preRaceTemplate.getElementById("p1_sprite").attr("src",channel.episode.players[0].sprite_url);
             preRaceTemplate.getElementById("p1_name").text(channel.episode.players[0].name);
@@ -316,7 +333,6 @@ private void ProcessWebsiteResponse(String json) {
 
             // LEFT TEAM PLAYER 2
             if(channel.episode.players.length >= 3) {
-                startStreamLink(Screen.BOTTOM_LEFT,channel.episode.players[2].streaming_from);
                 preRaceTemplate.getElementById("p3_logo").attr("src",channel.episode.players[2].logo_url);
                 //preRaceTemplate.getElementById("p3_sprite").attr("src",channel.episode.players[2].sprite_url);
                 preRaceTemplate.getElementById("p3_name").text(channel.episode.players[2].name);
@@ -339,7 +355,6 @@ private void ProcessWebsiteResponse(String json) {
 
 
             // RIGHT TEAM PLAYER 1
-            startStreamLink(Screen.TOP_RIGHT,channel.episode.players[1].streaming_from);
             preRaceTemplate.getElementById("p2_logo").attr("src",channel.episode.players[1].logo_url);
             //preRaceTemplate.getElementById("p2_sprite").attr("src",channel.episode.players[1].sprite_url);
             preRaceTemplate.getElementById("p2_name").text(channel.episode.players[1].name);
@@ -349,7 +364,6 @@ private void ProcessWebsiteResponse(String json) {
 
             // RIGHT TEAM PLAYER 2
             if(channel.episode.players.length >= 4) {
-                startStreamLink(Screen.BOTTOM_RIGHT,channel.episode.players[3].streaming_from);
                 preRaceTemplate.getElementById("p4_logo").attr("src",channel.episode.players[3].logo_url);
                 //preRaceTemplate.getElementById("p4_sprite").attr("src",channel.episode.players[3].sprite_url);
                 preRaceTemplate.getElementById("p4_name").text(channel.episode.players[3].name);
@@ -362,9 +376,12 @@ private void ProcessWebsiteResponse(String json) {
             preRaceTemplate.getElementById("stage").text(channel.episode.stage);
             preRaceTemplate.getElementById("mode").text(channel.episode.mode);
             preRaceTemplate.getElementById("open").text(channel.episode.season.open ? "Open":"Invitational");
+
             if(channel.episode.is_playoff && (channel.episode.background.toLowerCase().contains("power") || channel.episode.background.toLowerCase().contains("game4"))) {
                 raceTemplate.getElementById("mode").removeClass("hidden");
                 raceTemplate.getElementById("mode").text(channel.episode.mode);
+            } else {
+                raceTemplate.getElementById("mode").remove();
             }
 
 
@@ -408,6 +425,9 @@ private void ProcessWebsiteResponse(String json) {
             cardContainer.appendChild(scheduleCard);
 
             for (Division division : channel.divisions) {
+                if(division.teams == null){
+                    division.teams = new Team[0];
+                }
                 ArrayList<Team> teams = new ArrayList<>(Arrays.asList(division.teams));
                 teams.sort(Comparator.comparingInt(team -> team.points));
                 Collections.reverse(teams);
@@ -429,6 +449,7 @@ private void ProcessWebsiteResponse(String json) {
                 cardContainer.appendChild(standingsCard);
             }
 
+        try {
             cardContainer.child(0).addClass("active");
 
             FileWriter raceOut = new FileWriter(new File(baseDir,"RaceLayout.html"));
@@ -453,6 +474,7 @@ private void ProcessWebsiteResponse(String json) {
             playlist.close();
 
 
+            obsRelay.reloadLayout();
         } catch (IOException e) {
             e.printStackTrace();
             consoleTopLeft.appendError("Error Writing Files");
@@ -482,6 +504,7 @@ private void fetchRaceTimeData(String roomURL) {
                                 FileWriter timeOut = new FileWriter(new File(baseDir, "PreRaceLayout.html"));
                                 timeOut.write(doc.outerHtml());
                                 timeOut.close();
+                                obsRelay.reloadLayout();
                             } catch (IOException e) {
                                 consoleTopLeft.appendError("Error updating layout with race start time.");
                             }
@@ -599,6 +622,10 @@ private void miOBSSettings(ActionEvent e) {
         panel.setVisible(true);
     }
 
+    private void ReloadStaff(ActionEvent e) {
+        fetchChannel(channel.twitch_name, false);
+    }
+
     private enum Screen {
         TOP_LEFT,TOP_RIGHT,BOTTOM_LEFT,BOTTOM_RIGHT
     }
@@ -632,6 +659,7 @@ private void initComponents() {
         obsStatus = new StatusLight();
         btnOBSConnect = new JButton();
         btnOBSSaveCrop = new JButton();
+        btnReloadStaff = new JButton();
 
         //======== pMain ========
         {
@@ -777,7 +805,8 @@ private void initComponents() {
                         "insets 0,hidemode 3",
                         // columns
                         "[fill]" +
-                        "[grow,fill]",
+                        "[532,grow,fill]" +
+                        "[right]",
                         // rows
                         "[]"));
 
@@ -801,6 +830,11 @@ public void mouseClicked(MouseEvent e) {
                     btnOBSSaveCrop.setEnabled(false);
                     btnOBSSaveCrop.addActionListener(e -> btnOBSSaveCrop(e));
                     pUtil.add(btnOBSSaveCrop, "cell 0 0");
+
+                    //---- btnReloadStaff ----
+                    btnReloadStaff.setText("Reload Staff");
+                    btnReloadStaff.addActionListener(e -> ReloadStaff(e));
+                    pUtil.add(btnReloadStaff, "cell 2 0");
                 }
                 pBody.add(pUtil, "cell 0 2 6 1");
             }
@@ -837,5 +871,6 @@ public void mouseClicked(MouseEvent e) {
     private StatusLight obsStatus;
     private JButton btnOBSConnect;
     private JButton btnOBSSaveCrop;
+    private JButton btnReloadStaff;
     // JFormDesigner - End of variables declaration  //GEN-END:variables  @formatter:on
 }
diff --git a/src/main/java/com/alttprleague/LeagueRestreamTool.jfd b/src/main/java/com/alttprleague/LeagueRestreamTool.jfd
index 79d6a48..3918091 100644
--- a/src/main/java/com/alttprleague/LeagueRestreamTool.jfd
+++ b/src/main/java/com/alttprleague/LeagueRestreamTool.jfd
@@ -1,4 +1,4 @@
-JFDML JFormDesigner: "8.0.0.0.194" Java: "17.0.4.1" encoding: "UTF-8"
+JFDML JFormDesigner: "8.1.1.0.298" Java: "17.0.8" encoding: "UTF-8"
 
 new FormModel {
 	contentType: "form/swing"
@@ -141,7 +141,7 @@ new FormModel {
 				} )
 				add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
 					"$layoutConstraints": "insets 0,hidemode 3"
-					"$columnConstraints": "[fill][grow,fill]"
+					"$columnConstraints": "[fill][532,grow,fill][right]"
 					"$rowConstraints": "[]"
 				} ) {
 					name: "pUtil"
@@ -167,6 +167,13 @@ new FormModel {
 					}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
 						"value": "cell 0 0"
 					} )
+					add( new FormComponent( "javax.swing.JButton" ) {
+						name: "btnReloadStaff"
+						"text": "Reload Staff"
+						addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "ReloadStaff", true ) )
+					}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+						"value": "cell 2 0"
+					} )
 				}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
 					"value": "cell 0 2 6 1"
 				} )
diff --git a/src/main/java/com/alttprleague/OBSClient.kt b/src/main/java/com/alttprleague/OBSClient.kt
index 0a60676..2cdfd83 100644
--- a/src/main/java/com/alttprleague/OBSClient.kt
+++ b/src/main/java/com/alttprleague/OBSClient.kt
@@ -7,18 +7,21 @@ import io.obswebsocket.community.client.OBSRemoteController
 import io.obswebsocket.community.client.WebSocketCloseCode
 import io.obswebsocket.community.client.listener.lifecycle.ReasonThrowable
 import io.obswebsocket.community.client.message.event.sceneitems.SceneItemTransformChangedEvent
-import io.obswebsocket.community.client.message.request.Request
-import io.obswebsocket.community.client.message.request.Request.Data
+import io.obswebsocket.community.client.message.request.inputs.PressInputPropertiesButtonRequest
+import io.obswebsocket.community.client.message.request.ui.GetStudioModeEnabledRequest
+import io.obswebsocket.community.client.message.response.RequestResponse
 import io.obswebsocket.community.client.message.response.config.SetStreamServiceSettingsResponse
+import io.obswebsocket.community.client.message.response.inputs.PressInputPropertiesButtonResponse
 import io.obswebsocket.community.client.message.response.sceneitems.GetGroupSceneItemListResponse
 import io.obswebsocket.community.client.message.response.sceneitems.GetSceneItemListResponse
 import io.obswebsocket.community.client.message.response.sceneitems.GetSceneItemTransformResponse
 import io.obswebsocket.community.client.message.response.sceneitems.SetSceneItemTransformResponse
 import io.obswebsocket.community.client.message.response.scenes.GetGroupListResponse
 import io.obswebsocket.community.client.message.response.scenes.GetSceneListResponse
+import io.obswebsocket.community.client.message.response.ui.GetStudioModeEnabledResponse
 import org.slf4j.LoggerFactory
 import java.util.*
-import kotlin.collections.HashMap
+
 
 class OBSClient(
     private val obsStatus: StatusLight,
@@ -205,6 +208,7 @@ class OBSClient(
     }
 
     private fun pushCrop(containers: HashMap<String, Int>, top: Int, left: Int, right: Int, bottom: Int) {
+        if (!isConnected) return
         for (container in containers.keys) {
             val sourceID = containers[container]!!
             obsRemoteController.getSceneItemTransform(container, sourceID) { tResponse: GetSceneItemTransformResponse ->
@@ -234,7 +238,7 @@ class OBSClient(
 
     fun setStreamKey(key: String?) {
         if (!isConnected) return
-        val settings = JsonObject()
+        val settings: JsonObject = JsonObject()
         settings.addProperty("bwtest", false)
         settings.addProperty("key", key)
         settings.addProperty("server", "auto")
@@ -249,6 +253,13 @@ class OBSClient(
         }
     }
 
+    fun reloadLayout() {
+        if (!isConnected) return
+        obsRemoteController.sendRequest(PressInputPropertiesButtonRequest.builder().inputName("PreRaceLayout").propertyName("refreshnocache").build()) { _: PressInputPropertiesButtonResponse -> return@sendRequest }
+        obsRemoteController.sendRequest(PressInputPropertiesButtonRequest.builder().inputName("WebLayout").propertyName("refreshnocache").build()) { _: PressInputPropertiesButtonResponse -> return@sendRequest }
+        obsRemoteController.sendRequest(PressInputPropertiesButtonRequest.builder().inputName("PostRaceLayout").propertyName("refreshnocache").build()) { _: PressInputPropertiesButtonResponse -> return@sendRequest }
+    }
+
     companion object {
         private val log = LoggerFactory.getLogger(OBSClient::class.java.name)
     }
diff --git a/src/main/resources/html/FourPlayer.html b/src/main/resources/html/FourPlayer.html
index 3400642..3605991 100644
--- a/src/main/resources/html/FourPlayer.html
+++ b/src/main/resources/html/FourPlayer.html
@@ -27,6 +27,7 @@
       color: white;
       font-family: "Roboto", sans-serif;
       font-weight: 700;
+      font-size: 2.5rem;
     }
 
     iframe {
@@ -87,7 +88,7 @@
     }
 
     .hidden {
-      display: none;
+      visibility: hidden;
     }
 
     #layout {
@@ -156,6 +157,8 @@
       height: 49px;
       width: 477px;
       z-index: 1;
+      overflow: hidden;
+      text-align: center;
     }
 
     .player_name {
@@ -163,6 +166,8 @@
       height: 49px;
       width: 249px;
       z-index: 1;
+      overflow: hidden;
+      text-align: center;
     }
 
     #t1_name {
@@ -283,7 +288,7 @@
         src="https://images.alttprleague.com/static/league/img/player-default.png"
         alt="logo"
 />
-<div class="team_name resize_center" id="t1_name">Team 1</div>
+<div class="team_name resize_center" id="t1_name" data-path="players.team1.player1.name">Team 1</div>
 <div class="player_name resize_center" id="p1_name">Player 1</div>
 <div class="player_name resize_center" id="p3_name">Player 3</div>
 <div class="left_wins hidden" data-wins="0" id="t1_wins">
diff --git a/src/main/resources/html/TwoPlayer.html b/src/main/resources/html/TwoPlayer.html
index ee1cdd5..9642cda 100644
--- a/src/main/resources/html/TwoPlayer.html
+++ b/src/main/resources/html/TwoPlayer.html
@@ -5,7 +5,7 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=1920, initial-scale=1.0" />
     <title>TwoPlayer</title>
-    <script src="https://images.alttprleague.com/RestreamTool/js/textFit.js"></script>
+<!--    <script src="https://images.alttprleague.com/RestreamTool/js/textFit.js"></script>-->
     <script src="https://images.alttprleague.com/RestreamTool/js/common.js" defer></script>
     <style>
       @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap");
@@ -24,6 +24,7 @@
         color: white;
         font-family: "Roboto", sans-serif;
         font-weight: 700;
+        font-size: 2rem;
       }
 
       iframe {
@@ -84,7 +85,7 @@
       }
 
       .hidden {
-        display: none;
+        visibility: hidden;
       }
 
       #layout {
@@ -146,12 +147,18 @@
         position: absolute;
         height: 72px;
         width: 490px;
+        overflow: hidden;
+        text-align: center;
+        font-size: 3rem;
       }
 
       .player_name {
         position: absolute;
         height: 76px;
         width: 436px;
+        overflow: hidden;
+        text-align: center;
+        font-size: 3rem;
       }
 
       #t1_name {