Hotstar X: Let’s talk {widgets}!

Unblock Hotstar in UAE

Our previous post outlined key motivations on why it was imperative for us to think about a rewrite of our stack. In this post, we will geek out on key fundamentals of how we represented visual elements of our product for deployment via our server architecture.

We wanted greater agility in releasing new features and experiences for all our customers across all our platforms, without being tightly coupled to app release and adoption cycles.

This pushed us towards server driven UI (SDUI) and reusable UX components (aka widgets) that could be built once but composed and reused multiple times to unleash new experiences.

Photo by Ashkan Forouzani on Unsplash

The idea is simple — let your server send all the information on how to render the experience. If you oversimplify, the conventional web stack where the server vends out a fully hydrated HTML response object is a type of SDUI.

What is a widget, really? Is every single visual element a widget on its own? Is it just a property of an encapsulating widget? For instance, within a form widget, is the submit button a widget or just a property of the form widget?How granular should one go? The answer lies in picking the conceptual data models that you choose.

Also, a choice has to be made on how far along will one go on to vend out rendering information from the server. How much of the layout, rendering, behavioural properties will be controlled by the server vs. the client? Do we degenerate SDUI to become a pseudo HTML vending machine?

We settled on some key tenets to ground these discussions:

  1. Client are “above average”: While we want the client to be as lean and reusable as possible, it is okay for the client to know about the layout and some behavioural aspects of a widget. For instance, the pixel positions of elements within a widget are client side knowledge and server needn’t control them. Likewise, for complex interactions such as playback play and pause, client will take full control of managing those interactions rather than the server sending javascript or native code fragments to control those actions.Each client is a reasonable powerful edge device.
  2. Widget “Concern”: A widget is a customer facing interaction unit, that packages function and behavior. Therefore, elements like heading, text-input button, etc. are not widgets by themselves but parts of a widget. Units such as image carousel (we refer to them as trays), ads-units, subscription-plans comparator are widgets that can be consumed as independent yet complete UX features.
  3. Widgets can composed: It is okay for a widget to contain child widgets and this composition unlocks powers of the SDUI wherein we leverage reusable components. However, this is in direct tension with tenet no. 2 and we need to avoid the desire for extreme widgetization. A rule of thumb we used was: wear your design hat and ask if you’d ever use this sub-component on its own or drop-in as-is within another UX unit. If not, it’s not a child widget but rather a property of the widget. If you are unsure, better consult your product and design stakeholders to vet the assumptions of reuse.

With these rules grounded, we moved on to identify our core domain model aka the page response spec.

We settled on three core concepts that allowed us to model various pieces of information architecture (IA) and UX flexibly between the client and server.

  1. Page: A page is the main view on the client screen which the customer sees/interacts with. This is the top-level container.
  2. Space: A space is a sub area on a page, whose position (x, y, z), size is pre-defined. Spaces are containers used to divide the page for a logical IA and allows the server to think in terms of “slots” that can be used to drop-in “compatible” widgets. The spaces can have other UI constraints and predetermined behaviours on user interactions such as scroll. These constraints are pre-defined and cannot change at run-time.
  3. Widget: A widget is a functional interface through which user interacts with the application. It exposes certain elements which can be controlled by backend and is compatible with certain space only. i.e. you can’t fit any widget in any space. This compatibility is defined ahead of time and known to the server.

Following is a visual representation of how the Hotstar home-page gets broken down to these concepts:

Page / Spaces / Widgets in Spaces
Child/nested widgets: Each item in the carousel is a widget on its own

We then laid out rules on how each of these conceptual models would be understood and exchanged between client and server. Essentially, the server needs to vend out a page response that talks in terms of these entities so that the client can then render them.

  1. Page Layout Type : Every page will have an associated layout type that is a detailed spec with details like:
  • What spaces appear on the page and where (by platform)
  • Limits like min/max widgets that can be accommodated in each space
  • How will the layout differ in case of portrait/landscape mode on mobile clients

2. Space Type: A set of enums to represent various types of “slots” that a page layout may contain. The association of what page layout contains supports what spaces is known on the server and client side.

3. Widget Type/Widget Template: The widget template spec defines:

  • The UI/UX of the widget
  • The schema of the data returned by the server for widgets of this template
  • Details of what fields returned by the server should be data bound to what components of the widget
  • Recursive links to specs of templates of each possible nested widget that this widget may contain.
  • All possible interactions with the widget, along with animations, etc.

