Implementation of Stream Measurement

Implementation of Stream Measurement


Version

2.3

Date

11.11.2019

Author

Kantar Media Division

Version history

Version

Date

Author

Comments

0.1

23.05.2005

Christopher Wirtz

Initial

0.5

26.05.2005

Christopher Wirtz

Changes

1.0

02.06.2005

Christopher Wirtz

Release

1.1

12.03.2007

Christopher Wirtz

Flash added

1.2

06.07.2009

Christopher Wirtz

Review/Changes

1.3

14.07.2009

Christopher Wirtz

Added Example to set the „duration“ manually in ActionScript

1.4

29.06.2010

Christopher Wirtz

Unload Delay

1.5

02.11.2010

Frank Kammann

Add Example using a NetStreamAdapter, Streaming Segments and parallel Streams

1.6

14.03.2011
23.03.2011
24.03.2011

Frank Kammann
Ralf Cornehl
Ralf Cornehl

Description of the general Operation (API) and Information on the Security Settings for Flash
Review/Changes
Translation into English

1.7

17.09.2012

23.10.2012

Christopher Wirtz

Ralf Cornehl

Adapting the position-based Stream Measurement to event-based Measurement

Review, Adapting Explanations

1.8

04.12.2012

04.12.2012

Frank Kammann

Ralf Cornehl

Mobile Streaming added

Review, Adapting Explanations, Translation into English

1.9

13.05.2013

13.05.2013

26.09.2013

Frank Kammann

Katrin Joachimsky

Ralf Cornehl

Chapter HTML5 added

Translation into English

Adapting Stream Name Guidelines

2.0

22.01.2014

10.11.2014

19.08.2015

Hendrik Vermeylen

Ralf Cornehl

Ralf Cornehl

Offline Mode for App Streaming

Opt-Out Method

URL-scheme added for iOS

2.125.09.2015Ralf CornehliOS9 Support (URL-Scheme-White-listing, ATS, Bitcode)
2.218.10.2017Marc BornAndroid Streaming Sensor now uses Singleton template for creation
2.311.11.2019Christopher KirchImplementation Streaming for Cordova

Content


Introduction

Whether news shows, product presentations or online radio programs - all are now using audio and video streams as medium of communication.

Even more than on the static pages, not only the number of visitors but also the behavior of users within a stream is of interest. For how long the clip was actually viewed, which sections were of particular interest and which were not?

When streaming media is called from a web site that is composed by the COM model and/or Flash, it is not only possible to track the number of users, but also watching the users’ behavior.

This document describes the integration of streaming measurement for different applications.

Concept of Measurement

In order to measure any streaming content, a sensor on the client side is necessary, which measures which sequences of the stream were played by the user.

These sequences can be defined by time intervals in seconds - therefore, the player or the playing instance must be able to provide a method for delivering the actual position on the stream in seconds.

The regular reading of the current position allows the tracking of all actions on a stream. Winding operations can be identified, if there are unexpected jumps in reading out the position. Stop or pause operations are identified by the fact, that the current position will not change.

User actions and operations like stop or pause are not measured directly (not player event based) but are instead derived from measuring the current position on stream.

The following chart shows the architecture of the API:

The simplicity of the API and the concept lead to the following features:

  • Any desired streaming content, that is able to provide the current position in seconds on the stream, can be measured.
  • The API can easily be ported to other programming languages.
  • The same method is used in a cross-technology manner, which makes the same streaming content comparable even on different frameworks and technologies.

Basic Use of the API

The following discussion is presented in JavaScript syntax. The procedure is more or less the same in any programming language used.

// 1. Instantiate the API object
var sensors = new SpringStreams("site code");

// 2. Create a description object for streaming content
var desc = {
   "stream":"videos/mystream",
   "duration":600, // in seconds
   "sx":video.width,
   "sy":video.height
};

// 3. Provide a content object
var content = ...; // Any object, which is able to deliver the current position on the stream in seconds

// 4. Register the content with the description object on the sensor
var stream = sensors.track(content, desc);

Remarks:

to 1: Instantiate the API object

  • (warning) The correct site code must be setup. The site code is delivered by the owner of the streaming solution.

    var sensors = new SpringStreams("site code");
    
  • One instance of the object SpringStreams has to be setup once on the framework. From there, one or more streams can be transmitted to the measurement by the track method.

to 2: Create a description object for streaming content

  • In the description object, at least the name of the stream must be given

    	
    var desc = {
       "stream":"videos/mystream",
       "duration":600 // in seconds
    };
    
  • With the description object more information about the streaming content can be passed to the API. The following variables are permitted or can be understood by the measurement system:

Variable

Optional

Description

stream

No

Name of the stream, which is ideally given as a hierarchy.

Example:
BroadCaster_OD/Channel/Program/Season
/Episode/Streamname
BroadCaster_Live/Channel/Program/Season/Episode/Streamname

In general there is no limitation of the charset – conform to the JSON standard http://json.org – , but for having a better interpretation of stream paths in scores we advice to use the described standards below.

The allowable character set for all values in the tagging is UTF-8. It is however recommended to limit to the following characters:

a-z
A-Z
0-9
comma ","
point "."
dash "-"
underscore "_"

The slash "/" is used as a separator within the stream name for creating different hierarchy levels

dur, duration

Yes

The duration must be setup if there's no adapter implementation.
For live streams it can be omitted, or supplied with the value 0

ct

Yes

Any value, that makes a statement about the content type can be used
For example, the value "ad" can be given to the stream to mark it as advertising

cqYesAny value, that makes a statement about the content quality can be used
For example, the value "programme-ID" can be given to the stream to mark it as an unique content

sx

Yes

The width of the stream window - if it's a movie
For radio streams it can be omitted, or supplied with the value 0

sy

Yes

The height of the stream window - if it's a movie
For radio streams it can be omitted, or supplied with the value 0


to 3: Provide a content object

  • The only requirement of the streaming API for this object is delivering the current position on a stream in seconds. Depending on the programming language used certain technical software solutions are possible, such as the implementation of interfaces or the implementation of adapter classes.

    var content = ...; 
    

to 4: Register the content with the description object on the sensor

  • In the next step the streaming content and the description object can be transmitted to the API method track. From this point on the current position on the stream is tracked. At this point it's possible to measure several parallel streams, where each additional streaming content with the accompanying description object is given to the track method.
var stream = sensors.track(content, desc);
  • The measurement of individual streams may be stopped at any time by calling the method Stream.stop().

Internal Function Description of the API

After the streaming content with the accompanying description object has been passed to the track method, the current position on the streaming content is queried every 200ms. Internally, all intervals are collected, which were viewed by the user on the stream. At a constant viewing this is one interval.
Once it's determined that the current position changes by more than 1 second to the expected position a start, stop or winding event or even a buffering phase can be assumed. In this case, a new interval is created.

An interval is defined as:

[start, end, timestamp]

start

The starting second on the stream

end

The ending second on the stream

timestamp

The client-side time stamp by creating the interval

All the collected usage information of the measured streams are encoded in an HTTP request and regularly transmitted to the measuring system. The following rules apply:

  • at each interaction (start, stop or winding events or buffering phase) an update is sent immediately.
  • at a constant viewing at least every 20 seconds.

Data Transport

// System Object
[{"sx":1280,
"sy":1024,
"pl":"FlashPlugin",
"plv":"WIN 10,0,45,2"
},
// Usage Information
{"stream":"spring/teststream", 
     "dur":"600", 
     "sx":"400",
     "sy":"300",
     "uid":"267fgut",
      "vt":356, 
     "pst":[[0,0,"kzog7e"],[22,378,"kzog7e"]]
}]

