Hangouts

Against

Humanity

(viewing link: http://bit.do/hah2014)




Cards Against Humanity


A party game for horrible people

Pick a card


           
      

Repeat


And Repeat


2-card hands


3-card hands


Google+ Hangouts


A cross-browser, cross-OS platform for video chat applications

Extension Apps


All HTML/JS/CSS

Extension Apps


The Server



How to deal with multiple card sets?


Lay out card sets on the server and deliver them with Go (Google App Engine hosted because REASONS)


https://hangouts-against-humanity.appspot.com/white?sets=base+first+second
https://hangouts-against-humanity.appspot.com/black?sets=base+first+second

Dealing with Game State


Many items to watch:

  • What's the score?
  • What's the current question?
  • Can I still submit cards?
  • Am I the current reader?
  • Who has submitted questions?
  • Who won the round?

gapi.hangout.data



State managed through this namespace and methods

gapi.hangout.data.getState()
> Object {
    Holmes : 'Elementary, my dear Watson',
    Terminator : 'Hasta la vista, baby'
  }
 
State represented as JSON-based key-value store

gapi.hangout.data


submitDelta lets you set several keys at once
gapi.hangout.data.submitDelta({
    foo: 'bar',
    lorem: 'ipsum'
  });
gapi.hangout.data.getState();
> Object {
    Holmes : 'Elementary, my dear Watson',
    Terminator : 'Hasta la vista, baby',
    foo : 'bar',
    lorem : 'ipsum'
  }

gapi.hangout.data



submitDelta can also optionally remove keys
gapi.hangout.data.submitDelta({}, ['Holmes', 'Terminator']);
> undefined
gapi.hangout.data.getState();
> Object {
    foo : 'bar',
    lorem : 'ipsum'
  }

gapi.hangout.data


Helper methods:
gapi.hangout.data.getValue('foo');
> 'bar'
gapi.hangout.data.setValue('foo', 'baz');
> undefined
gapi.hangout.data.getState();
> Object { foo : 'baz' , lorem : 'ipsum' }
gapi.hangout.data.clearValue('foo');
> undefined
gapi.hangout.data.getState();
> Object { lorem : 'ipsum' }

gapi.hangout.data


How to listen to changes in the State
gapi.hangout.data.onStateChanged.add(function(ev) {
  console.log(ev.state);
});

ev object contains the new state, as well as valuable metadata
ev = {
  addedKeys: [ Metadata for all added/changed keys in this update ],
  removedKeys: [ All removed keys in this update ],
  state: { Key-value store of the current shared State },
  metadata: [ Metadata for all keys in the state ]
}  

onStateChanged and Angular

Watch all items I want to track for game state:

gapi.hangout.data.onStateChanged.add(function(evt) {  var questionChanged = false,      readerChanged = false;
angular.forEach(evt.addedKeys, function(metadata) { questionChanged = (metadata.key === QUESTIONKEY ? true : questionChanged); readerChanged = (metadata.key === READERKEY ? true : readerChanged); });
if(questionChanged || readerChanged) { $rootScope.$apply(function() { gameState.question = evt.state[QUESTIONKEY]; gameState.reader = evt.state[READERKEY]; }); }});

Meanwhile, in the Controller

function AmICurrentReader() {
  return gameState.reader === LOCAL_PARTICIPANT_ID;
}
function CurrentQuestion() {
  return gameState.question;
}

$scope.$watch(AmICurrentReader, function(IAmCurrentReader) {
  if(IAmCurrentReader) {
    // You're the reader, do the right thing
  }
});

$scope.$watch(CurrentQuestion, function(newQuestion) {
  // Question has changed, update the viewmodel
});

Things not covered


Angular-time vs. Non-Angular-time
(Who $applys, after all?)

Removing Listeners

State oddities
(Timing, not true JSON representations, abstracting away)

gapi.hangout



General-purpose namespace describing information about the Hangout, participants in the Hangout, and other information

gapi.hangout.getStartData();gapi.hangout.hasAgeRestriction();gapi.hangout.onChangeTopic.add(eventHandler);

Participants


Need to track participants who are engaged in the app, and
differentiate from those not engaged (ie. enabled)

Need to react to people being enabled or disabled

gapi.hangout

getEnabledParticipants()
gapi.hangout.getEnabledParticipants();
> [ Array of Participants who have enabled the application ]
Participants
participant = {
  id : 'Unique Identifier for this Participant in this Hangout',
  displayIndex: '0-based index of participant in the filmstrip',
  person: {
    id: 'Google+ ID of person',
    displayName: 'Display name of participant',
    image: {
      url: 'URL of participant's avatar'
    }
  }
}

gapi.hangout


getLocalParticipant()
gapi.hangout.getLocalParticipant();
> { The Participant who is running the app }


getParticipantById(HANGOUT_ID)
gapi.hangout.getParticipantById(HANGOUT_ID);
> { The Participant who is identified by HANGOUT_ID };

gapi.hangout.onApiReady



Fires when API is fully initialized
Used to trigger Angular code
gapi.hangout.onApiReady.add(function(eventObj) {
  if (eventObj.isApiReady) {
    angular.bootstrap(document, ['ModuleName']);
  }
});

Audio/Video effects



Play sounds locally or globally

Add visual information to the outgoing video feed

Face-and-hand-tracking info

Control aspects of the local participant's audio/video

gapi.hangout.av


Lets you make modifications to the user's A/V stream, including camera, microphone, speakers and volume levels

gapi.hangout.av.muteParticipantMicrophone(HANGOUT_ID);gapi.hangout.av.isAudible();gapi.hangout.av.onMicrophoneMute.add(function() { ... });

gapi.hangout.av.effects

Add effects to a participant's A/V stream


Get an Image Resource
gapi.hangout.av.effects.createImageResource('http://google.com/favicon.ico');


Get an Audio Resource
gapi.hangout.av.effects.createAudioResource('http://myapp.com/wah-waaah.wav');

Image Resources

Apply ImageResources to the video feed with Overlays
googleFavIconResource.showOverlay({
  position: { x: 0.5, y : 0.5 },
  rotation: Math.PI / 2,
  scale: {
    magnitude: 0.1,
    reference: gapi.hangout.av.effects.ScaleReference.WIDTH
  }
}

Multiple Overlays per Resource are possible! 

Face-Tracking Images!


Face-Tracking Overlays


googleFavIconResource.showFaceTrackingOverlay({
  trackingFeature: gapi.hangout.av.effects.FaceTrackingFeature.MOUTH_CENTER,
  offset: { x: 0, y: 0 },
  rotateWithFace: true,
  rotate: 0,
  scaleWithFace: true,
  scale: {
    magnitude: 0.15,
    reference: gapi.hangout.av.effects.ScaleReference.HEIGHT
  }
});

Audio Resources


Play AudioResources on the audio stream with Sounds

wahWaaahResource.createSound({ localOnly: false, loop: true, volume: 0.5 });wahWaaahSound.play();
 
Sadly, only very specific audio files are recognized
(at this time)



Putting it all together

(demo-time!)



Things I've Learned



(developer bomb-throwing ahead!)






vim is the only editor sane people use






Two space indentation or GTFO






Java is bloated, verbose, and difficult to grok.
It's days are numbered.






Javascript is a perfectly fine language.
Your framework sucks.

References



API Documentation


Show these guys some love





Thanks!

Hangouts against humanity

By Mike McElroy

Hangouts against humanity

  • 3,295