Google+ Hangouts provide an easy way to create 10-way video conferences. This is a pretty cool feature on its own, but with the Hangout API you can extend them.
You can author your own JavaScript applications that have access to a data structure that is automatically synchronized between all participants in the hangout. You can also programmatically manipulate many aspects of the running hangout environment, including adding image overlays to the video streams of hangout participants.
As fun as it is to experiment alone, inviting friends takes baking disasters to the next level. You can distribute the cost of your baking party by making it an ingredient potluck. Your baking friends can each volunteer to bring some of the ingredients for the recipe that you’re going to attempt together.
The process of deciding who will bring what is highly collaborative. It’s a perfect case for a Hangout App. This is a great opportunity to extend your baking disasters web application into a hangout with a Potluck Party Planner app.
Potluck Party Planner allows the hangout participants to select a recipe that they wish to attempt. Next, a list of ingredients is displayed. Participants can then volunteer to bring an ingredient to the party by clicking it. The participant who has agreed to bring the most ingredients is rewarded with a virtual chef’s hat made possible by a media overlay. Finally, participants share a reminder to Google+ with a list of the ingredients that they have volunteered to bring. The flow is shown in Figure 5-1.
A Hangout Application consists of three parts, as shown in Figure 5-2: a gadget.xml
specification file, JavaScript for the real-time interaction, and optional
server-side APIs. These APIs allow your hangout application to access data
that resides outside the hangout.
You’re probably eager to dive into the code, but to save headaches later you need to take care of a few things first. There are special considerations you must take into account when hosting them.
The sample applications provided by Google all run on Google App Engine. App Engine provides a great place to host Hangout Apps, but it does not support PHP and hence can’t run Baking Disasters. To achieve tight integration between your web application and Hangout App you will need to make some changes. You’ll need to serve files over SSL and configure cache control headers.
SSL is a must both during development and later when your application is in production. Hangouts themselves run in SSL. You’re free to create hangout apps that include content served over unencrypted HTTP, but web browsers do not like it when you do this. Even in web browsers configured with the default security settings you’ll see mixed content warnings and even errors resulting from files failing to load.
You must serve your static files and API requests over SSL. There are a few ways to do this. If you’re running on a subdomain of a shared domain from your provider, as is the case with Google App Engine’s appspot.com, you can leverage their existing wildcard SSL certificate. If that’s not available or you are running on your own domain, you’ll need to purchase and install an SSL certificate.
If this is a lengthy process, there is an alternative that you can use during development. You can use a self-signed SSL certificate and force your web browser or operating system to trust it.
In addition to being able to serve content over SSL, you should have control over the HTTP cache headers. While you develop your hangout application you’re going to be going through the familiar code, deploy, and test loop. In fact, you’re going to be doing it quite rapidly. The hangout APIs will respect cache directives in serving these files. This means that the default cache directives set by your web server will probably make development quite cumbersome. While you are developing your application you should disable caching, but don’t forget to then it back on when you’re ready to release your application to the world!
With these two issues taken care of, you’re ready to build.
Now that you know what you’re going to build and where you’re going to host the code, it’s time to start hacking. Starter apps are a great place to start, and the Hangouts API is no different. As mentioned above, the Google-provided sample applications are all intended to be hosted on Google App Engine, but with a few tweaks you can convert them to your PHP environment.
Download a copy of the starter app from the hangouts sample apps page: https://developers.google.com/+/hangouts/sample-apps. This archive contains several files, as shown in Figure 5-3.
Two YAML files contain App Engine configuration. Because we’re not
running on App Engine, you can ignore them. The app.js
and app.xml
files in the static folder contain
most of the Hangout App code. Following the instructions in the included
readme, update the URLs inside of them to refer to your web host.
The initial contents of app.js
in Example 5-1 become Example 5-2, and so on
elsewhere in the starter project files.
Example 5-1. The initial contents of app.js
var
serverPath
=
'//YOUR_APP_ID.appspot.com/'
;
function
countButtonClick
()
{
Example 5-2. app.js with a server path specified
var
serverPath
=
'//bakingdisasters.com/potluck-party-planner/hangoutstarter/'
;
function
countButtonClick
()
{
Next, open main.py
. As shown in
Example 5-3, this file contains a simple AJAX
handler.
Example 5-3. A simple AJAX handler in Python for App Engine
class
MainHandler
(
webapp
.
RequestHandler
)
:
def
get
(
self
)
:
# Set the cross origin resource sharing header to allow AJAX
self
.
response
.
headers
.
add_header
(
"Access-Control-Allow-Origin"
,
"*"
)
# Print some JSON
self
.
response
.
out
.
write
(
'{"message":"Hello World!"}\n'
)
def
main
()
:
application
=
webapp
.
WSGIApplication
([(
'/'
,
MainHandler
)],
debug
=
True
)
util
.
run_wsgi_app
(
application
)
if
__name__
==
'__main__'
:
main
()
Reproducing it in PHP is easy. Create a file named index.php
in the root of your project with the
code in Example 5-4.
Finally, deploy the updated starter project to your web host.
There is one last step that you must complete to run the Hangout App starter project. You must enable the Hangouts API on the Baking Disasters API console project. Follow these steps to configure your API console project and start the hangout in development mode.
Return to the service panel for Baking Disasters on the API console: https://developers.google.com/console#:services.
Enable the hangouts API on the services panel by toggling the switch to ON as shown in Figure 5-4.
Switch to the newly revealed Hangouts panel shown in Figure 5-5. Paste the URL for your deployed app.xml
into the Application URL
field.
Scroll to the bottom of the page, as shown in Figure 5-6. Click Save and then the Enter a hangout link to initiate a development mode hangout running your starter project.
If you’re asked about permissions, grant them to allow the hangout app to start.
The application, shown in Figure 5-7, is quite
simple. It consists of a count that is synchronized across all users,
and a Get Message button, which uses AJAX to fetch
the response from index.php
.
Alongside the features contained in your starter project there are a some development tools. The Reset app state button deletes the contents of the shared state, and the Reload app button reloads the app’s html and JavaScript, but it does not clear the shared state. If you are not careful with your code, using these buttons independently may result in some unusual behavior.
To fully experience the starter application, now is a good time to recruit a friend to join your hangout. Since your application is still in development mode, you must add your collaborators to your development team on the API console. You can use the team panel, shown in Figure 5-8 of the API console to add your collaborators: https://developers.google.com/console#:team
Once you have added them to your project, restart your hangout and invite them to join you.
As simple as the starter app is, it contains basic usage of all of the APIs that you will need to write the potluck planner app. It uses the shared state and callbacks to maintain the synchronized count and makes AJAX calls to APIs hosted server side.
The starter project provides a solid foundation. It’s time to mold
it into the potluck party planner. Taking a bottom-up approach this
involves enhancing the AJAX handler to communicate recipes and
ingredients, modifying the HTML in app.xml
, and then adding JavaScript to
synchronize participant state and manipulate the HTML. This design is
shown in Figure 5-9.
Once the core interaction features are implemented, you can loop back and add enhancements like the ingredient reminder sharing and the chef’s hat overlay for the top contributor.
The hangout app needs data from Baking Disasters to function. Specifically, it needs access to a list of all available recipes and a way to list the ingredients required for each of those recipes.
REST APIs are a great way to communicate data to AJAX
applications. Implementing simple REST APIs is quite easy in PHP. Create
a new file, api.php
. Start by adding
some headers to turn this php file into a REST API, as shown in Example 5-5.
Example 5-5. HTTP headers that allow a PHP file to be used as an AJAX handler
<?
php
header
(
"Access-Control-Allow-Origin: *"
);
header
(
"Content-type: application/json"
);
include_once
(
"util.php"
);
Use GET parameters to control which data you will return. If recipes is set, you know that the hangout is asking for a list of all available recipes, so fetch them from the database, convert, and print them into the response, as shown in Example 5-6.
Example 5-6. Print a list of all recipes as JSON
if
(
$_GET
[
'recipes'
])
{
$recipes
=
list_recipes
();
$response
=
array
();
foreach
(
$recipes
as
$recipe
)
{
array_push
(
$response
,
array
(
'id'
=>
$recipe
[
'rowid'
],
'name'
=>
$recipe
[
'name'
],
'imageUrl'
=>
$recipe
[
'photo_url'
]
));
}
}
If ingredients is set, use the id parameter to look up the ingredients for that recipe and respond with them in the same way, as shown in Example 5-7.
Example 5-7. Print a list of ingredients for a recipe as JSON
}
else
if
(
$_GET
[
'ingredients'
])
{
$recipe
=
get_recipe
(
$_GET
[
'id'
]);
if
(
$recipe
)
{
$response
=
array
(
'name'
=>
$recipe
[
'name'
],
'ingredients'
=>
explode
(
"
\r\n
"
,
$recipe
[
'ingredients'
]));
echo
str_replace
(
'\/'
,
'/'
,
json_encode
(
$response
));
}
}
And finally, fall through to a simple error if neither are set, as shown in Example 5-8.
Example 5-8. Respond to all other requests with a 404 status code
}
else
{
header
(
"HTTP/1.0 404 Not Found"
);
echo
'{"error":"not found"}'
;
}
This single file API provides all of the data access that the hangout application needs.
In practice on your application, the implementation of any APIs you provide will be different. If you’re using an MVC framework, it probably provides an easy way to expose entities in your application as simple APIs.
Hangout apps, at their core, are HTML web applications that run
inside of an iframe. All user interaction is made through HTML. The
initial state of this HTML is specified inside the app.xml
file and manipulated as the app runs
by JavaScript.
The potluck party planner has two screens: one for recipe selection and another for ingredient selection. An easy way to model this is to create both screens within the initial HTML. Use CSS to hide them at startup and then use JavaScript to display at the right time.
Merge the header from Baking Disasters into app.xml
to provide a foundation for further
features. Example 5-9 shows the result of this
merger.
Example 5-9. The result of merging the Baking Disasters layout with the app.xml file from the Hangout Apps starter project
<!DOCTYPE html>
<html>
<head>
<title>
Baking Disasters 2.0</title>
<script
src=
"//hangoutsapi.talkgadget.google.com/hangouts/api/hangout.js?v=1.0"
>
</script>
<script
src=
"//bakingdisasters.com/potluck-party-planner/final/app.js?foo=bar"
>
</script>
<script
src=
"//apis.google.com/js/plusone.js"
></script>
<link
rel=
"stylesheet"
href=
"//bakingdisasters.com/potluck-party-planner/final/style.css"
/>
<link
rel=
"shortcut icon"
href=
"//bakingdisasters.com/potluck-party-planner/final/images/logo_favicon.png"
/>
</head>
<body>
<header
class=
"blog-header"
>
<img
id=
"blog-logo"
src=
"//bakingdisasters.com/potluck-party-planner/final/images/logo.png"
/>
<h1>
Baking Disasters</h1>
<p>
Because sometimes molecular gastronomy explodes.</p>
</header>
<section
class=
"content"
>
</section>
<footer>
By<a
href=
"http://plus.google.com/116852994107721644038"
>
Baking Disasters</a>
</footer>
</body>
</html>
With this foundation created add some content panes. The design calls to start with a list of recipes, but calling the recipe list API may take some time so start with a content panel that displays a loading message, as shown in Example 5-10.
Example 5-10. A loading message that is displayed while the Hangout App starts up
<section
class=
"content"
id=
"loading"
>
<p>
Hang in there! Stuff is still loading.</p>
</section>
Once the app has finished loading it should display a list of recipes. Create a section for this but make it hidden by default, as shown in Example 5-11.
And finally, once the recipe has been selected, users need a way to volunteer to bring ingredients. The HTML for the ingredients panel is shown in Example 5-12.
Example 5-12. HTML for an empty ingredients list
<section class="content" id="ingredient-list-content" style="display: none;"> <h2> <a href="javascript:recipeUnselect()">←</a> Recipe selected: <span id="recipe-name"></span> </h2> <p>Select stuff to bring:</p> <table class="who-bring-what"> <thead> <tr><th>Who</th><th>What</th></tr> </thead> <tbody id="claim-list"></tbody> </table> </section>
JavaScript is the glue that puts this interface and the Baking Disasters REST APIs together, in a hangout.
You can put JavaScript in app.xml
too, but it quickly becomes
cumbersome. It’s easier to break it out into a separate file. Create a
new file, app.js
, and include it into
the head
element, as shown in Example 5-13.
When the hangout starts up, Potluck Party Planner must be initialized to render the list of recipes that available for selection. The Hangout API can be configured to call a function once loading is complete using the function shown in Example 5-14.
Within the init
function you
register a callback that executes when the Hangout API has finished its
own initialization. Within this function you can register other
callbacks, as shown in Example 5-15. In an application as
simple as this one, you can funnel the initialization and other
callbacks into a single updateUi
function, as shown in Example 5-16.
Example 5-15. Registering more specific callbacks within the init function
function init() { var apiReady = function(eventObj) { if (eventObj.isApiReady) {// set up the handlers for callback from the hangout gapi.hangout.data.onStateChanged.add(function(eventObj) {
updateUi(); }); gapi.hangout.onParticipantsChanged.add(function(eventObj) {
updateUi(); }); // Render the initial interface updateUi();
gapi.hangout.onApiReady.remove(apiReady);
} }; gapi.hangout.onApiReady.add(apiReady);
}
Register the apiReady
function to execute when the Hangouts API is ready for
instructions.
Wait for the API to become fully ready.
Update the user interface whenever the shared state changes.
Update the user interface when participants join or leave the hangout.
Render the initial user interface.
Remove the apiReady
callback to ensure that it does not run again.
Initially, the function shown in Example 5-17 renders the recipe list.
Example 5-17. Render the list of recipes fetched from app.php into the empty recipes list and make it visible
function renderRecipes() { // Toggle the recipe list to on document.getElementById("loading").style.display = "none";document.getElementById("ingredient-list-content").style.display = "none"; document.getElementById("recipe-list-content").style.display = "block"; var recipes = getRecipes();
var recipeList = document.getElementById("recipe-list"); recipeList.innerHTML = "";
for (var i in recipes) {
var recipeElement = document.createElement("li"); var recipeImage = document.createElement("img"); recipeImage.src = recipes[i].imageUrl; recipeElement.appendChild(recipeImage); recipeElement.appendChild(document.createTextNode(recipes[i].name)); recipeElement.id = "recipe_" + recipes[i].id; recipeElement.addEventListener("click", recipeSelect, false); recipeList.appendChild(recipeElement); } }
Toggle the interface to display the recipe list section and hide everything else.
Fetch the list of recipes from the simple API on Baking Disasters.
Clear the current recipe list if any. It may have since the last invocation.
Create HTML elements for the recipes and insert them into the recipe list.
The most important function of app.js
is to respond to participant actions
and to update everyone’s user interface. The Hangout API’s shared state
API takes care of the heavy lifting. All you need to do is update the
state in response to user actions and it will automatically trigger
callbacks in every participant’s app. This includes the participant who
triggered took the original action. The recommended sequence is shown in
Figure 5-10.
It is important that you never update the shared state from a function that is directly or indirectly executed as the result of a state change. This results in an infinite loop.
Participants can take three possible actions when using potluck party planner. They can select or unselect a recipe and they can volunteer to bring an ingredient. The interface HTML already has JavaScript methods specified for these actions, so all you need to do is fill in the implementations.
Recipe selection, shown in Example 5-18, happens first. When a participant clicks on a recipe, the hangout app determines the recipe that they selected. Next, it makes an API call to Baking Disasters to fetch the ingredients for that recipe.
Once all of the important details about the selected recipe are
known, you can update the shared state by calling gapi.hangout.data.submitDelta()
. The shared
state can only store strings, though, so all complex data, like the
ingredients list, must be converted into a string to be stored.
Example 5-18. Respond to user clicks on a recipe in the list
function recipeSelect() { var recipeId = this.id.split("_")[1]; var ingredientResponse = getIngredients(recipeId);var delta = {'recipeName':ingredientResponse.name};
var ingredients = new Array(); for(var i in ingredientResponse.ingredients) { ingredients.push({"claimedBy":null, "ingredient":ingredientResponse.ingredients[i]}); } delta['ingredients'] = JSON.stringify(ingredients);
gapi.hangout.data.submitDelta(delta);
}
Use submitDelta()
whenever
multiple keys are updated rather than many calls to setValue()
. Shared state updates are limited
to several updates per second, but submitDelta()
only counts as one
call.
Unselecting recipes is much simpler. No data needs to be fetched
from any API. Simply clear the ingredients list from the shared state
using the gapi.hangout.data.clearValue()
function, as
shown in Example 5-19.
Example 5-19. Return to the recipes list by clearing the ingredients list
function
recipeUnselect
()
{
gapi
.
hangout
.
data
.
clearValue
(
'ingredients'
);
}
When the shared state has been updated, the Hangouts API will trigger the callback function you registered earlier. Update this function to render the correct panel based on the new value in the shared state, as shown in Example 5-20.
Volunteering for ingredients follows a very similar flow, except
since only one value is updated in the shared state, gapi.hangout.data.setValue()
is
sufficient.
The basic flow is ready for testing. Deploy the code and start a hangout. It will look something like Figure 5-11. You and your friends can select a recipe and volunteer for ingredients together.
As it’s implemented, the final list of ingredients that each person selected is lost at the end of the hangout. The share link can help close this loop by allowing participants to post a reminder to Google+.
Content that is posted to Google+ must have a publicly accessible
URL. A simple way to implement this is to create a reminder.php
file, as shown in Example 5-22. It accepts reminder text as a GET parameter and
formats it for sharing on Google+ with schema.org markup.
Example 5-22. A PHP page that displays reminders, which are passed in via a GET parameter
<!DOCTYPE html> <html> <head> <title>Baking Disasters 2.0</title> <link rel="stylesheet" href="style.css"/> <link rel="shortcut icon" href="images/logo_favicon.png" /> </head> <body> <header class="blog-header"> <a href="index.php"><img id="blog-logo" src="images/logo.png"/></a> <h1>Baking Disasters</h1> <p>Because sometimes molecular gastronomy explodes.</p> </header> <section class="content" itemscope itemtype="http://schema.org/Thing"><h2 itemprop="name">Potluck Reminder</h2> <p>Remember to bring:</p> <pre itemprop="description"> <?= htmlspecialchars(urldecode($_GET["reminder"]), ENT_QUOTES, 'UTF-8'); ?>
</pre> <div style="clear: both;"></div> </section> <footer> By <a href="http://plus.google.com/116852994107721644038">Baking Disasters</a> </footer> </body> </html>
For large ingredient lists, this implementation will produce very large URLs. A more robust implementation might involve POSTing a reminder and creating a shareable URL that refers to that reminder by ID.
Wiring reminder.php
into the
hangout app involves adding a place for the share link to render and
rendering a share link with the GET parameter populated by the current
ingredient list.
Add a placeholder reminder button to app.xml
in the head element of the interface
HTML, as shown in Example 5-23.
Example 5-23. A placeholder share link
<a
style=
"display: none;"
id=
"reminder-share-link"
href=
"https://plus.google.com/share?url=http://bakingdisasters.com"
>
Share a shopping reminder on<img
src=
"https://www.gstatic.com/images/icons/gplus-16.png"
alt=
"Share a shopping reminder on Google+"
/></a>
Next, add a function to app.js
that updates the placeholder reminder link to share the list of
ingredients that the current user has committed to bring, as shown in
Example 5-24.
Example 5-24. JavaScript that updates the share link to the participants ingredient list
function renderReminderButton() { var ingredients = JSON.parse(gapi.hangout.data.getValue('ingredients'));var participantId = gapi.hangout.getParticipantId(); var reminderText = gapi.hangout.getParticipantById(participantId).person.displayName; reminderText += " will bring: "; for (var i in ingredients) { if(ingredients[i].claimedBy == participantId) { reminderText += ingredients[i].ingredient + "%0A";
} } if(reminderText.length > 0) { var reminderShareLink = document.getElementById("reminder-share-link"); var reminderUrl = "https://plus.google.com/share?url=" +
encodeURIComponent(BACKEND_BASE_URI + "/reminder.php?reminder=" + encodeURIComponent(reminderText)); reminderShareLink.href = reminderUrl;
reminderShareLink.onclick = function() {
window.open(reminderUrl, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600'); }; reminderShareLink.style.display = "inline";
} }
Recall the ingredients list from the shared state.
Crudely URL-encode the list of ingredients claimed by the current participant.
Construct the share link URL.
Update the href
attribute of
the reminder link to point to the share link URL.
Add a JavaScript callback to open the share link in a new window.
Display the share link if it is still hidden.
This share link must be updated in response to ingredient selection.
Add it to the updateUi()
function, as
shown in Example 5-25.
Example 5-25. The updateUi function enhanced to add the reminder share link.
function
updateUi
()
{
// If there's no ingredient state, display the recipes list
if
(
gapi
.
hangout
.
data
.
getValue
(
'ingredients'
)
==
null
)
{
renderRecipes
();
}
else
{
renderIngredients
();
renderReminderButton
();
}
}
The next time you run the hangout, a reminder link will appear. Clicking on the reminder link will bring up a share window. Share it with yourself, or whoever does your grocery shopping. An example reminder is shown in Figure 5-12.
The shared state APIs provide you everything you need to get the job done, but hangouts are supposed to be fun. You can use the media APIs to make the application a little more fun and provide an incentive for users to volunteer to bring ingredients: during ingredient selection, the user who has volunteered to bring the greatest number of ingredients to the party will be rewarded with a chef’s hat that appears in their thumbnail to everyone in the hangout.
Media overlays are created from images and later attached to a user’s face with a different API call. You can create media overlays dynamically with any URI, even a data URI, but this use case can be satisfied by reusing a single overlay. Create the overlay at startup from a static image and store it into a global variable for use later, as shown in Example 5-26.
Example 5-26. Create a chef’s hat overlay during initialization and store it into a global variable
var chefHatOverlay;... function createHatOverlay() {
var chefHat = gapi.hangout.av.effects.createImageResource( BACKEND_BASE_URI + '/images/chef_hat.png'); chefHatOverlay = chefHat.createFaceTrackingOverlay( {'trackingFeature': gapi.hangout.av.effects.FaceTrackingFeature.NOSE_ROOT, 'scaleWithFace': true, 'rotateWithFace': true, 'scale': 4, 'offset': {x: 0, y: -0.3}}); }
This code specifies some seemingly arbitrary values for the attachment point, scale, and offset. These parameters behave consistently, but it’s usually easiest to guess some reasonable values for your image and make adjustments to the working application.
Now that you have an overlay to apply, you must tell the app when to
render it. The app already has an updateUI()
function that updates the interface
after a user selects an ingredient. Use the same logic to trigger the
assignment of the chef’s hat, as shown in Example 5-27.
Example 5-27. Recalculate the chef’s hat assignment within the updateUi+ function
function
updateUi
()
{
// If there's no ingredient state, display the recipes list
if
(
gapi
.
hangout
.
data
.
getValue
(
'ingredients'
)
==
null
)
{
renderRecipes
();
}
else
{
renderIngredients
();
assignChefHat
();
renderReminderButton
();
}
}
Each time an ingredient is selected you can inspect the shared state to determine the participant who has volunteered to bring the greatest number of ingredients. Next, loop through the participants, assign the hat to the top contributor and clear out the hats worn by everyone else. Example 5-28 shows how to do this.
Example 5-28. Determine the leading participant and assign them the chef’s hat
function assignChefHat() { var ingredients = JSON.parse(gapi.hangout.data.getValue('ingredients'));var totals = new Array(); for(var i in ingredients) {
var ingredient = ingredients[i]; var person = ingredient.claimedBy; if(person != null) { if(totals[person]) { totals[person]++; } else { totals[person] = 1; } } } var hatOwner = null; var currentMax = 0; for(person in totals) { if(totals[person] > currentMax) { currentMax = totals[person]; hatOwner = person; } } console.log(hatOwner + " gets the hat with total of " + currentMax); if(hatOwner == gapi.hangout.getParticipantId()) {
chefHatOverlay.setVisible(true); } else { chefHatOverlay.setVisible(false); } }
Deploy the code, start a hangout, and select a few ingredients to see a chef’s hat render above your head, as shown in Figure 5-13.
In this exercise you applied a media overlay. The media APIs provide other features that may be useful in your application. You can play sounds in the hangout. The sound APIs leverage the noise-canceling features of the hangout to ensure they are only heard by the correct participants. You can also affix images to the thumbnail view itself instead of the participants’ faces. You can even access the coordinates of the participants’ facial features programmatically. You can learn more about the available media API methods in the Hangout API reference documentation: https://developers.google.com/+/hangouts/api/gapi.hangout.av.effects
The Hangouts API is quite broad. It has many other methods and callbacks related to other hangout features. You can change the state of cameras and microphones, make your app react to changes in the On Air broadcast state of the hangout, and even embed video feeds into your main application pane. The best way to explore these features is to pursue the API reference documentation: https://developers.google.com/+/hangouts/api/gapi.hangout
Now that you have a working hangout application, it’s time to allow people outside of your development team to use it. Follow these steps to make your hangout app public.
Return to the API console, https://developers.google.com/console.
Open the Hangouts panel for your application.
Provide URLs for the Privacy Policy, Terms of Service, and Contact fields.
Create a Chrome Web Store account and verify it by paying a one-time $5 fee, as shown in Figure 5-14.
Check the published checkbox on the API console, shown in Figure 5-15, and click Save.
Create and add the hangout button to your website. Start by copying your application ID from the URL in the address bar on the API console, as shown in Figure 5-16.
Create a button with that ID. The resulting markup is shown in Example 5-29.
Example 5-29. HTML markup for a hangout button
<a
href=
"https://plus.google.com/hangouts/_?gid=1234567890"
style=
"text-decoration:none;"
>
<img
src=
"https://ssl.gstatic.com/s2/oz/images/stars/hangout/1/gplus-hangout-20x86-normal.png"
alt=
"Start a Hangout"
style=
"border:0;width:86px;height:20px;"
/>
</a>
Add it to a webpage, such as the Baking Disasters main index as shown in Figure 5-17, and click the button to start a hangout.
Use the built in hangout invitation features to invite your friends. You can now plan your potluck disaster.