Two objects are sent by the data transport:

  • The system object that contains information such as screen resolution, player name and player version.

    Variable

    Optional?

    Description

    sx

    No

    The width of the screen in pixel

    sy

    No

    The height of the screen in pixel

    pl

    No

    The player name

    plv

    No

    The player version

  • The usage information of a measurement

    Variable

    Optional?

    Description

    plv

    No

    The player version

    stream

    No

    The stream name

    dur,duration

    No, if it isn't a live stream.

    The stream length in seconds

    sx

    No

    The width of the stream window in pixel or the value 0

    sy

    No

    The height of the stream window in pixel or the value 0

    uid

    No

    The unique Id of the viewing sequence

    vt

    No

    The view time in seconds. It's measured from the call to the method SpringStreams.track(...)

    pst

    No

    The play states. The list of viewing intervals on the stream.

The description of the Encoding for the HTTP request is not part of this documentation. The request is designed in a manner that the variables are still clearly identifiable during debugging activities.

Implementation via Javascript for any Player (also proprietary ones)

Prior to using the springStreams-functions the Javascript-library must be loaded.
This is achieved by the following script-tag:

<script src=".../springstreams.js"></script>

The springStreams-functions are provided by the class SpringStreams. The code of a site will be passed on with the creation of an object of this class:

	
var sensors = new SpringStreams("test"); 

(warning) test = The site code in this case is “test”. You have to replace the site code "test" by the site code (unique name provided by Kantar) for the measured site.

To measure any player – even proprietary ones – an adapter to the specific player can be used to pass on data to the sensors of the measurement system. This adapter provides the following three functions:

var myAdapter = {

	"getMeta" : function(id) {
		return {
			"pl" :"own player",
			"plv" :"version1",
			"sx" : screen.width,
			"sy" : screen.height
		}
	},
	"getDuration" : function(id) {
		return streamlength in seconds;
	},
	"getPosition" : function(id) {
		return new Date().getTime() / 1000;
	}
};

1. The function getMeta is used to acquire a description of the player and the available screen area. By calling this function a return of an object with the following attributes is expected:

pl

player description

plv

player version

sx

screen width in pixel

sy

screen height in pixel

2. The function getDuration is used to acquire the total duration of the stream in seconds. If this cannot be done (because it is e.g. a live stream) 0 should be returned.

3. The function getPosition is used to acquire the current position in the stream. The function delivers the current play position in seconds. This function is also used to determine the intervals within the streams being played, skipped or stopped.

By calling the object to be measured (from the first parameter of the call to sensors.track(…)) it will be transmitted to all functions.

The self-created adapter can now be used for the measurement:

In the next step the stream content, the description object and the adapter can be transmitted to the API method track.  


sensors.track(someObject, desc, myAdapter);

The measurement can be monitored by using the method debug on the SpringStreams object:

sensors.debug = function(v) {
        window.status = " " + v;
}

Upon each transmission of the sensor to the measurement system this method is attached as a parameter (string) to the URL called.

SpringStreams does not intervene with the event model of the surrounding document. Therefore it is necessary to notify the sensors of any termination event of the streams by using events outside of the player. The most simple option for such a method is the insertion of an unload function by calling the method sensors.unload(), when the website is left by the user:

function unload() {
        sensors.unload();
}

Because the browser will close the web site after the unload sequence is finished, it is possible that the closing counting impulse was not committed. An improvement can be achieved by inserting a piece of code at the end of the unload sequence.

function unload() {
	sensors.unload();
        // give time for submission
	var start= new Date(); var now = null; do now = new Date();  
        while(now-start < 100);
}

To do so, this function can be added into the body tag:

<body onunload="unload();">

Single streams can also be terminated directly, for example by changing the play list:

var stream = sensors.track(someObject, desc, myAdapter);
...
stream.stop();

For other embedded players than the WMP, an adapter can be used. Find below an example for the Real Player with the id rvplayer:

sensors.track(rvplayer, description, sensors.RVStreamAdapter);

Example for a proprietary player

<html>
<head>
<title>spring sensors</title>
<script src="springstreams.js"></script>
</head>
<body onunload="unload();">
<script type="text/javascript">

var sensors = new SpringStreams("test"); 
var adapter = {
	"getMeta" : function() {
		return {
			"pl" :"own player",
			"plv" :"version1",
			"sx" : screen.width,
			"sy" : screen.height
		}
	},
	"getDuration" : function() {
		return streamlength in seconds;
	},
	"getPosition" : function() {
		return new Date().getTime() / 1000;
	}
};

var desc = {
	"stream": "videos/teststream"
}

sensors.track("someid", desc, adapter);

// uncomment for debugging
//	sensors.debug = function(v) {
//		window.status = v;
//	}

function unload() {
	sensors.unload();
        // give time for submission
	var start= new Date(); var now = null; do now = new Date();  
        while(now-start < 100);
}
</script>	
</body>

Implementation via Javascript (Windows Media player)

Prior to using the springStreams-functions the Javascript-library must be loaded.
This is achieved by the following script-tag:

<script src=".../springstreams.js"></script>

The springStreams-functions are provided by the class SpringStreams. The code of a site will be passed on with the creation of an object of this class:

var sensors = new SpringStreams("test"); 

(warning) test = The site code in this case is “test”. You have to replace the site code “test” by the site code (unique name provided by Kantar) for the measured site.

A media player embedded into the website can now be measured directly. For this, the object which is to be measured is required. In most cases this will be available through the id of the object’s tag. In the following example an object with the ID wmplayer with a reference to the Windows Media Player is being used.

sensors.track(wmplayer, {});

Beyond the ID of the object a number of descriptive information can be passed on in form of a JavaScript object:

var description = {
	"stream": "videos/teststream"
	"desc": "some additional description" 
};
sensors.track(wmplayer, description);

It is possible to include more additional attributes to the description. The declaration of stream with the name or the hierarchy of the stream is expected as a minimum.

More than one object can be measured.

Example Windows Media Player

<html>
<head>
<title>spring sensors</title>
<script src="springstreams.js"></script>
</head>
<body onunload="unload();">

<SCRIPT type="text/javascript">
      if(-1 != navigator.userAgent.indexOf("MSIE"))
      {
        document.write('<OBJECT id="wmplayer" width="435" height="326" CLASSID="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" type="application/x-ms-wmp">');
        document.write('                <param name="URL" value="http://wstreaming.zdf.de/zdf/veryhigh/070912_iaa_mim.asx">');
        document.write('                <param name="AutoStart" VALUE="1">');
        document.write('                <param name="ShowStatusBar" VALUE="1">');
        document.write('</object>');
     }
      else  {
       document.write('<OBJECT id="wmplayer" width="435" height="326" type="application/x-ms-wmp">');
       document.write('                 <param name="URL" value="http://wstreaming.zdf.de/zdf/veryhigh/070912_iaa_mim.asx">');
       document.write('                 <param name="AutoStart" VALUE="1">');
       document.write('                 <param name="ShowStatusBar" VALUE="1">');
       document.write(' </object>');
      }         
    </SCRIPT>
		
<script type="text/javascript">

// "test" is the name of the tracked website
var sensors = new SpringStreams("test");

// wmplayer is the id of the stream above
sensors.track(wmplayer, {"stream":"videos/teststream"});

// uncomment for debugging
//sensors.debug = function(v) {
//	window.status = v;
//}

function unload() {
	sensors.unload();
        // give time for submission
	var start= new Date(); var now = null; do now = new Date();  
        while(now-start < 100);
}
</script>	
</body>


Implementation via HTML5

Before using the springstreams-functions, the javascript library has to be uploaded. This is done via the following script-tag:


<script src=".../springstreams.js"></script>

The springstreams-functions are available via  SpringStreams class. During creation of an object in this class, the website-identification is transferred:


var sensors = new SpringStreams("test"); 

(warning) test is the identification of the website in this case. This identification has to be replaced by the own identification provided by Kantar.

