[:en]IPTV Application Development[:fr]Développement d’applications IPTV[:] | VESTEL B2B Support Portal

[:en]IPTV Application Development[:fr]Développement d’applications IPTV[:]

[:en]IPTV Application Development[:fr]Développement d’applications IPTV[:]

[:en]

This document gives a basic introduction to Client Device configuration and by using HTML, CSS and JavaScript, we will make basic applications such as;

  • Basic Menu Application : A menu application that shows how to handle remote keys and navigate through pages and channels.
  • Basic Video Player : A video player that is used to stream videos.
  • Basic Channel List Application : An application that shows how to upload channel list and how to stream channels on client devices.

Client devices has a special mode named “Hotel Mode” and when it is enabled, client devices fetch a given URL after waking up and navigates through that page. This specific page is called “starturl” and for running any web application, a “starturl” should be configured on to the client. You could find necessary details for configuring the starturl URL and “Hotel Mode” for each client from Client Startup Guide-Overview document.

You need to serve the URL through a server (IIS, Apache etc.).  On server side, there should be a folder named “conntest” and this folder should contain a file called “2kb.txt“(Figure 1). This file helps client device to check whether the given URL is valid or not. Regardless of its content, the file should be 2KB sized.

Figure 1: 2kb.txt File

As stated above, once the “starturl” is configured any web application could run on client device. There are some very basic application which you could run on client devices and this document will cover them. The first sample application will be Basic Menu Application.

Basic Menu Application

This section tries to explain how to create a basic menu application by using both CSS and JavaScript. By creating this menu application you could handle the remote controller keys and send them through this application. This can give the opportunity to control and navigate over the pages and channels.

Pure CSS Based Menu

The TV’s browser can navigate across the anchor tags with arrow keys. It is one of the approaches that you could follow. In this example we write four links and travel around them using arrow keys. You should specify style of focused link by using “focus” pseudo-class in CSS file. It helps you to identify focused anchor tags. Here is the simple example.


 <a href="#">Link 1 </a>
 <a href="#">Link 2 </a>
 <a href="#">Link 3 </a>
 <a href="#">Link 4 </a>

a
{
 text-decoration:none;
 font-size:20px;
 color:Black;
}
a:focus
{
 text-decoration:none;
 color:White;
 font-size:30px;
}

Pure Javascript Based Menu

You could also write the same example with JavaScript. It can be decrease DOM dependencies and you could also customize navigation by writing your own key handler methods. Here is the same page with JavaScript.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="main.js"></script>
<link href="main.css" rel="stylesheet" type="text/css" />
<title>Vestek Tutorial</title>
</head>
<body>
<div id="menu"></div>
<script>
vestek.boot();
</script>
</body>
</html>

body
{
background:gray;
}
#menu
{
text-decoration:none;
font-size:20px;
color:Black;
}
#menu .selected
{
text-decoration:none;
color:White;
font-size:30px;
}

var vestek = {};
vestek.menuContent = [{ "title" : "Link 1", "url" : "#"} ,{ "title" :
"Link 2", "url" : "#"},{ "title" : "Link 3", "url" : "#"},{ "title" :
"Link 4", "url" : "#"} ];
vestek.menuIndex = 0;
vestek.boot = function () {
for(index in vestek.menuContent){
vestek.menuContent[index].element = document.createElement("div");
vestek.menuContent[index].element.innerHTML = vestek.menuContent[index]['title'];
document.getElementById("menu").appendChild(
vestek.menuContent[index].element);
}
vestek.menuContent[this.menuIndex].element.className = "selected";
document.onkeydown = function (evt) {
vestek.keyHandler(evt.keyCode);
};
};
vestek.keyHandler = function (code) {
switch (code){
case this.keyEnum.OK:
document.location.href =
vestek.menuContent[this.menuIndex]['url'];
break;
case this.keyEnum.UP:
if(this.menuIndex != 0){
vestek.menuContent[this.menuIndex].element.className =
"";
this.menuIndex--;
vestek.menuContent[this.menuIndex].element.className =
"selected";
}
break;
case this.keyEnum.DOWN:
if(this.menuIndex != this.menuContent.length - 1){
vestek.menuContent[this.menuIndex].element.className =
"";
this.menuIndex++;
vestek.menuContent[this.menuIndex].element.className =
"selected";
}
break;
default:
break;
}
};
vestek.keyEnum = {
OK : 13,
UP : 38,
DOWN : 40,
RIGHT : 39,
LEFT : 37,
GREEN : 404,
RED : 403,
YELLOW : 405,
BLUE : 406,
BACKSPACE : 461,
OK : 13,
PLAY : 415,
PAUSE : 19,
FW : 417,
BW : 412,
STOP : 413,
LANGUAGE : 312,
HOME : 407,
HELP : 156,
BACK : 461,
ALARM : 309,
ZERO : 48,
ONE : 49,
TWO : 50,
THREE : 51,
FOUR : 52,
FIVE : 53,
SIX : 54,
SEVEN : 55,
EIGHT : 56,
NINE : 57,
PROGUP : 310,
PROGDOWN : 311,
FRONTPROGUP : 314,
FRONTPROGDOWN : 313,
};

Menu content is just a JSON. It also gives you a flexibility to write Ajax based applications. All default key values are written as enum. You could abstract key values from your application code. You could also change key map via object API which will be mentioned later. You could find more information about key mapping in the OIPF document.

After creating the menu application, we will look into the special objects which are used to stream videos or channels on client device. The following applications will use each of these objects and will cover its methods to create an application. First we will use “video/mp4” object and create a Basic Video Player.

Basic Video Player

In this application we will create a “video/mp4” type object and by selecting the object we will use its methods which are declared in the OIPF document. After covering the most common methods, we will simply create the application and with this application it will be possible to play video files on client device.

Creating the video object

As stated above, in order to create the application first we need to create a “video/mp4” object. With the sample code below we can create the object and we are ready to use its methods.  The methods we need for the application will be explained one by one and at the end the application will be finalized.

<object id='video' type='video/mp4' ></object>
var video = documen.getElemenById("video");

Play() method

You could see a sample usage of play() method below. First we specify the data which we wish to play and simply calling the method, the data is played.

video.data = "http://itv.ard.de/video/timecode.php/video.mp4";
video.play();

In this example a MP4 file is played from HTTP source. The method also supports many network protocols and media formats. Please read spec document for full list. Here is another example for TS format from RTSP.

video.data = "rtsp://192.168.1.2:121/aVideo.ts";
video.play();

Stop() method

You could see a sample usage of stop() method below.

video.stop();

Pausing/Resuming the video

You could see a sample usage of pausing and resuming the video.

video.play(0); //pausing the video
video.play(1); //resume the video

