Tech Archives - Streamhub.co.uk https://streamhub.co.uk/category/tech/ Streamhub.co.uk Mon, 30 Mar 2026 09:28:43 +0000 en-GB hourly 1 https://streamhub.co.uk/wp-content/uploads/2019/07/cropped-favicon-32x32.png Tech Archives - Streamhub.co.uk https://streamhub.co.uk/category/tech/ 32 32 171833033 Architecting Scalable Chart Modules in Streamhub Analytics: Part II – Strategy & Factory Patterns https://streamhub.co.uk/architecting-scalable-chart-modules-in-streamhub-analytics-part-ii-strategy-factory-patterns/ Mon, 30 Mar 2026 09:07:55 +0000 https://streamhub.co.uk/?p=35398 Reading Time: 4 minutes

The post Architecting Scalable Chart Modules in Streamhub Analytics: Part II – Strategy & Factory Patterns appeared first on Streamhub.co.uk.

]]>
Reading Time: 4 minutes
In the previous post, we introduced the architectural challenges behind supporting multiple chart types within a scalable dashboard solution. We established that different visualisation libraries require data in different structural formats. This mismatch makes the transformation layer a critical part of the architecture.

In this post, we focus on how the system decides which transformation logic to apply for a given chart type. Two design patterns make this possible: Strategy and Factory. Together, they allow us to select and execute the correct transformation algorithm at runtime without introducing tight coupling or complex conditionals.

 

Strategy Pattern: Chart-Specific Data Transformations

The Strategy pattern allows us to define a family of algorithms (transformations), encapsulate each one, and make them interchangeable. In our chart module, each chart type has its own transformation strategy responsible for converting raw report data into a format suitable for the rendering library.

 

Structure

The implementation revolves around a contract interface that defines two key methods:

// Interface defining the strategy contract 
export interface IChartDataTransformer 
{  
  /**  
  * Determines if this transformer supports the given chart type.  
  * Each transformer checks if it can handle the visualization type.  
  */  
  supports(chartType: string): boolean;  
 
  /**  
  * Transforms raw report data into chart-specific format.  
  * Each transformer implements its own transformation algorithm.  
  */  
  transform(reportData: any, chartType?: string, report?: any): any; 
}

Each chart transformer implements this interface:

@Injectable({ providedIn: 'root' })
export class BarChartTransformer implements IChartDataTransformer {
  supports(chartType: string): boolean {
    // Returns true for bar chart type identifiers
  }

  transform(reportData: any): any[] {
    // Analyzes dimensions and metrics
    // Normalizes data structure
    // Applies color mapping
    // Enriches with metadata
    // Returns chart library-compatible format
  }
}

 

What Each Strategy Handles

Each transformation strategy encapsulates logic specific to a chart type. This includes:

  • Dimensional Analysis: Understanding how many dimensions and metrics are present, which affects grouping and series construction.

  • Data Normalisation: Converting the consistent API response into the structure expected by the specific visualisation library (e.g., row-based data for ag-Grid vs. series-based arrays for Highcharts).

  • Metadata Enrichment: Adding display labels, tooltip formatting, and contextual information derived from report metadata.

*Quick note: At first glance, this example may appear to violate the Single Responsibility Principle. However, in practice, the transformer acts as an orchestration layer, delegating specific responsibilities to dedicated services. The example is simplified to keep the focus on the pattern rather than internal service composition.

 

Benefits

  • Each chart type can evolve independently without impacting others.

  • Adding support for a new chart only requires implementing the interface—no changes to existing code.

  • Clear separation of concerns between transformation and rendering logic.

  • High testability: each strategy can be unit-tested in isolation using mock datasets.

 

Factory Pattern: Selecting the Right Transformer

While strategies encapsulate transformation logic, we still need a mechanism to select the correct strategy at runtime. This is where the Factory pattern comes into play.

The factory acts as a lookup mechanism that returns the appropriate transformer based on the chart type, without consumers needing to know about concrete implementations.

 

Structure

We use an injection token to collect all chart transformers, and a factory that receives them via dependency injection:

// Injection token for collecting all chart transformers
export const CHART_TRANSFORMERS = new InjectionToken<IChartDataTransformer[]>(
  'CHART_TRANSFORMERS'
);

@Injectable({ providedIn: 'root' })
export class ChartDataTransformerFactory {
  constructor(@Inject(CHART_TRANSFORMERS) private transformers: IChartDataTransformer[]) {}

  getTransformer(chartType: string): IChartDataTransformer | null {
    // Searches for transformer where supports() returns true
    // This is the common method enforced by the interface defined earlier.
    return this.transformers.find(t => t.supports(chartType)) || null;
    // Returns null if no transformer found
  }
}

The factory is used by the main chart data transformer (which participates in the pipeline we’ll see in the next post):

@Injectable({ providedIn: 'root' })
export class ChartDataTransformer implements ReportTransformer {
  constructor(private chartFactory: ChartDataTransformerFactory) {}

  isApplicable(reportData: any): boolean {
    // Checks for visualization type presence
  }

  apply(reportData: any): any[] {
    // Gets transformer from factory
    const transformer = this.chartFactory.getTransformer(..);
    if (!transformer) {
      // Throws error if no transformer found
    }
    // Delegates to transformer's transform method
    // Remember, this is part of the contract defined earlier in strategy pattern.
    return transformer.transform(..);
  }
}

 

How It Fits Together

The factory leverages Angular’s dependency injection to receive all registered transformers via the custom token. It acts as a registry and lookup: it doesn’t create transformers itself—they’re registered elsewhere (we’ll cover that in a future post). The factory is completely decoupled from specific transformer implementations.

When no transformer is found for a given chart type, the system throws a descriptive error rather than returning null. This fail-fast approach ensures bugs are caught early and production errors are clearly identifiable.

 

Benefits

  • Decouples transformer selection from usage—consumers don’t need to know which transformer to use

  • Centralised logic for transformer selection—single point of control

  • Easy to extend with new chart types—just register a new transformer

  • Type-safe transformer selection through TypeScript interfaces

  • Strategies are unaware of each other; the factory is unaware of strategy implementation details

 

Conclusion

Pairing Strategy with a simple Factory keeps the chart transformation layer flexible and disciplined. Each chart type encapsulates its transformation logic behind a common interface, while the factory selects the appropriate strategy at runtime. This approach creates a fail-fast system that detects misconfigurations early and allows new visualisations to be added without modifying existing code.


Ravindra Soman

Ravindra Soman

Senior Full Stack Data Engineer

Ravindra is a Senior Full Stack Data Engineer specialising in frontend architecture and complex data visualisation systems. In his spare time, he likes to cook new dishes for his family, read fiction and travel the world.