A video- or audio element (see: http://www.w3schools.com/tags/ref_av_dom.asp) embedded into the website can now be measured directly. In order to perform the measurement, the object to be measured is needed. Usually this is available via the id of the object-tag. In this example, the object with the ID video5 is used with a reference to an HTML video element.


sensors.track(video5, {});

In addition to the ID of the object to be measured, descriptive information concerning the object in form of Javascript-object can be indicated:


var description = {
    "stream": "videos/teststream"
    "desc": "some additional description" 
};
sensors.track(video5, description);

There is the possibility to add additional attributes to the description. Minimum the identification of stream indicating the name/hirarchy of the stream is expected.

Example HTML5 video


<!DOCTYPE html>
<html lang="en">
<head>
  <title>HTML5 Video Example</title>
  <script src="./springstreams.js"></script>
</head>
<body onunload="unload()">
<script type="text/javascript">
    var sensors = new SpringStreams("test"); 
</script>
 
   <div align="center">
    <video id='video' controls preload='none' >
      <source id='mp4' src="./wetter.ard.20101209.mp4" type='video/mp4; codecs="avc1, mp4a"'></source>
      <p>Your user agent does not support the HTML5 Video element.</p>
    </video>
   </div>
 
    <script type="text/javascript">
    var description = {
        "stream":"videos/news/wetter.ard.20101209.mp4"
    };
 
    var handle = sensors.track(video, description, sensors.HTML5Adapter);
    function unload() {
      sensors.unload();
    }
 
</script>
</body>
</html>

Implementation in Flash

The implementation in Flash is similar to the one in Javascript. To use the springStreams functions the library springstreams.as3.swc (resp. springstreams.as2.swc for Actionscript2) is embedded into the application.

Now the springStreams-functions are available via the class SpringStreams. When an object of this class is created, the name of the website is being passed on:

var sensors:SpringStreams = new SpringStreams("test"); 

(warning) test = The site code in this case is “test”. You have to replace the site code “test” by the site code (unique name provided by Kantar) for the measured site.

The measurements are passed on via an http call to the measurement systems. In the case that this call should be done via SSL (HTTPS) this can be achieved by passing on this information to the sensor:

var sensors:SpringStreams = new SpringStreams("test", true); 

A NetStream object can now be measured directly:

sensors.track(ns,{});

Beyond the ID of the object a number of descriptive information can be passed on as a JavaScript object:

var description:Object = {
	"stream": "videos/teststream",
	"desc": "some additional infos",
	"sx": video.width,
	"sy": video.heigth,
};
sensors.track(ns, description);

It is possible to include more additional attributes to the description. The declaration of stream with the name of the stream or the hierarchy and sx respectively sy with the sizing description of the visible area is expected as a minimum.

Requests to the measurement system can be monitored via the ActionScript function trace in the debug environment.

The class SpringStreams defines a function SPRING_UNLOAD via the Flash class ExternalInterface (if available). Analogous to the Javascript version, this function can be used to signal the termination of the runtime environment. This can be implemented as follows:

<body onunload="unload()">
...
function unload() {
   FlexProject.SPRING_UNLOAD();
}

Because the browser will close the web site after the unload sequence is finished, it is possible, that the closing counting impulse was not initiated. An improvement will be reached by inserting a part of a little application at the end of the unload sequence, which will not be noticed by customers.

function unload() {
	FlexProject.SPRING_UNLOAD();
        // give time for submission
	var start= new Date(); var now = null; do now = new Date();  
        while(now-start < 100);
}

FlexProject is the ID of the flash object. If the Flash application already uses an unload mechanism, this mechanism can call the static function SpringStreams.unload().

Use as an Embedded Player and Security Settings

If the player should be used as Embedded Player on an other website, the security settings have to be respected.

The following settings must be setup (siehe: ExternalInterface#addCallback())

  1. Set in the HTML page at the object tag for the SWF file the following parameters:

    <param name="allowScriptAccess" value="always" />
    
  2. Paste in the SWF file the following ActionScript code:

    flash.system.Security.allowDomain( sourceDomain )
    

At absence of these settings, a security error appears in the API, because the trial of registering the SPRING_UNLOAD() function on the website will fail. This error is handled by the API and will be ignored. However, it isn't essential for the proper functioning of the measurement.
Furthermore, for the proper functioning the entry of the attribute onunload="..." in the body tag is necessary in order to report the leaving of the side to the API. At absence of these settings, it's possible that the last event at the end of the measurement is no longer transmitted to the measurement system.

Example integration in Flash

 var nsClient:Object = {};
 nsClient.onMetaData = ...
 nsClient.onCuePoint = ...  

 nc = new NetConnection();
 nc.connect(null);

 ns = new NetStream(nc);
 ns.play(streamlocation);
 ns.client = nsClient;

 video = new Video();
 video.attachNetStream(ns);
 
 addChild(video);
                
 var tracker:SpringStreams = new SpringStreams("test");
 tracker.track(ns,{"stream":"videos/teststream","sx":video.width,"sy":video.height});

The determination of the stream’s total duration is achieved by an intermediate stream-client, which accesses all meta-events of the stream. This stream-client transparently routes all events to the already registered client (nsClient in the above example).

A meta event (onMetaData) containing an info-object is expected. The attribute duration (info.duration) is requested from this info-object and interpreted as the length of the stream.

If this information should not be provided by your streams, the duration of the stream can be assigned externally (in seconds).

via the stream reference:

var nsClient:Object = {};
 nsClient.onMetaData = ...
 nsClient.onCuePoint = ...  

 nc = new NetConnection();
 nc.connect(null);

 ns = new NetStream(nc);
 ns.play(streamlocation);
 ns.client = nsClient;

 video = new Video();
 video.attachNetStream(ns);
 
 addChild(video);
                
 var tracker:SpringStreams = new SpringStreams("test");
 var stream:Stream = tracker.track(ns,{"stream":"videos/teststream","sx":video.width,"sy":video.height});

 stream.setDuration(600/*10 minutes*/);

Measurement for any Streaming Content in Adobe Flash

As described in the last chapter, you need a library for a flash.net.NetStream object to measure a streaming content. Inside the measurement, the actual position on the stream object will be readout.

The measurement is constantly calling the property NetStream.time.

var net:NetConnection = new NetConnection();
net.connect(null);
var ns:NetStream = new NetStream(net);
var currentPosition:Number = ns.time;

With this knowledge it is possible to measure any streaming object, where the actual position in seconds on the stream is transmitted.

For this you have to implement an own NetStream object and overwrite the call ns.time.

In the following example a NetStream adapter for the flash.media.Sound object is implemented.

package 
{
import flash.media.SoundChannel;
import flash.net.NetConnection;
import flash.net.NetStream;
	
public class SoundNetStreamAdapter extends NetStream
{
	private var channel:SoundChannel;
	
	public function SoundNetStreamAdapter(net:NetConnection, c:SoundChannel)
	{
		super(net);
		channel = c;
	}
		
	override public function get time():Number { 
            // return the position in seconds
		return channel.position/1000;
	}

}
}

This adapter is also a NetStream object and can be transmitted to the streaming library. By implementing, the property time is overwritten. This property delivers the actual position in seconds. This value is divided by 1000 because the SoundChannel object delivers milliseconds instead of seconds. This is necessary to adapt the interface description of the NetStream object.

The length of the stream is taken by the property Sound.length, transformed to seconds and is written into the description object with the name duration.

var tracker:SpringStreams = new SpringStreams("test");
			
var s:Sound = new Sound();
s.load(new URLRequest("file:///pathto/sound.mp3"));

var desc:Object = {
	"stream":"sounds/sound.mp3",
	"sx":0,"sy":0,
};
			
var nc:NetConnection = new NetConnection();
nc.connect(null);

var ns:NetStream = new SoundNetStreamAdapter(nc, s.play());
var stream:Stream = tracker.track(ns, desc);
stream.setDuration(s.length/1000); // duration in seconds

Advanced Topics

Segmentation of Streams

Possibly there is a need for a deeper/different segmentation of streams. This can be desirable in case of a live stream or recorded streams with several content parts, so that the individual segments or parts of the stream can be distinguished.

This can also be realized with the library by measuring each consignment separately. To do this, the player needs to know when and what is broadcasted. It simply needs something like a program structure or "play list".

Example for the measurement of individual segments or parts on a live stream

 var nsClient:Object = {};
 nsClient.onMetaData = ...
 nsClient.onCuePoint = ...  

 nc = new NetConnection();
 nc.connect(null);

 ns = new NetStream(nc);
 ns.play(streamlocation);
 ns.client = nsClient;

 video = new Video();
 video.attachNetStream(ns);
 
 addChild(video);
                
// 20:00 - 20:15 Tagesschau
// 20:15 - 21:00 PlusMinus
var tracker:SpringStreams = new SpringStreams("test");
// 20:00
var stream:Stream = tracker.track(ns,{"stream":"livestreams/ard/Tagesschau","sx":video.width,"sy":video.height});

// 20:15
stream.stop();
stream = tracker.track(ns,{"stream":"livestreams/ard/PlusMinus","sx":video.width,"sy":video.height});

Several Streams in parallel Measurement

The library is able to measure several streams in parallel. All the streams which have been started can be measured directly by being transmitted with the method “track” to the measurement of streaming.

Example for the measurement of two parallel streams

...
ns1 = new NetStream(...);
...
ns2 = new NetStream(...);
...
var tracker:SpringStreams = new SpringStreams("test");
var stream1:Stream = tracker.track(ns1,{"stream":"streams/stream1","sx":video.width,"sy":video.height});
var stream2:Stream = tracker.track(ns2,{"stream":"streams/stream2","sx":video.width,"sy":video.height});


Adapting the position-based stream measurement to event-based measurement.

To adapt the standard position based stream measurement to an event-based model an adapter can be used to negotiate between the different models. The adapter allows to the event-based left side (the client side) to signal events like Start and Stop and mediates these events to stream positions at the right side. The mediation is simply done by increasing a simulated stream position internally using a timer for all states where the stream is playing (after the Start event etc).

function EventAdapter(pInfo, pVersion) {
	var playerInfo = pInfo;
	var playerVersion = pVersion;

	this.onStart = onStart;
	this.onStop  = onStop;
	this.onPause = onPause;
	this.onResume = onResume;
	this.onProgress = onProgress;
	this.setDuration = setDuration;

	var timer = setInterval(collect, 1000);

	var state = 0;
	var position = 0;

	function collect() {
		if(state == 1) position ++; 
	}


	/* external API (left side) */ 
	function onStart() {
		state = 1;
		position = 0;
	}


	function onStop() {
		state = 0;
		position = 0; // most players rewind on stop
	}

	

	function onPause() {
		state = 2;
	}

	

	function onResume() {
		state = 1;
	}

	

	/*
	 * sets the progress o the stream in seconds
	 * @param v the progress in seconds
	 * @return
	 */

	function onProgress(v) {
		if(typeof v != 'number') {
			v = parseInt(v);
		}

		if(v >= 0) position = v;
	}

	

	function setDuration(v) {
		if(typeof v != 'number') {
			v = parseInt(v);
		}

		if(v >= 0)
			duration = v;
	}

	

	/*  internal API (right side) */
	this.getMeta = getMeta;
	this.getDuration = getDuration;
	this.getPosition = getPosition;

	function getMeta(v) {
		return {
			"pl" : playerInfo,
			"plv": playerVersion,
			"sx" : screen.width,
			"sy" : screen.height
		}
	}

	

	function getDuration(v) {
		return duration;
	}

	function getPosition(v) {
		return position;
	}

}

To use the adaptor it is instantiated with the player-information

var adapter =  new EventAdapter("myOwnPlayer","V2.2");

and then added to the sensor using the track method:

sensors.track("stream1", desc, adapter);

The full example is 

<html>
<head>
<script src="springstream.js"></script>
<script src="eventadapter.js"></script>
</head>
<body onunload="unload();">
<form name="f"><input type="text" name="t" size="120"/></form>
<hr>

<form name="player">
<!-- 

	 example shows how to control the eventadapter using input-buttons 

-->
<input type="submit" value="Start" onClick="adapter.onStart(); return false;"/>
<input type="submit" value="Stop" onClick="adapter.onStop(); return false;"/>
<input type="submit" value="Pause" onClick="adapter.onPause(); return false;"/>
<input type="submit" value="Resume" onClick="adapter.onResume(); return false;"/>
<input type="text" name="position"/>
<input type="submit" value="&lt;- setPosition" onClick="adapter.onProgress(player.position.value); return false;"/>
</form>


<script type="text/javascript">

var sensors = new SpringStreams("test"); 
var adapter =  new EventAdapter("myOwnPlayer","V2.2");

adapter.setDuration(1000); // duration of the stream in seconds

var desc = {
		"stream": "teststream"
}

sensors.track("stream1", desc, adapter);
sensors.debug = function(v) {
	f.t.value = v;
}

function unload() {
	sensors.unload();
	// give time for submission
	var start= new Date(); var now = null; do now = new Date(); while(now-start < 100);
}

</script>	
</body>
</html>

Mobile App Streaming


Features on Platforms

Offline-Mode


For Android and iOS version, Kantar Media Audiences-lib has a feature the so-called "offlineMode". This mode can be switched on and off by using public API: KMA_SpringStreams.offlineMode.

If the lib is configured to offlineMode, KMA_SpringStreams library will hold all requests in a local buffer and send them when the device goes back online. 

KMA_SpringStreams lib checks the internet connection regularly by using the iOS Timer and send the data as soon as possible.

Please notice:

  • Old requests will be dropped if too many requests pump into local buffer for the limitation in buffer size. 
  • The default buffer size is 500. (Note: in case of streaming requests only the last request of a singular streaming sequence is stored, in other words it allows for 500 views to be stored)
  • The data will be stored in a local file, so the lib will not lose the requests even if the application terminates
  • the Kantar Media Audiences-lib tries to send the requests in a fixed rate, 10 seconds by default, and sends them if device is online again

How the library acts when offline-mode is enabled:

  • The device will keep trying to empty the ring buffer by attempting to send requests to the measuring system.
  • When there is a connection, the requests seamlessly pass through the ring buffer.
  • When there is no connection, the requests will be retained indefinitely.

For more information, check the chapter about offline-mode below.


iOS Identifiers


Device ID (did)Advertising ID (ai)Mac ID (mid)ID_For_Vendor (ifv)
iOS4------(plus)---
iOS5------(plus)---
iOS6---(plus)(plus)(plus)
iOS7---(plus)---(plus)
iOS8---(plus)---

(plus)

iOS9

(plus)

(plus)


Considering that the Apple private policy is changing all the time, Kantar Media Audiences libs have to adapt different UDIDs for identifying the end user's devices.

  • Since iOS7 the Mac ID is not available anymore,
  • Since iOS7 introducing of Advertising ID and ID_For_Vendor

Please attention:
Apple will reject all the applications which retrieve Advertising ID (ai) but with no advertising content provided.

So Advertising-Framework is linked as optional in Kantar Media Audiences lib,
If the Advertising ID should be used as udid, please import Advertising-Framework into your projects.



Migration to iOS9 (warning)


URL Scheme White-listing

Starting on iOS9, iOS apps will have to declare what URL schemes they would like to be able to check for and open in the configuration files (plist file) of the app as it is submitted to Apple.
So if your application is combining with a specific "Panel App" (e.g. Virtualmeter App), please register the URL scheme in your application, the syntax is as following:


<key>LSApplicationQueriesSchemes</key> 
     <array> 
         <string>**.***.ipm</string> //please change **.*** to the parameters given in your library delivery 
         <string>*****PanelApp</string> //please change ***** to the parameters given in your library delivery  
     </array>
App Transport Security (ATS)

Starting from iOS 9.0, App Transport Security (ATS) enforces best practices in the secure connections between an app and its back end.
Migrating from "http" to "https" has to be planed for the more secure communication.
However for this moment, if you decide to enable ATS, a temporary solution can be adapted by adding an exception for Kantar Media Audiences measurement box:


<key>NSAppTransportSecurity</key> 
 <dict> 
         <key>NSAllowsArbitraryLoads</key> 
         <false/> 
         <key>NSExceptionDomains</key> 
         <dict> 
             <key>*****</key> //please change ***** to the parameters given in your library delivery 
             <dict> 
                 <key>NSIncludesSubdomains</key> 
                 <true/> 
                 <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> 
                 <true/> 
                 <key>NSExceptionRequiresForwardSecrecy</key> 
                 <false/> 
             </dict> 
         </dict> 
     </dict>
if ATS is not enabled in your application, as NSAllowsArbitraryLoads = true, then you don't need to modify anything.
Bitcode

Bitcode is a new feature on iOS 9, an intermediate representation of a compiled program. Now you have the new Kantar Media Audiences lib with Bitcode enabled for your application you have the chance to enable or disable the Bitcode service.

Android Identifier

Google Advertising ID

Beginning with Kantar Media Audiences lib 1.6.0, GoogleAdvertisingID is adapted to identify the android devices.
In order to achieve this, please import google-play-services_lib to your application.
Follow the tutorial detailed info: http://developer.android.com/google/play-services/setup.html

  • Install the Google Play Services SDK.
  • Import the project into your application.
  • Don't forget to add following items into your project AndroidManifest.xml file:
     

    <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />



Streaming Measurement in iOS

This chapter describes the basic use of the sensor for the measurement of streaming content.


Properties of the Library

PropertyDefaultDescription
trackingtrue

If this property is set to false, no requests are sent to the measuring system  

offlineModefalse

If this property is set to true, requests to the measuring system pass through a persistent ringbuffer.

debugfalseIf the value is set to true, the debug messages are issued by the library 
timeout10Timeout Setting for the HTTP-Request


Lifecycle of the Measurement

This chapter explains step by step  the use of the sensor and the measurement of streaming content . The following steps are necessary in order to measure a content.

  1. Generating the sensor
  2. Implementation of the adapter
  3. Beginning of the measurement
  4. End of the measurement


Generating the Sensor

When you start the app the sensor must be instantiated once. It is not possible to change the site name or the application name after this call.

KMA_SpringStreams *spring = [KMA_SpringStreams getInstance:@"sitename" a:@"TestApplication"];

The site name and application name is specified by the operator of the measurement system.


From this point on, the method getInstance must be used .

KMA_SpringStreams *spring = [KMA_SpringStreams getInstance];


Implementation of the Adapter

In principle it is possible to measure any media library with an adapter, that is available within an app to use streaming content .


Therefore the protocol Meta and the interface StreamAdapter must be implemented. The meta object supplies the information regarding the used player, the player version and the screen size.

The library must be able to read continuously the current position on a stream in seconds or  the current position of player. 
 

@interface KMA_Player_Meta : NSObject<NSCoding, NSCopying> {
}
@property (retain,readwrite) NSString *playername;
@property (retain,readwrite) NSString *playerversion;
@property (assign,readwrite) int screenwidth;
@property (assign,readwrite) int screenheight;

@end

@protocol KMA_StreamAdapter
@required
-(KMA_Player_Meta*) getMeta;
-(int) getPosition;
-(int) getDuration;
-(int) getWidth;
-(int) getHeight;
@end

Beginning of the Measurement

This chapter explains step by step how a streaming content is transferred to the library for the measurement.


In the library an adapter for class AVPlayerViewController from the AVKit ramework is settled.

The source code for this implementation can be found in Anhang A and in the library.


The following code block shows an example for the use of the library.

...
@property (strong,nonatomic) AVPlayerViewController *player;
...

player = [[AVPlayerViewController alloc] init];
player.player = [AVPlayer playerWithURL:url];

self.player.view.frame = self.videoPlaceholder.frame;
[self.view addSubview:self.player.view];

KMA_MediaPlayerAdapter *adapter = [[KMA_MediaPlayerAdapter alloc] adapter:self.player];
//Call track to track the video player

NSMutableDictionary * atts = [[NSMutableDictionary alloc] init];
[atts setObject:@"live/iOS/teststream" forKey:@"stream"]; //mandatory
// [atts setObject:@"test" forKey:@"cq"]; //optional see implementation guideline
// [atts setObject:@"1369" forKey:@"vt"]; //optional see implementation guideline
// [atts setObject:@"testSiteName" forKey:@"sitename="]; //optional see implementation guideline
Stream *stream = [self.spring track:adapter atts:atts];
[atts release];
...

First, the player and the object needs to be instantiated, that is able to deliver the current position on a stream in seconds. In the second step the adapters must be produced, which implements this requirement accurately.

  Then an NSDictionary is generated in order to formulate more detailed information about the stream.   Therefore the attribute stream must always be specified


The attribute stream is always required and must be specified for each measurement

Next, the method track  is called with the adapter and the description property of the stream. From this point on, the stream is measured by the library as long as the application remains in foreground  and the measured data are cyclically transmitted to the measuring system.

When the application goes into the background , all measurements are stopped and closed, i.e. when the application comes to the foreground again, the method track must be called again.

A stream is measured as long as the application is in the foreground. When the application goes into the background, the current status is transmitted to the measurement system and the measurement stopsIf the stream should be measured again, when the application will come back to the foreground, the method track must be called again.


End of the Measurement

After the measurement of a stream has been started, this stream is measured by the sensor. The measurement can be stopped by calling the method stop on the stream object. All measurements will be automatically stopped by the library, when the application goes into the background.

// start streaming measurement
Stream *stream = [spring track:adapter atts:atts];
...
// stop measurement programmatically
[stream stop];

If the stream should be measured again, when the application comes to the foreground or after the method stop has been called, the method track must be called again.


Foreground- and Background Actions


Once the application is in the background all measurements in the library are stopped, i.e. when the application goes to the foreground, the measurement on a stream must be restarted.


Ending the application

If the application is closed, the method unload can be called. This call sends the current state of the measurements to the measuring system and then terminates all measurements. This method is automatically called by the library, when the application goes into the background.

...
[[KMA_SpringStreams getInstance] unload];


Appendix A

In the following example, the adapter has been implemented for the media player from the standard API.

//
//  KMA_MediaPlayerAdapter.m
//  KMA_SpringStreams
//
//  Created by Frank Kammann on 26.08.11.
//  Copyright 2011 spring GmbH & Co. KG. All rights reserved.
//

#import "KMA_SpringStreams.h"

/**
 * Implementation of a KMA_StreamAdapter.
 * 
 * @see KMA_StreamAdapter
 */
@implementation KMA_MediaPlayerAdapter

AVPlayerViewController *controller;
KMA_Player_Meta *meta;

/**
 * Initialize the adapter with the MPMoviePlayerController
 *
 * @see http://developer.apple.com/library/ios/#documentation/mediaplayer/reference/MPMoviePlayerController_Class/Reference/Reference.html
 * 
 */
- (KMA_MediaPlayerAdapter*)adapter:(AVPlayerViewController *)player {
    //if (self) {
        meta = [[KMA_Player_Meta alloc] init];
        meta.playername = @"AVPlayerViewController";
        controller = player;
    //}
    return [super init];
}

/**
 * Returns the meta object.
 *
 * @see MediaPlayerMeta
 */
- (KMA_Player_Meta*) getMeta {
    return meta;
}

/**
 * Returns the current position on the KMA_Stream in seconds by calling
 * the method MPMediaPlayback.currentPlaybackTime
 *
 * @see http://developer.apple.com/library/ios/#documentation/mediaplayer/reference/MPMediaPlayback_protocol/Reference/Reference.html#//apple_ref/occ/intf/MPMediaPlayback#
 */
- (int) getPosition {
    int position = CMTimeGetSeconds(controller.player.currentItem.currentTime);
    // in initialize phase in controller this value is set to -2^31
    if(position < 0) position = 0;
    return position;
}

/**
 * Returns the duration of the KMA_Stream in seconds by calling
 * the method MPMediaPlayback.duration.
 *
 */
- (int) getDuration {
    return CMTimeGetSeconds(controller.player.currentItem.duration);
}

/**
 * Returns the width by the controller view.
 *
 * @return contoller.view.bound.size.width
 */
- (int) getWidth {
    return controller.view.bounds.size.width;
}

/**
 * Returns the height by the controller view.
 *
 * @return contoller.view.bound.size.height
 */
- (int) getHeight {
    return controller.view.bounds.size.height;
}

@end



@implementation KMA_Player_Meta

/**
 * Returns the player name
 *
 * @return the string "MediaPlayer"
 */
@synthesize playername;

/**
 * Returns the player version.
 * The itselfs has no version so the system version is delivered.
 *
 * @see http://developer.apple.com/library/ios/#documentation/uikit/reference/UIDevice_Class/Reference/UIDevice.html
 *
 * @return The version my calling [UIDevice currentDevice].systemVersion
 */
@synthesize playerversion;

/**
 * Returns the screen width my calling the method
 * [[UIScreen mainScreen] bounds].screenRect.size.width
 *
 * @see http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScreen_Class/Reference/UIScreen.html
 *
 * @return the width
 */
@synthesize screenwidth;

/**
 * Returns the screen width my calling the method
 * [[UIScreen mainScreen] bounds].screenRect.size.height
 *
 * @see http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScreen_Class/Reference/UIScreen.html
 *
 * @return the height
 */
@synthesize screenheight;

- (id) init {
    self = [super init];
    if (self) {
        self.playername = @"iOS Player";
        self.playerversion = [UIDevice currentDevice].systemVersion;
        
        CGRect screenRect = [[UIScreen mainScreen] bounds];
        self.screenwidth = screenRect.size.width;
        self.screenheight = screenRect.size.height;
    }
    return self;
}

- (id) copyWithZone:(NSZone *)zone {
    id copy = [[[self class] alloc] init];
    
    if (copy) {
        // Copy NSObject subclasses
        [copy setPlayername:[self.playername copyWithZone:zone]];
        [copy setPlayerversion:[self.playerversion copyWithZone:zone]];
        
        // Set primitives
        [copy setScreenwidth:self.screenwidth];
        [copy setScreenheight:self.screenheight];
    }
    return copy;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"Meta pl=%@ plv=%@ sx=%ld sy=%ld",
            self.playername,
            self.playerversion,
            (long)self.screenwidth,
            (long)self.screenheight];
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.playername forKey:@"playername"];
    [encoder encodeObject:self.playerversion forKey:@"playerversion"];
    [encoder encodeInt:self.screenwidth forKey:@"screenwidth"];
    [encoder encodeInt:self.screenheight forKey:@"screenheight"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    self.playername = [decoder decodeObjectForKey:@"playername"];
    self.playerversion = [decoder decodeObjectForKey:@"playerversion"];
    self.screenwidth = [decoder decodeIntForKey:@"screenwidth"];
    self.screenheight = [decoder decodeIntForKey:@"screenheight"];
    return self;
}

@end
 

Implementation of the URL Scheme in iOS

Generally for spring measuring purpose, only some modifications need to be applied in your App, if a Panel App is used in your market. 
(This blog may assist your implementation):

Register the URL Scheme accordingly. 

In order to register your URL Scheme into your iOS App, you need to edit the Info.plist file under the "Supporting Files" in your project folder, two ways:

  1. you can edit it in any editor, if you do so, please insert the following code:

    	<key>CFBundleURLTypes</key>
    	<array>
    		<dict>
    			<key>CFBundleURLName</key>
    			<string>***</string>      //please change *** to your URL name, not so important
    			<key>CFBundleURLSchemes</key>
    			<array>
    				<string>***</string>  //very important, please replace 
    			</array>
    		</dict>
    	</array>
  2. Or you can edit this in xcode, add an item into Info.plist, named "URL types", expand "Item 0" under "URL types", and add two items: "URL identifier", "URL Schemes".

    For "URL identifier", assign your identifier, and for "URL Schemes", add a new item within it named "Item0", REGISTER A UNIQUE URL SCHEME FOR YOUR APP, VERY IMPORTANT!

    It should be like the following:



How to use the different Files in the Library Package

FileDescription
spring-appsensor-device.aThis is the version that has been compiled with ARM support and which is intended for execution on iOS devices
spring-appsensor-simulator.aThis is the version that has been compiled with x86 support and which is intended for execution on iOS simulator
spring-appsensor-fat.aThis is a combined version of the two libraries above, which can be executed on both, iOS devices and iOS simulator
because it contains code for ARM and x86 execution.
This file is called "fat" as it is roughly double the size (because it combines both versions).


If size does not matter for the app, the "fat" version is the carefree option to be used for execution on simulator and real devices.

Tutorial on how to import KMA measurement Objective-C library into Swift project

 

In this tutorial, you will find how to import KMA measurement Objective-C library into your Swift project.

The sample will be given with KMA tagging library, the steaming library will be very similar, please adapt the changes on your own project.

 

  1. Import the library into your project as before: 


  2. If you want to import the Objective-C static library into your project, you will need a Bridging-Header file. In your project, create a new file, header file, the content will be like this:
    you will need <Foundation/Foundation.h> and import "KMA_Spring.h" or "KMA_SpringStreams"



  3. After import the library in, you will have to link the bridging file as the Objective-C Bridging header in your project,
    the setting is in 'Build Settings', search for "Objective-C Bridging Header", and drag the bridging header file in.



  4. The first 3 steps will allow you the access to the library, the rest will be the implementation in your code just like before:

        var springsensor: KMA_Spring?
    
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
            springsensor = KMA_Spring(siteAndApplication: "test",application: "testapp");
    
            springsensor?.debug = true;
    
            springsensor?.ssl = true;
    
            let map: NSDictionary = ["stream":"testApp", SPRING_VAR_ACTION:SPRING_APP_STARTED]
    
            springsensor?.commit(map as [NSObject : AnyObject])
    
            // Override point for customization after application launch.
    
            return true
    
        }

