How To Sync User Experiences In Live Streams

Devin Dixon
BingeWave

--

The future of online experiences will involve real-time two-way interactions between the participants to create more engaging experiences. Immersive experience needs to be in real-time and connect the users in a fluid way.

This tutorial will cover building a widget that will create an interface that keeps all participants in sync with the on-screen content. We will use a human anatomy map as an example for this application where when a user clicks on a part of the body, the clicked area will be highlighted for all users.

Video Tutorial

In addition to this written tutorial, the lesson can be watched in the video tutorial presented below.

Getting Started And Github Repo

To get started, for developers that are new to building interactive widgets with BingeWave, please visit this short explainer on the Widget Builder and the process of building interactive solutions:

https://developers.bingewave.com/widgets/gettingstarted

All of our tutorials are accompanied by a Github Repo to assist developers in creating their widgets. To view the repository for this tutorial, please see here:

https://github.com/BingeWave/Bingewave-Widget-Anatomy

Creating The Image Map

To get started building our widget, we will use abdchehadeh’s solution found at their Github Repo here. Their codebase has an HTML Area map that will be the starting point of our development and showcase how to convert existing code into a widget for live experiences.

The first thing we are going to do is create an image map. Because maps are not used very often in development, we will review them quickly. In HTML, there is a tag called <map>, which divides an image into several clickable areas using the <area> tag. The <area> tags are positioned with coordinates over the image, allowing the ability to precisely define clickable shapes and boundaries.

Looking at the index.html file in the Github Repo, copy and paste just the map into the HTML editor on the Interface Builder. Very important, original code from the repository has the HREF attribute left blank will goes the code to change the page. Either remove the HREF attribute or assign the value to an ‘#’.

Image Maps must be associated with an image, and the repository provides an image to be associated with the image map. The image should be stored in a CDN and referenced inside the application. We have added this image for you to use here. Utilize it in your HTML as such:

<img id=”{{namespace}}_body_image” src=”https://bw-pubic-images.s3.amazonaws.com/males-1859518_960_720.jpg" usemap=”#image-map”>

In the above code, notice the {{namespace}}, which utilizes two of BingeWave’s features of a namespace and a dynamic placeholder. Every widget has a unique identifier called a namespace. Namespaces are used to prevent collisions if two widgets use the same attributes, ie HTML ids. Dynamic Placeholders are temporary values that are swapped out with real data at runtime. In the above example, we ensure the HTML id is unique by using the namespace and using a dynamic placeholder.

Finally, at the bottom of our code, we are going to add the following from the index file with a few modifications from the original code:

Again, we use namespace and a dynamic placeholder to create a unique reference. In this div, we will place the values of the objects when they are clicked on the image map.

The final HTML inside the editor should look like this:

Now we can move on and quickly implement some CSS to add style to our widget.

Implementing The CSS

The CSS editor can load CSS onto the screen widget’s screen. For this tutorial, our CSS is relatively straightforward. Copy and paste the following into the CSS Widget editor:

This CSS merely indicates how the image map will act when a user hovers over the image and some of border coloring. Next, we will get to the meat of our application, the Javascript.

Image Mapping in Javascript

In our next step, we need a mechanism for listening to when parts of the image map are clicked and how the app should respond. To begin, there is a JQuery library called ImageMapster. We are going to use the library in this widget. Start with finding the CDN for the ImageMaster and pasting the CDN url in the Javascript Libraries section at the bottom of the editor.

The ImageMapster will now be loaded into widget’s session at runtime. Next, head into the Javascript editor and start writing code. Start by pasting the following two references:

//Container for holding selected parts
let selectionsID = ‘#’ + BWProperties.namespace + ‘_selections’;
//The id of the image
let imageID = ‘#’ + BWProperties.namespace + “_body_image”;

Remember the namespaces we defined in creating our ids in HTML? We are now going to reference the namespace in Javascript. BWProperties is a special object passed into all widgets that contains essential information about the widget and session. One piece of information is the namespace identifier that we reference above in a string.

Next, we will utilize the IDs by calling the image and the selection area using JQuery and the ID.

//The image
let image = $(imageID);
//Items currently selected
let selectedItems = [];
$(selectionsID).html(selectedItems.length > 0 ? “<b>Selected body Parts: </b>” + selectedItems : “<b>Please select a body part</b>”);

To break down the above code, we get the image by the image id created before. Then, we create a selectedItems as an array and using the selectionsID, we will populate the selected items div with items from the selectedItems array. Now for the moment everyone has been waiting for….the code for the ImageMap!

In the above code, we take our image and use JQuery ImageMapster to make our image map controllable via JavaScript. ImageMapster provides many options that we will not cover in this tutorial but encourage reading into the documentation to learn more. In this tutorial, we will focus on the onClick function.