Fast Forwarding

You could see a sample usage of fast forwarding.

video.play(2); //plays x2 times forward
video.play(4); //plays x4 times forward

Fast Rewinding

You could see a sample usage of fast forwarding.

video.play(-2); //plays x2 times rewind
video.play(-4); //plays x4 times rewind

onPlayStateChange() event

The player also keeps its state. You could specify event handlers to the object and you could see the state of the player.

video.onPlayStateChange = onPlayStateChangeHandler;
var eventnames = ['stopped', 'playing', 'paused', 'connecting', 'buffering', 'finished', 'error'];

// event handlers
function onPlayStateChangeHandler()
{
	var video = document.getElementById('video');
	var state = video.playState;
	var ename = 'Unknown event state ' + state;
	if (state>=0 || state < eventnames.length)
	{
		ename = eventnames[state]+' ('+state+')';
	}
	
	printEvent("Play state changed: " + ename);
}

“playState” property will give us new state. Now you could determine any handler method for player state changes. Play state values and descriptions are listed in the OIPF document.

Also it is possible to add a progress bar to onPlayStateChange event. Progress bar is consisted of a division and a time label. When video object switches to ‘playing’ state, progress bar should be updated.When its not playing (buffering, stopped, finished etc.) progress bar should not be updated anymore. Here is a sample.

// event handlers
function onPlayStateChangeHandler()
{
	...
	// if video is playing stop updating time
	if(state != 1)  {
		clearTimeout(progressBarTimer);
	} else {
		updateProgressBar();
	}
 	...
}

function updateProgressBar()
{
	var total = new Duration(video.playTime);
	var current = new Duration(video.playPosition);
	if (total.duration <= 0) { return; } // set division width // format label text (too long to put here) if (current.duration + 1000 >= total.duration) {
		return;
	}
	
	progressBarTimer = setTimeout(updateProgressBar, 1000);
}

Key Handling

Some applications may require restriction of specific keys in specific conditions. Therefore key handling might be needed occasionally. Below sample code indicates a key handling function. Additionally by key handling, you could change the operation state of any key. The sample Javascript code is below.

function registerKeyEventListener() {
        document.addEventListener("keydown", function(e) {
            if (handleKeyCode(e.keyCode)) {
                e.preventDefault();
            }
        }, false);
    }
function handleKeyCode(keycode) {
    if(isInFullscreenMode)
    {
        if(keycode == 461) // back key for remote 5110
        {
            // switch to windowed mode
            switchToFullScreen(false);
        }
        return true; // key handled
    }
    return false; // key is not handled
}

The usage of each method is explained above. Now we can combine them and create our basic video application. You could see that the key handling event has been used and for each case(specified as the key codes) another event is triggered.

/**
 *	A query function to short document.getElementById routine.
 */
var $ = function (id) { return document.getElementById(id); };

window.onload = function()
{
	document.onkeydown = function(evt) { handleKeys(evt); }
	$("video").onPlayStateChange = function(){
		var state = $("video").playState;
		log("onPlayStateChange()");
		log("state : " + state ); 
	}
	initMp4();
				log(navigator.userAgent);
}

/**
 *	Video Object initialization. In order to call this function define video object type as type='video/mp4' !!!
 */
function initMp4(url)
{		
	try
	{
		url = typeof url !== 'undefined' ? url : 'http://www.largesound.com/ashborytour/sound/brobob.mp3'; /*'rtsp://192.168.0.98/vod/music/01_1709_Bach.ts';*/ /*'http://itv.ard.de/video/timecode.php/video.mp4';*/
		log("initMp4(" + url +");","green");		
		$("video").stop();
		$("video").data = url;
		$("video").play(1);
	}	
	catch(ex)
	{
		log("Exception : " + ex.message ,"red");
	}	
}

/**
 *	A generic log function can be used for variables and objects. 
 *	maxDepth: Defined for depth of objects children.
 *	dump : recursive object dump function. Be carefull for giving depth. Browsers have number of function call limit.
 *	You can scroll up and down to log division by keys.
 */
function log(message,color)
{
	var maxDepth = 0;
	var dump = function(obj, name, depth, tab){  
		if (depth > maxDepth) {  
			return name + ' - Max depth
';  
		}  
  
		if (typeof(obj) == 'object') {  
			var child = null;  
			var output = tab + name + '
';  
			tab += '__';  
			for(var item in obj){  
				child = obj[item];  
				if (typeof(child) == 'object') {  
					output += dump(child, item, depth + 1, tab);  
				} else {  
					output += tab + item + ': ' + child + '
';  
				}  
			}  
		}  
		return output;  
	}
	
	color = typeof color !== 'undefined' ? color : 'gray';
	$("logs").style.top = "0px"
	if(typeof(message) == 'object')
	{
		$("logs").innerHTML = ""+ dump(message, "Object :", 0, '') + "
" + $("logs").innerHTML;
	}
	else
	{	
		$("logs").innerHTML = ""+ message + "
" + $("logs").innerHTML;
	}
}

function handleKeys(evt) {
	var evtobj = window.event? event : evt;
	var unicode = evtobj.charCode? evtobj.charCode : evtobj.keyCode; 
	switch (unicode){
		
		case 13: //ok : page refresh for testing new content
			log("Stop video!","green");
			$("video").stop();
			log("Page will be refreshed!!","red");
			setTimeout(function(){document.location.href = "./";},300);
		break;
		case 38 : //up
			//scroll up the logs div
			var top = ($("logs").style.top.replace(/px/, '') * 1);
			if( top != 0 ) $("logs").style.top = (top + 60) + "px"; 
		break;
		case 40 : //down 
			//scroll down logs div
			var top = ($("logs").style.top.replace(/px/, '') * 1);
			$("logs").style.top = (top - 60) + "px"; 				
		break;
		case 461 : //back 
			log("Stop video!","green");
			$("video").stop();
			setTimeout(function(){document.location.href = "../";},300);
		break;
		case 49 : 
			log("rewind","green");
			$("video").play(-4);
		break;		
		case 50 :  
			log("pause","green");
			$("video").play(0);
		break;		
		case 51 :
			log("forward","green");
			$("video").play(4);
		break;	
		case 52 :
			log("stop","green");
			$("video").stop();
		break;
		case 53 :
			log("play","green");
			$("video").play(1);
		break;
		case 54:
			log("jump","green");
			$("video").seekVideo(1*60);
		break;
		case 55:
			var vid = document.getElementById('video');
			var total = new Duration(vid.playTime);
			log("duration: "+total.duration,"green");
			break;
		
		default:
			log(" Key '" + unicode + "' is pressed.","green");
		break;
	}	
}

