From 68d358c1ee347990d1c2469405aae5965dcbeec9 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Mon, 17 Sep 2018 19:26:27 -0700 Subject: [PATCH] Add a display type for showing a Twitch stream. --- field/display.go | 3 ++ static/css/twitch_display.css | 12 +++++++ static/js/twitch_display.js | 23 +++++++++++++ templates/twitch_display.html | 23 +++++++++++++ web/twitch_display.go | 61 +++++++++++++++++++++++++++++++++++ web/twitch_display_test.go | 33 +++++++++++++++++++ web/web.go | 2 ++ 7 files changed, 157 insertions(+) create mode 100644 static/css/twitch_display.css create mode 100644 static/js/twitch_display.js create mode 100644 templates/twitch_display.html create mode 100644 web/twitch_display.go create mode 100644 web/twitch_display_test.go diff --git a/field/display.go b/field/display.go index cb5f60b..68e244a 100644 --- a/field/display.go +++ b/field/display.go @@ -31,6 +31,7 @@ const ( AudienceDisplay FieldMonitorDisplay PitDisplay + TwitchStreamDisplay ) var DisplayTypeNames = map[DisplayType]string{ @@ -40,6 +41,7 @@ var DisplayTypeNames = map[DisplayType]string{ AudienceDisplay: "Audience", FieldMonitorDisplay: "Field Monitor", PitDisplay: "Pit", + TwitchStreamDisplay: "Twitch Stream", } var displayTypePaths = map[DisplayType]string{ @@ -49,6 +51,7 @@ var displayTypePaths = map[DisplayType]string{ AudienceDisplay: "/displays/audience", FieldMonitorDisplay: "/displays/fta", PitDisplay: "/displays/pit", + TwitchStreamDisplay: "/displays/twitch", } var displayRegistryMutex sync.Mutex diff --git a/static/css/twitch_display.css b/static/css/twitch_display.css new file mode 100644 index 0000000..95840e3 --- /dev/null +++ b/static/css/twitch_display.css @@ -0,0 +1,12 @@ +/* + Copyright 2018 Team 254. All Rights Reserved. + Author: pat@patfairbank.com (Patrick Fairbank) +*/ + +html { + overflow: hidden; +} +body { + margin: 0; + background-color: #000; +} diff --git a/static/js/twitch_display.js b/static/js/twitch_display.js new file mode 100644 index 0000000..bdee1f0 --- /dev/null +++ b/static/js/twitch_display.js @@ -0,0 +1,23 @@ +// Copyright 2018 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Client-side logic for the Twitch stream display. + +var websocket; + +$(function() { + // Read the configuration for this display from the URL query string. + var urlParams = new URLSearchParams(window.location.search); + + // Embed the video stream. + new Twitch.Embed("twitchEmbed", { + channel: urlParams.get("channel"), + width: window.innerWidth, + height: window.innerHeight, + layout: "video" + }); + + // Set up the websocket back to the server. + websocket = new CheesyWebsocket("/displays/twitch/websocket", { + }); +}); diff --git a/templates/twitch_display.html b/templates/twitch_display.html new file mode 100644 index 0000000..2e6d464 --- /dev/null +++ b/templates/twitch_display.html @@ -0,0 +1,23 @@ +{{/* + Copyright 2018 Team 254. All Rights Reserved. + Author: pat@patfairbank.com (Patrick Fairbank) + + Display to show a configurable Twitch live video stream. +*/}} + + + + Twitch Stream Display - {{.EventSettings.Name}} - Cheesy Arena + + + + +
+ + + + + + + + diff --git a/web/twitch_display.go b/web/twitch_display.go new file mode 100644 index 0000000..0767659 --- /dev/null +++ b/web/twitch_display.go @@ -0,0 +1,61 @@ +// Copyright 2018 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Web routes for a display to show a configurable Twitch live video stream. + +package web + +import ( + "github.com/Team254/cheesy-arena/model" + "github.com/Team254/cheesy-arena/websocket" + "net/http" +) + +// Renders the Twitch stream view. +func (web *Web) twitchDisplayHandler(w http.ResponseWriter, r *http.Request) { + if !web.userIsReader(w, r) { + return + } + + if !web.enforceDisplayConfiguration(w, r, map[string]string{"channel": "team254"}) { + return + } + + template, err := web.parseFiles("templates/twitch_display.html") + if err != nil { + handleWebErr(w, err) + return + } + data := struct { + *model.EventSettings + }{web.arena.EventSettings} + err = template.ExecuteTemplate(w, "twitch_display.html", data) + if err != nil { + handleWebErr(w, err) + return + } +} + +// The websocket endpoint for sending configuration commands to the display. +func (web *Web) twitchDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { + if !web.userIsReader(w, r) { + return + } + + display, err := web.registerDisplay(r) + if err != nil { + handleWebErr(w, err) + return + } + defer web.arena.MarkDisplayDisconnected(display) + + ws, err := websocket.NewWebsocket(w, r) + if err != nil { + handleWebErr(w, err) + return + } + defer ws.Close() + + // Subscribe the websocket to the notifiers whose messages will be passed on to the client. + ws.HandleNotifiers(web.arena.DisplayConfigurationNotifier, web.arena.ReloadDisplaysNotifier) +} diff --git a/web/twitch_display_test.go b/web/twitch_display_test.go new file mode 100644 index 0000000..4f340b1 --- /dev/null +++ b/web/twitch_display_test.go @@ -0,0 +1,33 @@ +// Copyright 2018 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package web + +import ( + "github.com/Team254/cheesy-arena/websocket" + gorillawebsocket "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestTwitchDisplay(t *testing.T) { + web := setupTestWeb(t) + + recorder := web.getHttpResponse("/displays/twitch?displayId=1&channel=team254") + assert.Equal(t, 200, recorder.Code) + assert.Contains(t, recorder.Body.String(), "Twitch Stream Display - Untitled Event - Cheesy Arena") +} + +func TestTwitchDisplayWebsocket(t *testing.T) { + web := setupTestWeb(t) + + server, wsUrl := web.startTestServer() + defer server.Close() + conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/displays/twitch/websocket?displayId=123", nil) + assert.Nil(t, err) + defer conn.Close() + ws := websocket.NewTestWebsocket(conn) + + // Should get a few status updates right after connection. + readWebsocketType(t, ws, "displayConfiguration") +} diff --git a/web/web.go b/web/web.go index ba8c729..1204037 100644 --- a/web/web.go +++ b/web/web.go @@ -148,6 +148,8 @@ func (web *Web) newHandler() http.Handler { router.HandleFunc("/displays/fta/websocket", web.ftaDisplayWebsocketHandler).Methods("GET") router.HandleFunc("/displays/pit", web.pitDisplayHandler).Methods("GET") router.HandleFunc("/displays/pit/websocket", web.pitDisplayWebsocketHandler).Methods("GET") + router.HandleFunc("/displays/twitch", web.twitchDisplayHandler).Methods("GET") + router.HandleFunc("/displays/twitch/websocket", web.twitchDisplayWebsocketHandler).Methods("GET") router.HandleFunc("/match_play", web.matchPlayHandler).Methods("GET") router.HandleFunc("/match_play/{matchId}/load", web.matchPlayLoadHandler).Methods("GET") router.HandleFunc("/match_play/{matchId}/show_result", web.matchPlayShowResultHandler).Methods("GET")