The post Architecting Scalable Chart Modules in Streamhub Analytics: Part II – Strategy & Factory Patterns appeared first on Streamhub.co.uk.

]]>
35398
Architecting Scalable Chart Modules in Streamhub Analytics: Part I https://streamhub.co.uk/architecting-scalable-chart-modules-in-streamhub-analytics/ Mon, 16 Feb 2026 17:17:35 +0000 https://streamhub.co.uk/?p=35241 Reading Time: 4 minutes

The post Architecting Scalable Chart Modules in Streamhub Analytics: Part I appeared first on Streamhub.co.uk.

]]>
Reading Time: 4 minutes

Problem Space & Design Goals

In modern web applications, data visualisation is a critical component that requires flexibility, maintainability, and extensibility. When building a charting system, supporting multiple chart types across heterogeneous rendering requirements quickly becomes an architectural challenge.

In our case, the system needed to support 15+ chart types that users could dynamically add to dashboards. Each chart could be powered by different charting libraries, and although the API responses were consistent, each library expected the input data in its own specific format. What initially seemed like a UI concern soon revealed itself to be a deeper issue involving data transformation, extensibility, and long-term maintainability.

This post, focuses on the challenges we faced and explains the architectural direction we chose. In the next posts we’ll see how we addressed them with a small set of design patterns.


 

Technical Context 

Our Analytics product is built using Angular as the primary frontend framework. For visualisation, we support multiple libraries depending on the widget type, including AG Grid for tabular data and Highcharts for basic chart-based visualisations, along with a few custom-rendered charts.

While the API responses remain consistent, each library imposes its own expectations on how data should be structured. This divergence in data contracts across libraries became a central factor in shaping the overall architecture.


 

 

The Core Mismatch: Same Data, Different Library

Expectations

One of the key challenges was that different visualisation components required the same dataset to be shaped differently.

For example, consider a dataset fetched from a consistent API:

[

    {"device": "android", "views": 12,000},

    { "device": "iphone", "views": 15,000 }

]

 

A data-grid widget powered by ag-Grid expects a flat row-based structure:

rowData = [

    {"device": "android", "views": 12,000},

    { "device": "iphone", "views": 15,000 }

];

However, a bar chart rendered using Highcharts expects a categorized structure:

series = [{

    name: "views",

    data: [12000, 15000]

}];

categories = ["android", "iphone"];

The underlying API data remains consistent, but each library requires a distinct transformation. As the number of supported chart types and libraries grows, this transformation layer becomes a central architectural concern rather than a simple formatting step.

 

 

Why Naive Approaches Break Down

A typical early implementation often resembles this:

switch (chartType) {

    case 'bar': return transformToHighchartsBar(data);

    case 'grid': return transformToAgGridRows(data);

    case 'line': return transformToHighchartsLine(data);

}

In practice, this switch statement and the corresponding transformation logic lived inside a single class responsible for orchestrating chart rendering. Over time, this class accumulated multiple responsibilities:

  • Deciding which chart type to render

  • Transforming raw API data

  • Handling library-specific formatting

This violated the Single Responsibility Principle and tightly coupled transformation logic with rendering decisions. Every new chart type or library variation requires modifying existing logic, violating the Open/Closed Principle and increasing the risk of regressions.


 

Core Challenges

 

1. Data Transformation Complexity

Although the API responses are consistent, each visualisation library enforces its own structural contract. Supporting multiple libraries means handling:

  • Structural reshaping (row-based vs series-based vs hierarchical)

  • Aggregation or grouping logic specific to chart types

  • Consistent normalization so that transformations remain predictable

The transformation layer must therefore be reusable and decoupled from the rendering library’s internal expectations.


 

2. Extensibility and Maintainability

As new chart types or visualisation libraries are introduced, the system should extend without requiring modifications to existing transformation logic. Without a modular design, each addition risks breaking unrelated visualisations.

Extensibility is therefore not just desirable—it is essential for long-term maintainability.


 

3. Shared State Interactions

Charts rarely operate in isolation. They react to shared dashboard inputs such as filters, metrics, and time ranges. Without clear architectural boundaries, state changes can trigger cascading re-computations that are difficult to trace and debug.

A scalable solution must ensure that state orchestration does not leak into transformation logic.


 

4. Separation of Concerns

These challenges highlighted the need for a clear separation of responsibilities across layers. Mixing decision-making, data shaping, and rendering logic within a single class created tight coupling and reduced clarity about where specific responsibilities belonged.

A more layered approach was necessary to ensure each concern could evolve independently.


 

From Naive to Scalable: Direction of Evolution

These challenges made it clear that the existing structure could not scale. The architectural evolution looked like this:                     

 

Naive Approach

In the naive approach, both decision-making and data shaping were embedded in a single class. This made the system rigid and prone to errors as new chart types were introduced.

               

 
Target Direction
 
 

The target direction separates responsibilities into distinct layers, allowing each concern to evolve independently while reducing coupling and technical debt.


 

Design Goals and Evaluation Criteria

Before settling on an architectural approach, we defined a set of guiding criteria:

  • Extensibility: Supporting a new chart type should not require changing existing transformations

  • Reusability: Common data shaping logic should be shareable across chart variants

  • Library Agnosticism: Switching or adding charting libraries should not affect core transformation logic

  • Testability: Data transformation steps should be independently unit-testable

  • Predictability: Shared dashboard state should not cause uncontrolled recomputations

These constraints ruled out monolithic conditional logic and pushed us toward a more modular, pattern-oriented architecture.


 

Architectural Direction (High-Level)

Based on these goals, we converged on three guiding ideas:

  1. A strategy-driven transformation layer to encapsulate chart-specific data shaping
  2. A factory-based resolution mechanism to select appropriate transformations dynamically
  3. An adapter layer to isolate charting library dependencies from core logic

This direction allows the system to support new chart types and libraries incrementally, without rewriting existing components.

In next post, we will see how we implemented right transformation logic for each chart type.

Ravindra Soman

Ravindra Soman

Senior Full Stack Data Engineer

Ravindra is a Senior Full Stack Data Engineer specialising in frontend architecture and complex data visualisation systems. In his spare time, he likes to cook new dishes for his family, read fiction and travel the world.

The post Architecting Scalable Chart Modules in Streamhub Analytics: Part I appeared first on Streamhub.co.uk.

]]>
35241
Tech Webinar: RxJS Essentials for Angular Developers https://streamhub.co.uk/tech-webinar-report/ Wed, 25 Oct 2023 16:25:34 +0000 https://streamhub.co.uk/?p=34685 Reading Time: 2 minutes    We conducted our first front-end focused tech webinar titled ‘RxJS Essentials for Angular Developers’ on October 8th. It was focused on the fundamentals of RxJS library and its benefits organised by our Angular experts Ravindra, a Senior Full stack data engineer at Streamhub, and Luis, our Senior FE engineer. Here is the video […]