The first video object has been created and used for playing video files. We can also upload channels to client device and create a Basic Channel List Application with a different object called “video/broadcast”.

Basic Channel List Application

In previous section we used “video/mp4” object and created a basic video player. In this section, we will use “video/broadcast” object and create a basic channel list application. By using the “video/broadcast” object, you could configure DVB-IP, DVB-S, DVB-T, DVB-C and Analog channels to be broadcasted.

Similar to configuring “starturl.txt”, it is possible to configure a URL for channel list. If the URL is valid, client device can reach to channel list and download it. Channel list configuration procedure for each client can be found on Client Startup Guide-Overview document.

Please note that the channel list should have a proper format. You could observe an example “channellist.xml” below. Also detailed information about creating channel list can be found on Client Startup Guide-Overview document.

<dvb:ServiceDiscovery xmlns:dvb="urn:dvb:ipisdns:2006" xmlns:tva="urn:tva:metadata:2005" xmlns:mpeg7="urn:tva:mpeg7:2005">
  <dvb:BroadcastDiscovery DomainName="ard.de" Version="1" ChannelListVersion="10381">
    
     
       <dvb:ServiceLocation Network="DVB-S">
        <dvb:TunerConfiguration Frequency="11778000" Polarisation="V" SymbolRate="27500" Satelite="Astra 1 (19.2E)" DiSEqC="4"/>
       
       <dvb:TextualIdentifier ServiceName="CNN INTERNATIONAL"/>
       <dvb:LogicalChannelNumber ChannelNumber="1"/>
       <dvb:DVBTriplet OrigNetId="1" ServiceId="28522" TSId="1068"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="hqfn5NNMZF"/>
     
     
       <dvb:ServiceLocation Network="DVB-C">
         <dvb:TunerConfiguration Frequency="474000" Modulation="256QAM" SymbolRate="6900"/>
       
       <dvb:TextualIdentifier ServiceName="TRT1 HD"/>
       <dvb:LogicalChannelNumber ChannelNumber="2"/>
       <dvb:DVBTriplet OrigNetId="1070" ServiceId="10601" TSId="31001"/>
       <dvb:SI ServiceEncrypted="0" ServiceType="1" ServiceLocked="0" ServiceVMX="0"/>
     
     
       <dvb:ServiceLocation Network="DVB-IP">
         <dvb:IPMulticastAddress Address="239.0.0.1" Port="1234"/>
       
       <dvb:TextualIdentifier ServiceName="Yol TV"/>
       <dvb:LogicalChannelNumber ChannelNumber="3"/>
       <dvb:DVBTriplet OrigNetId="0" ServiceId="1" TSId="0"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="DYd4J6Zjwp"/>
     
     
       <dvb:ServiceLocation Network="DVB-IP">
         <dvb:IPMulticastAddress Address="239.0.0.2" Port="1234"/>
       
       <dvb:TextualIdentifier ServiceName="IMC TV"/>
       <dvb:LogicalChannelNumber ChannelNumber="4"/>
       <dvb:DVBTriplet OrigNetId="0" ServiceId="1" TSId="0"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="ummHgSwvuE"/>
     
   
 

You could see a proper formatted channel list with DVB-S and DVB-IP channels. The format of the channel list is crucial because if the format is not valid, the client device can not download the channel list.

Creating Broadcast object

After configuring the channel list with proper format and give the valid URL to TV, you could reach to channels by calling the “broadcast object”. Now the object has been created and ready to use.

<object id='video' type='video/broadcast' style='position: absolute; left:0px; top: 0px; width: 1280px; height: 720px;'></object>
var myChannel = document.getElementById("video")

Getting the Channel List

After creating the broasdcast object, now you could get the channel list by using the broadcast object. The sample code is below.

var myChannelList = document.getElementById("video").getChannelConfig().channelList;

By using the code above you get the channels as an array. This array contains channels that TV gets from the served channel list URL. A channel object can be an IP channel or analog channel.

In the following sections, we will cover the most common methods for broadcast object nad create the sample channel list application. Further information on all the methods for broadcast object is in the OIPF document.

Setting a specific channel

In order to set a channel from the array, setChannel() method should be used. Sample usage is below.

void setChannel( Channel channel, Boolean trickplay, String
contentAccessDescriptorURL, Integer offset )

If you want to have the TV to broadcast first channel of your channel array, a sample is below.

document.getElementById("video").setChannel(myChannelList[0], false);

Binding to a Channel

In above sample, you gave directly the channel entity to API. Instead, you could directly bind to current channel by using bindToCurrentChannel() method. Sample usage is below.

document.getElementById("video").bindToCurrentChannel();

Changing channel up/down

In this section we will cover the channel changing methods. Sample usage is below.

document.getElementById("video").nextChannel(); // increase channel index
document.getElementById("video").prevChannel(); // decrease channel index 

Switching to Full Screen

In this section we will cover the setFullScreen() method for setting the full screen. Sample usage is below.