All the resources can be downloaded here: 

For tagging: Swift_Bridging_Header.h

For Streaming: Swift_Bridging_Header_Streaming.h

               

 


Streaming Measurement in Android

This chapter describes the basic use of the sensor for the measurement of streaming content.


Properties of the Library

PropertyDefaultDescription
trackingtrue

If this property is set to false, no requests are sent to the measuring system  

offlineModefalse

If this property is set to true, requests to the measuring system pass through a persistent ringbuffer.

debugfalseIf the value is set to true, the debug messages are issued by the library.
timeout10Timeout Setting for the HTTP-Request.


Lifecycle of the Measurement

This chapter explains step by step  the use of the sensor and the measurement of streaming content . The following steps are necessary in order to measure a content.

  1. Generating the sensor
  2. Implementation of the adapter
  3. Beginning of the measurement
  4. End of the measurement


Generating the Sensor

When you start the app the sensor must be instantiated once. It is not possible to change the site name or the application name after this call.


String site = ...;    // will delivered by the measurement provider
Spring appName = ...; // will delivered by the measurement provider
SpringStreams sensor = SpringStreams.getInstance(site, appName, getApplicationContext());

The site name and application name is specified by the operator of the measurement system.


Implementation of the Adapter

In principle it is possible to measure any media library with an adapter, that is available within an app to use streaming content .