The post Tech Webinar: RxJS Essentials for Angular Developers appeared first on Streamhub.co.uk.

]]>
Reading Time: 2 minutes

 

 

We conducted our first front-end focused tech webinar titled ‘RxJS Essentials for Angular Developers’ on October 8th. It was focused on the fundamentals of RxJS library and its benefits organised by our Angular experts Ravindra, a Senior Full stack data engineer at Streamhub, and Luis, our Senior FE engineer.

Here is the video and technical summary bellow.

We started the discussion by providing a high level overview of the Angular framework. This was followed by a succinct explanation about the need for reactive programming and how RxJS library helps developers in achieving that objective. Also, the audience for a deep-dive into each fundamental concept of RxJS.

 

1. Observable

An observable is a unicast object which passes stream of data from data source to the observer.

 

2. Observer

An observer, is an object with set of callbacks, which consumes the data passed by an observable.

 

3. Subscription

Subscription is an object which contains reference to the subscription to an observable.

 

4. Subject

Subject is a special type of observable which supports multicasting.

 

5. Scheduler

Scheduler helps in defining an execution context to control when observer will be notified about new data.

 

6. Operators

Operators are a set of functions that help in easy composition of complex asynchronous code. All these concepts were bolstered by many coding examples and some interesting real life anecdotes. These examples were especially designed to help developers use correct concepts in different situations. e.g. use of observable vs. use of subject, using different operators to make code easier to write and manage.

 

 

To summarise, this webinar gave its audience a brief but in-depth overview of RxJS and reactive programming. We believe these concepts will surely help developers in designing complex solutions in a more elegant and efficient way.

 

Finally, we are looking for engineers to join our team at Streamhub!
Here is currently available positions. If you are interested, or you know anyone who would be, feel free to get in touch.

The post Tech Webinar: RxJS Essentials for Angular Developers appeared first on Streamhub.co.uk.

]]>
34685
The Quest To Unify Measurement: Adding Live + Cloud DVR viewing data https://streamhub.co.uk/unifying-measurement-adding-live-cloud-dvr-services/ Thu, 05 May 2022 12:30:04 +0000 https://streamhub.co.uk/?p=33822 Reading Time: 3 minutes

The post The Quest To Unify Measurement: Adding Live + Cloud DVR viewing data appeared first on Streamhub.co.uk.

]]>
Reading Time: 3 minutes
live streaming sports

More services = need for better measurement

Last week Streamhub launched a new suite of analytics and post-campaign reporting for Live streaming and cloud DVR services that combines it with VOD reporting to provide deduplicated and incremental reach measurement across devices. 

Live streaming reports are not new but this marks a major strategic step forward for the commercial broadcasters who can now provide fully consolidated reporting across VOD and Live streaming for both ads and programmes. 

The Challenges

Live streaming provides incremental inventory and reach to the TV channels through its dynamic ad insertion proposition. The move towards real-time dynamic ad insertion(DAI) represents a significant technical challenge, where client-side reporting is essential to provide accurate reporting compared to server-side log-based reporting. 

As streaming services and data sources proliferate the provision of a user-friendly analytics and reporting experience was of high importance, especially being a service  used by both sell-side and buy-side.

Moreover, there was a need to provide data orchestration as-a-service to clean up and automate all the different viewing logs from the different broadcasters and to standardise this data against the online digital currency panel to provide cross-service and cross-device reach and frequency metrics. 

The market implications

Prior to 2022, the legislation forbade broadcasters from providing a 24/7 live online stream of licensed linear broadcast channels in Japan. Now, however, broadcasters have finally executed their ambitions to make Live & DVR content a core part of their respective OTT offerings. 

For viewers:

Not only are they gaining access to timely Live and DVR content to enjoy, but there will also be a far more personalised advertising and content experience than could be achieved through traditional linear TV. 

For Ad sales: 

Broadcasters now have a wider pool of targetable inventory to sell, and importantly, more contextual audience data to use for activation – leading to more choice for the Advertisers and to lead to optimised yields across all inventory. 

For Editorial and Marketing: 

Furthermore, marketing and editorial teams are also benefiting greatly from the added level of contextual insights for Live and DVR consumption which can be analysed alongside VOD and linear TV audiences. This kind of apples-to-apples parity in reporting opens the door for a more nuanced understanding of the habits and behaviours of different audience segments, which if utilised well, will ultimately power a more engaged relationship between audiences and the streaming services they use every day.

How is Streamhub supporting?

Streamhub is providing the following: 

  • Data orchestration as a service
  • Panel fusion methodology across Live + VOD
  • Multi-source Analytics and reporting platform 
  • Presenting multiple viewing data types in a single platform
  • Activation platform for addressable advertising

Better measurement strategy

Maximising monetisation and audiences through connected services is a global agenda for all broadcasters to counter the halt in the growth of traditional TV advertising – especially in markets where linear TV targeting is unavailable. The Japanese market is no exception to this and the ultimate goal to protect and increase CPMs is a key mission with the introduction of the Live / cloud DVR services as the Linear TV CPMs are unusually low compared to other key markets. 

The Japanese market may be latecomers to the game, but by taking the time to consider the rollout carefully, they have been able to implement an advanced data and tech strategy where the Live streaming, DAI system and Analytics stack are shared between them to keep costs manageable but above all, as there is consensus that there is no point competing with the tech piping. 

Next steps

Through Streamhub’s shared reporting and data stack, broadcasters can now focus on developing new addressable propositions across all their connected devices and start working on incremental reach methodologies to include a diverse set of linear TV data including set-top-box data and panel data into its ad proposition.

Thanks for reading to the end – we hope this article has been insightful. For more information about Streamhub’s data services and products, head to our contact page or drop an email to bizdev@streamhub.co.uk

The post The Quest To Unify Measurement: Adding Live + Cloud DVR viewing data appeared first on Streamhub.co.uk.

]]>
33822
Technical challenges behind Streamhub’s Audience Segment Builder https://streamhub.co.uk/technical-challenges-behind-streamhubs-audience-segment-builder/ Tue, 23 Nov 2021 17:15:01 +0000 https://streamhub.co.uk/?p=33100 Reading Time: 10 minutes

The post Technical challenges behind Streamhub’s Audience Segment Builder appeared first on Streamhub.co.uk.

]]>
Reading Time: 10 minutes

activate segment builder

During the summer of 2020, we decided to build Streamhub’s audience segment builder, which is a key component of our latest product module – Activate – currently available for beta users.

In the process of doing so, we faced some interesting challenges, which constitute the inspiration for this blog post.

But, wait … First, what is an audience segment builder?