function switchToFullScreen(full)
{
	isInFullscreenMode = full;
	var video = document.getElementById('video');
	
	if(full == true)
	{
		video.setFullScreen(true);
		video.setAttribute("style", "position: absolute; left: 0px; top: 0px; width: 1280px; height: 720px;"); 
	}
	else
	{
		video.setAttribute("style", "position: absolute;         left: 370px; top: 75px; width: 850px; height: 478px;");
		videoElement.setFullScreen(false);
	}

The necessary methods for creating the channel list application is explained above. Now we can make a channel list application and run different kind of channels on client device.


<html>
<head>
    <meta http-equiv="content-Type" content="text/html; charset=utf-8" />
    <title>Vestek Simple Player</title>
</head>
<script language="javascript" >
    /*
        Members
    */
    var channelList;
    var channelIndex;
    
    /*
        A query function to short document.getElementById routine.
    */
    var $ = function (id) { return document.getElementById(id); };

    window.onload = function()
    {
        document.onkeydown = function(evt) { handleKeys(evt); }
        initBroadcast();
    }
    
    /*
        Broadcast initialization : Getting channelList from video object api and play 
    */
    function initBroadcast()
    {
        try
        {
            channelIndex = 0;
            
            $("video").bindToCurrentChannel();
            $("video").setFullScreen(false);
            
            //log($('oipfcfg').configuration);
            channelList = $("video").getChannelConfig().channelList;
        
            log("---channel list---","green");
            for (var i=0; i<channelList.length ; i++) 
                log("channel [" + i + "] : " + channelList[i].name);
            log("---end channel list---","green");
            
            $("video").setChannel(channelList[channelIndex], false);
            log(channelList[channelIndex]);
            //log(channelList); 
            //$("video").stop();
    
        }
        catch(e)
        {
            log("Exception : " + e.message ,"red");
        }
    }
    
    /*
        A generic log function can be used for variables and objects. 
        maxDepth: Defined for depth of objects children.
        dump : recursive object dump function. Be carefull for giving depth. Browsers have number of function call limit.
        You can scroll up and down to log division by keys.
    */
    function log(message,color)
    {
        var maxDepth = 0;
        var dump = function(obj, name, depth, tab){  
            if (depth > maxDepth) {  
                return name + ' - Max depth
';  
            }  
      
            if (typeof(obj) == 'object') {  
                var child = null;  
                var output = tab + name + '
';  
                tab += '__';  
                for(var item in obj){  
                    child = obj[item];  
                    if (typeof(child) == 'object') {  
                        output += dump(child, item, depth + 1, tab);  
                    } else {  
                        output += tab + item + ': ' + child + '
';  
                    }  
                }  
            }  
            return output;  
        }
        
        color = typeof color !== 'undefined' ? color : 'gray';
        $("logs").style.top = "0px"
        if(typeof(message) == 'object')
        {
            $("logs").innerHTML = ""+ dump(message, "Object :", 0, '') + "
" + $("logs").innerHTML;
        }
        else
        {    
            $("logs").innerHTML = ""+ message + "
" + $("logs").innerHTML;
        }
    }
    
    function handleKeys(evt) {
        var evtobj = window.event? event : evt;
        var unicode = evtobj.charCode? evtobj.charCode : evtobj.keyCode;
        
        switch (unicode){
            case 13: //ok : page refresh for testing new content
                try
                {
                    log("Stop video!","green");
                    $("video").stop();
                    log("Page will be refreshed!!","red");
                    setTimeout(function(){document.location.href = "./playBroadcast.html";},300);
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }    
            break;
            case 37 : //left : previous channel
                try
                {                
                    if(channelIndex==0) channelIndex = channelList.length-1;
                    else channelIndex--;
                    log("channelList["+channelIndex+"] = "+channelList[channelIndex].name, "green");
                    $("video").setChannel(channelList[channelIndex]);
                    
                    //or you can simply use that function
                    //$("video").prevChannel();
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }
            break;
            case 39 : //right : next channel
                try
                {
                    if(channelIndex == channelList.length-1) channelIndex = 0;
                    else channelIndex++;
                    log("channelList["+channelIndex+"] ="+channelList[channelIndex].name, "green");
                    $("video").setChannel(channelList[channelIndex]);
                    
                    //or you can simply use that function
                    //$("video").nextChannel();
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }
            break;
            case 38 : //up
                //scroll up the logs div
                var top = ($("logs").style.top.replace(/px/, '') * 1);
                if( top != 0 ) $("logs").style.top = top + 60; 
            break;
            case 40 : //down 
                //scroll down logs div
                var top = ($("logs").style.top.replace(/px/, '') * 1);
                $("logs").style.top = top - 60;                 
            break;    
            default:
                log(" Key '" + unicode + "' is pressed.","green");
            break;
        }    
    }
</script>

<body style="background:gray;">
    <h2 style="position: absolute;left: 15px; top: 0px; margin-top: 15px;font-size:1.6em;">
        <img src="vestek.jpg" alt="Vestek logo" style="vertical-align:middle"/> Simple Player
    </h2>
    <div id="logs_container" style="position:absolute; left:0px; top:55px; background:black; overflow:hidden; height:540px; width:310px;">
        <div id="logs" style="position:absolute; top:0px; color:white;">
        </div>
    </div>
    <table style="position: absolute;left: 15px; top: 610px; z-index:100; width:930px; ">    
        <tr>
            <td style="width:30px">
                <img src="ud_.png">
            </td>
            <td>
                Use up and down arrow keys to scroll the logs.
            </td>
        </tr>
        <tr>
            <td style="width:30px">
                <img src="rl_.png">
            </td>
            <td>
                Use rigth and left keys to change the channels.
            </td>
        </tr>
        <tr>
            <td style="width:30px">
                <img src="ok_.png">
            </td>
            <td>
                Press "OK" to refresh the page.
            </td>
        </tr>
    </table>
    <div id="video_container" style='position: absolute; left: 320px; top: 55px; width: 960px; height: 540px;'>
        <object id='video' type='video/broadcast' style='width: 960px; height: 540px;'></object>
    </div>
    <object id='appmgr' type='application/oipfApplicationManager' style='position: absolute;left: 0px; top: 0px; width: 0px; height: 0px;'></object>
    <object id='oipfcfg' type='application/oipfConfiguration' style='position: absolute;left: 0px; top: 0px; width: 0px; height: 0px;'></object>
    <object id='NetRangeDevice' type='application/debug-plugin'></object>
</body>
</html>

 

[:fr]

Ce document donne une introduction de base à la configuration de l’appareil client. En utilisant HTML, CSS et JavaScript, nous ferons des applications de base :

  • Application de menu de base : Une application de menu qui montre comment gérer les touches de télécommande et naviguer à travers les pages et les chaînes.
  • Lecteur vidéo de base : Un lecteur vidéo utilisé pour diffuser des vidéos.
  • Application de liste de chaînes de base : Une application montrant comment télécharger une liste de chaînes et comment diffuser des chaînes sur des appareils clients.

Les appareils clients ont un mode spécial nommé « Mode Hôtel » et, lorsqu’il est activé, les appareils clients récupèrent une URL donnée après s’être réveillés et naviguent sur cette page. Cette page spécifique est appelée starturl et, pour exécuter n’importe quelle application Web, une starturl doit être configurée sur le client. Vous pouvez trouver les détails nécessaires pour configurer l’URL starturl et le mode Hôtel pour chaque client dans le document Guide de démarrage pour les clients – Vue d’ensemble.

Vous devez servir l’URL via un serveur (IIS, Apache, etc.).  Côté serveur, il doit y avoir un dossier nommé « conntest », et ce dossier doit contenir un fichier nommé 2kb.txt (Figure 1). Ce fichier aide l’appareil client à vérifier si l’URL donnée est valide ou non. Quel que soit son contenu, le fichier doit avoir une taille de 2 ko.

Figure 1: Fichier 2kb.txt

Comme indiqué ci-dessus, une fois la « starturl » configurée, toute application Web peut s’exécuter sur l’appareil client. Il existe des applications très basiques que vous pouvez exécuter sur les appareils clients, et ce document les couvrira. Le premier exemple d’application sera Application de menu de base.

Application de menu de base

Cette section essaie d’expliquer comment créer une application de menu de base en utilisant à la fois CSS et JavaScript. En créant cette application de menu, vous pouvez gérer les touches de la télécommande et les envoyer via cette application. Cela peut donner la possibilité de contrôler et de naviguer sur les pages et les chaînes.

Menu basé sur du CSS pur

Le navigateur du téléviseur peut parcourir les balises d’ancrage à l’aide des touches fléchées. C’est une des approches que vous pouvez suivre. Dans cet exemple, nous écrivons quatre liens et parcourons ceux-ci à l’aide des touches fléchées. Vous devez spécifier le style du lien ciblé en utilisant la pseudo-classe « focus » dans le fichier CSS. Elle vous aidera à identifier les balises d’ancrage ciblées. Voici un exemple simple.


 <a href="#">Link 1 </a>
 <a href="#">Link 2 </a>
 <a href="#">Link 3 </a>
 <a href="#">Link 4 </a>

a
{
 text-decoration:none;
 font-size:20px;
 color:Black;
}
a:focus
{
 text-decoration:none;
 color:White;
 font-size:30px;
}

Menu basé sur du JavaScript pur

Vous pouvez également écrire le même exemple avec JavaScript. Cela peut réduire les dépendances DOM, et vous pouvez également personnaliser la navigation en écrivant vos propres méthodes de gestion des touches. Voici la même page avec JavaScript.

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="main.js"></script>
<link href="main.css" rel="stylesheet" type="text/css" />
<title>Vestek Tutorial</title>
</head>
<body>
<div id="menu"></div>
<script>
vestek.boot();
</script>
</body>
</html>

body
{
background:gray;
}
#menu
{
text-decoration:none;
font-size:20px;
color:Black;
}
#menu .selected
{
text-decoration:none;
color:White;
font-size:30px;
}

var vestek = {};
vestek.menuContent = [{ "title" : "Link 1", "url" : "#"} ,{ "title" :
"Link 2", "url" : "#"},{ "title" : "Link 3", "url" : "#"},{ "title" :
"Link 4", "url" : "#"} ];
vestek.menuIndex = 0;
vestek.boot = function () {
for(index in vestek.menuContent){
vestek.menuContent[index].element = document.createElement("div");
vestek.menuContent[index].element.innerHTML = vestek.menuContent[index]['title'];
document.getElementById("menu").appendChild(
vestek.menuContent[index].element);
}
vestek.menuContent[this.menuIndex].element.className = "selected";
document.onkeydown = function (evt) {
vestek.keyHandler(evt.keyCode);
};
};
vestek.keyHandler = function (code) {
switch (code){
case this.keyEnum.OK:
document.location.href =
vestek.menuContent[this.menuIndex]['url'];
break;
case this.keyEnum.UP:
if(this.menuIndex != 0){
vestek.menuContent[this.menuIndex].element.className =
"";
this.menuIndex--;
vestek.menuContent[this.menuIndex].element.className =
"selected";
}
break;
case this.keyEnum.DOWN:
if(this.menuIndex != this.menuContent.length - 1){
vestek.menuContent[this.menuIndex].element.className =
"";
this.menuIndex++;
vestek.menuContent[this.menuIndex].element.className =
"selected";
}
break;
default:
break;
}
};
vestek.keyEnum = {
OK : 13,
UP : 38,
DOWN : 40,
RIGHT : 39,
LEFT : 37,
GREEN : 404,
RED : 403,
YELLOW : 405,
BLUE : 406,
BACKSPACE : 461,
OK : 13,
PLAY : 415,
PAUSE : 19,
FW : 417,
BW : 412,
STOP : 413,
LANGUAGE : 312,
HOME : 407,
HELP : 156,
BACK : 461,
ALARM : 309,
ZERO : 48,
ONE : 49,
TWO : 50,
THREE : 51,
FOUR : 52,
FIVE : 53,
SIX : 54,
SEVEN : 55,
EIGHT : 56,
NINE : 57,
PROGUP : 310,
PROGDOWN : 311,
FRONTPROGUP : 314,
FRONTPROGDOWN : 313,
};

Le contenu du menu est du simple code JSON. Cela vous donne également la possibilité d’écrire des applications basées sur Ajax. Toutes les valeurs de clé par défaut sont écrites sous forme d’énumération. Vous pouvez extraire les valeurs de touche de votre code d’application. Vous pouvez également modifier la carte des touches via l’API d’objets qui sera présentée plus tard. Vous trouverez plus d’informations sur le mappage des touches dans le Document OIPF.

Après avoir créé l’application de menu, nous examinerons les objets spéciaux utilisés pour diffuser des vidéos ou des chaînes sur l’appareil client. Les applications suivantes utiliseront chacun de ces objets et couvriront ses méthodes pour créer une application. Nous utiliserons d’abord un objet video/mp4 et créerons un Lecteur vidéo de base.

Lecteur vidéo de base

Dans cette application, nous allons créer un objet de type video/mp4 et, en sélectionnant l’objet, nous utiliserons ses méthodes déclarées dans le Document OIPF. Après avoir couvert les méthodes les plus courantes, nous allons simplement créer l’application et, avec cette application, il sera possible de lire des fichiers vidéo sur l’appareil client.

Création de l’objet vidéo

Comme indiqué ci-dessus, afin de créer l’application, nous devons d’abord créer un objet video/mp4. Avec l’exemple de code ci-dessous, nous pouvons créer l’objet, et nous sommes prêts à utiliser ses méthodes.  Les méthodes dont nous avons besoin pour l’application seront expliquées une par une et, après cela, l’application sera finalisée.

<object id='video' type='video/mp4' ></object>
var video = documen.getElemenById("video");

Méthode play()

Vous pouvez voir un exemple d’utilisation de la méthode play() ci-dessous. D’abord, nous spécifions les données que nous souhaitons lire, et nous appelons simplement la méthode pour le faire.

video.data = "http://itv.ard.de/video/timecode.php/video.mp4";
video.play();

Dans cet exemple, un fichier MP4 est lu à partir d’une source HTTP. La méthode prend également en charge de nombreux protocoles réseau et formats multimédias. Lisez le document de spécification pour la liste complète. Voici un autre exemple de format TS de RTSP.

video.data = "rtsp://192.168.1.2:121/aVideo.ts";
video.play();

Méthode stop()

Vous pouvez voir un exemple d’utilisation de la méthode stop() ci-dessous.

video.stop();

Pause/Reprise de la vidéo

Vous pouvez voir un exemple d’utilisation de la pause/reprise de la vidéo.

video.play(0); //pausing the video
video.play(1); //resume the video

Avance rapide

Vous pouvez voir un exemple d’utilisation de l’avance rapide.

video.play(2); //plays x2 times forward
video.play(4); //plays x4 times forward

Rembobinage rapide

Vous pouvez voir un exemple d’utilisation du rembobinage rapide.

video.play(-2); //plays x2 times rewind
video.play(-4); //plays x4 times rewind

onPlayStateChange() event

Le lecteur conserve également son état. Vous pouvez spécifier des gestionnaires d’événements pour l’objet, et vous pouvez voir l’état du lecteur.

video.onPlayStateChange = onPlayStateChangeHandler;
var eventnames = ['stopped', 'playing', 'paused', 'connecting', 'buffering', 'finished', 'error'];

// event handlers
function onPlayStateChangeHandler()
{
	var video = document.getElementById('video');
	var state = video.playState;
	var ename = 'Unknown event state ' + state;
	if (state>=0 || state < eventnames.length)
	{
		ename = eventnames[state]+' ('+state+')';
	}
	
	printEvent("Play state changed: " + ename);
}

La propriété playState nous donnera un nouvel état. Vous pouvez maintenant déterminer n’importe quelle méthode de gestion pour les changements d’état du lecteur. Les valeurs et les descriptions de l’état de lecture sont répertoriées dans le Document OIPF.

Il est également possible d’ajouter une barre de progression à l’événement onPlayStateChange. La barre de progression est composée d’une division et d’une étiquette de temps. Lorsque l’objet vidéo passe à l’état « Lecture », la barre de progression doit être mise à jour. Lorsque la lecture est arrêtée (mise en mémoire tampon, arrêt, fin de la vidéo, etc.), la barre de progression ne doit plus être mise à jour. Voici un exemple.

// event handlers
function onPlayStateChangeHandler()
{
	...
	// if video is playing stop updating time
	if(state != 1)  {
		clearTimeout(progressBarTimer);
	} else {
		updateProgressBar();
	}
 	...
}

function updateProgressBar()
{
	var total = new Duration(video.playTime);
	var current = new Duration(video.playPosition);
	if (total.duration <= 0) { return; } // set division width // format label text (too long to put here) if (current.duration + 1000 >= total.duration) {
		return;
	}
	
	progressBarTimer = setTimeout(updateProgressBar, 1000);
}