Every time an element is clicked, we check if it is in the selectedItems array using the inArray function. If it’s not, we add it; otherwise, we remove it. After, we output the results to the selectedItems div. This function will be important later because we will use to sync the experience between multiple users.

If you start the test area up at this point, you will play around with a clickable body image. Congrats on completing the first part! Next, we will sync actions across all users utilizing the widget.

Publishing and Subscribing To Events

Right now, we have a problem where if one user clicks the ImageMap, the map is not highlighting for the other users, creating a disjointed experience. We want this image map to be synced for all users, meaning when one user clicks it, a part of the body lights for all the users. To add this feature, we will use publishers and subscribers, also known as BWEvents.

BWEvents is a messaging tool used to communicate between different widgets and users in the session. A subscriber is when an event is being listened to and receives a message, and a publisher sends a message to all the listeners (subscribers). To start, add the following code to the top of the Javascript editor:

//Event Publisher/Listener when part is selected
let selectAreaEvent = BWProperties.namespace + ‘_area_selected’;
//Event Publisher/Listener for when a part is deselected
let deSelectAreaEvent = BWProperties.namespace + ‘_area_deselected’;
//Record events published
let sentEvents = [];

The selectAreaEvent will be the name for an event when a user selects an area on the image map. The deSelectAreaEvent will be a user unselects an area on the image map. When events are created, we will record their IDs in the sentEvents array.

Let us start with sending the messages with events. Messages are sent with BWEvents.publish function, and it requires the identifier of the event and the data to be sent as the message related to the event. Inside the onClick function, let’s add the following two functions:

Breaking apart this code, if the item is in the array, we want to deselect it on the ImageMap, remove it from the array and then publish a deSelectAreaEvent that will be sent to all the other users listening. The message being sent is the key-value pair of an area that is associated with the image map item pressed, in the form of an object {area: e.key}.

BWEvents.publish(deSelectAreaEvent, {
area: e.key
}).then(response => {
if (response.id) {
sentEvents.push(response.id);
}
});

Publish events return promises, and every message sent has a unique ID. We will store that event id in the sentEvents array.

Next, if the clicked area is not in the selectedItems array, we want to store it in the selectedItems and send a message to the other users that the area has been selected. Again, the message being sent is the key-value pair of an area that is associated with the image map item pressed, in the form of an object {area: e.key}.

BWEvents.publish(selectAreaEvent, {
area: e.key
}).then(response => {
if (response.id) {
sentEvents.push(response.id);
}
});

Now that we have completed how to publish events and send messages, we need a way for every other user to listen and receive the message. At the bottom of our Javascript code, we are going to add two subscribers to listen when messages have been sent. First, we are going to add the subscriber for when an area is selected:

In the callback, the event object is passed in as the first parameter, and the id of the event is passed in as the second parameter. Remember in the previous step how we store the event id in the sentEvents array?

In the function, we check for the event id to ensure that the user who is sending the message is also not executing it when the event is published.

If the event id is not stored, we used Mapster to highlight that area of the body on the image map and store the highlighted area in the selected area. The deselect listener works similarly:

Here we check to make sure the event id is not in the sentEvents array, and then we have Mapster unselect the area and remove it from the selectedItems array.

Congratulations! Every user’s image map will now be synced to show the areas that are selected and unselected. To finally complete this widget, we need to work on the state.

Setting The State

Our widget still has a slight problem; if a user becomes disconnected or joins late, their information will not be up-to-date. This is because Published and Subscriber events do not last forever and are ephemeral only when they are sent. To solve this, we use what is known as a state.

All widgets have what is known as a state, which is persistent data maintained in the widget. In this tutorial, we will use the state by saving data to it and then pulling that data out when the application loads.

BWState has two functions, a set and get. The set takes in a key-value pair where the key is the identifier to access the value later, and the value is data to be stored. We are going to add the setState to the onClick as such near the bottom of the function:

In the set function on line 27, we are storing the selectedItems array. Now we need to add a function that when a user joins, we pull the latest items and highlight them for the users:

In the code above, we use the same key, ‘selected_items’, to retrieve any items that have been stored. First, we check to make sure the promises response was a success and that data exist. After we iterate through the array’s data, use the Mapster to highlight the map’s areas. We now have an application that can be used to keep users in sync.

Creating Synced Experienced

The goal of this tutorial was to give a simple example of how to create synced experiences between users who are viewing the same experience. Creating real-time two-way engagement will help build powerful applications that users will feel in-tuned with experience.

Utilize the concepts learned with BWEvents and BWState further develop your widgets along with our other tutorials.

--

--