Let’s assume a hypothetical customer that has programs and advertisement logs. This customer might also have CRM data, QoS or Panel data. Let’s say to simplify that the CRM data includes fields like gender, age, email, and subscription model for each of that customer’s user base.

Now, let’s consider this customer wants to target the users that are males, on premium or unlimited devices subscription, and have watched “Prison Break” for more than 20 hours in the last month.

With the audience segment builder, we can select dimensions from the datasets that the customer exposes and visually build a query with groups and nested subgroups, including program’s dimensions conditions (here, `have watched “Prison Break”`) as well as user-based conditions (here, `male users on Premium or unlimited devices subscription`) metrics conditions (here, `timeWatched > 20h`) or time-based conditions (here, `in the last month`) in the query.

From the system perspective, the output of the segment builder is a JSON payload that represents an Activate query. From the customer perspective, the output is a list of user identifiers distributed over 3 possible categories: cookies, device identifiers (AAID, IDFA), and PPID (Publisher Provided Identifiers)

This is what the audience segment builder user interface looks like nowadays.

activate segment builder

 

Figure 1: defining a segment in the audience segment builder Activate

 

A very first internal iteration of the audience segment builder UI looked like

old streamhub audience builder

Figure 2: Proof-of-Concept UI for the audience segment builder

You can see quite a few changes were made, just by looking at the 2 screenshots! But what changed and why?

Simplified and intuitive UX is the key for an audience segment builder

 

Let’s get this out of the way, the first internal version of our audience segment builder was not very user-friendly. From the 2nd screenshot, it’s obvious, just looking at the colours or the way the tool is supposed to be manipulated, with the user first having to select one of the 3 operators “All”, “Any” and “None” and then only configure the values for a particular condition.

Frontend developers among our readers might already have spotted the Angular Tree component is at use here. The whole experience was built upon the capabilities of the Angular tree component. Using a tree component was very helpful to develop the PoC quickly. However, it comes at a price: a ridiculously poor user experience.

Indeed, to express a query like

Select all the users who have watched “The Crown” and are either female or aged between 18 and 24.

With the old version, the customer would need to:

  1. Select the All operator on the root element
  2. Add and configure a condition node below it as the “have watched ‘The Crown’” condition
  3. Create a sub-group with the Any operator
  4. Add 2 conditions within the sub-group: a.  “Gender is female” / b.  “Age between 18 and 24”

While this way of thinking might be natural for a Lisp programmer, it’s not intuitive for a regular user.

So the first and main motivation to provide a newer UI and UX was to enable a more natural and linear experience similar to how most users think, starting with the condition first (“users that have watched ‘The Crown’”) and then adding an “And” operator if, and only if, a second condition is required. Other factors that led to rewriting this component included a complete change of design and theming for the UI, as well as the need to incorporate new features without being limited by the Angular Tree component or any 3rd party ready-made component.

The problem and objective were set, let’s crack on with it!

 

The design of an Audience Segment Builder

 

From a technical point of view, we knew that the new component would need to satisfy these requirements:

  • It needed to manipulate a hierarchical in-memory tree-like data structure. We will refer to it as the Query Tree for the rest of the post.
  • The query tree would be composed of groups, sub-groups, standalone conditions, and operators.
  • Within a group, the elements could be conditions, operators or sub-groups- Sub-groups could contain other sub-groups and therefore allow for any level of nesting.
  • Different types of conditions would require a different type of Angular component, meaning that the component would need to be dynamically instantiated depending on the context. To illustrate this, let’s consider:
    • a condition like “Device type is mobile, desktop, tv” can be configured with a visual component that requires 2 lists:
      • The list on the left will contain all the possible device values while the list on the right will contain the user-selected values.
    • On the other hand, a condition like “The average time watched is greater than 200 minutes” will require a different type of UI, with inputs that can accept numerical values.
    • Another case could be a condition like “users having watched ‘Breaking Bad Season 1 Ep. 3‘” which would require an autocomplete search component.
  • The system would require high consistency. To illustrate:
    • If a user linearly adds condition1 and condition2 or condition 3 within a single group, we don’t know if the brackets should be placed around (condition1 and condition2) or placed around (condition 2 or condition3).
    • In this case, we ask the user to disambiguate the situation by letting him choose whether he wants all the operators to be ands or ors.
    • Alternatively, the user can also choose to create a nested sub-group within the current sub-group to hold either one of the 2 arrangements of brackets. Sub-groups are a metaphor for brackets.
  • The query tree would need to be marshalled to be persisted or processed by the backend stack. Upon reloading a segment, the query tree would need to be reconstructed (we’ll come back to this in detail)

Key Angular components

What was a nice realisation was that most of the above technical points could be implemented with simple Angular primitives:

  • The NgForOf directive would let us iterate through the elements of a group or subgroup and render them.
  • Angular @Component would let us define a few wrapper components for the elements manipulated by the SB. We defined:
    • GroupComponent, to represent a top-level group
    • RuleSubGroupComponent, which is a metaphor for brackets. It can contain conditions and other RuleSubGroupComponents thus allowing for any level of nested subqueries within a query
    • RuleOperatorComponent, which represents an operator between 2 top-level groups (possible values are and, or, except) or between 2 subgroups (possible values are and, or)
    • ConditionPlaceholderComponent, a placeholder component to add a new condition to the query tree.
    • SegmentBuilderWidgetComponent: used as an envelope to dynamically instantiate any other kind of component, like the TwoListsComponent mentioned in the requirements.
  • @ViewChildren, @Input(), @Output() decorators would let us define clear channels of communication within the component tree
    • Between child and parent (example, condition → enclosing subgroup)
    • Between parent and children (example, subgroup → conditions, subgroup → operators)
    • Between siblings (example, between 2 conditions enclosed within 2 distinct subgroups)
  • Angular’s ComponentFactoryResolver would allow us to dynamically instantiate components depending on the context.

Have a look at a (simplified) version of the HTML involved to render the Segment Builder.

In segment.builder.component.html the root *ngFor loop renders the first level of the Query Tree (the top-level groups):

 
<!-- The segment builder rendering logic -->
<div *ngFor="let element of queryTree"><!-- rendering each node of the query tree --> 
 <group-operator #groupOperator [element]="element" ...></group-operator>
 <group #group (onRemoveGroup)="on...($event)" [element]="element" ...></group>
</div>
<!-- *ngFor -->

Now, let’s deep dive into a Group component’s HTML. The inner *ngFor loop will render the elements within the group.

 
<div *ngIf="isGroup(element)">
 <div ...>{{"GROUP" | translate}}</div>
 <div ...>

   <!-- control-flow components here. skipping that part --> 

   <!-- Group’s inner *ngFor loop will render the elements within a group --> 
   <div *ngFor="let child of element.value" class="...">

     <!-- the element can be a Generic segment-builder-widget-component (an envelope to construct any other components at runtime) -->
     <segment-builder-widget-component #widgets
                                       [element]="child"
                                       [groupData]="element" ...>             
</segment-builder-widget-component>

     <!-- element can also be a rule operator ... ->
     <rule-operator #ruleOperator
                    [siblings]="element.value"
                    [element]="child" ...></rule-operator>


     <!-- … or a placeholder for a new condition ->
     <condition-placeholder
       [element]="child" ...></condition-placeholder>

     <!-- … or a Sub-Group -->
     <rule-sub-group #ruleSubGroup
       [groupData]="element"
       [element]="child" ...>
     </rule-sub-group>

   </div>
   <!-- *ngFor -->
   <!-- a few more controls come here. Skipping that part -->
   <!-- Recency & Frequency component. Skipping that part →
 </div>
</div>

Finally, the most nested *ngFor loop within the rule-sub-group.component.html will render the leaves of the Audience Segment Builder. Note that the structure is recursive here with the rule-sub-group component being a possible element within the inner loop of rule-sub-group.component.html 

 
<div ...>
 <div ...>{{"SUB_GROUP" | translate}}</div>
 <div ...>


   <!-- skipping controls here -->

   <div *ngFor="let child of element.value">

     <!-- nested sub-group -->
     <rule-sub-group #ruleSubGroup
                     ...
                     [element]="child"
                     [groupData]="groupData" ...>
     </rule-sub-group>

     <!-- skipping other nested components ... -->
   </div> </div> </div>

To summarise, given a hierarchical tree data structure (the Query Tree), a couple of user-defined components (group, subgroup, condition, operator, widget), the ngForOf directive, and a few sass-based classes (to materialize the layer of nesting mainly) we can render any segment in the Segment Builder.

Of course, there are other aspects which we are not covering here, for example regarding the flow of events within the SB: adding and removing conditions/operators/sub-groups, managing the internal states of the SB (locking/unlocking the SB when the user operates on the current node), but from the strict point of view of rendering the Query Tree, this is pretty much what was required. 

On top of what Angular already provided, we added 2 services:

  • A TreeAlgorithmService, which implemented generic versions of Depth-First search and Breadth-first search algorithms as higher-order functions. 
    • Being higher-order functions, these functions can accept another function as a parameter to represent the matching criteria to select a node. This is useful to select any kind of node in the query tree based on simple predicates.
  • A MarshallingService, which implemented the logic to transform the query tree into a JSON equivalent payload that could be sent to the API.

Challenges of marshalling and unmarshalling the query tree

As we saw, the in-memory structure that the segment builder is manipulating is called a Query Tree. This is a simple data structure that is made of JavaScript Arrays and Objects. The default query tree when starting a new empty segment resembles this 

[
 {
   id: uuid(),
   nodeType: "GROUP",
   root: true,
   name: "",
   datasetId: -1,
   recencyAndFrequency: {},
   value: [
     {
       id: uuid(),
       nodeType: "RULE_SUB_GROUP",
       root: true,
       name: "",
       value: [
         {
           id: uuid(),
           nodeType: "CONDITION_PLACEHOLDER",
           root: true,
           value: "SELECT_DIMENSION_PLACEHOLDER"
         }
       ]
     }
   ]
 }
]

Figure 3: the query tree manipulated by the audience segment builder in Activate

As we see, any object in it can have a value property that itself contains an array of objects, and therefore can be iterated.

These arrays of objects are literally what the Angular ngForOf directives are iterating over when rendering the DOM. The query tree is very close to the HTML that is rendered in the UI. By contrast, the way the segment is persisted into the DB storage or processed by the backend API is quite different from the query tree. To illustrate, this is how a small portion of a segment looks like once stored in MongoDB: 