Gestion des touches

Certaines applications peuvent nécessiter la restriction de touches spécifiques dans des conditions particulières. Par conséquent, la manipulation des touches peut parfois être nécessaire. L’exemple de code ci-dessous indique une fonction de gestion des touches. De plus, en gérant les touches, vous pouvez modifier l’état de fonctionnement de n’importe quelle touche. L’exemple de code JavaScript se trouve ci-dessous.

function registerKeyEventListener() {
        document.addEventListener("keydown", function(e) {
            if (handleKeyCode(e.keyCode)) {
                e.preventDefault();
            }
        }, false);
    }
function handleKeyCode(keycode) {
    if(isInFullscreenMode)
    {
        if(keycode == 461) // back key for remote 5110
        {
            // switch to windowed mode
            switchToFullScreen(false);
        }
        return true; // key handled
    }
    return false; // key is not handled
}

L’utilisation de chaque méthode est expliquée ci-dessus. Nous pouvons maintenant les combiner et créer notre application vidéo de base. Vous pouvez voir que l’événement de gestion de clé a été utilisé et que, pour chaque cas (spécifié par les codes de touche), un autre événement est déclenché.

/**
 *	A query function to short document.getElementById routine.
 */
var $ = function (id) { return document.getElementById(id); };

window.onload = function()
{
	document.onkeydown = function(evt) { handleKeys(evt); }
	$("video").onPlayStateChange = function(){
		var state = $("video").playState;
		log("onPlayStateChange()");
		log("state : " + state ); 
	}
	initMp4();
				log(navigator.userAgent);
}

/**
 *	Video Object initialization. In order to call this function define video object type as type='video/mp4' !!!
 */
function initMp4(url)
{		
	try
	{
		url = typeof url !== 'undefined' ? url : 'http://www.largesound.com/ashborytour/sound/brobob.mp3'; /*'rtsp://192.168.0.98/vod/music/01_1709_Bach.ts';*/ /*'http://itv.ard.de/video/timecode.php/video.mp4';*/
		log("initMp4(" + url +");","green");		
		$("video").stop();
		$("video").data = url;
		$("video").play(1);
	}	
	catch(ex)
	{
		log("Exception : " + ex.message ,"red");
	}	
}

/**
 *	A generic log function can be used for variables and objects. 
 *	maxDepth: Defined for depth of objects children.
 *	dump : recursive object dump function. Be carefull for giving depth. Browsers have number of function call limit.
 *	You can scroll up and down to log division by keys.
 */
function log(message,color)
{
	var maxDepth = 0;
	var dump = function(obj, name, depth, tab){  
		if (depth > maxDepth) {  
			return name + ' - Max depth
';  
		}  
  
		if (typeof(obj) == 'object') {  
			var child = null;  
			var output = tab + name + '
';  
			tab += '__';  
			for(var item in obj){  
				child = obj[item];  
				if (typeof(child) == 'object') {  
					output += dump(child, item, depth + 1, tab);  
				} else {  
					output += tab + item + ': ' + child + '
';  
				}  
			}  
		}  
		return output;  
	}
	
	color = typeof color !== 'undefined' ? color : 'gray';
	$("logs").style.top = "0px"
	if(typeof(message) == 'object')
	{
		$("logs").innerHTML = ""+ dump(message, "Object :", 0, '') + "
" + $("logs").innerHTML;
	}
	else
	{	
		$("logs").innerHTML = ""+ message + "
" + $("logs").innerHTML;
	}
}

function handleKeys(evt) {
	var evtobj = window.event? event : evt;
	var unicode = evtobj.charCode? evtobj.charCode : evtobj.keyCode; 
	switch (unicode){
		
		case 13: //ok : page refresh for testing new content
			log("Stop video!","green");
			$("video").stop();
			log("Page will be refreshed!!","red");
			setTimeout(function(){document.location.href = "./";},300);
		break;
		case 38 : //up
			//scroll up the logs div
			var top = ($("logs").style.top.replace(/px/, '') * 1);
			if( top != 0 ) $("logs").style.top = (top + 60) + "px"; 
		break;
		case 40 : //down 
			//scroll down logs div
			var top = ($("logs").style.top.replace(/px/, '') * 1);
			$("logs").style.top = (top - 60) + "px"; 				
		break;
		case 461 : //back 
			log("Stop video!","green");
			$("video").stop();
			setTimeout(function(){document.location.href = "../";},300);
		break;
		case 49 : 
			log("rewind","green");
			$("video").play(-4);
		break;		
		case 50 :  
			log("pause","green");
			$("video").play(0);
		break;		
		case 51 :
			log("forward","green");
			$("video").play(4);
		break;	
		case 52 :
			log("stop","green");
			$("video").stop();
		break;
		case 53 :
			log("play","green");
			$("video").play(1);
		break;
		case 54:
			log("jump","green");
			$("video").seekVideo(1*60);
		break;
		case 55:
			var vid = document.getElementById('video');
			var total = new Duration(vid.playTime);
			log("duration: "+total.duration,"green");
			break;
		
		default:
			log(" Key '" + unicode + "' is pressed.","green");
		break;
	}	
}