Therefore the protocol Meta and the interface StreamAdapter must be implemented. The meta object supplies the information regarding the used player, the player version and the screen size.

The library must be able to read continuously the current position on a stream in seconds or  the current position of player. 
 

package de.spring.mobile;

/**
 * This adapter must be used to support some players by the library.
 * 
 * @see Meta
 * @see SpringStreams
 * @see SpringStreams#track(StreamAdapter, java.util.Map)
 * 
 * @author <a href="mailto:support@spring.de">spring GmbH &amp; Co.KG</a>
 */
public interface StreamAdapter {

	public interface Meta {
		public String getPlayerName();
		public String getPlayerVersion();
		public int getScreenWidth();
		public int getScreenHeight();
	}
	
	public Meta getMeta();
	
	public int getPosition();
	
	public int getDuration();
	
	public int getWidth();
	
	public int getHeight();	
}



Beginning of the Measurement

This chapter explains step by step how a streaming content is transferred to the library for the measurement.


In the library an adapter for class android.widget.VideoView

The source code for this implementation can be found in Appendix A and in the library.


The following code block shows an example for the use of the library.

 
// Create a sensor once in the application
SpringStreams sensor = SpringStreams.getInstance(site, appName, getApplicationContext());
...
 
public MyVideoActivity extends Activity {
	
	private static SpringStreams sensor;
	private Stream stream;
 
	public MyVideoActivity(SpringStreams sensor) {
		if(MyVideoActivity.sensor == null) 
			MyVideoActivity.sensor = sensor;
	}
 
	@Override
	protected void onStart() {
		Map<String, Object> atts = new HashMap<String, Object>();
		atts.put("stream", "android/teststream"); // mandatory
		// atts.put("cq", "4711"); // optional see implementation guideline
		// atts.put("ct", "mobile"); // optional see implementation guideline
		stream = sensor.track(new VideoViewAdapter(this.videoView), atts);
		super.onStart();
	}
	
	@Override
	protected void onStop() {
		stream.stop();
		super.onStop();
	}
 
	...
}
 

