To be apart of live action, whether it be sports, competitions, or gaming, audiences no longer need to be in the stadium. In fact, with the explosion of mobile, audiences don't even need to be at home.
And today, users expect live game casts, statistics, and updates as they happen. With real-time publish/subscribe and a broadcast design pattern, we can push updates in real time from one controller to any number of subscribers simultaneously.
As a result, we can build sports game cast applications, live blogging platforms, and or even trigger device action on connected IoT devices.
What is a Broadcast Design Pattern?
A broadcast pub/sub messaging design pattern is publishing data from one publisher (broadcaster) to multiple subscribers (listeners) in real time. Updates are sent from the broadcaster, and are received on the subscriber simultaneously.
The design pattern in its simplest form below:
Real-time Gamecast Tutorial Overview
In this two part blog post, we're going to build a sportscast gamecast web and mobile application with AngularJS.
In part one, we'll build the controller application, where a user can edit different fields then publish the updates that will me immediately displayed on the subscriber application (part two).
The whole application is actually two applications:
- Controller (publisher): Edit fields and publish the updates.
- End-user application (subscriber(s)): Updates are received and displayed in real time.
We have a live-working demo that you can play with here. Open up a single gamecast controller, then a couple of the end-user subscriber windows. Push updates and watch as they update in real time!
Let's get started!
It’s simple to build with the PubNub Pub/Sub Messaging API. This tutorial is a demonstration of how easy and fun it is to develop with the PubNub API. It is intended for anyone interested in getting started with PubNub and AngularJS. It requires no previous experience.
<html> <head> <script src="https://cdn.pubnub.com/pubnub.min.js"></script> <script src="https://cdn.pubnub.com/pubnub-crypto.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script> <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script src="http://pubnub.github.io/angular-js/scripts/pubnub-angular.js"></script> <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"> <link rel="stylesheet" href="styles.css"> <link href='http://fonts.googleapis.com/css?family=Lato' rel='stylesheet' type='text/css'> </head> <body ng-app="PubNubAngularApp" ng-controller="MainCtrl"> <div class="stats col-md-4 col-md-offset-1"> <h1>Game Controller</h1> <hr> <input class="col-md-4" type="text" ng-model="state.home_team" placeholder="Home Team" /> <input class="col-md-2" type="text" ng-model="state.home_score" placeholder="Home Score" /> <br /><br /> <input class="col-md-4" type="text" ng-model="state.away_team" placeholder="Away Team"/> <input class="col-md-2" type="text" ng-model="state.away_score" placeholder="Away Score"/> <br /><br /> <input class="col-md-4" type="text" ng-model="state.inning" placeholder="Inning"/> <br /><br /> <button ng-click="click('balls')">Ball</button> <button ng-click="click('strikes')">Strike</button> <button ng-click="click('outs')">Out</button> <br /><br /> <button ng-click="click('first_bases')">On First</button> <button ng-click="click('second_bases')">On Second</button> <button ng-click="click('third_bases')">On Third</button> <br /><br /> <input class="col-md-3" type="text" ng-model="state.last_play" placeholder="Last Play" /> <br /> <hr> <button ng-click="reset()" class="reset">Reset Game</button> <button ng-click="publish()" class="send">Send Game Stats</button> </div> <div class="broadcast col-md-4 col-md-offset-1 vcenter"> <h1>Live Game</h1> <hr> <h2>{{state.home_team}}: {{state.home_score}}</h2> <h2>{{state.away_team}}: {{state.away_score}}</h2> <h2>Inning: {{state.inning}}</h2> <hr> <br /> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="400" id="gamecast_svg"> <g> <rect id="base1" ng-class="{first_base:true,rect:true,first_base_on:state.first_bases[0]}" width="100" height="100" x="-60" y="350" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" /> <rect id="base2" ng-class="{second_base:true,rect:true,second_base_on:state.second_bases[0]}" width="100" height="100" x="-60" y="240" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" /> <rect id="base3" ng-class="{third_base:true,rect:true,third_base_on:state.third_bases[0]}" width="100" height="100" x="-170" y="240" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" /> <path id="b1" ng-class="{ball:true,circle:true,ball_on:state.balls[0]}" d="m 185,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="b2" ng-class="{ball:true,circle:true,ball_on:state.balls[1]}" d="m 235,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="b3" ng-class="{ball:true,circle:true,ball_on:state.balls[2]}" d="m 285,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="b4" ng-class="{ball:true,circle:true,ball_on:state.balls[3]}" d="m 335,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="k1" ng-class="{strike:true,circle:true,strike_on:state.strikes[0]}" d="m 185,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="k2" ng-class="{strike:true,circle:true,strike_on:state.strikes[1]}" d="m 235,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="k3" ng-class="{strike:true,circle:true,strike_on:state.strikes[2]}" d="m 285,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="o1" ng-class="{out:true,circle:true,out_on:state.outs[0]}" d="m 185,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="o2" ng-class="{out:true,circle:true,out_on:state.outs[1]}" d="m 235,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> <path id="o3" ng-class="{out:true,circle:true,out_on:state.outs[2]}" d="m 285,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/> </g> </svg> <h3>Last Play: {{state.last_play}}</h3> <br /> <hr> </div> <script> var app = angular.module('PubNubAngularApp',["pubnub.angular.service"]); app.controller("MainCtrl", function($rootScope, $scope, PubNub){ $scope.userId = "User " + Math.round(Math.random()*1000); $scope.channel = 'Gamecast Game'; if (!$rootScope.initialized) { PubNub.init({ subscribe_key: 'demo', publish_key: 'demo', uuid:$scope.userId }); $rootScope.initialized = true; } $scope.reset = function() { $scope.state = { home_team: "Home team", away_team: "Away team", home_score: 0, away_score: 0, inning: "", first_bases: [false, false], second_bases: [false, false], third_bases: [false, false], balls: [false, false, false, false], strikes: [false, false, false], outs: [false, false, false], last_play: "" }; $scope.count = { first_bases: 0, second_bases: 0, third_bases: 0, balls: 0, strikes: 0, outs: 0 }; }; $scope.reset(); $scope.click = function(type) { $scope.count[type] = ($scope.count[type] + 1) % $scope.state[type].length; for (var i = 0; i < $scope.state[type].length; i++) { $scope.state[type][i] = (i < $scope.count[type]); } } $scope.publish = function() { PubNub.ngPublish({ channel: $scope.channel, message: $scope.state }); }; }); </script> </body> </html>
AngularJS Gamecast Application Concept
Below is a .gif of it in action. A live-working controller demo and end-user application demo are available here as well.
The concept is simple: assign values to a state variable and publish them in a channel as a message. Anyone who connects to the channel through PubNub can view the message! Can you think of other data you could send this way, besides baseball game stats? Feeling creative? Head on over to the PubNub community and share your ideas.
In this particular tutorial, the game cast will involve a broadcaster updating and sending game data to spectators. We’ll need three files: broadcaster.html
, spectator.html
, and styles.css
.
In PubNub API terminology, the broadcaster publishes game data to a channel, and spectators subscribe to that channel. The spectators will be able to view the scores of the home and away team, the inning, the current number of balls, strikes, and outs, and the most recent play.
Part 1: Building the Broadcaster
The broadcaster edits the state of the game data and publishes the entire state of the game to channel. The spectators can view the game data by subscribing to that channel.
Include Libraries
Start by including PubNub, AngularJS, jQuery, and Bootstrap libraries in broadcaster.html
.
We’ll also build a stylesheet for the game board and add a nice font.
Design and Style
This is how we specify what the game board will look like when in play. The shapes will be generated as a scaled vector graphic (SVG) (more on that later). In the CSS code we determine the look of the board.
The Structure
Declare the PubNubAngularApp in ng-app
and name the ng-controller
in the body tag so the HTML and JavaScript can interact.
Display an application title, team names, and respective scores. Also use ng-click
to allow your Angular code to call methods defined in the $scope.
Game Board SVG
An SVG for the game board was generated with InkScape. Each ball, strike, and out is a circle. Bases are rotated rectangles. It is NOT necessary to generate this code by hand. The code below is for reference.
Each of the SVG elements is given an id
and ng-class
. The ng-class
is a map of the type, shape, and state. For example, for <path>
with id="b1"
, the ng-class
is {ball:true,circle:true,ball_on:state.balls[0]}
. This means its classes are ball and circle. If a ball is called, ball_on
style is applied to the first ball in the balls
array. To reiterate, in the code below a “rect” tag represents the base. The “path” tag is a circle
Game Board Broadcaster
Below is a preview of what our controller (broadcaster) will look like:
The broadcaster needs a way to communicate that the game state has changed. This can be accomplished with text input, reset, and send buttons. When the buttons are clicked, the state is updated with ng-model
and the reset
andpublish
functions are called via ng-click
.
We’ll give the Broadcaster a preview of the state they’ll be sending to the spectators.
Making it all work: The Angular Controller
Create PubNub Angular module, define the controller, and specify the user and channel so the PubNub and JavaScript can interact with the HTML.
Initialize PubNub so we can use its methods, like publish and message event, for the broadcaster and spectator.
Reset and Publish
The default states can be established in the reset
function.
We can keep track of the game data in $scope.state and $scope.count. The counts will increment as the buttons for the balls, strikes, and outs are clicked. The value for the bases will either be zero or one, depending on if that particular base is occupied.
Finally, the broadcaster publishes the state of the entire board to the spectators in the channel.
Wrapping up
And that's the end of part one: building the controller/broadcaster. Now it's time to build the subscriber (end-user application), and that full tutorial can be seen here!
This is just one of the many things you can do with a broadcast pub/sub design. Pretty much anytime you want to stream or send data from one publisher to several subscribers, broadcast is what you're looking for. Use cases include live-blogging, statistics, streaming stock quotes, and triggering device actions.