Le premier objet vidéo a été créé et utilisé pour lire des fichiers vidéo. Nous pouvons également télécharger des chaînes sur l’appareil client et créer uen Application de liste de chaînes de base avec un autre objet appelé « video/broadcast ».

Application de liste de chaînes de base

Dans la section précédente, nous avons utilisé l’objet video/mp4 et créé un lecteur vidéo de base. Dans cette section, nous utiliserons un objet video/broadcast et créerons une application de liste de chaînes de base. En utilisant l’objet video/broadcast, vous pouvez configurer les chaînes DVB-IP, DVB-S, DVB-T, DVB-C et analogiques à diffuser.

Comme pour la configuration de starturl.txt, il est possible de configurer une URL pour la liste des chaînes. Si l’URL est valide, l’appareil client peut accéder à la liste des chaînes et la télécharger. La procédure de configuration de la liste des chaînes pour chaque client se trouve dans le Guide de démarrage pour les clients – Vue d’ensemble.

Notez que la liste des chaînes doit avoir un format approprié. Vous trouverez un exemple de channellist.xml ci-dessous. Vous trouverez également des informations détaillées sur la création d’une liste de chaînes dans le document Guide de démarrage pour les clients – Vue d’ensemble.

<dvb:ServiceDiscovery xmlns:dvb="urn:dvb:ipisdns:2006" xmlns:tva="urn:tva:metadata:2005" xmlns:mpeg7="urn:tva:mpeg7:2005">
  <dvb:BroadcastDiscovery DomainName="ard.de" Version="1" ChannelListVersion="10381">
    
     
       <dvb:ServiceLocation Network="DVB-S">
        <dvb:TunerConfiguration Frequency="11778000" Polarisation="V" SymbolRate="27500" Satelite="Astra 1 (19.2E)" DiSEqC="4"/>
       
       <dvb:TextualIdentifier ServiceName="CNN INTERNATIONAL"/>
       <dvb:LogicalChannelNumber ChannelNumber="1"/>
       <dvb:DVBTriplet OrigNetId="1" ServiceId="28522" TSId="1068"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="hqfn5NNMZF"/>
     
     
       <dvb:ServiceLocation Network="DVB-C">
         <dvb:TunerConfiguration Frequency="474000" Modulation="256QAM" SymbolRate="6900"/>
       
       <dvb:TextualIdentifier ServiceName="TRT1 HD"/>
       <dvb:LogicalChannelNumber ChannelNumber="2"/>
       <dvb:DVBTriplet OrigNetId="1070" ServiceId="10601" TSId="31001"/>
       <dvb:SI ServiceEncrypted="0" ServiceType="1" ServiceLocked="0" ServiceVMX="0"/>
     
     
       <dvb:ServiceLocation Network="DVB-IP">
         <dvb:IPMulticastAddress Address="239.0.0.1" Port="1234"/>
       
       <dvb:TextualIdentifier ServiceName="Yol TV"/>
       <dvb:LogicalChannelNumber ChannelNumber="3"/>
       <dvb:DVBTriplet OrigNetId="0" ServiceId="1" TSId="0"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="DYd4J6Zjwp"/>
     
     
       <dvb:ServiceLocation Network="DVB-IP">
         <dvb:IPMulticastAddress Address="239.0.0.2" Port="1234"/>
       
       <dvb:TextualIdentifier ServiceName="IMC TV"/>
       <dvb:LogicalChannelNumber ChannelNumber="4"/>
       <dvb:DVBTriplet OrigNetId="0" ServiceId="1" TSId="0"/>
       <dvb:SI ServiceVMX="0" ServiceType="1" ServiceLocked="0" ServiceUniqueId="ummHgSwvuE"/>
     
   
 

Vous pouvez voir une liste de chaînes formatée avec les chaînes DVB-S et DVB-IP. Le format de la liste des chaînes est crucial, car si le format n’est pas valide, l’appareil client ne peut pas télécharger la liste des chaînes.

Création d’un objet de diffusion

Après avoir configuré la liste des chaînes avec le format approprié et donné la bonne URL au téléviseur, vous pouvez accéder aux chaînes en appelant l’objet de diffusio. L’objet est maintenant créé et prêt à être utilisé.

<object id='video' type='video/broadcast' style='position: absolute; left:0px; top: 0px; width: 1280px; height: 720px;'></object>
var myChannel = document.getElementById("video")

Obtenir la liste des chaînes

Après avoir créé l’objet de diffusion, vous pouvez maintenant obtenir la liste des chaînes avec. L’exemple de code se trouve ci-dessous.

var myChannelList = document.getElementById("video").getChannelConfig().channelList;

En utilisant le code ci-dessus, vous obtenez les chaînes sous forme de tableau. Ce tableau contient les chaînes que le téléviseur obtient à partir de l’URL de liste des chaînes diffusée. Un objet de chaîne peut être une chaîne IP ou analogique.

Dans les sections suivantes, nous couvrirons les méthodes les plus courantes pour l’objet de diffusion et créerons un exemple d’application de liste de chaînes. De plus amples informations sur toutes les méthodes pour l’objet de diffusion sont disponibles dans le Document OIPF.

Définir une chaîne spécifique

Afin de définir une chaîne à partir du tableau, la méthode setChannel() doit être utilisée. Un exemple d’utilisation est présenté ci-dessous.

void setChannel( Channel channel, Boolean trickplay, String
contentAccessDescriptorURL, Integer offset )

Si vous souhaitez que le téléviseur diffuse la première chaîne de votre tableau de chaînes, un exemple se trouve ci-dessous.

document.getElementById("video").setChannel(myChannelList[0], false);

Liaison à une chaîne

Dans l’exemple ci-dessus, vous avez donné directement l’entité de chaîne à l’API. Au lieu de cela, vous pouvez directement lier la chaîne actuelle en utilisant la méthode bindToCurrentChannel(). Un exemple d’utilisation est présenté ci-dessous.

document.getElementById("video").bindToCurrentChannel();

Changement de chaîne (suivante/précédente)

Dans cette section, nous couvrirons les méthodes de changement de chaîne. Un exemple d’utilisation est présenté ci-dessous.

document.getElementById("video").nextChannel(); // increase channel index
document.getElementById("video").prevChannel(); // decrease channel index 

Passer en plein écran

Dans cette section, nous aborderons la méthode setFullScreen() pour définir le plein écran. Un exemple d’utilisation est présenté ci-dessous.

function switchToFullScreen(full)
{
	isInFullscreenMode = full;
	var video = document.getElementById('video');
	
	if(full == true)
	{
		video.setFullScreen(true);
		video.setAttribute("style", "position: absolute; left: 0px; top: 0px; width: 1280px; height: 720px;"); 
	}
	else
	{
		video.setAttribute("style", "position: absolute;         left: 370px; top: 75px; width: 850px; height: 478px;");
		videoElement.setFullScreen(false);
	}

Les méthodes nécessaires pour créer l’application de liste de chaînes sont expliquées ci-dessus. Nous pouvons maintenant créer une application de liste de chaînes et exécuter différents types de chaînes sur l’appareil client.


<html>
<head>
    <meta http-equiv="content-Type" content="text/html; charset=utf-8" />
    <title>Vestek Simple Player</title>
</head>
<script language="javascript" >
    /*
        Members
    */
    var channelList;
    var channelIndex;
    
    /*
        A query function to short document.getElementById routine.
    */
    var $ = function (id) { return document.getElementById(id); };

    window.onload = function()
    {
        document.onkeydown = function(evt) { handleKeys(evt); }
        initBroadcast();
    }
    
    /*
        Broadcast initialization : Getting channelList from video object api and play 
    */
    function initBroadcast()
    {
        try
        {
            channelIndex = 0;
            
            $("video").bindToCurrentChannel();
            $("video").setFullScreen(false);
            
            //log($('oipfcfg').configuration);
            channelList = $("video").getChannelConfig().channelList;
        
            log("---channel list---","green");
            for (var i=0; i<channelList.length ; i++) 
                log("channel [" + i + "] : " + channelList[i].name);
            log("---end channel list---","green");
            
            $("video").setChannel(channelList[channelIndex], false);
            log(channelList[channelIndex]);
            //log(channelList); 
            //$("video").stop();
    
        }
        catch(e)
        {
            log("Exception : " + e.message ,"red");
        }
    }
    
    /*
        A generic log function can be used for variables and objects. 
        maxDepth: Defined for depth of objects children.
        dump : recursive object dump function. Be carefull for giving depth. Browsers have number of function call limit.
        You can scroll up and down to log division by keys.
    */
    function log(message,color)
    {
        var maxDepth = 0;
        var dump = function(obj, name, depth, tab){  
            if (depth > maxDepth) {  
                return name + ' - Max depth
';  
            }  
      
            if (typeof(obj) == 'object') {  
                var child = null;  
                var output = tab + name + '
';  
                tab += '__';  
                for(var item in obj){  
                    child = obj[item];  
                    if (typeof(child) == 'object') {  
                        output += dump(child, item, depth + 1, tab);  
                    } else {  
                        output += tab + item + ': ' + child + '
';  
                    }  
                }  
            }  
            return output;  
        }
        
        color = typeof color !== 'undefined' ? color : 'gray';
        $("logs").style.top = "0px"
        if(typeof(message) == 'object')
        {
            $("logs").innerHTML = ""+ dump(message, "Object :", 0, '') + "
" + $("logs").innerHTML;
        }
        else
        {    
            $("logs").innerHTML = ""+ message + "
" + $("logs").innerHTML;
        }
    }
    
    function handleKeys(evt) {
        var evtobj = window.event? event : evt;
        var unicode = evtobj.charCode? evtobj.charCode : evtobj.keyCode;
        
        switch (unicode){
            case 13: //ok : page refresh for testing new content
                try
                {
                    log("Stop video!","green");
                    $("video").stop();
                    log("Page will be refreshed!!","red");
                    setTimeout(function(){document.location.href = "./playBroadcast.html";},300);
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }    
            break;
            case 37 : //left : previous channel
                try
                {                
                    if(channelIndex==0) channelIndex = channelList.length-1;
                    else channelIndex--;
                    log("channelList["+channelIndex+"] = "+channelList[channelIndex].name, "green");
                    $("video").setChannel(channelList[channelIndex]);
                    
                    //or you can simply use that function
                    //$("video").prevChannel();
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }
            break;
            case 39 : //right : next channel
                try
                {
                    if(channelIndex == channelList.length-1) channelIndex = 0;
                    else channelIndex++;
                    log("channelList["+channelIndex+"] ="+channelList[channelIndex].name, "green");
                    $("video").setChannel(channelList[channelIndex]);
                    
                    //or you can simply use that function
                    //$("video").nextChannel();
                }    
                catch(ex)
                {
                    log("Exception : " + ex.message ,"red");
                }
            break;
            case 38 : //up
                //scroll up the logs div
                var top = ($("logs").style.top.replace(/px/, '') * 1);
                if( top != 0 ) $("logs").style.top = top + 60; 
            break;
            case 40 : //down 
                //scroll down logs div
                var top = ($("logs").style.top.replace(/px/, '') * 1);
                $("logs").style.top = top - 60;                 
            break;    
            default:
                log(" Key '" + unicode + "' is pressed.","green");
            break;
        }    
    }
</script>

<body style="background:gray;">
    <h2 style="position: absolute;left: 15px; top: 0px; margin-top: 15px;font-size:1.6em;">
        <img src="vestek.jpg" alt="Vestek logo" style="vertical-align:middle"/> Simple Player
    </h2>
    <div id="logs_container" style="position:absolute; left:0px; top:55px; background:black; overflow:hidden; height:540px; width:310px;">
        <div id="logs" style="position:absolute; top:0px; color:white;">
        </div>
    </div>
    <table style="position: absolute;left: 15px; top: 610px; z-index:100; width:930px; ">    
        <tr>
            <td style="width:30px">
                <img src="ud_.png">
            </td>
            <td>
                Use up and down arrow keys to scroll the logs.
            </td>
        </tr>
        <tr>
            <td style="width:30px">
                <img src="rl_.png">
            </td>
            <td>
                Use rigth and left keys to change the channels.
            </td>
        </tr>
        <tr>
            <td style="width:30px">
                <img src="ok_.png">
            </td>
            <td>
                Press "OK" to refresh the page.
            </td>
        </tr>
    </table>
    <div id="video_container" style='position: absolute; left: 320px; top: 55px; width: 960px; height: 540px;'>
        <object id='video' type='video/broadcast' style='width: 960px; height: 540px;'></object>
    </div>
    <object id='appmgr' type='application/oipfApplicationManager' style='position: absolute;left: 0px; top: 0px; width: 0px; height: 0px;'></object>
    <object id='oipfcfg' type='application/oipfConfiguration' style='position: absolute;left: 0px; top: 0px; width: 0px; height: 0px;'></object>
    <object id='NetRangeDevice' type='application/debug-plugin'></object>
</body>
</html>

[:]