Each widget type can have a different UI based on the platform, owing to form factor differences, or platform interaction models. But the UI, UX of the widget is expected to serve the same purpose on all platforms.

Additionally, which widget templates are compatible with what space-type is known on the server side ahead of time.

These known types and type associations then allow the server to give a page response that contains all the required data and metadata for the client to compose a page out of its spaces, wherein the spaces compose themselves from their constituent widgets and so on.

The widget object along with the widget template type contains three crucial pieces of information that allows a widget to truly come to life:

  1. Widget data: This is the data that the client uses to paint the widget. Note that a widget may be rendered using different templates on different devices (e.g. when browsing content trays, web platforms will show an expanded image with content details in hover state whereas app will only show poster images with minimal content data). Depending on the chosen template, the widget data model may vary accordingly.
{
id: "",
template: "REGULAR_SCROLLABLE_TRAY",
version: "1.0.0",
data: {
"title": "Best of Superheroes",
"num_rows": 2 // 1-3,
"itemType": "", //ITEM_CONTENT_CARD, ITEM_CONTENT_POSTER, ...
"widget_items': [ // all items of the same type
{ITEM_CONTENT_CARD}// or ITEM_CONTENT_POSTER or ...
],
},
pagination: {
"nextPageUrl": "...",// nullable -> returns more {items[], pagination}
},
}

2. Widget Actions: A widget that just renders and shows something is good, but interactivity sparks life into it. This is where actions come in. Actions are predefined and well understood interactions that a widget supports and reacts to. What to do on a particular action can be communicated by the server using some known types. Here are some standard action types that we support as of today:

  1. Navigation
  2. Animations
  3. Standard Macros like logout, Add to Watchlist
....
"action": { // there can be multiple actions like click, hover, long press
"onclick": {
"type": "navigation", // on click can have multiple effects like animation, navigation
"data": {
"type": "to_page", // this tells the kind of navigation, like to_page, to_widget, to_focus_widget, to_widget_on_space
"data": { // this data schema will be specific to the type above
"page_type": "detail",
"uri": "/in/movies/avengers/12345678"
}
}
}
}
....

3. Widget Analytics: In order to measure the performance, engagement and effectiveness of our experiences, we perform tonnes of instrumentation at page, space and widget levels and collect a bunch of interesting data from client side experiences. This is the third critical bit of data in the widget response (more on this in a separate blog).

Putting it all together, here’s how a sample page response from the server would look like

{
"success": true,
"data": {
"page": {
"page_type": "landing",
"spaces":[
{
"space_id":"Tray_spacev1"
"space_type": "tray",
"data": {
"next_page": "/v1/pages/{page_id}/spaces/{space_id}?offset=10",
"max_widgets": "10",
"min_widgets": "5"
},
"widgets": [
{
"template": {
"id": "Regular_Scrollable_tray",
"version": "1.2.10"
},
"data": {
"title": "Best of Superheroes",
},
pagination: {
"nextPageUrl": "?offset=5 &limit=10",// nullable -> returns more {items[], pagination}
},
"widget_items": [
{
"template": {
"id": "content_card",
"version": "3.0.3"
},
"data": {
"image": "http://images.hotstar.com/avenger_endgame",
"action": {
"onclick": {
"type": "navigation",
"data": {
"type": "to_page",
"data": {
"uri": "/in/movies/avengers/12345678"
}
}
}
}
},
{
"template": {
"id": "content_card",
"version": "3.0.3"
},
"data": {
"image": "http://images.hotstar.com/captain_america",
"action": {
"onclick": {
"type": "navigation",
"data": {
"type": "to_page",
"data": {
"uri": "/in/movies/captain_america/345678"
}
}
}
}
}
}],
]
}

Alright, so a quick recap on what we’ve learnt so far about the Hotstar X architecture:

  1. We adopted a widget based architecture and defined tenets to drive the framework.
  2. We identified key components, their boundaries and properties including what aspects would be known to the server vs. the client.
  3. For a widget, we identified and defined the three critical property sets that allow us to send out a complete widget data model from the server.
  4. We wrapped up with a sample end to end page response data model that could be understood and rendered by the client.

Once this was done, we were able to kickstart the development on the client and server side. In the next article, we will deep dive into our server side architecture and how we went about building our flavour of a Backend For Frontend (BFF) architecture.

Want to code with the authors of this blog? Write your own? Work on mind-melting, tough engineering problems? We’re always hiring! Check out openings at https://careers.hotstar.com/.