{
    "groupOperator": "UNION",
    "groupChildren": [
      {
        "groupRule": {
          "name": "G0",
          "query": {
            "datasetId": {
              "$numberInt": "0"
            },
            "dimensions": [],
            "filters": {
              "children": [
                {
                  "rule": {
                    "dimension": "programs:id",
                    "value": [
                      {
                        "$numberLong": "xxx"
                      }
                    ],
                    "operator": "Any"
                  }

Figure 4: an Activate segment, once stored into MongoDB

Depending on the context, converting one representation into another could get complicated. This is because some parts of a segment will be treated differently by the UI and by the API.

For instance, if a date range is applied to a segment, the API expects the segment payload to contain an additional ‘date’ dimension for each sub-groups defined by the segment, connected by an AND operator with the rest of the conditions in the sub-group. Let’s assume a user has created a segment with 2 subgroups: (condition A or condition B) and (condition C or condition D)

Now, if the user wants to apply a date range restriction to the segment, the generated payload should be set as:daterange and ((condition A or condition B) and  (condition C or condition D))

So there are transformations involved. These transformations are relatively easy to implement in one direction (from Query Tree to API payload) but would be more difficult to implement in the other direction (reconstructing the original user-defined Query Tree from the API payload)

Besides, we wanted to have full flexibility on the UI side of things and provide a flow that is as convenient as possible for the end-user.

To reduce the code complexity and guarantee that once loaded back the segment is faithful to what the user had created, we chose to convert between the UI representation and the API representation in only 1 direction, that is in the marshalling direction.

In practice, what it means is that we store the Query Tree itself (or rather an altered version), as a property of the Segment payload. 

The steps involved;

  1. First, we transform the Query Tree into a binary-equivalent representation. 
  2. Then, we use a lossless compression algorithm to reduce the size of the binary-equivalent representation
  3. Finally, we take the base64 of the compressed binary representation and store it as a property of the Segment payload, namely the uiData property.

By doing that, we save on the marshalling complexity when reloading an existing segment into the Segment Builder because we only need to decode and uncompress the query tree from the API responses into memory, as opposed to having to reconstruct the Query Tree from the API response itself.

Collaterally, we also avoid potentially hard to detect bugs. Given that segments can be created, then loaded back and updated multiple times, any glitch in the marshalling logic would only increase the damage done over time. 

Another advantage of using the Query Tree as an intermediate UI representation of a segment is that it allows performing some optimizations while marshalling it. 

For instance, it becomes possible to simplify the payload to get rid of unnecessary levels of nesting, which in turn, makes processing the payload faster as it takes fewer operations on the backend to handle it.

Below are some of the optimizations we perform when converting the Query tree into the API payload equivalent:

  • If a sub-group X contains a nested sub-group Y which itself contains a single condition C, then the structure can be simplified so that C becomes a direct child node of X.
  • Any group that contains a single direct sub-group can also be flattened so that the sub-groups children are exposed directly
  • Empty sub-groups can be eliminated.
  • Any subgroup that connects its children with the same operator that connects the sub-group with a standalone condition can also be flattened. To illustrate, this is the case where the user has defined: (condition C and (condition D and condition E)) which can be simplified by getting rid of the subgroup surrounding (D and E) =>  (condition C and condition D and condition E)

While we perform these optimizations during the marshalling phase, we do not modify the user-defined query tree. We only reduce the complexity and size of the generated API payload.

When loading back the same segment later in the SB, the segment is presented exactly as it was created originally by the user.

Voila! I hope you have enjoyed this reading. Please let me know if you have any questions!

 
Tony Broyez

Tony Broyez

Senior Full Stack Data Integration Engineer at Streamhub

Tony is an experienced and versatile programmer with more than 15 years of experience mainly in the media/tech industry. His skills have ramifications into data collection and preparation, data computation and export, as well as data visualisations. When he’s not coding, he can be found having fun with his 2 young kids Theo and Tom.

Would you like to work with the guy behind this article? Why don’t you join our engineering team?

Senior Data Operations Engineer (Contract / Part Time / Remote)

Applications Engineer (Microservices|Java|K8s) – PERM / Bengaluru

 

 

Read more blogs:

The post Technical challenges behind Streamhub’s Audience Segment Builder appeared first on Streamhub.co.uk.

]]>
33100
Streamhub grows team and hires for Engineering and Marketing https://streamhub.co.uk/streamhub-grows-team-and-hires-for-engineering-and-marketing/ Tue, 26 Oct 2021 13:49:35 +0000 https://streamhub.co.uk/?p=32710 Reading Time: 2 minutes

The post Streamhub grows team and hires for Engineering and Marketing appeared first on Streamhub.co.uk.

]]>
Reading Time: 2 minutes

From Bangalore to London, Streamhub is speeding up and growing fast, as 2021 sees the team almost double in size and there are still vacancies to be filled in Engineering and Marketing teams.

Have a look if you have the profile and share the opportunity if your network if you know someone who can be among the next talent we hire.

Senior Data Operations Engineer (Contract / Part Time / Remote)

Some of the skills required for this position are:

  • Solid experience in supporting complex data pipelines with technologies like Airflow, AWS, and Kubernetes.
  • Solid experience in operationally maintaining Kubernetes cluster.
  • Experience in working with scripting languages – Python/Ruby
  • Experience in cloud computing, preferably AWS.

Another vacancy open in the Engineering team is:

Applications Engineer (Microservices|Java|K8s) – PERM

This is a position for an Engineer to join our team in Bengaluru, India, or to work remotely.

To apply for this job position you must have:

  • Solid experience in Scala or Java.
  • Exposure to building user interfaces preferably using Angular or ReactJS.
  • Solid experience with Docker, container services like Kubernetes, infrastructure tools like Terraform/Ansible. Including operational handling.

On another note, we are also looking for an industry-savvy, experienced Marketing Analyst to join our efforts on B2B initiatives and to proactively participate in the building of 2022’s strategy and action plans.

Marketing Analyst (London, UK / Full Time) 

This role suits better a generalist with a background in either Ad Tech companies or Broadcasters and those are some of the skills and experience we are looking for on the candidates for this role:

  • Event Organization (On-line/Off-line)
  • Creating and Editing Sales Collateral
  • Google Tag Manager, Mailchimp, Hubspot, Pipedrive, Slack, Trello
  • Content Creation)
  • Media and Partners Relations
  • Market Research
  • B2B Data or Ad Tech Company or OTT/Streaming/Broadcast/Publisher/AdTech Industry
  • Native-level English and UK Work permit

Have a look also on our values to check if you are the perfect match and prepare ahead for the job interview!

Be a part of this exciting startup/scaleup that is accelerating fast, with offices in London, Tokyo and Bangalore and is a restless data-driven and highly creative team and apply today.

 

Read more blogs:

The post Streamhub grows team and hires for Engineering and Marketing appeared first on Streamhub.co.uk.

]]>
32710
How to build an Asynchronous layer over Snowflake https://streamhub.co.uk/an-approach-to-building-asynchronous-services-async-in-next-generation-cloud-data-warehouses/ Tue, 25 May 2021 16:38:26 +0000 https://streamhub.co.uk/?p=32246 Reading Time: 5 minutes

The post How to build an Asynchronous layer over Snowflake appeared first on Streamhub.co.uk.

]]>
Reading Time: 5 minutes

Building an asynchronous solution for your analytic use-cases can be super powerful, especially building a deep-dive on-demand reporting solution. When we started building this at Streamhub, we found very little literature around this, so I am sharing this, hoping people pondering over this, can benefit from it. Our core analytical solution for such on-demand deep-dive use-cases is build upon Snowflake, based on design considerations listed in our previous blog, and our asynchronous layer runs over this.

What is an asynchronous service?

Asynchronous service is one of the common methods of interacting with backend systems for processes which takes a long time to compute. Our async approach is based on polling and involves the 3 simple steps:

  1. Execute the process asynchronously & get the processId immediately.
  2. Poll the status using the processId for every x seconds until the process completes 
  3. Return the response

How to achieve this?

There are a number of ways to implement Async in Snowflake. Here is the method we found to be most effective. It is based on polling, and we made full use of Snowflake’s information_schema.query_history. 

1. Executing an Async query

Scala and Python both have different ways of submitting an async query, Let’s look at both examples

Scala:

Once you get the connection string, using the preparedStatement you can unwrap the SnowflakePreparedStatement which allows you to execute an Async query using executeAsyncQuery() method. Return the queryId back to the service which triggered the sql.

def run(): (queryId, resultSet) = {
 val sqlString = “Select * from dual”
 val conn = getSnowflakeConnection()

 val prep_statement = conn.prepareStatement(sqlString)

 val (query_id, rs) = 
 // run Async query
 if (asyncRequest) {
  val resultSet =  prep_statement.unwrap(classOf[SnowflakePreparedStatement]).executeAsyncQuery
  val asyncQueryId = resultSet.unwrap(classOf[SnowflakeResultSet]).getQueryID
  prep_statement.close()
  conn.close()
  (asyncQueryId, None)
 } 
 // run sync query
 else { 
  val resultSet = prep_statement.executeQuery
  val queryId = resultSet.unwrap(classOf[SnowflakeResultSet]).getQueryID
  (queryId, Some(resultSet))
 }
}

Code: Submitting Async & Sync queries to SF(Scala)

 

Python:

It is straightforward in python, just passing the param _no_results=True will submit an async query

def run():
  sql_string = "select * from dual"
  cursor = get_snowflake_connection()
  if async_request:
     cursor.execute(sql_string, _no_results=True)
     query_id = cursor.sfqid
     return query_id, None
  else:
     cursor.execute(sql_string)
     result_set = cursor.fetchall()
     return query_id, result_set

