This chapter focuses on the technical implementation of the Topspin tracker, with platform specific code examples on the right hand side of the page.
/* The integration follows the Topspin API layer in the browser.
This layer is placed by the platform developer.
The platform developer makes sure the Topspin tracker is initialised
with the appropriate label values (see previous section for more details
on the labels and corresponding values). Below is an example.
*/
<script type="text/javascript">
topspinLayer = {
comScoreName: 'vroegevogels.ps.media.358019.pagina',
contentId: 'crid://vara.nl/cms/episode/358019', // Optional
brand: 'vroegvogels',
section: 'ps',
broadcaster: 'bnnvara',
subBroadcaster: 'vara',
brandType: 'programma',
avType: 'audio', // Optional
channel: 'nporadio1', // Optional
user: 'foob-1234-arfo', // Optional
profile: '1234-abcd-5678', // Optional
platform: 'site',
platformType: 'site'
};
</script>
// Once the Topspin Layer is placed can be loaded at the end of the page as such:
<script src="//topspin.npo.nl/tt/topspin.js"></script>
// The Topspin Tracker can be integrated as a cocoapod b putthing the following line in the Podfile:
pod 'Topspin', :git => 'https://github.com/npo-topspin/topspin-tag-ios-release.git',
:tag => '1.2.0'
// Integration into the iOS app:
TSTopspinFactory* factory = [[TSTopspinFactory alloc] init];
TSTopspin* topspin = [factory build];
TSPageLabels* labels = [topspin pageLabels];
labels.brand = @"your-own-npo-brand-here";
labels.brandType = TSEventBrandTypeBroadcastPortal;
labels.broadcaster = @"enter-appropriate-broadcaster-here";
// To disable logs:
[TSTopspin enableLogs:NO];
// To register the app to automatically send app-lifecycle events:
[topspin registerAppLifecycle];
// This creates a Topspin client with all default settings. The first time it’s used, it will generate
// a Divolte cookie ID and store it in the standard user defaults. Upon subsequent use, it will load this
// cookie ID from the user defaults.
// For testing purposes it can be useful to disable network calls. Use to following to disable network calls:
[[TSTopspin sharedInstance] disableNetworkCalls:YES];
// Make sure your app has Internet permissions
// Add the Topspin library as a dependency of your application as follows;
repositories {
maven {
url "http://npo-topspin.bintray.com/public"
credentials {
username '<USERNAME>'
password '<API_KEY>'
}
}
}
dependencies {
compile 'nl.npo.topspin:topspin-android:1.4.0'
}
Initialising the topspin tracker is easy, just follow the below steps for your platform of choice. If you require more context for how to set a field with it’s appropriate value, please refer to the data context
In this section we will guide you through the different possible event measurements, how to set them up, and what additional parameters to pass along.
A pageView
only really exists in browser land as the concept doesn’t hold within an app. A pageView event is generated every time a new page is loaded. This functionality is automatically activated when the Topspin tracker JavaScript version is implemented.
Send a pageView event
A pageView is automatically implemented when the Topspin Tracker is loaded.
The concept of a pageView is not relevant for a mobile application and is thus not implemented. See contentView instead.
The concept of a pageView is not relevant for a mobile application and is thus not implemented. See contentView instead.
For mobile devices we register a contentView instead of a pageView.
Send a contentView event
The concept of a contentView is not implemented in the javaScript Topspin Tracker. See pageView instead.
// A tracker object must be created to track events:
TSTracker* tracker = [[TSTopspin sharedInstance] createNewTracker];
tracker.section = @"navigation";
tracker.comscoreName = @"tester.navigation";
tracker.subBroadcaster = @"example.broadcaster";
// To send a contentView:
tracker.contentId = @"crid://your/url/scheme/here"; // see contentId
[[tracker contentView] send];
public class MainActivity extends Activity {
Tracker mTracker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// page view, inheriting global labels from app
mTracker = TopspinInstance.get()
.createTracker()
// set required labels for a page
.setSection("start") // example value
.setContentId("crid://startpagina") // example value
.setComscoreName("pagina.start") // example value
.setSubBroadcaster(""); // example value
// perform actual pageview
mTracker.contentView()
// optional: referrer
.setReferrer("homescreen") // example value
.send();
}
}
From the player we receive a set of events, such as a streamStart for every time a player/stream is started. Other possibilities include streamPause, streamWaypoint and streamComplete.
Send player events
to be implemented
to be implemented
// To start tracking streaming (player) events, first initialise a tracker object:
- (void)viewDidLoad {
[super viewDidLoad];
_tracker = [[TSTopspin sharedInstance] createNewTracker];
_tracker.section = @"playback";
_tracker.contentID = [NSString stringWithFormat:@"%@/player/%@", TSDemoBaseCrid,
@"someContentID"];
_tracker.comscoreName = @"tester.player";
_tracker.subBroadcaster = @"example.broadcaster";
...
}
// Send the events as follows:
- (void)startPlayback {
_streamTracker = [[TSStreamTracker alloc] initWithTracker:_tracker
mid:@"KNO_2308383"];
[_streamTracker load];
}
- (void)pause {
[_avPlayer pause];
CMTime position = [_avPlayer currentTime];
[_streamTracker pause:CMTimeGetSeconds(position)];
}
Search events are gathered from queries to the search box, there are two possible scenarios:
A quick search triggers offer
events everytime content is suggested from the box, along with a corresponding choice
event if the user clicks such a suggestion. Each new set of suggestions trigger their own set of offer events, with the query text as “source”
Send search events
<!-- quick search example using auto-suggest, searchterm = 'lui' -->
<ul data-ts-panel="auto-suggest" data-ts-source="lui">
<li>
<a title="Kijkenluisteren" href="https://npo.nl/kijkenluisteren/POMS_S_NTR_360196" data-ts-offer data-ts-search
<div class="npo-asset-tile-container asset-image">
<div class="npo-asset-tile">
<div class="npo-asset-tile-image"></div>
</div>
<div class="npo-asset-tile-title">
<h2>Kijkenluisteren</h2>
<p></p>
</div>
</div>
</a>
</li>
<li>
<a title="De Luizenmoeder" href="https://npo.nl/de-luizenmoeder/AT_2072508" data-ts-offer data-ts-search="auto-
<div class="npo-asset-tile-container asset-image">
<div class="npo-asset-tile">
<div class="npo-asset-tile-image" style="background-image: url('https://www-assets.npo.nl/uploads/m
</div>
<div class="npo-asset-tile-title">
<h2>De Luizenmoeder</h2>
<p></p>
</div>
</div>
</a>
</li>
etc...
</ul>
To clarify with an example; a user is looking for “Zondag met Lubach” so starts typing “zon” in the searchbox. This triggers offer events for the suggestions displayed. The user continues typing “zondag” which triggers another query to the search engine, and yet another set of offer events. Finally the user selects a suggestion, resulting in a choice event for the specific content.
The second scenario is when a user does not use the quick search feature and presses the “Alle resultaten” button. This will navigate to a page with two lanes; “Programma’s” and “Afleveringen”. Both these lanes should be registered as invididual search events, with their own panel name and index, along with the query text.
<!-- an extended search example, searchterm = 'lui' -->
<div id="component-franchises" data-ts-panel="search-franchise" data-ts-source="lui">
<input type="hidden" name="nextLink" value="">
<input type="hidden" name="selfLink"
value="/search/extended?pageSize=10&query=lui&filter=programs&dateFrom=2014-01-01">
<input type="hidden" name="tileType" value="teaser">
<input type="hidden" name="tileMapping" value="normal">
<input type="hidden" name="pageType" value="search">
<div class="npo-grid-teaser">
<div class="npo-ankeiler-tile-container npo-ankeiler-tile-weblink npo-tile" id="VPWON_1271886">
<a href="https://npo.nl/danny-zoekt-stemmers/VPWON_1271886" title="Danny zoekt stemmers"
class="npo-ankeiler-tile"
data-scorecard="{"name":"collecties.programmas.danny-zoekt-stemmers.VPWON_1271886"}"
data-ts-offer
data-ts-search="search-franchise-base"
data-ts-destination="VPWON_1271886">
<div class="npo-ankeiler-tile-image"
style="background-image: url('https://images.poms.omroep.nl/image/s320/c320x180/870521.jpg');">
<h2>Danny zoekt stemmers</h2>
</div>
</a>
</div>
<div class="npo-ankeiler-tile-container npo-ankeiler-tile-weblink npo-tile" id="POMS_S_NPO_4008736">
<a href="https://npo.nl/speellijst/POMS_S_NPO_4008736" title="Discriminatie, racisme en uitsluiting " class="npo-ankeiler-tile
data-scorecard="{"name":"collecties.programmas.discriminatie-racisme-en-uitsluiting.POMS_
data-ts-offer
data-ts-search="search-franchise-base"
data-ts-destination="POMS_S_NPO_4008736">
<div class="npo-ankeiler-tile-image">
<h2>Discriminatie, racisme en uitsluiting </h2>
</div>
</a>
</div>
etc...
</div>
<!-- episodes blok -->
<div id="component-episodes" data-ts-panel="search-episode" data-ts-source="lui">
<input type="hidden" name="nextLink"
value="/search/extended?pageSize=10&page=2&query=lui&filter=episodes&dateFrom=2014-01-01">
<input type="hidden" name="selfLink"
value="/search/extended?pageSize=10&query=lui&filter=episodes&dateFrom=2014-01-01">
<input type="hidden" name="tileType" value="asset">
<input type="hidden" name="tileMapping" value="search">
<input type="hidden" name="pageType" value="search">
<div class="npo-grid-asset">
<div class="npo-asset-tile-container npo-tile " id="POW_03205575">
<a href="https://npo.nl/tip-de-muis/22-03-2018/POW_03205575" class="npo-tile-link" data-radio=""
data-mediatarget="" data-scorecard="{"name":"zoeken.tip-de-muis.POW_03205575"}"
data-order=""
data-ts-offer
data-ts-search="search-episode-base"
data-ts-destination="POW_03205575">
<div class="npo-asset-tile ">
<div class="npo-asset-tile-availability" data-from="2018-03-22T12:36:20Z"
data-to="2018-03-29T11:36:20Z" data-prediction="" data-premium-from="2018-03-22T12:36:20Z"
data-premium-to="" data-premium-prediction="" style="display: block;">Nog 2 uur beschikbaar
</div>
<div class="npo-asset-tile-timer">
7 min
</div>
<div class="npo-asset-tile-image">
<img src="https://images.poms.omroep.nl/image/s320/c320x180/1028211.jpg"
alt="Tip de muis: Wat een moeite!" onerror="this.src='/images/tile-placeholder.png'"
class="thumb-fallback">
</div>
<div class="npo-asset-tile-new">nieuw</div>
<div class="npo-asset-tile-play"></div>
<div class="npo-asset-tile-watched">bekeken</div>
<div class="npo-asset-tile-progress">
<div class="npo-asset-tile-progress-bar" style="width: %"></div>
</div>
</div>
<h2>Tip de muis: Wat een moeite!</h2>
<p>Afl. 16 - Do 22 mrt 13:30</p>
</a>
</div>
<div class="npo-asset-tile-container npo-tile
<a href="https://npo.nl/koek-ei-op-safari/20-08-2014/VPWON_1227223" class="npo-tile-link" data-radio=""
data-mediatarget=""
data-scorecard="{"name":"zoeken.koek-ei-op-safari.VPWON_1227223"}" data-order=""
data-ts-offer
data-ts-search="search-episode-base"
data-ts-destination="VPWON_1227223">
<div class="npo-asset-tile ">
<div class="npo-asset-tile-availability" data-from="" data-to="" data-prediction=""
data-premium-from="2017-03-29T00:00:00Z" data-premium-to="2017-03-29T00:00:00Z"
data-premium-prediction=""></div>
<div class="npo-asset-tile-timer">
7 min
</div>
<div class="npo-asset-tile-image">
<img src="https://images.poms.omroep.nl/image/s320/c320x180/502308.jpg"
alt="Koek & Ei op safari: De vermorzeldame"
onerror="this.src='/images/tile-placeholder.png'" class="thumb-fallback">
</div>
<div class="npo-asset-tile-new">nieuw</div>
<div class="npo-asset-tile-play"></div>
<div class="npo-asset-tile-watched">bekeken</div>
<div class="npo-asset-tile-progress">
<div class="npo-asset-tile-progress-bar" style="width: %"></div>
</div>
</div>
<h2>Koek & Ei op safari: De vermorzeldame</h2>
<p>Afl. 3 - 20 aug 2014 11:01</p>
</a>
</div>
etc...
</div>
</div>
The player shows a set of related recommendations when pausing the stream on a full-screen view (except on smaller mobile screens). These items are gathered as recommendation offer
events, with as source the streamid that is currently being played. These events should pass through the recommender (name) from the api response.
Personal recommendations are currently only available as lane on the homepage. When this lane is visible in the screen, it should fire offer
/ choice
events just like the other lanes. It should provide the recommender name as given by the api response.
For NPO Start we have included the possibility to collect user data on the different lanes present on the portal. Showing an item from a lane to a user is considered a recommendation. Clicking one of these items is thus considered a choice. We need to register the type of lane, the type of page it is shown on, the type of lane it is shown in and the position of the lane in the form of an index. Let’s run through the different optional values for these fields.
The type of the page is important to register, because a specific lane can sometimes be found on different types of pages. At the time of writing we register the following possible values for pageType):
Currently we identify two different types of lanes on NPO Start, the normal lane, and a lane that holds five items of different size. The appropriate values for these different lane types are:
The position of the lane is important to infer how far down a user scrolls before the appropriate content is found. The index starts counting at 0 (zero), so the top most lane will have an index of zero. The second lane will have an index of 1, etc.
It is also important to indicate with what content the recommendations are offered. We refer to this as the source of the recommendation.
The above information is provided by the NPO Start API, and accessible via the field name of a component named panel.
This needs to explain how to link an Editorial tracker to the editorial offers, and what fields need to be passed where.
This needs to be implemented.
This needs to be implemented.
With the Topspin Tracker, it is possible to create hand-tailored events. As a simple example, we want to know how many users click a specific button on the homepage. Implementing a custom event type here, e.g. buttonClicked
, would allow insights in which users click (think of registered vs. anonymous) the button and at what time of day.
This needs to explain how to implement custom events, maybe stick to the 'buttonClicked' example.
This needs to be implemented.
This needs to be implemented.