First, the player and the object needs to be instantiated, that is able to deliver the current position on a stream in seconds. In the second step the adapters must be produced, which implements this requirement accurately.

  Then an NSDictionary is generated in order to formulate more detailed information about the stream.   Therefore the attribute stream must always be specified


The attribute stream is always required and must be specified for each measurement

Next, the method track  is called with the adapter and the description property of the stream. From this point on, the stream is measured by the library as long as the application remains in foreground  and the measured data are cyclically transmitted to the measuring system.

When the application goes into the background , all measurements are stopped and closed, i.e. when the application comes to the foreground again, the method track must be called again.

A stream is measured as long as the Activity is in the foreground. When the Activity goes into the background, the current status is transmitted to the measurement system and the measurement stopsIf the stream should be measured again, when the application will come back to the foreground, the method track must be called again.


End of the Measurement

After the measurement of a stream has been started, this stream is measured by the sensor. The measurement can be stopped by calling the method stop on the stream object. All measurements will be automatically stopped by the library, when the application goes into the background.

// start streaming measurement
Stream *stream = sensor.track(adapter, atts);
...
// stop measurement programmatically
stream.stop();



If the stream should be measured again, when the Activity comes to the foreground or after the method stop has been called, the method track must be called again.


Ending the application

If the application is closed, the method unload can be called. This call sends the current state of the measurements to the measuring system and then terminates all measurements. This method is automatically called by the library, when the application goes into the background.

...
sensor.unload();


Using the SpringStreamsActivity

In the library is a  SpringStreamsActivity included, that can be used to largely automate the measurement. The source code of this class can be found in Appendix B and in the library.


public class VideoPlayer extends SpringStreamsActivity {

	VideoView videoView;

	/**
	 * 
	 */
	public VideoPlayer() {
		super("test", "Streaming Test App");
	}
	
	
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		videoView = ...; 
	}

	@Override
	protected void onStart() {
		Map<String, Object> atts = new HashMap<String, Object>();
		atts.put("stream", "android/teststream");
		super.track(new VideoViewAdapter(this.videoView), atts);
		super.onStart();
	}
}

ProGuard


Please Note: If you are using ProGuard, our library could be compromised.
Therefore it is necessary to add the following lines into the ProGuard configuration file:

# Keep spring files
-keep class de.spring.** { *; }

If using version spring-appstreaming-android-1.2.5 (and lower), it is necessary to add the following lines into the ProGuard configuration file:


# Keep spring files
-keep class de.spring.** { *; }
-keep class org.apache.** { *; }


If you want to suppress the warnings regarding library-program-class-dependencies , please add to the configuration file

-dontwarn android.webkit.WebView
-dontwarn android.webkit.WebViewClient


Appendix A

In the following example, the adapter has been implemented for the VideoView from the standard API.

/**
 * Implementation of an adapter for the {@link VideoView} in standard 
 * android api.
 * 
 * @author <a href="mailto:support@spring.de">spring GmbH &amp; Co.KG</a>
 */
public class VideoViewAdapter implements StreamAdapter {

		
	private VideoView videoView;
	
	public VideoViewAdapter(VideoView videoView) {
		if(videoView == null) 
			throw new NullPointerException("videoView may not be null");
		this.videoView = videoView;
	}

	@Override
	public Meta getMeta() {
		return new Meta() {
			public String getPlayerName() {
				return "android.widget.VideoView";
			}
			public String getPlayerVersion() {
				return Build.VERSION.RELEASE;
			}
			public int getScreenHeight() {
				return (((WindowManager)videoView.getContext().getSystemService(Context.WINDOW_SERVICE))
                                       .getDefaultDisplay()).getHeight();
			}
			public int getScreenWidth() {
				return (((WindowManager)videoView.getContext().getSystemService(Context.WINDOW_SERVICE))
					.getDefaultDisplay()).getWidth();
			}
		};
	}
	
	@Override
	public int getPosition() {
		return (int)Math.round(videoView.getCurrentPosition()/1000.0);
	}

	@Override
	public int getDuration() {
		return (int)Math.round(videoView.getDuration()/1000.0);
	}

	@Override
	public int getWidth() {
		return videoView.getWidth();
	}

	@Override
	public int getHeight() {
		return videoView.getHeight();
	}
}
 


Appendix B

The source code of class SpringStreamsActivity

package de.spring.mobile;

import java.util.Map;
import android.app.Activity;
import android.os.Bundle;

/**
 * 
 * @author  <a href="mailto:support@spring.de">spring GmbH &amp; Co.KG</a>
 */
public abstract class SpringStreamsActivity extends Activity {
	private static SpringStreams sensor;
	private static int instances = 0;
	private Stream stream;
	
	
	public SpringStreamsActivity(String site, String appName) {
		this(site, appName, 10);
	}
	
	public SpringStreamsActivity(String site, String appName, int timeout) {
		if(site == null) throw new NullPointerException("parameter site may not be null");
		if(appName == null) throw new NullPointerException("parameter appName may not be null");
		
		if(sensor == null) {
			sensor = SpringStreams.getInstance(site, appName, getApplicationContext());
			sensor.setTimeout(timeout);
		}
	}
	public Stream getStream() {
		return stream;
	}
	public void setSensorDebug(boolean debug) {
		sensor.setDebug(debug);
	}
	
	public boolean isSensorDebug() {
		return sensor.isDebug();
	}
	
	public void setTracking(boolean tracking) {
		sensor.setTracking(tracking);
	}
	
	public boolean isTracking() {
		return sensor.isTracking();
	}	
	
	public void track(StreamAdapter adapter, Map<String, Object> atts) {
		if(this.stream != null) {
			stream.stop();
			stream = null;
		}
		stream = sensor.track(adapter, atts);
	}
	/**
	 * @see android.app.Activity#onCreate(android.os.Bundle)
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		instances++;
		super.onCreate(savedInstanceState);
	}
	
	
	/**
	 * @see android.app.Activity#onStop()
	 */
	@Override
	protected void onStop() {
		if(stream != null) {
			stream.stop();
			stream = null;
		}
		super.onStop();
	}
	
	/**
	 * @see android.app.Activity#onDestroy()
	 */
	@Override
	protected void onDestroy() {
		instances--;
		if(instances <= 0) {
			sensor.unload();
			sensor = null;
			stream = null;
		}
		super.onDestroy();
	}
}


App Streaming Measurement Off-line Mode (if supported by your library version)

This chapter describes the basic working of the sensor for the measurement of streaming content in off-line mode.

As soon as the offlineMode is set to true, the following behavior of the library will change:

Properties of the offline-mode in the library:
  • Requests to the measuring system pass through an internal persistent ring buffer.
  • The ring buffer can contain up to 500 entries.
  • The ring buffer persists even after the Application is closed.
  • The rule for the ring buffer is "FIFO": First in, First out.
  • If the buffer is full, the oldest request will be replaced.
How the library acts when offline-mode is enabled:
  • The device will keep trying to empty the ring buffer by attempting to send requests to the measuring system.
  • When there is a connection, the requests seamlessly pass through the ring buffer.
  • When there is no connection, the requests will be retained indefinitely.
  • Within a single viewing session, only the last request of the session is kept in the ring buffer. This one request contains all the information of the entire viewing session.
