Skip to content

Commit

Permalink
Blackmagic hyperdeck recording (#190)
Browse files Browse the repository at this point in the history
* added fields to settings for blackmagic recorder ip/port

* added recording

* fixed typo

* added support for multiple devices. records and stops in sync with match.

* removed debug log statements

---------

Co-authored-by: Jack Doherty <[email protected]>
  • Loading branch information
thatnerdjack and Jack Doherty authored Jul 1, 2024
1 parent 4278439 commit fcdf8d6
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 0 deletions.
60 changes: 60 additions & 0 deletions field/arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"log"
"math"
"net"
"reflect"
"time"

Expand Down Expand Up @@ -459,6 +460,7 @@ func (arena *Arena) AbortMatch() error {
arena.AudienceDisplayModeNotifier.Notify()
arena.AllianceStationDisplayMode = "logo"
arena.AllianceStationDisplayModeNotifier.Notify()
arena.stopRecording()
return nil
}

Expand Down Expand Up @@ -548,6 +550,7 @@ func (arena *Arena) Update() {
arena.AudienceDisplayModeNotifier.Notify()
arena.AllianceStationDisplayMode = "match"
arena.AllianceStationDisplayModeNotifier.Notify()
arena.startRecording()
if game.MatchTiming.WarmupDurationSec > 0 {
arena.MatchState = WarmupPeriod
enabled = false
Expand Down Expand Up @@ -599,6 +602,7 @@ func (arena *Arena) Update() {
auto = false
enabled = false
sendDsPacket = true
arena.stopRecording()
go func() {
// Leave the scores on the screen briefly at the end of the match.
time.Sleep(time.Second * matchEndScoreDwellSec)
Expand Down Expand Up @@ -1087,3 +1091,59 @@ func (arena *Arena) runPeriodicTasks() {
arena.updateEarlyLateMessage()
arena.purgeDisconnectedDisplays()
}

// Connect to Blackmagic device. Returns connection object.
func connectToRecorder(connString string) (net.Conn, error) {
const MAX_CONN_ATTEMPTS = 3
const CONN_TIMEOUT = time.Millisecond * 100

var err error
var conn net.Conn

for i := 0; i < MAX_CONN_ATTEMPTS; i++ {
conn, err = net.DialTimeout("tcp", connString, CONN_TIMEOUT)
if err != nil {
log.Println("CONNECTION ERROR*******", "connString:***", connString, "***error: ", err)
} else {
return conn, nil
}
}

return nil, err
}

// Starts recording using configured, on-network Blackmagic Device
func (arena *Arena) startRecording() {
var connections []net.Conn

for i := 0; i < len(arena.EventSettings.RecorderAddresses); i++ {
recCon, err := connectToRecorder(arena.EventSettings.RecorderAddresses[i])
if recCon == nil {
log.Println("recorder connection error: ", err)
} else {
connections = append(connections, recCon)
}
}

for i := 0; i < len(connections); i++ {
fmt.Fprint(connections[i], "record\n")
}
}

// Stops recording using configured, on-network Blackmagic Device
func (arena *Arena) stopRecording() {
var connections []net.Conn

for i := 0; i < len(arena.EventSettings.RecorderAddresses); i++ {
recCon, err := connectToRecorder(arena.EventSettings.RecorderAddresses[i])
if recCon == nil {
log.Println("recorder connection error: ", err)
} else {
connections = append(connections, recCon)
}
}

for i := 0; i < len(connections); i++ {
fmt.Fprint(connections[i], "stop\n")
}
}
2 changes: 2 additions & 0 deletions model/event_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type EventSettings struct {
TeamSignBlue2Address string
TeamSignBlue3Address string
TeamSignBlueTimerAddress string
RecorderAddressesRaw string
RecorderAddresses []string
WarmupDurationSec int
AutoDurationSec int
PauseDurationSec int
Expand Down
13 changes: 13 additions & 0 deletions templates/setup_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,19 @@
</div>
</div>
</fieldset>
<fieldset class="mb-4">
<legend>Instant Replay Match Recording</legend>
<p>
If you are using a Blackmagic HyperDeck device to record matches, enter device information here.
Format entries as ip_address:port (e.g. 192.168.0.123:9993). One entry per line.
</p>
<div class="row mb-3">
<label class="col-lg-6 control-label">IP Address</label>
<div class="col-lg-6">
<textarea class="form-control" name="recorderAddresses">{{.RecorderAddressesRaw}}</textarea>
</div>
</div>
</fieldset>
<fieldset class="mb-4">
<legend>Game-Specific</legend>
<div class="row mb-3">
Expand Down
12 changes: 12 additions & 0 deletions web/setup_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -94,6 +95,17 @@ func (web *Web) settingsPostHandler(w http.ResponseWriter, r *http.Request) {
eventSettings.TeamSignBlue2Address = r.PostFormValue("teamSignBlue2Address")
eventSettings.TeamSignBlue3Address = r.PostFormValue("teamSignBlue3Address")
eventSettings.TeamSignBlueTimerAddress = r.PostFormValue("teamSignBlueTimerAddress")

eventSettings.RecorderAddressesRaw = r.PostFormValue("recorderAddresses")
const recAddrEntryPattern = "^(?:\\b(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|[0-9])\\b(?:\r?\n|$))+$"
match, _ := regexp.MatchString(recAddrEntryPattern, eventSettings.RecorderAddressesRaw)
if match {
eventSettings.RecorderAddresses = strings.Split(strings.ReplaceAll(eventSettings.RecorderAddressesRaw, "\r", ""), "\n")
} else {
web.renderSettings(w, r, "Recorder address entry error. Please ensure all addresses entered correctly.")
return
}

eventSettings.WarmupDurationSec, _ = strconv.Atoi(r.PostFormValue("warmupDurationSec"))
eventSettings.AutoDurationSec, _ = strconv.Atoi(r.PostFormValue("autoDurationSec"))
eventSettings.PauseDurationSec, _ = strconv.Atoi(r.PostFormValue("pauseDurationSec"))
Expand Down

0 comments on commit fcdf8d6

Please sign in to comment.