Code: Submitting Async & Sync queries to SF (Python)

2. Polling for results 

Let’s poll the results, let’s say for every 5s using the queryId. Nice thing about Snowflake is that the history table provides the running status of the query in realtime and it is queryable as well as similar to any other table and it consumes a tiny amount of credits. This can be reduced by adding conditions in where clauses like warehouse, date etc to limit the search. I will leave that to you 

So for every 5 s the below query will be used to check the status. 

"""select query_id, execution_status, error_message from table(information_schema.query_history()) 
   where query_id = '{0}'""".format(query_id)

Sql: to poll history table

Snowflake History UI

Keep polling until the status is in running

def get_query_status(self, query_id):
   sql = """select query_id, execution_status, error_message from table (information_schema.query_history())
   where query_id = '{0}'""".format(query_id)
   try:
       self.cursor.execute(sql)
       record = self.cursor.fetchone()
       if record is not None:
           print(record)
           if record[1] == 'FAILED_WITH_ERROR':
               return 500, record[1], record[2]
           else:
               return 200, record[1], record[2]
       else:
           return 404, "FAILED_WITH_ERROR", "query_id Not found"

   except snowflake.connector.errors.ProgrammingError as e:
       raise Exception('Error {0} ({1}): {2} ({3})'.format(e.errno, e.sqlstate, e.msg, e.sfqid))

Code: to get query status(Python)

3. Returning the response

Once the status is changed from running, we can handle it in different ways based on the status. Here we are interested mostly in Succeeded or Failed.

  • If Succeeded(internally Snowflake returns execution status as SUCCESS), get the results from persisted cache using below query, and format the response according to your api response needs.
        “””select * from table (result_scan(‘{0}’))”””.format(query_id)

Sql: to get result

 

  • If Failed (internally Snowflake returns execution status as FAILED_WITH_ERROR) , the error_message from Snowflake can be directly redirected to the api response with an error code.
def get_results(self, query_id):
   sql = """select * from table (result_scan('{0}'))""".format(query_id)
   print(sql)

   items = []
   try:
       self.cursor.execute(sql)
       result_set = self.cursor.fetchall()
       # get column headers
       columns = [col[0] for col in self.cursor.description]
       total_columns = len(columns)

       for row in result_set:
           row_list = (list(row))
           items.append(dict(zip(columns, row_list)))

       results = {'columns': {
                               'total': total_columns,
                               'columns': columns
                              },
                  'items': items}
       return results

   except snowflake.connector.errors.ProgrammingError as e:
       raise Exception('Error {0} ({1}): {2} ({3})'.format(e.errno, e.sqlstate, e.msg, e.sfqid))

Code: to get formatted results

4. Asynchronous Flow

A simple flow depicting the async service built using Api-Gateway, Lambda, Snowflake

Asynchronous flow diagram 

5. Time to weigh our apporach

Pros:

  • If your processing function is using AWS Lambda then be aware of the Lambda timeout limit which is 15 mins.
  • There is no need to build a logic to know the status of the query. Snowflake has a nice and reliable history table which maintains the status of the query in real time, which can be queried as well.

Cons:

  • There is a small warehouse cost incurred each time a poll request is sent to the history table, whilst insignificant in small volumes this cost can quickly grow.

Hopefully you enjoyed this microblog, and it has helps you in building an asynchronous service on a modern data warehouse like Snowflake. Watch out for more of our technical blogs.

 

Sakthi Murugan

Sakthi Murugan

Senior Data Engineer

Sakthi is a highly experienced and accomplished programmer with a passion for all things Big Data, by night he is also a talented visual effects artist. 

The post How to build an Asynchronous layer over Snowflake appeared first on Streamhub.co.uk.

]]>
32246
SpotX and Google Ad Manager integrations added to Streamhub’s 1st party activation platform https://streamhub.co.uk/new-integrations-extra-connectivity-for-your-ott-audience-data/ Wed, 27 Jan 2021 14:42:12 +0000 https://streamhub.co.uk/?p=31506 Reading Time: 3 minutes

The post SpotX and Google Ad Manager integrations added to Streamhub’s 1st party activation platform appeared first on Streamhub.co.uk.

]]>
Reading Time: 3 minutes

 

What’s All The Fuss About?

Since it’s inception in 2015 Streamhub has been providing online video services with industry-leading analytics. 2020 marked a huge jump forwards when we announced the release of our flagship 1st party DMP module – designed to enable total audience segmentation & targeting in a post GDPR and Cookie-less world.

So we have been hard at work crafting a host of new integrations to make your video audience data richer and more accessible for everyday use in your diverse AVOD and SVOD ecosystems.

Today, we’re super excited to share some good news for our AVOD and Hybrid customers. 

 

Ad Servers & SSPs

Re-write the playbook for your AVOD sales team. With these new ad-server integrations you can offer hyper-targeted audience campaigns making best use of all your 1st party data.

Better targeting = higher CPMs and yields

 

The flagship Ad-Serving platform offering from Google.

The world’s leading specialist video ad-server

What have we enabled?

  • Connect your 1st party audience / video data in GAM or SpotX to improve Ad targeting across all devices.

How does it work?  

  • Link your accounts and decide which data you want to sync.
  • Start creating any audience segments in the user-friendly UI.
  • Send or schedule segment exports to send those custom audiences to your Ad servers or SSPs

What’s the value? 

  • Enhanced user experience through better targeted Ads and Promos.
  • Increased CPMs through more specific, layered segmentation.
  • Reduce wasted ad-spend (increased campaign efficiency).

Specific use cases? 

  • So you’re a BOVD service and your agency wants to target Female iOS users, aged between 18-34, who live in London and have an affinity with Horror movies and have previously watched 5 Samsung Ads? No problem! This multi-layered approach to targeting is exactly what our integrations enable.

 

 

Cloud Storage Integrations

Now everyone in your team has the power to fuse, manipulate and export data. Ultimate control.

What have we enabled?

  • Total control of your 1st party data; demographic information, payment plans & registration data, location, device types, content affinity and so much more (only limited by the data you have consented access to).
  • Export custom segments directly to your Amazon S3 or GCS folder(s) of choice.

How does it work? 

  • Simply connect with your cloud credentials
  • Create custom segments on the fly, send data straight away as a one-off export to your chosen location or schedule the exports to run at regular intervals for use by your Data scientists

What’s the value? 

  • Limitless portability of data.
  • Simplicity: non-data experts now have new superpowers! 

Specific use cases? 

  • Let’s say you’re an SVOD based streamer or publisher, you’ll want to do everything in your power to convert free trial users into paying customers – then keep those paying customers highly engaged month-on-month. Now you can create custom segments (e.g trial ends in three days) and use those exports to drive highly contextual email marketing campaigns.

Final Thoughts On Your New Superpowers 

Hopefully, this article has helped you think about the new possibilities of empowering your 1st party data that come with a truly connected data ecosystem for Ad Sales, Marketing and Editrial. 

We’ve got plenty more integrations in the pipeline – so watch this space for upcoming announcements, or get in touch if you have questions or suggestions for new integrations you’d like to see.

To learn how Streamhub can help extract the untapped value from your existing video audience data – reach out via our contact form or live chat today. The Streamhub team are here to help!

Written by Dan Turner

Business Development Lead

A multi skilled marketeer and OTT industry specialist, Dan has a huge passion for the power of online video as a medium for connecting people and ideas. His experience in online video began at the Xbox Live division of Microsoft, before moving on to a number of innovative start-up and scale-up businesses in the OTT space.

The post SpotX and Google Ad Manager integrations added to Streamhub’s 1st party activation platform appeared first on Streamhub.co.uk.

]]>
31506
Google Analytics or Streamhub? 7 Signs it’s time to look beyond GA for your Video/OTT Analytics https://streamhub.co.uk/7-signs-its-time-to-look-beyond-google-analytics/ Thu, 11 Jun 2020 07:32:59 +0000 http://streamhub.co.uk/?p=31136 Reading Time: 6 minutes

The post Google Analytics or Streamhub? 7 Signs it’s time to look beyond GA for your Video/OTT Analytics appeared first on Streamhub.co.uk.

]]>
Reading Time: 6 minutes

The free version of Google Analytics (GA) is a good tool for general web tracking and the natural starting point for all kinds of businesses wanting to make sense of what’s happening on their website. But we all know there’s no such thing as a free lunch…

The world of online video is a different battleground where a more specialised set of tools can make all the difference in understanding and engaging audiences correctly. Without it, it’s a bit like taking a Boeing 747 into an aerial dogfight – a great piece of machinery, but it’s not going to help you give any edge over your competition.

 

Moreover – many content owners are blissfully unaware that it’s possible to leverage their data to go beyond useful insights, to actually expose real 1st party data segments to power and automate Ad targeting, Marketing, and Editorial workflows. 

We’ve prepared this article to highlight what you give up in taking GA’s free service, looking at the differences with Streamhub to help you understand the right set of tools for your business. In many cases – a combination of GA and Streamhub can provide the most powerful and cost effective solution.

 

You should consider using Streamhub alongside Google (or other generalists) if:

1. You want real audience data to power your workflows

The first thing to point out is the whole world of possibilities for your data beyond insights and reporting – that’s data activation. This means connecting your audience data directly with other essential tools used to run your video business, such as Marketing Platforms, Ad Servers or CRMs, and using it to directly power your actions.

Free Google Analytics provides you with insights and reporting – but in terms of leveraging your data, that’s where it ends.

The combination of Streamhub Analytics and Activate allows you go to beyond insights and use your real 1st and even 2nd party data to power your digital workflows and hit real world business KPIs.

2. Video is a serious investment/part of your service offering

To measure video engagement properly, you need content metadata based reporting, time-watched metrics, technical stream quality metrics and audience profile information all coming together to create a holistic report of what’s really going on. This is not something generalist services can do.

Let’s face it, monitoring video metrics in GA is like pushing a square peg into a round hole!

Streamhub was built by online video professionals, for online video professionals – providing the necessary metrics to help you understand streaming audiences is what we do best.

3. Data ownership is important to your business and its future

Did you know that google owns all the data you push through GA and can use it as they see fit? Not to mention it’s a data privacy issue time bomb. 

With Streamhub, your data is YOUR data and it is processed just on your behalf in a GDPR compliant way. 

4. You want to manage your data, your way

With GA you have no rights or capability to export your own data for use elsewhere. 

Our tools give you the flexibility to export your raw or aggregated data and manage it according to your business needs.

5. You want to understand your users, not just count them

Google provides quick aggregated metrics for your overall web interactions – but it only handles anonymous data AND it’s often sampled so you don’t get customer context nor precision.

Streamhub enables the analysis of each user behind the plays, what content they like and how their behaviour change over time.

6. Your service is on many platforms and needs deduplicated counting across-platform

Whilst GA does a good job in covering website traffic, it gets much more difficult when you add mobile and other connected devices – moreover, your users are ‘duplicated’ as they move across devices.

Streamhub can track your unique users across web, mobile, connected TV to harmonise the data cross-platform using your userIDs.

7. You prefer support from real video data experts

Google won’t give you live support unless you spend $150k a year with them.

Streamhub’s dedicated team of data experts are on hand via email, phone or live chat to answer your questions and support your needs. 

When should I use Free GA? 

GA should be sufficient for your business if:

  • Video is a side-kick.
  • Your priority is the website and not apps / connected devices.
  • You’re more focussed on overall trends (page views, bounce rates, unique visitors, etc) than tracking the interests or profiles of your individual users.
  • You don’t care about data ownership nor to be able to use your data down the line
  • You’re only interested in tracking the effectiveness of your off-service marketing activities.
  • You’re not looking for precision (e.g. conversions, revenues) and happy using sampled data. 

When should I use Streamhub? 

You should consider Streamhub if:

  • You’re serious about video as content.
  • You want to own your data and manage/export it for use elsewhere.
  • You want to get a deep understanding of your individual users and audience segments, to the extent of communicating with individual customers based on what the data says.
  • You need to fuse metadata such as rich program data, user registration data or subscription data to the video viewing.
  • You operate multiple channels / brands / apps, and need all the data harmonised in one place.
  • You want to gauge the quality of the delivery & streaming you are delivering across all devices.

In short, many customers end up using Google Analytics and Streamhub together if video as content, or as a means of monetization, is crucial to their business. A few have also upgraded to use the paid Google Analytics 360 at $150,000+ per year, or to other alternatives such as Kissmetrics and Adobe Analytics, whilst keeping Streamhub as the solution to cover all things related to video or OTT engagement. 

Hopefully this article has helped you think about the most important metrics for your video business right now, and made it clear how Streamhub Analytics could further enhance your use of data. 

If you’re still unsure which tools are right for you – please don’t hesitate to reach out via our contact form or live chat. The Streamhub team are here to help.

Written by Dan Turner

Business Development Lead

A multi skilled marketeer and OTT industry specialist, Dan has a huge passion for the power of online video as a medium for connecting people and ideas. His experience in online video began at the Xbox Live division of Microsoft, before moving on to a number of innovative start-up and scale-up businesses in the OTT space.

The post Google Analytics or Streamhub? 7 Signs it’s time to look beyond GA for your Video/OTT Analytics appeared first on Streamhub.co.uk.

]]>
31136