How and when to use the offline-mode:

 We recommend that you switch on the offline-mode only when downloaded content is being played (regardless if the device has an active and functioning internet connection).

Streaming Measurement in Cordova

This chapter describes the basic use of the JavaScript sensor for the measurement of streaming content with the Cordova framework.

The following platforms are supported with this method:

  • Android
  • iOS

Other target platforms may work, but are not tested and therefore considered as not supported.

Notice

This approach cannot be tested in "browser" mode.

If this sensor detects "browser" as the target platform it defaults to the HTML5 sensor implementation of the pure JavaScript sensor.

Integration

Add plugins to config.xml

The integration requires a few dependencies to be installed within the config.xml of the Cordova-based project.

    <plugin name="cordova-plugin-device" spec="~2.0.3" />
    <plugin name="cordova-plugin-idfa" spec="~1.1.0">
        <variable name="PLAY_SERVICES_ADS_VERSION" value="15.0.+" />
    </plugin>
    <plugin name="cordova-plugin-app-version" spec="^0.1.9" />
    <plugin name="cordova-plugin-file" spec="~6.0.2" />
    <plugin name="cordova-plugin-advanced-http" spec="~2.2.0" />

They need to go below the closing of the last </platform>.

Example:

<platform name="android">
        <allow-intent href="market:*" />
</platform>
<platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
</platform>

<plugin name="cordova-plugin-device" spec="~2.0.3" />
<plugin name="cordova-plugin-idfa" spec="~1.1.0">
  <variable name="PLAY_SERVICES_ADS_VERSION" value="15.0.+" />
</plugin>
<plugin name="cordova-plugin-app-version" spec="^0.1.9" />
<plugin name="cordova-plugin-file" spec="~6.0.2" />
<plugin name="cordova-plugin-advanced-http" spec="~2.2.0" />
Install plugins and its dependencies

To install these plug-ins, use Cordova's prepare.

cordova prepare

Output (Sample):

Plugin 'cordova-plugin-device' found in config.xml... Migrating it to package.json
Plugin 'cordova-plugin-idfa' found in config.xml... Migrating it to package.json
Plugin 'cordova-plugin-app-version' found in config.xml... Migrating it to package.json
Plugin 'cordova-plugin-file' found in config.xml... Migrating it to package.json
Plugin 'cordova-plugin-advanced-http' found in config.xml... Migrating it to package.json
Discovered saved plugin "cordova-plugin-device". Adding it to the project
Installing "cordova-plugin-device" for android
Installing "cordova-plugin-device" for ios
Discovered saved plugin "cordova-plugin-idfa". Adding it to the project
Installing "cordova-plugin-idfa" for android
Installing "cordova-support-android-plugin" for android
Subproject Path: CordovaLib
Subproject Path: app
Installing "cordova-plugin-idfa" for ios
Discovered saved plugin "cordova-plugin-app-version". Adding it to the project
Installing "cordova-plugin-app-version" for android
Installing "cordova-plugin-app-version" for ios
Discovered saved plugin "cordova-plugin-file". Adding it to the project
Installing "cordova-plugin-file" for android

The Android Persistent storage location now defaults to "Internal". Please check this plugin's README to see if your application needs any changes in its config.xml.

If this is a new application no changes are required.

If this is an update to an existing application that did not specify an "AndroidPersistentFileLocation" you may need to add:

      "<preference name="AndroidPersistentFileLocation" value="Compatibility" />"

to config.xml in order for the application to find previously stored files.
        
Installing "cordova-plugin-file" for ios
Discovered saved plugin "cordova-plugin-advanced-http". Adding it to the project
Installing "cordova-plugin-advanced-http" for android
Plugin dependency "cordova-plugin-file@6.0.2" already fetched, using that version.
Dependent plugin "cordova-plugin-file" already installed on android.
Subproject Path: CordovaLib
Subproject Path: app
Installing "cordova-plugin-advanced-http" for ios
Plugin dependency "cordova-plugin-file@6.0.2" already fetched, using that version.
Dependent plugin "cordova-plugin-file" already installed on ios.
Alternative: Install plugins via "cordova plugin"

Alternatively the plug-ins can be installed manually:

cordova plugin add cordova-plugin-device
cordova plugin add cordova-plugin-idfa
cordova plugin add cordova-plugin-app-version
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-advanced-http

Content-Security-Policy:

It is vital to permit external connections to the provided tracking site i.e.:

connect-src test.2cnt.net;

(warning) test = The site code in this case is “test”. You have to replace the site code “test” by the site code (unique name provided by Kantar) for the measured site.

This includes loading images from that site, i.e.:

img-src test.2cnt.net;

Inline same-origin scripts also need to be allowed:

script-src 'self' 'unsafe-inline';

Implementation

The sensor's script (SpringStreams.js) needs to be added to the project at:

www/js/springstreams.js

The rest of the integration works as stated in Basic Use of the API, with some key differences.

The location to load the sensor script from will not include the www as stated in the path above.

<script src="js/springstreams.js"></script>

To work with Cordova, the track method requires the HTML5CordovaAdapter as adapter argument:

//Sensor tracking
sensors.track(document.getElementById('video'), description, sensors.HTML5CordovaAdapter);

Example Integration

The following integration shows a complete integration on a simple HTML page.

<html>
    <head>       
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; connect-src *.2cnt.net; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
        <meta name="format-detection" content="telephone=no">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
        <link rel="stylesheet" type="text/css" href="css/index.css">
        <!-- Sensor javascript file -->
        <script src="js/springstreams.js"></script>
        <!-- Sensor initialization -->
        <script type="text/javascript">
            var sensors = new SpringStreams("test");
        </script>
        <title>Hello World</title>
    </head>
    <body>
        <div>
            <h1>Apache Cordova</h1>
            <!-- http://camendesign.com/code/video_for_everybody/test.html -->
            <video width="100%" id="video" src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" controls />
            <script type="text/javascript">
                document.addEventListener("deviceready", function () {
                    //Sensor description
                    var description = {
                        "stream": "MyVideoName"
                    };
                    //Sensor tracking
                    sensors.track(document.getElementById('video'), description, sensors.HTML5CordovaAdapter);
                }, false);
            </script>           
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

Information (optional)

The user can be informed at some point that the application monitors the user actions and transmits them to a measuring system. Furthermore, the user must be informed that he has the possibility to switch of the tracking in the application and can contradict this way. (see: Implementation of Stream Measurement#Opt-Out)

For this purpose, you can include data privacy information in your language into an appropriate place of your app implementation:

Our app uses the "mobile app streaming sensor" of Kantar Media spring, Saarlouis, Germany, to gather statistics about the usage of our site. This data is collected anonymously.
This measurement of the mobile usage uses an anonymized device identifier for recognition purposes. To ensure that your device ID can not be clearly identified in our systems, it is encrypted and will be reduced by half. Only the encrypted and shortened device identifier is used in this measurement context.
This mobile measurement was developed under the observance of data protection laws. The aim of the measurement is to determine the intensity of use, the extent of use and the number of users of a mobile application. At no time, individual users will be identified. Your identity is always protected. You get no advertising by this system.
You can opt-out of the measurement by our app with the following activation switch.

Please note that only the measurement of our app is disabled. It may be that you will continue to be measured by other broadcasters using the "mobile app streaming sensor".


Mobile App Streaming Sensor Measurement: |On/Off|

Opt-Out

The application developer can give users the ability to stop the further tracking of the user actions. For this purpose the library offers the following methods:

/**
 * When the value <code>false</code> is specified, the sending of
 * requests to the measuring system is switched off.
 * This value is <code>true</code> by default.
 */
public void setTracking(boolean tracking) { }
/**
 * Delivers the value <code>true</code> when the tracking
 * is activated otherwise the value is <code>false</code>.
 */
public boolean isTracking() { }

A persistent saving of the opt-out decision in the library is not provided and needs to be implemented by the app developer.