mirror of https://bitbucket.org/ausocean/av.git
Merged in modify-update (pull request #506)
vidforward: only update broadcast if changed Resolves issues #405 and #406 Approved-by: Trek Hopton Approved-by: David Sutton
This commit is contained in:
commit
d232734d50
|
@ -51,7 +51,7 @@ type BroadcastBasic struct {
|
||||||
// ManagerBasic is a crude version of the BroadcastManager struct use to simplify
|
// ManagerBasic is a crude version of the BroadcastManager struct use to simplify
|
||||||
// marshal/unmarshal overriding.
|
// marshal/unmarshal overriding.
|
||||||
type ManagerBasic struct {
|
type ManagerBasic struct {
|
||||||
Broadcasts map[MAC]Broadcast
|
Broadcasts map[MAC]*Broadcast
|
||||||
SlateExitSignals []MAC
|
SlateExitSignals []MAC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,10 +86,10 @@ func TestBroadcastUnmarshal(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
in []byte
|
in []byte
|
||||||
expect Broadcast
|
expect *Broadcast
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
expect: Broadcast{
|
expect: &Broadcast{
|
||||||
mac: testMAC,
|
mac: testMAC,
|
||||||
urls: []string{testURL},
|
urls: []string{testURL},
|
||||||
status: statusActive,
|
status: statusActive,
|
||||||
|
@ -106,7 +106,7 @@ func TestBroadcastUnmarshal(t *testing.T) {
|
||||||
t.Errorf("could not marshal json for test no. %d: %v", i, err)
|
t.Errorf("could not marshal json for test no. %d: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !broadcastsEqual(got, test.expect) {
|
if !broadcastsEqual(&got, test.expect) {
|
||||||
t.Errorf("did not get expected result.\nGot: %v\nWnt: %v\n", got, test.expect)
|
t.Errorf("did not get expected result.\nGot: %v\nWnt: %v\n", got, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,8 +124,8 @@ func TestBroadcastManagerMarshal(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
in: broadcastManager{
|
in: broadcastManager{
|
||||||
broadcasts: map[MAC]Broadcast{
|
broadcasts: map[MAC]*Broadcast{
|
||||||
testMAC: Broadcast{
|
testMAC: &Broadcast{
|
||||||
testMAC,
|
testMAC,
|
||||||
[]string{testURL},
|
[]string{testURL},
|
||||||
statusSlate,
|
statusSlate,
|
||||||
|
@ -165,8 +165,8 @@ func TestBroadcastManagerUnmarshal(t *testing.T) {
|
||||||
{
|
{
|
||||||
in: []byte("{\"Broadcasts\":{\"" + testMAC + "\":{\"MAC\":\"" + testMAC + "\",\"URLs\":[\"" + testURL + "\"],\"Status\":\"" + statusSlate + "\"}},\"SlateExitSignals\":[\"" + testMAC + "\"]}"),
|
in: []byte("{\"Broadcasts\":{\"" + testMAC + "\":{\"MAC\":\"" + testMAC + "\",\"URLs\":[\"" + testURL + "\"],\"Status\":\"" + statusSlate + "\"}},\"SlateExitSignals\":[\"" + testMAC + "\"]}"),
|
||||||
expect: broadcastManager{
|
expect: broadcastManager{
|
||||||
broadcasts: map[MAC]Broadcast{
|
broadcasts: map[MAC]*Broadcast{
|
||||||
testMAC: Broadcast{
|
testMAC: &Broadcast{
|
||||||
testMAC,
|
testMAC,
|
||||||
[]string{testURL},
|
[]string{testURL},
|
||||||
statusSlate,
|
statusSlate,
|
||||||
|
@ -201,7 +201,7 @@ func broadcastManagersEqual(m1, m2 broadcastManager) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcastMapsEqual(m1, m2 map[MAC]Broadcast) bool {
|
func broadcastMapsEqual(m1, m2 map[MAC]*Broadcast) bool {
|
||||||
return mapsEqual(m1, m2, broadcastsEqual)
|
return mapsEqual(m1, m2, broadcastsEqual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +238,13 @@ func watchdogNotifiersEqual(w1, w2 watchdogNotifier) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcastsEqual(b1, b2 Broadcast) bool {
|
func broadcastsEqual(b1, b2 *Broadcast) bool {
|
||||||
|
if b1 == nil {
|
||||||
|
panic("b1 is nil")
|
||||||
|
}
|
||||||
|
if b2 == nil {
|
||||||
|
panic("b2 is nil")
|
||||||
|
}
|
||||||
if b1.mac != b2.mac || !reflect.DeepEqual(b1.urls, b2.urls) || b1.status != b2.status ||
|
if b1.mac != b2.mac || !reflect.DeepEqual(b1.urls, b2.urls) || b1.status != b2.status ||
|
||||||
((b1.rv == nil || b2.rv == nil) && b1.rv != b2.rv) {
|
((b1.rv == nil || b2.rv == nil) && b1.rv != b2.rv) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -36,7 +36,9 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -89,13 +91,22 @@ type Broadcast struct {
|
||||||
rv *revid.Revid // The revid pipeline which will handle forwarding to youtube.
|
rv *revid.Revid // The revid pipeline which will handle forwarding to youtube.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// equal checks to see if the broadcast is equal to another broadcast.
|
||||||
|
// NOTE: This is not a deep equal, and is only used to check if a broadcast
|
||||||
|
// should be updated.
|
||||||
|
func (b *Broadcast) equal(other Broadcast) bool {
|
||||||
|
return b.mac == other.mac &&
|
||||||
|
b.status == other.status &&
|
||||||
|
reflect.DeepEqual(b.urls, other.urls)
|
||||||
|
}
|
||||||
|
|
||||||
// broadcastManager manages a map of Broadcasts we expect to be forwarding video
|
// broadcastManager manages a map of Broadcasts we expect to be forwarding video
|
||||||
// for. The broadcastManager is communicated with through a series of HTTP request
|
// for. The broadcastManager is communicated with through a series of HTTP request
|
||||||
// handlers. There is a basic REST API through which we can add/delete broadcasts,
|
// handlers. There is a basic REST API through which we can add/delete broadcasts,
|
||||||
// and a recv handler which is invoked when a camera wishes to get its video
|
// and a recv handler which is invoked when a camera wishes to get its video
|
||||||
// forwarded to youtube.
|
// forwarded to youtube.
|
||||||
type broadcastManager struct {
|
type broadcastManager struct {
|
||||||
broadcasts map[MAC]Broadcast
|
broadcasts map[MAC]*Broadcast
|
||||||
slateExitSignals map[MAC]chan struct{} // Used to signal to stop writing slate image.
|
slateExitSignals map[MAC]chan struct{} // Used to signal to stop writing slate image.
|
||||||
log logging.Logger
|
log logging.Logger
|
||||||
dogNotifier *watchdogNotifier
|
dogNotifier *watchdogNotifier
|
||||||
|
@ -106,7 +117,7 @@ type broadcastManager struct {
|
||||||
func newBroadcastManager(l logging.Logger) (*broadcastManager, error) {
|
func newBroadcastManager(l logging.Logger) (*broadcastManager, error) {
|
||||||
m := &broadcastManager{
|
m := &broadcastManager{
|
||||||
log: l,
|
log: l,
|
||||||
broadcasts: make(map[MAC]Broadcast),
|
broadcasts: make(map[MAC]*Broadcast),
|
||||||
slateExitSignals: make(map[MAC]chan struct{}),
|
slateExitSignals: make(map[MAC]chan struct{}),
|
||||||
}
|
}
|
||||||
notifier, err := newWatchdogNotifier(l, terminationCallback(m))
|
notifier, err := newWatchdogNotifier(l, terminationCallback(m))
|
||||||
|
@ -359,27 +370,42 @@ func (m *broadcastManager) isActive(ma MAC) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// createOrUpdate creates or updates a Broadcast record. The revid pipeline
|
// createOrUpdate creates or updates a Broadcast record. If the record already
|
||||||
// corresponding to the broadcast MAC is firsty configured/re-configured, and
|
// exists, it will be updated with the new data. If it doesn't exist, a new
|
||||||
// the pipeline is "started", which will ready it for receiving video on its
|
// revid pipeline will be created and started. Actions occur according to the
|
||||||
// input. In the case that the status is "slate", we will spin up a routine to
|
// status field of the broadcast i.e. whether we expect data from a source
|
||||||
// handle writing a slate image to the pipeline.
|
// or write the slate image.
|
||||||
func (m *broadcastManager) createOrUpdate(broadcast Broadcast) error {
|
func (m *broadcastManager) createOrUpdate(broadcast Broadcast) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
// Try to get any old broadcasts for the provided MAC.
|
||||||
|
maybeOld, ok := m.broadcasts[broadcast.mac]
|
||||||
|
|
||||||
|
// If there's no old broadcast, we need to create a new revid pipeline.
|
||||||
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
broadcast.rv, err = newRevid(m.log, broadcast.urls)
|
broadcast.rv, err = newRevid(m.log, broadcast.urls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialise revid: %w", err)
|
return fmt.Errorf("could not initialise revid: %w", err)
|
||||||
}
|
}
|
||||||
|
maybeOld = &broadcast
|
||||||
m.broadcasts[broadcast.mac] = broadcast
|
|
||||||
err = broadcast.rv.Start()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not start revid pipeline: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the URLS have changed, we need to update the revid pipeline.
|
||||||
|
// We won't enter this if we've just created a new revid pipeline.
|
||||||
|
if !reflect.DeepEqual(maybeOld.urls, broadcast.urls) {
|
||||||
|
urls := strings.Join(broadcast.urls, ",")
|
||||||
|
err := maybeOld.rv.Update(map[string]string{"RTMPURL": urls})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not update revid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the status has changed, we need to update accordingly.
|
||||||
|
// i.e. if the status is slate, we need to start writing the slate image.
|
||||||
|
// We won't enter this if we've just created a new revid pipeline.
|
||||||
|
if maybeOld.status != broadcast.status {
|
||||||
switch broadcast.status {
|
switch broadcast.status {
|
||||||
case statusActive, statusPlay, statusCreate:
|
case statusActive, statusPlay, statusCreate:
|
||||||
m.log.Info("updating configuration for mac", "mac", broadcast.mac)
|
m.log.Info("updating configuration for mac", "mac", broadcast.mac)
|
||||||
|
@ -403,13 +429,27 @@ func (m *broadcastManager) createOrUpdate(broadcast Broadcast) error {
|
||||||
signalCh := make(chan struct{})
|
signalCh := make(chan struct{})
|
||||||
m.slateExitSignals[broadcast.mac] = signalCh
|
m.slateExitSignals[broadcast.mac] = signalCh
|
||||||
|
|
||||||
err = writeSlateAndCheckErrors(broadcast.rv, signalCh, m.log)
|
err := writeSlateAndCheckErrors(broadcast.rv, signalCh, m.log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown status string: %s", broadcast.status)
|
return fmt.Errorf("unknown status string: %s", broadcast.status)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to give the revid pipeline to the new broadcast record.
|
||||||
|
broadcast.rv = maybeOld.rv
|
||||||
|
|
||||||
|
// And then replace the old record with the new one in the map.
|
||||||
|
m.broadcasts[broadcast.mac] = &broadcast
|
||||||
|
|
||||||
|
// Start the pipeline (it's a no-op if it's already started).
|
||||||
|
err := broadcast.rv.Start()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not start revid pipeline: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue