Development Guide
Creating a Video Application¶
Lead
This tutorial demonstrates the use of the video features of the Samsung Apps service. These are needed to create various kinds of applications, like video catch-up services and User Created Content (UCC) services. The video features are introduced by developing a sample application featuring video playback.
Contents
The tutorial application reads title, description, and URL information for each video clip from an RSS feed on a server, and then allows the user to play, stop, and pause the video, and skip forward and backward through it. The application positions the video playback in a window surrounded by graphics, and switches to a full-screen video display. It also provides volume control functions.
To use the HAS (HTTP Adaptive Streaming) features of the Samsung Applications service, see Tutorial: Creating a Video Application With HAS (HTTP Adaptive Streaming). With HAS, users can enjoy seamless video streaming service over IP networks with best possible video quality.
You can also make a video application respond to an interactive remote control such as a mobile device and create a video application using Flash.
Figure. View of the video application
Development Environment¶
Use Samsung Smart TV SDK to create the application. You can also use the emulator provided with the SDK to debug and test the application before uploading it in your TV. Later, you can run the application on a TV; see Testing Your Application on a TV. Note that applications may perform better on the TV than on the emulator.
You can find general instructions for creating applications in Implementing Your Application Code.
Prerequisites¶
To create applications that run on a TV screen, you need:
- Samsung TV connected to the Internet
- SDK or a text editor for creating HTML, JavaScript and CSS files (using Samsung Smart TV SDK is recommended)
Source Files¶
Note
files needed for the sample application are here. The common modules, images, and basic preview code needed to create the application are provided.
This tutorial does not supply video files for playback for copyright reasons. At the time of writing (May 2009), it is recommended to use either MP4 or ASF (WMV) format video.
The directory structure of the application:
| Directory/File | Description |
|---|---|
| Common/API | Contains the common modules provided by the Application Manager. |
| CSS | Contains the CSS files for the application. |
| Images | Contains the image files for the application. |
| Javascript | Contains the JavaScript files for the application. |
| config.xml | Contains information for executing the application. |
| index.html | The HTML file which runs in the application. |
Application Design¶
The video application is composed of the following parts:
- TV
- controlling the parts of the TV
- Network
- accessing data from the network
- Graphics
- displaying information graphically in the browser, and
- Control
- managing actions in response to user input
Figure. Design diagram
Class Description¶
This section lists the classes that for part of the application and describes them.
| Class | Description |
|---|---|
| Main | Responsible for key handling and coordination of all the application components. |
| Player | Controls the audio and video playback from the content server using a plugin. |
| Audio | Controls the audio volume level using a plugin. |
| Server | Handles the retrieval of RSS feed from the data server using AJAX. |
| Data | Handles the storage of video data within the application. |
| Display | Responsible for displaying the graphics and text information using dynamic HTML. |
Basic Application and Data Retrieval¶
This tutorial task briefly describes the initial configuration of the application.
Creating the Basic Application¶
Start the SDK.
Create a new application using the following config.xml file.
<?xml version="1.0" encoding="UTF-8"?> <widget> <previewjs>PreviewVideoTutorial</previewjs> <type>user</type> <cpname></cpname> <cplogo></cplogo> <cpauthjs></cpauthjs> <ThumbIcon>Images/icon/Tutorial_Video_106.png</ThumbIcon> <BigThumbIcon>Images/icon/Tutorial_Video_115.png</BigThumbIcon> <ListIcon>Images/icon/Tutorial_Video_85.png</ListIcon> <BigListIcon>Images/icon/Tutorial_Video_95.png</BigListIcon> <ver></ver> <mgrver></mgrver> <fullwidget>y</fullwidget> <movie>y</movie> <srcctl>y</srcctl> <ticker>n</ticker> <childlock>n</childlock> <audiomute>n</audiomute> <videomute>n</videomute> <dcont>y</dcont> <widgetname></widgetname> <description></description> <width>960</width> <height>540</height> <author> <name>Samsung Electronics Co. Ltd.</name> <email></email> <link>http://www.sec.com</link> <organization>Samsung Electronics Co. Ltd.</organization> </author> </widget>
The following settings are used:
- <fullwidget>y</fullwidget>
Makes the application run in full screen mode. This affects what keys are registered by default.
- <type>user</type>
Enables the user application feature for testing on a real TV set. This tag has no effect on the emulator.
- <movie>y</movie>
Optimises the application behaviour for movie playback.
Add the index.html file with the following code.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/div/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Tutorial Video App</title> <!-- Common App API --> <script type="text/javascript" src="$MANAGER_WIDGET/Common/API/widget.js"></script> <script type="text/javascript" src="$MANAGER_WIDGET/Common/API/TVKeyValue.js"></script> <!-- App code --> <script type="text/javascript" src="Javascript/Main.js"></script> </head> <body onload="Main.onLoad();" onunload="Main.onUnload();"> </body> </html>
Note
Use $MANAGER_WIDGET/Common/API/Widget.js for running the application on TV and use Common/API/Widget.js to include the common JavaScript files for running it on simulator.
Add Main.js file with the following code in the Javascript folder:
var widgetAPI = new Common.API.Widget(), tvKey = new Common.API.TVKeyValue(), Main = {}; Main.onLoad = function() { alert("Main.onLoad()"); } Main.onUnload = function() { alert("Main.onUnload()"); }
Start the SDK emulator. If the alert() : Main.onLoad() message is displayed in the log manager, the application is created successfully.
Retrieving Data from the Server¶
To retrieve data from the server,
Get the RSS data from the server.
Extract from the RSS data the information about the location of the video streams, and additional information about each video. A basic RSS format is used to store the data. See the example XML code given below.
<?xml version="1.0"?> <rss version="2.0"> <channel> <item> <title>First video</title> <link>http://videoURL1</link> <description> This is a video</description> </item> <item> <title>Second video</title> <link>http://videoURL2</link> <description>This is another video</description> </item> </channel> </rss>
To create the XML file,
Set the <title> and <description> tags with information about each video.
Set valid paths to video content in the <link> tags. The video content can be located on an HTTP server or in a local directory on the development computer.
HTTP serverUse an HTTP server:For running the application on TV and emulator
In a production application
Example link tag: <link>http://111.222.333.444/MyVideoApp/content/video.mp4</link>
Local directoryUse a local directory:For running the application on the emulator
In the application development when no web server is available
Example link tag: <link>c:\my\path\to\video files\another_video.mp4</link>
Note
The <link> tag must contain an absolute path, no matter where the video files are stored. The relative paths to video content do not work because the path is interpreted by a native plugin, and not by the web browser.
Add the following code to Main.onLoad.
if (Server.init()) { // Start retrieving data from server Server.dataReceivedCallback = function() { alert("All data received"); } Server.fetchVideoList(); /* Request video information from server */ widgetAPI.sendReadyEvent(); } else { alert("Failed to initialise"); }
Add Server.js in the JavaScript folder with the following code:
var Server = { /* Callback function to be set by client */ dataReceivedCallback : null, XHRObj : null, url : "XML/videoList.xml" }; Server.init = function() { var success = true; if (this.XHRObj) { this.XHRObj.destroy(); // Saves memory this.XHRObj = null; } return success; } Server.fetchVideoList = function() { if (this.XHRObj == null) { this.XHRObj = new XMLHttpRequest(); } if (this.XHRObj) { this.XHRObj.onreadystatechange = function() { if (Server.XHRObj.readyState == 4) { Server.createVideoList(); } } this.XHRObj.open("GET", this.url, true); this.XHRObj.send(null); } else { alert("Failed to create XHR"); } } Server.createVideoList = function() { alert("XHR Object status===============================>" + this.XHRObj.status); var xmlElement = this.XHRObj.responseXML.documentElement, videoNames = [], videoURLs = [], videoDescriptions = [], index, items, titleElement, descriptionElement, linkElement; if (!xmlElement) { alert("Failed to get valid XML"); } else { // Parse RSS // Get all "item" elements items = xmlElement.getElementsByTagName("item"); for (index = 0; index < items.length; index++) { titleElement = items[index].getElementsByTagName("title")[0]; descriptionElement = items[index].getElementsByTagName("description")[0]; linkElement = items[index].getElementsByTagName("link")[0]; if (titleElement && descriptionElement && linkElement) { videoNames[index] = titleElement.firstChild.data; videoURLs[index] = linkElement.firstChild.data; videoDescriptions[index] = descriptionElement.firstChild.data; } } Data.setVideoNames(videoNames); Data.setVideoURLs(videoURLs); Data.setVideoDescriptions(videoDescriptions); if (this.dataReceivedCallback) { this.dataReceivedCallback(); /* Notify all data is received and stored */ } } }
The value of the url variable should be the path to the RSS XML file that was created. There are two options for the location of this file:
On an HTTP server This can be the same web server that is used for testing applications on TV. The Apache server installed with the SDK can be used.
Example url: http://111.222.333.444/MyVideoApp/videoList.xml
In a local folder inside the application This is not as flexible as using an HTTP server. If the application is running on a TV, it needs to be synchronised each time the XML file is changed. This option can be used with the emulator, and it does not require a web server. To use this option, create a new folder alongside the other folders in the application (Images , Javascript) and name it XML. Then use a relative path for url variable.
Example url : XML/videoList.xml
Add Data.js in the JavaScript folder and add the following code.
var Data = { videoNames : [], videoURLs : [], videoDescriptions : [] }; Data.setVideoNames = function(list) { this.videoNames = list; } Data.setVideoURLs = function(list) { this.videoURLs = list; } Data.setVideoDescriptions = function(list) { this.videoDescriptions = list; } Data.getVideoURL = function(index) { var url = this.videoURLs[index]; if (url) { // Check for undefined entry (outside of valid array) return url; } else { return null; } } Data.getVideoCount = function() { return this.videoURLs.length; } Data.getVideoNames = function() { return this.videoNames; } Data.getVideoDescription = function(index) { var description = this.videoDescriptions[index]; if (description)// Check for undefined entry (outside of valid array) { return description; } else { return "No description"; } }
To include the JavaScript files in the main HTML structure, add the following code in the index.html file:
<script type="text/javascript" src="Javascript/Server.js"></script> <script type="text/javascript" src="Javascript/Data.js"></script>
When the application is run in the emulator, the following message appears in the log manager.
alert() : All data received
The Server JavaScript object that was just created calls back to the Main JavaScript object when the XML data is loaded and parsed successfully.
The following message is displayed if there is an error in the web server configuration or the URL used for the XML file. XXX is the HTTP error code (for example, 404 means “Not found”).
alert() : XML Server Error XXX
The following message is displayed if the XML structure in the file contains errors (for example, a tag such as <link> without a closing tag </link>).
alert() : Failed to get valid XML
A message is sent to the log when data is received. Actions can be executed for this event using the data retrieved.
Video Playback¶
This tutorial task explains adding code to the application to play the first video in the XML list in a small window on the display.
Creating a Box for Video Playback¶
In this section, a box on the display for the video to be played in is created. The box consists of 4 .png graphic files, with opaque pixels in the border and transparent pixels in the centre. The transparent pixels allow the video to show through, and the opaque pixels hide the video (video is behind the web browser graphics in the TV z-order).
Add the following code inside the <body> tag of index.html.
<!-- Layout begins --> <div id="main"> <div id="logo">Video Tutorial</div> <div id ="rightHalf"> <div id="videoBox_top"></div> <div id="videoBox_left"></div> <div id="videoBox_right"></div> <div id="videoBox_bottom"></div> </div> </div>
Add Main.css file to the CSS folder and add the code below.
/* Defaults */ { padding: 0; margin: 0; border: 0; position: relative; color:#FFFFFF; background-color: transparent; } /* Layout */ body { width: 960px; height: 540px; } #main { position: absolute; left: 0px; top: 0px; width: 960px; height: 540px; } #logo { position: absolute; top: 0px; height: 35px; width: 100%; background-image: url("../Images/BgLogo.png"); background-repeat: repeat-x; font-size: 25px; padding-top: 3px; padding-left: 20px; } /* Right Half */ #rightHalf { position: absolute; left: 420px; top: -5px; width: 540px; height: 540px; } #videoBox_top { position: absolute; left: 30px; top: 55px; width: 488px; height: 8px; background-image: url("../Images/videoBox/top.png"); } #videoBox_left { position: absolute; left: 30px; top: 55px; width: 8px; height: 278px; background-image: url("../Images/videoBox/left.png"); } #videoBox_right { position: absolute; left: 510px; top: 55px; width: 8px; height: 278px; background-image: url("../Images/videoBox/right.png"); } #videoBox_bottom{ position: absolute; left: 30px; top: 333px; width: 488px; height: 56px; background-image: url("../Images/videoBox/bottom.png"); }
The CSS sets the default values for the display (no margins, borders or padding to give better control over sizes); relative positioning; a colour for text; and a transparent background for the video to show through. The body size is set as full display size (960 x 540). The display is divided into sections. A <div> element called ‘main’ is set to cover the entire display. Within ‘main’, 2 <div> elements are added:
- logo at the top Within logo, a PNG graphic file is displayed repeatedly. The graphic looks like a bar and contains the application title.
- rightHalf at the right side Within rightHalf, the CSS creates 4 other <div> elements:
- videoBox_top
- videoBox_left
- videoBox_right
- videoBox_bottom
PNG images are set as background images for these elements. These 4 PNG images look like a box. See Displaying Video Status and Usable Buttons for <div> tags for time and volume, and status display.
Add a link to Main.css in the in the <head> tag of index.html file after the links to the JavaScript files.
<!-- Style sheets --> <link rel="stylesheet" href="CSS/Main.css" type="text/css">
Run the emulator. The ‘Video Tutorial’ title appears in the bar located at the top of the display. The box appears at the right-middle area of the display. The inside of the box shows the simulated TV picture. The box border is of solid colour, not displaying the TV picture. The video clips being played appear on the same plane as the TV picture.
Note
A more complete user interface with multiple views can be developed. For example, you can create one view for the list of videos available, and another view showing more details about the chosen video. This is done by preparing top-level <div> elements, each with their own internal layout. To switch the display, hide one and show another using Display.hide() and Display.show(). A separate JavaScript object is needed to manage each display.
Adding Basic Video Playback¶
This section describes adding the basic video playback functionality.
To implement the Play key to play the first video in the list,
A single anchor is used for receiving the key events. Add the following code to the index.html file at the beginning of the <body> element.
<!-- Dummy anchor as focus for key events --> <a href="javascript:void(0);" id="anchor" onkeydown="Main.keyDown();"></a>
Add the following code in the Main.onLoad function to initialize a new Player module, enable key handling, and replace the current alert() call when data is loaded.
if (Player.init() && Server.init()) { // Start retrieving data from server Server.dataReceivedCallback = function () { /* Use video information when it has arrived */ Main.updateCurrentVideo(); } Server.fetchVideoList(); /* Request video information from server */ // Enable key event processing this.enableKeys(); widgetAPI.sendReadyEvent(); } else { alert("Failed to initialise"); }
Add the following line to Main.onUnload().
Player.deinit();
Add the following functions to Main.js.
Main.updateCurrentVideo = function () { Player.setVideoURL(Data.getVideoURL(0)); } Main.enableKeys = function () { document.getElementById("anchor").focus(); } Main.keyDown = function () { var keyCode = event.keyCode; alert("Key pressed: " + keyCode); switch(keyCode) { case tvKey.KEY_PLAY: alert("PLAY"); Player.playVideo(); break; default: alert("Unhandled key"); break; } }
Add the following code to Data.js.
var Player = { plugin : null, state : -1, originalSource : null, STOPPED : 0, PLAYING : 1, PAUSED : 2, }; Player.init = function () { var success = true; alert("success vale : " + success); this.state = this.STOPPED; this.plugin = document.getElementById("pluginPlayer"); if (!this.plugin) { alert("success vale this.plugin : " + success); success = false; } alert("success vale : " + success); this.setWindow(); alert("success vale : " + success); this.plugin.OnCurrentPlayTime = 'Player.setCurTime'; this.plugin.OnStreamInfoReady = 'Player.setTotalTime'; this.plugin.OnBufferingStart = 'Player.onBufferingStart'; this.plugin.OnBufferingProgress = 'Player.onBufferingProgress'; this.plugin.OnBufferingComplete = 'Player.onBufferingComplete'; alert("success vale : " + success); return success; } Player.deinit = function () { alert("Player deinit !!! "); if (this.plugin) { this.plugin.Stop(); } } Player.setWindow = function () { this.plugin.SetDisplayArea(458, 58, 472, 270); } Player.setVideoURL = function (url) { this.url = url; alert("URL = " + this.url); } Player.playVideo = function () { if (this.url == null) { alert("No videos to play"); } else { this.state = this.PLAYING; this.plugin.Play(this.url); alert("Playing..."); } } // Global functions called directly by the player startDrawLoading = function () { alert("startDrawLoading"); } endDrawLoading = function () { alert("endDrawLoading"); } getBandwidth = function (bandwidth) { alert("getBandwidth " + bandwidth); } onDecoderReady = function () { alert("onDecoderReady"); } onRenderError = function () { alert("onRenderError"); } popupNetworkErr = function () { alert("popupNetworkErr"); } setCurTime = function (time) { alert("setCurTime " + time); } setTottalTime = function (time) { alert("setTottalTime " + time); } stopPlayer = function () { alert("stopPlayer"); } setTottalBuffer = function (buffer) { alert("setTottalBuffer " + buffer); } setCurBuffer = function (buffer) { alert("setCurBuffer " + buffer); } onServerError = function () { alert("onServerError"); }
Add the following code to Player.init().
this.plugin.OnCurrentPlayTime = 'Player.setCurTime'; this.plugin.OnStreamInfoReady = 'Player.setTotalTime'; this.plugin.OnBufferingStart = 'Player.onBufferingStart'; this.plugin.OnBufferingProgress = 'Player.onBufferingProgress'; this.plugin.OnBufferingComplete = 'Player.onBufferingComplete';
Add the following line to Player.setWindow().
this.plugin.SetDisplayArea(458, 58, 472, 270);
Add the following code to Player.js.
// Global functions called directly by the player Player.onBufferingStart = function () { Display.status("Buffering..."); } Player.onBufferingProgress = function (percent) { Display.status("Buffering:" + percent + "%"); } Player.onBufferingComplete = function () { Display.status("Play"); } Player.setCurTime = function (time) { Display.setTime(time); } Player.setTotalTime = function () { Display.setTotalTime(Player.plugin.GetDuration()); } onServerError = function () { Display.status("Server Error!"); } onNetworkDisconnected = function () { Display.status("Network Error!"); } getBandwidth = function (bandwidth) { alert("getBandwidth " + bandwidth); } onDecoderReady = function () { alert("onDecoderReady"); } onRenderError = function () { alert("onRenderError"); } stopPlayer = function () { Player.stopVideo(); } setTottalBuffer = function (buffer) { alert("setTottalBuffer " + buffer); }
The Player.init() function gets a reference to an object in the HTML page with the ID playerPlugin. This plugin gives the applications access to the video playback functions of the TV set. The function also gets a temporary reference to the TV middleware plugin (pluginTVMW). The TV middleware plugin is used for switching the display to show video from the player plugin, instead of the TV receiver. Without this, the video playback does not work correctly. The current source setting (for example TV, External input, or Computer input) is saved to be restored in Player.deinit() just before the application closes. The player plugin can call many functions during its operation. These functions must be defined to avoid runtime errors from the JavaScript engine. In the Player.setWindow() function , the SetDisplayArea() function provided by the plugin is called to position the video at (x = 458, y = 58, width = 472, height = 270). This position and size are aligned with the video box created in Creating a Box for Video Playback. In the Player.playVideo() function , the Play() function provided by the plugin is called to play the video at the chosen URL. The final task is to add the player plugin, the TV middleware plugin and the new Javascript file to the HTML page.
Add the following code in the <head> tag of index.html after the style sheet link.
<!-- Plugins --> <object id="pluginPlayer" border=0 classid="clsid:SAMSUNG-INFOLINK-PLAYER" style="position:absolute;z-index:99;left:458px;top:58px;width:472px;height:270px"></object> <object id="pluginTVMW" border=0 classid="clsid:SAMSUNG-INFOLINK-TVMW"></object>
“z-index” used in the project, “z-index” value is set among the highest.
The width and height denote the size of the video playback area, and left and top denote the location of the video playback area.
style="position:absolute; z-index:99;left:458px;top:58px;width:472px;height:270px"
Add the following script link to index.html .
<script type="text/javascript" src="Javascript/Player.js"></script>
Run the application in the emulator or on a real TV. The first video link from your XML file is played. If there is no playback, check the log manager to make sure the correct URL has been retrieved from the XML file on the server.
Video Control and Feedback¶
This tutorial task adds controls and displays to the video playback application. The task consists of the following parts:
- Adding Video Controls
- Displaying Video Time and Progress Bar
- Displaying Video Status and Usable Buttons
Adding Video Controls¶
This section describes adding controls for the video playback using the remote control keys for Pause, Fast Forward, and Rewind.
Add the following code in the keyDown() function of Main.js.
Main.keyDown = function() { var keyCode = event.keyCode; alert("Key pressed: " + keyCode); switch(keyCode) { case tvKey.KEY_PLAY: alert("PLAY"); this.handlePlayKey(); break; case tvKey.KEY_STOP: alert("STOP"); Player.stopVideo(); break; case tvKey.KEY_PAUSE: alert("PAUSE"); this.handlePauseKey(); break; case tvKey.KEY_FF: alert("FF"); Player.skipForwardVideo(); break; case tvKey.KEY_RW: alert("RW"); Player.skipBackwardVideo(); break; default: alert("Unhandled key"); break; } }
Add the following functions to Main.js.
Main.handlePlayKey = function() { switch ( Player.getState() ) { case Player.STOPPED: Player.playVideo(); break; case Player.PAUSED: Player.resumeVideo(); break; default: alert("Ignoring play key, not in correct state"); break; } } Main.handlePauseKey = function() { switch ( Player.getState() ) { case Player.PLAYING: Player.pauseVideo(); break; case Player.PAUSED: Player.resumeVideo(); break; default: alert("Ignoring pause key, not in correct state"); break; } }
Note
The keys handled in Main.keyDown() are already registered to full-screen applications. Therefore widgetAPI.registKey() need not be called. The functions Main.handlePlayKey() and Main.handlePauseKey() have been added. Play or Pause key resumes the video playback when paused.
Add the following functions to Player.js.
Player.pauseVideo = function() { this.state = this.PAUSED; this.plugin.Pause(); alert("Paused..."); } Player.stopVideo = function() { if (this.state != this.STOPPED) { this.state = this.STOPPED; this.plugin.Stop(); alert("Stopped"); } else { alert("Ignoring stop request, not in correct state"); } } Player.resumeVideo = function() { this.state = this.PLAYING; this.plugin.Resume(); alert("Playing..."); } Player.skipForwardVideo = function() { this.plugin.JumpForward(5); } Player.skipBackwardVideo = function() { this.plugin.JumpBackward(5); } Player.getState = function() { return this.state; }
The following new Player plugin API functions are used:
- Pause()
- Pauses the video playback. Audio stops playing and the video display shows a frozen frame.
- Resume()
- Resumes video playback after pausing. Audio and video begin playing again immediately.
- Stop()
- Stops video playback completely. Video and audio start again only if the Play() function is called specifying a URL.
- JumpForward()/JumpBackward()
- Jumps forward or backward in the video file by the specified number of seconds. This tutorial uses 5 seconds as a fixed value. These calls cause the player to re-start the video from the new position, with some delay for buffering of video data.
The video playback can now be controlled using these keys.
Displaying Video Time and Progress Bar¶
The setCurTime(time) event function, called by the player plugin, displays the alert() : setCurTime XXXX message in the log manager of the SDK every second. The data passed to this function is used for making a display showing the progress of the video.
Add the following mark-up within the <div> named videoBox_bottom of index.html.
<div id="time"> <div id="progressBarBG"> <div id="progressBar"></div> </div> <div id="timeInfo"></div> </div>
Add the following code in Main.css.
#time { position: absolute; left:8px; top:0px; width:472px; height:24px; } #progressBarBG { position: absolute; left: 4px; top: 8px; width: 400px; height: 8px; background-image: url("../Images/videoBox/progressBarBG.png"); } #progressBar { position: absolute; left: 0px; top:0px; width:0%; height: 8px; background-image: url("../Images/videoBox/progressBar.png"); } #timeInfo { position: absolute; left: 408px; top: 4px; width: 60px; height: 16px; text-align: center; font-size: 12px; }
In the videoBox_bottom area (under the video box), 2 new <div> tags, timeBar and progressBarBG are added. In the progressBarBG area, a new <div> tag progressBar is added. Background images for progressBarBG and progressBar,a specific position for these tags, and timeInfo are set. Width and height matching exactly the image file chosen for the background are specified, except for the width of progressBar. The width of `progressBar is specified as percentage in relation to the current play time. When the application is launched first, the background of progressBar does not appear. progressBarBG and progressBarv make up a progress bar display. The progress bar display is animated in response to the setCurTime() calls from the player plugin. The current time and total video play time are displayed using timeInfo.
In Player.js,replace the implementation of setCurTime function and setTotalTime function with the following code.
Player.setCurTime = function(time) { Display.setTime(time); } Player.setTotalTime = function() { Display.setTotalTime(Player.plugin.GetDuration()); }
The player plugin calls setTotalTime() to show the total length of the video file in milliseconds using GetDuration() from the player plugin. The player plugin calls setCurTime function every second during playback to show the current playback position in the video file in milliseconds.
Modify Main.onLoad() as follows.
if (Player.init() && Display.init() && Server.init()) { ...
Add a new JavaScript file Display.js with the following code.
var Display = {}; Display.init = function() { return true; } Display.setTotalTime = function(total) { this.totalTime = total; } Display.setTime = function(time) { var timePercent = (100 * time) / this.totalTime, timeElement = document.getElementById("timeInfo"), timeHTML = "", timeHour = 0, timeMinute = 0, timeSecond = 0, totalTimeHour = 0, totalTimeMinute = 0, totalTimesecond = 0; document.getElementById("progressBar").style.width = timePercent + "%"; if (Player.state == Player.PLAYING) { totalTimeHour = Math.floor(this.totalTime / 3600000); timeHour = Math.floor(time / 3600000); totalTimeMinute = Math.floor((this.totalTime % 3600000) / 60000); timeMinute = Math.floor((time % 3600000) / 60000); totalTimeSecond = Math.floor((this.totalTime % 60000) / 1000); timeSecond = Math.floor((time % 60000) / 1000); timeHTML = timeHour + ":"; if (timeMinute == 0) { timeHTML += "00:"; } else if (timeMinute < 10) { timeHTML += "0" + timeMinute + ":"; } else { timeHTML += timeMinute + ":"; } if (timeSecond == 0) { timeHTML += "00/"; } else if (timeSecond < 10) { timeHTML += "0" + timeSecond + "/"; } else { timeHTML += timeSecond + "/"; } timeHTML += totalTimeHour + ":"; if (totalTimeMinute == 0) { timeHTML += "00:"; } else if (totalTimeMinute < 10) timeHTML += "0" + totalTimeMinute; else { timeHTML += totalTimeMinute; } if (totalTimeSecond == 0) { timeHTML += "00"; } else if (totalTimeSecond < 10) { timeHTML += "0" + totalTimeSecond; } else timeHTML += totalTimeSecond; } else { timeHTML = "0:00:00/0:00:00"; } widgetAPI.putInnerHTML(timeElement, timeHTML); }
Display.init() does not have a role yet, but it is used later. In response to the time events from the player plugin, the background of progressBar's width percentage is changed within its parent <div> progressBarBG. The background of progressBarBG is not considered in the calculations as the progressBar width uses a percentage (this is done by the web browser). The totalTime variable is initialized to ‘1’ to prevent division by ‘0’ if Display.setTime() is called before Display.setTotalTime(). The current play time, and the hour, minute and second of the total video play time are displayed using timeInfo tag.
Add a link to the new Display.js file in index.html.
<script type="text/javascript" src="Javascript/Display.js"></script>
The position of the time bar begins at the left when the application is started.
Add the following code to Main.onLoad(). This is executed only if the initialization is successful.
Display.setTime(0);
The position of the time bar is reset to the left when the video is stopped.
After the call to this.plugin.Stop(), add the following code to Player.stopVideo():
Display.setTime(0);
The time bar is rest when the video reaches the end and stops by itself. Even when the playback stops, the Stop() function is called, to receive another Play() request. This is achieved by calling Player.stopVideo().
In Player.js,replace the implementation of the stopPlayer function with the following code.
Player.stopVideo = function() { if (this.state != this.STOPPED) { this.state = this.STOPPED; this.plugin.Stop(); Display.setTime(0); } }
The stopPlayer() function is called by the player plugin when the video reaches the end and is stopped automatically.
Run the application. The time information and progress bar are displayed under the video display, moving from left to right as the video plays. Pausing, skipping backward and forward, and stopping the video all have the appropriate effect on the time display.
The time progress bar is updated only when a notification of the current time is received from the player plugin. Because this only happens every second, the animation is not smooth. Also, after a forward or backward skip, the position is wrong for a short time until the next time update is received from the player.
The smoothness and accuracy of the progress bar can be improved using a timer to express the progress bar background to a calculated position, and using the setCurTime() function to correct any time drift that happens. However, this is a more complex DHTML implementation.
Displaying Video Status and Usable Buttons¶
This section demonstrates displaying the current status of the application and usable button function on the display. The video playing, paused or stopped status, and any errors are displayed along with the usable buttons. A heading is also added to the application.
To display the video status and usable buttons,
Add the following mark-up to index.html, inside the <div> named videoBox_bottom.
<div id="videoControl"> <div id="rewind"></div> <div id="play"></div> <div id="stop"></div> <div id="pause"></div> <div id="forward"></div> </div> <div id="status"></div>
Add the following code at the end of Main.css.
#videoControl { position: absolute; left: 250px; top: 25px; width: 100px; height: 20px; } #rewind { position: absolute; left: 0px; top: 0px; width: 20px; height: 20px; background-image: url("../Images/control/krew.png"); opacity: 0.2; } #play { position: absolute; left: 20px; top: 0px; width: 20px; height: 20px; background-image: url("../Images/control/kplay.png"); opacity: 1.0; } #stop { position: absolute; left: 40px; top: 0px; width: 20px; height: 20px; background-image: url("../Images/control/kstop.png"); opacity: 0.2; } #pause { position: absolute; left: 60px; top: 0px; width: 20px; height: 20px; background-image: url("../Images/control/kpause.png"); opacity: 0.2; } #forward { position: absolute; left: 80px; top: 0px; width: 20px; height: 20px; background-image: url("../Images/control/kfwd.png"); opacity: 0.2; } #status { position: absolute; left: 390px; top: 28px; width: 98px; height: 16px; text-align: left; font-size: 12px; }
In the videoBox_bottom area (under the video box), new <div> tags videoControl and status are added. In the videoControl area, new <div> tags rewind, play, stop, pause and forward are added. The background image and opacity for these tags in the videoControl area are specified. The usable state is expressed using opacity. If the opacity is ‘1.0’, the button is usable and if the opacity is ‘0.2’, it is not. A button that is not usable does not work when it is pressed. The width, height and position for these tags are specified. Only the status tag has specified font-size and text alignment. The style for the heading is not specified with CSS to consider the default positioning and size. This part of the display is not dynamic, it is simple HTML.
Add the following line to Display.js, in the definition of var Display.
statusDiv : null,
Add the following code to :file`Player.js`, in the definition of var Player.
skipState : -1, stopCallback : null, /* Callback function to be set by client */
Replace the implementation of Display.init() with the following code.
Display.init = function() { var success = true; this.statusDiv = document.getElementById("status"); if (!this.statusDiv) { success = false; } return success; }
Replace the Player.js functions with the following code.
Player.playVideo = function() { if (this.url == null) { alert("No videos to play"); } else { this.state = this.PLAYING; document.getElementById("play").style.opacity = '0.2'; document.getElementById("stop").style.opacity = '1.0'; document.getElementById("pause").style.opacity = '1.0'; document.getElementById("forward").style.opacity = '1.0'; document.getElementById("rewind").style.opacity = '1.0'; Display.status("Play"); this.setWindow(); this.plugin.Play(this.url); Audio.plugin.SetSystemMutu(false); } } Player.pauseVideo = function() { this.state = this.PAUSED; document.getElementById("play").style.opacity = '1.0'; document.getElementById("stop").style.opacity = '1.0'; document.getElementById("pause").style.opacity = '0.2'; document.getElementById("forward").style.opacity = '1.0'; document.getElementById("rewind").style.opacity = '1.0'; Display.status("Pause"); this.plugin.Pause(); } Player.stopVideo = function() { if (this.state != this.STOPPED) { this.state = this.STOPPED; document.getElementById("play").style.opacity = '1.0'; document.getElementById("stop").style.opacity = '0.2'; document.getElementById("pause").style.opacity = '0.2'; document.getElementById("forward").style.opacity = '0.2'; document.getElementById("rewind").style.opacity = '0.2'; Display.status("Stop"); this.plugin.Stop(); Display.setTime(0); if (this.stopCallback) { this.stopCallback(); } } else { alert("Ignoring stop request, not in correct state"); } } Player.resumeVideo = function() { this.state = this.PLAYING; document.getElementById("play").style.opacity = '0.2'; document.getElementById("stop").style.opacity = '1.0'; document.getElementById("pause").style.opacity = '1.0'; document.getElementById("forward").style.opacity = '1.0'; document.getElementById("rewind").style.opacity = '1.0'; Display.status("Play"); this.plugin.Resume(); } Player.skipForwardVideo = function() { this.skipState = this.FORWARD; this.plugin.JumpForward(5); } Player.skipBackwardVideo = function() { this.skipState = this.REWIND; this.plugin.JumpBackward(5); }
Add the following function to Display.js.
Display.status = function(status) { alert(status); widgetAPI.putInnerHTML(this.statusDiv, status); }
This function displays a status message. The Player.playVideo, Player.stopVideo, Player.pauseVideo, and Player.resumeVideo functions call the alert() function when successful. Modify these functions to call Display.status() instead. The error and warning messages continue as alert() calls, as these messages are mostly too long to fit in the <div>. The usable video control button for every state (such as play, stop, pause, rewind and forward) is displayed using opacity. If the button is usable, make its opacity value 1.0. If it is not usable, make its opacity value 0.2.
Replace the Player.js global functions startDrawLoading(), endDrawLoading(), popupNetworkErr(), and onServerErr() with the following code.
Player.onBufferingStart = function() { Display.status("Buffering..."); switch (this.skipState) { case this.FORWARD: document.getElementById("forward").style.opacity = '0.2'; break; case this.REWIND: document.getElementById("rewind").style.opacity = '0.2'; break; } } Player.onBufferingProgress = function(percent) { Display.status("Buffering:" + percent + "%"); } Player.onBufferingComplete = function() { Display.status("Play"); switch (this.skipState) { case this.FORWARD: document.getElementById("forward").style.opacity = '1.0'; break; case this.REWIND: document.getElementById("rewind").style.opacity = '1.0'; break; } } onServerError = function() { Display.status("Server Error!"); } popupNetworkErr = function() { Display.status("Network Error!"); }
The alert() call at the start of Server.createVideoList() indicating an XML server error can be replaced with a call to Display.status().
Run the application in the emulator (or on a TV). The status of the application and usable video control buttons are displayed at the bottom of the video box.
Volume Control¶
This tutorial task describes the code for changing the volume and mute functions. The task consists of the following parts:
Changing the TV Volume and Mute Status¶
In this section, the code for handling the keys and the code for using an audio plugin to modify the volume are added.
Add the following code to the key handling switch statement in Main.keyDown().
case tvKey.KEY_VOL_UP: case tvKey.KEY_PANEL_VOL_UP: alert("VOL_UP"); if (this.mute == 0) { Audio.setRelativeVolume(0); } break; case tvKey.KEY_VOL_DOWN: case tvKey.KEY_PANEL_VOL_DOWN: alert("VOL_DOWN"); if (this.mute == 0) { Audio.setRelativeVolume(1); } break; case tvKey.KEY_MUTE: alert("MUTE"); this.muteMode(); break;
There are two keys for volume up, and two keys for volume down. The first one is the volume button on the remote control, and the second one is the button on the TV set. For consistent behaviour with other parts of the TV set, these button actions are implemented to meet user expectation. The Mute key is handled for mute function.
Add a new JavaScript file Audio.js with the following code:
var Audio = { plugin: null, init: function() { var success = true; this.plugin = document.getElementById("pluginAudio"); if (!this.plugin) { success = false; } return success; }, setRelativeVolume: function(delta) { this.plugin.SetVolumeWithKey(delta); Display.setVolume(this.getVolume()); }, getVolume = function() { return this.plugin.GetVolume(); } };
This code uses the audio plugin to change the volume up or down by a specific step (plugin API function SetRelativeVolume). In the key handler, ‘0’ is set for volume up, and ‘1’ for volume down.
Add the following code to Main.js, in the definition of variable Main.
mute: 0, NMUTE: 0, YMUTE: 1
Add the following code to Main.js.
Main.setMuteMode = function() { if (this.mute != this.YMUTE) { Audio.plugin.SetUserMute(true); this.mute = this.YMUTE; } }; Main.noMuteMode = function() { if (this.mute != this.NMUTE) { Audio.plugin.SetUserMute(false); this.mute = this.NMUTE; } }; Main.muteMode = function() { switch (this.mute) { case this.NMUTE: this.setMuteMode(); break; case this.YMUTE: this.noMuteMode(); break; default: alert("ERROR: unexpected mode in muteMode"); break; } };
The mute function acts in a toggle mode. When the state is mute, pressing the Mute button changes the system to non-mute status.
Modify the Main.onLoad() function as follows:
if (Player.init() && Audio.init() && Display.init() && Server.init()) { (...)
Add Audio.js file and the audio plugin to index.html.
<script type="text/javascript" src="Javascript/Audio.js"></script>
<object id="pluginAudio" border="0" classid="clsid:SAMSUNG-INFOLINK-AUDIO"></object>
Run the application. The volume is modified on pressing the volume up or volume down keys, and mute. The maximum volume degree is ‘100’ and the minimum is ‘0’. Pressing the volume button changes the volume 1 degree up or down.
Displaying the TV Volume and Mute Status¶
Adding a volume display is very similar to adding the progress bar display. This section does not explain the graphics and animation. For details about adding the volumeIcon <div>, see Video Control and Feedback.
Add the following mark-up after the end of the time <div> in index.html.
<div id="volume"> <div id="volumeIcon"></div> <div id="volumeBarBG"> <div id="volumeBar"></div> </div> <div id="volumeInfo"></div> </div>
Add the following code to the end of Main.css.
#volume { position: absolute; left: 8px; top: 24px; width: 472px; height: 24px; } #volumeIcon { position: absolute; left: 4px; top: 4px; width: 17px; height: 16px; background-image: url("../Images/videoBox/volume.png"); } #volumeBarBG { position: absolute; left: 22px; top: 8px; width: 144px; height: 8px; background-image: url("../Images/videoBox/volumeBarBG.png"); } #volumeBar { position: absolute; left: 0px; top: 0px; width: 0%; height: 8px; background-image: url("../Images/videoBox/volumeBar.png"); } #volumeInfo { position: absolute; left: 168px; top: 4px; width: 32px; height: 16px; text-align: left; font-size: 12px; }
These <div>s are inside the videoBox_bottom <div>. The position, width, height and background-image of the volumeIcon <div> tag are specified. The rest are similar to time progress bar.
Add the following function to Display.js.
Display.setVolume = function(level) { document.getElementById("volumeBar").style.width = level + "%"; var volumeElement = document.getElementById("volumeInfo"); widgetAPI.putInnerHTML(volumeElement, " " + Audio.getVolume()); }
Replace setMeteMode(), noMuteMode, and muteMode() in Main.js with the following code.
Main.setMuteMode = function() { if (this.mute != this.YMUTE) { var volumeElement = document.getElementById("volumeInfo"); Audio.plugin.SetUserMute(true); document.getElementById("volumeBar").style .backgroundImage = "url(Images/videoBox/muteBar.png)"; document.getElementById("volumeIcon").style .backgroundImage = "url(Images/videoBox/mute.png)"; widgetAPI.putInnerHTML(volumeElement, "MUTE"); this.mute = this.YMUTE; } } Main.noMuteMode = function() { if (this.mute != this.NMUTE) { Audio.plugin.SetUserMute(false); document.getElementById("volumeBar").style .backgroundImage = "url(Images/videoBox/volumeBar.png)"; document.getElementById("volumeIcon").style .backgroundImage = "url(Images/videoBox/volume.png)"; Display.setVolume( Audio.getVolume() ); this.mute = this.NMUTE; } } Main.muteMode = function() { switch (this.mute) { case this.NMUTE: this.setMuteMode(); break; case this.YMUTE: this.noMuteMode(); break; default: alert("ERROR: unexpected mode in muteMode"); break; } }
The volume is displayed as a bar and a number. It appears like the time progress bar. When the mute state is toggled, the volume icon also changes.
Add the following function to Audio.js.
Audio.getVolume = function() { return this.plugin.GetVolume(); }
The audio plugin function GetVolume() returns the current volume level of the TV set in the range of 0-100.
Add the following code to Audio.setRelativeVolume() after the call to the SetRelativeVolume() function of the audio plugin API.
Display.setVolume(this.getVolume());
To set the initial position to match the TV volume, add the following code to Main.onLoad() just before the call to Display.setTime.
Display.setVolume(Audio.getVolume());
Run the application. The current volume is displayed at the bottom of the video box.
Video Selection¶
In this tutorial task, the ability to select other videos in the list for playback is added. A list of available videos, and a description of each one will also be displayed.
The task consists of the following parts:
- Playing Multiple Video Clips
- Displaying the Video Title List
- Displaying the Video Description
- Displaying the UI Help Bar
Playing Multiple Video Clips¶
In this section, key handling functionality is added for playing the next video on the list by pressing the down key on the remote control, and playing the previous video by pressing the up key on the remote control.
Add the following code to the key handling switch statement of Main.keyDown().
case tvKey.KEY_DOWN: alert("DOWN"); this.selectNextVideo(); break; case tvKey.KEY_UP: alert("UP"); this.selectPreviousVideo(); break;
Add the following line to the declaration of the var Main object.
selectedVideo: 0,
Add the following functions to Main.js.
Main.selectNextVideo = function() { Player.stopVideo(); this.selectedVideo = (this.selectedVideo + 1) % Data.getVideoCount(); this.updateCurrentVideo(); } Main.selectPreviousVideo = function() { Player.stopVideo(); this.selectedVideo -= 1; if (this.selectedVideo <0) { this.selectedVideo += Data.getVideoCount(); } this.updateCurrentVideo(); }
The index of the selected video in the list of videos is now being tracked. The current video is stopped, and the selected video index is changed. The updateCurrentVideo() function uses this index to select a new video URL (currently it always uses the first URL). The number of videos available has to be retrieved from the Data object, for wrapping around at the ends of the list.
In Main.updateCurrentVideo() function, replace the existing call to Player.setVideoURL() with the following line.
Player.setVideoURL(Data.getVideoURL(this.selectedVideo));
Add the following function to Data.js.
Data.getVideoCount = function() { return this.videoURLs.length; }
Run the application. New videos from the list can be selected by pressing the up and down keys.
Displaying the Video Title List¶
In this section, functionality to display a list of available titles, and highlight the currently selected one is added. The application can display a maximum of 5 titles. If there are more than 5 videos, scroll function is displayed.
Add the following mark-up to the leftHalf <div> in index.html.
<div id="videoList" class="style_videoList"> <div id="video0"></div> <div id="video1"></div> <div id="video2"></div> <div id="video3"></div> <div id="video4"></div> <div id="videoCount"></div> </div> <div id="previous"></div> <div id="next"></div>
Add the following code to Main.css.
#videoList { position: absolute; left: 30px; top: 55px; width: 390px; height: 430px; background-image: url("../Images/listBox/listBox.png"); } #video0 { position: absolute; left: 30px; top: 70px; width: 330px; height: 47px; background-repeat: no-repeat; padding-left: 20px; padding-top: 10px; } #video1 { position: absolute; left: 30px; top: 130px; width: 330px; height: 47px; background-repeat: no-repeat; padding-left: 20px; padding-top: 10px; } #video2 { position: absolute; left: 30px; top: 190px; width: 330px; height: 47px; background-repeat: no-repeat; padding-left: 20px; padding-top: 10px; } #video3 { position: absolute; left: 30px; top: 250px; width: 330px; height: 47px; background-repeat: no-repeat; padding-left: 20px; padding-top: 10px; } #video4 { position: absolute; left: 30px; top: 310px; width: 330px; height: 47px; background-repeat: no-repeat; padding-left: 20px; padding-top: 10px; } #videoCount { position: absolute; left: 330px; top: 30px; width: 30px; height: 20px; text-align: left; font-size: 12px; } #previous { position: absolute; left: 210px; top: 80px; width: 30px; height: 30px; background-image: url("../Images/listBox/previous.png"); opacity: 0.2; } #next { position: absolute; left: 210px; top: 430px; width: 30px; height: 30px; background-image: url("../Images/listBox/next.png"); opacity: 0.2; }
A new area, videoList, is added to the left column of the display. There are 6 sub areas: video0, video1, video2, video3, video4, and videoCount. The previous and next areas are added for the scroll function. The video name and number are displayed in the videoList areas. The position, width and height for previous, next, videoList, and videoList ‘s sub <div> tag are specified. The background image for videoList, previous and next tag are also specified. To prevent the text from reaching up to or overlapping the selector, padding-left/top for videoList ‘s sub tags are specified.
Add the following functions to Display.js.
Display.setVideoList = function(nameList) { var listHTML = "", i=0, name; for (name in nameList) { this.videoList[i] = document.getElementById("video"+i); listHTML = nameList[name] ; widgetAPI.putInnerHTML(this.videoList[i], listHTML); i++; } this.videoList[this.FIRSTIDX].style .backgroundImage= "url(Images/listBox/selector.png)"; if (i>5) { document.getElementById("next").style.opacity = '1.0'; document.getElementById("previous").style.opacity = '1.0'; } listHTML = "1 / " + i; widgetAPI.putInnerHTML(document.getElementById("videoCount"), listHTML); } Display.setVideoListPosition = function(position, move) { var listHTML = "", i = 0; listHTML = (position + 1) + " / " + Data.getVideoCount(); widgetAPI.putInnerHTML(document.getElementById("videoCount"), listHTML); if (Data.getVideoCount() < 5) { for (i = 0; i < Data.getVideoCount(); i++) { if (i == position) { this.videoList[i].style.backgroundImage= "url(Images/listBox/selector.png)"; } else { this.videoList[i].style.backgroundImage= "url(none)"; } } } else if ( (this.currentWindow!=this.LASTIDX && move==Main.DOWN) || (this.currentWindow!=this.FIRSTIDX && move==Main.UP) ) { if (move == Main.DOWN) { this.currentWindow++; } else { this.currentWindow--; } for (var i = 0; i <= this.LASTIDX; i++) { if (i == this.currentWindow){ this.videoList[i].style.backgroundImage= "url(Images/listBox/selector.png)"; } else { this.videoList[i].style.backgroundImage= "url(none)"; } } } else if (this.currentWindow == this.LASTIDX && move == Main.DOWN) { if (position == this.FIRSTIDX) { this.currentWindow = this.FIRSTIDX; for (i = 0; i <= this.LASTIDX; i++) { listHTML = Data.videoNames[i] ; widgetAPI.putInnerHTML(this.videoList[i], listHTML); if (i == this.currentWindow) { this.videoList[i].style.backgroundImage= "url(Images/listBox/selector.png)"; } else { this.videoList[i].style.backgroundImage= "url(none)"; } } } else { for (i = 0; i <= this.LASTIDX; i++) { listHTML = Data.videoNames[i + position - this.currentWindow]; widgetAPI.putInnerHTML(this.videoList[i], listHTML); } } } else if (this.currentWindow == this.FIRSTIDX && move == Main.UP) { if (position == Data.getVideoCount()-1) { this.currentWindow = this.LASTIDX; for (i = 0; i <= this.LASTIDX; i++) { listHTML = Data.videoNames[i + position - this.currentWindow] ; widgetAPI.putInnerHTML(this.videoList[i], listHTML); if (i == this.currentWindow) { this.videoList[i].style.backgroundImage= "url(Images/listBox/selector.png)"; } else { this.videoList[i].style.backgroundImage= "url(none)"; } } } else { for (i = 0; i <= this.LASTIDX; i++) { listHTML = Data.videoNames[i + position] ; widgetAPI.putInnerHTML(this.videoList[i], listHTML); } } } }
The list of video names in each video# element is displayed. When another video is selected in the list, the highlight or titles move. If the highlight is located in the top area, on pressing the up button, the highlight does not move. The titles move one area down. If the highlight is located in the bottom area, on pressing the down button, the titles move one area down without the highlight moving. The video number is displayed in the videoCount area. If the number of videos is more than 5, the opacity of the previous and the next area is set to ‘1.0’ to support the scroll function.
To pass the correct information to these new functions, in the Main.onLoad() function, replace the current definition of Server.dataReceivedCallback with the following code.
Server.dataReceivedCallback = function() { // Use video information when it has arrived Display.setVideoList(Data.getVideoNames()); Main.updateCurrentVideo(); }
Add the following function to Data.js.
Data.getVideoNames = function() { return this.videoNames; }
Add the following code to the declaration of the var Main object.
mute: 0, NMUTE: 0, YMUTE: 1
In the Main.updateCurrentVideo function, add the following code.
Display.setVideoListPosition(this.selectedVideo); Display.setVideoListPosition(this.selectedVideo, move);
In the Main.keyDown function, add the following code.
case tvKey.KEY_DOWN: alert("DOWN"); this.selectNextVideo(this.DOWN); break; case tvKey.KEY_UP: alert("UP"); this.selectPreviousVideo(this.UP); break;
Add the following code in Main.js.
Main.selectNextVideo = function(down) { Player.stopVideo(); this.selectedVideo = (this.selectedVideo + 1) % Data.getVideoCount(); this.updateCurrentVideo(down); } Main.selectPreviousVideo = function(up) { Player.stopVideo(); if (--this.selectedVideo < 0) { this.selectedVideo += Data.getVideoCount(); } this.updateCurrentVideo(up); }
Run the application. A list of titles is displayed on the left side of the screen, with a highlight on the currently selected file. Pressing the up and down keys moves the highlight or the titles.
Displaying the Video Description¶
The RSS information retrieved from the server also contains a description for each video link. The description for the currently selected video is displayed. This is DHTML and quite similar to the previous tasks in this tutorial.
Add the following mark-up to index.html, after the end of the videoBox_bottom <div>, and in the rightHalf.
<div id="description_top"></div> <div id="description_bottom"></div> <div id="description"></div>
Add new CSS rules for these elements.
#description_top { position: absolute; left: 30px; top: 390px; width: 488px; height: 50px; background-image: url("../Images/descriptionBox/description_top.png"); } #description_bottom { position: absolute; left: 30px; top: 440px; width: 488px; height: 50px; background-image: url("../Images/descriptionBox/description_bottom.png"); } #description { position: absolute; left: 45px; top: 400px; width: 458px; height: 80px; text-align: left; font-size: 16px; overflow: hidden; text-overflow: ellipsis; }
The position, width and height for these <div> tags are specified. The background images for description_top and description_bottom, and a text attribute for description tag are also specified.
Add the following function to Display.js.
Display.status = function (status) { alert(status); widgetAPI.putInnerHTML(this.statusDiv, status); }
Add the following line to Main.updateCurrentVideo().
Display.setDescription(Data.getVideoDescription(this.selectedVideo));
Add the following function to Data.js.
Data.getVideoDescription = function (index) { var description = this.videoDescriptions[index]; if (description) { // Check for undefined entry (outside of valid array) return description; } else { return "No description"; } }
Run the application. The description of the selected video appears underneath the video window, and it changes on pressing the up or down key. You can see a list of titles on the left side of the screen, with a highlight on the one that is currently selected for playback. If you press the up or down key, you see this highlight or the titles move.
Displaying the UI Help Bar¶
Add the following mark-up to index.html, after the end of the leftHalf <div> .
<div id="navi"> <div id="help_navi"> <img src="images/navi/play.png"> <img src="images/navi/stop.png"> <img src="images/navi/pause.png"> <img src="images/navi/rewind.png"> <img src="images/navi/forward.png"> <a class="style_navi">Video Control</a> <img src="images/navi/enter.png"> <a class="style_navi">Full-screen/Window mode</a> <img src="images/navi/return.png"> <a class="style_navi">Return</a> </div> </div>
Add the following CSS rules for these elements.
#navi { position: absolute; top: 505px; height: 35px; width: 100%; background-image: url("../Images/BgNavigator.png"); background-repeat: repeat-x; } #help_navi { position: absolute; left: 275px; top: 5px; width: 785px; height: 28px; } .style_navi { bottom: 4px; height: 20px; font-size: 20px; padding-left: 1ex; margin-right: 8ex; }
A new area navi and a sub area help_navi are added. The position, width and height for these <div> tags are specified. The background image for navi area is specified. The background repeat on is set to display it like a bar. The UI information is displayed using <img> and <a> tags.
Full-Screen Video, Screensaver, and Picture Settings¶
Full-Screen Video¶
This section describes displaying the video using the entire TV screen. This section also describes removing all graphics from the screen, resizing the video, and switching back to the original window display.
Add the following functions to Display.js.
Display.hide = function () { document.getElementById("main").style.display="none"; } Display.show = function () { document.getElementById("main").style.display="block"; }
The common methods in DHTML for hiding and showing page elements are used. Since this is done with the top-level <div>, all graphics are hidden and shown again.
Add the following function to Player.js.
Player.setFullscreen = function () { this.plugin.SetDisplayArea(0, 0, 960, 540); }
This function is similar to the Player.setWindow() function. The only difference is that this specifies the video to appear across the entire screen (top = 0, left = 0, width = 960, height = 540).
Add the following inside the declaration of the var Main object.
mode: 0, WINDOW: 0, FULLSCREEN: 1
Add the following functions to Main.js.
Main.setFullScreenMode = function () { if (this.mode != this.FULLSCREEN) { Display.hide(); Player.setFullscreen(); this.mode = this.FULLSCREEN; } } Main.setWindowMode = function () { if (this.mode != this.WINDOW) { Display.show(); Player.setWindow(); this.mode = this.WINDOW; } } Main.toggleMode = function () { switch (this.mode) { case this.WINDOW: this.setFullScreenMode(); break; case this.FULLSCREEN: this.setWindowMode(); break; default: alert("ERROR: unexpected mode in toggleMode"); break; } }
Add the following code to the key handling switch statement in Main.keyDown().
case tvKey.KEY_ENTER: case tvKey.KEY_PANEL_ENTER: alert("ENTER"); this.toggleMode(); break;
The Enter key on the remote control now toggles between full screen video mode and the original windowed mode.
The display needs to be forced to window mode when the video is stopped to enable the user to select another video. This needs to happen even if video stops by itself. Add the following line inside the declaration of the var Player object.
stopCallback: null, // Callback function to be set by client
Add the following code to the Player.stopVideo() function, after the call to Display.status().
if (this.stopCallback) { this.stopCallback(); }
Add the following code to Main.onLoad() to be executed only if initialization is successful:
Player.stopCallback = function () { // Return to windowed mode when video is stopped // (by choice or when it reaches the end) Main.setWindowMode(); }
Player.stopVideo() is called when the user presses the Stop button and when a video reaches the end. This makes the callback function trigger a return to windowed mode.
Run the application in the emulator or on the TV. The mode can be switched to full screen and back to windowed using the Enter key. It is possible to change the volume, pause the video and skip forward and backward without leaving the full screen mode. When returning to windowed mode, the time and volume sliders are in the correct position. Although the HTML elements are hidden, they exist in the browser and their positions can be updated.
Switching the Screensaver On/Off¶
During video playback, the screensaver may appear. Therefore, set the screensaver off before video playback.
To set the screensaver on or off, add the following code:
var PLUGIN = new Common.API.Plugin();
PLUGIN.setOnScreenSaver(); //on screen saver - TV Menu "AUTO PROTECTION TIME" set Time.
PLUGIN.setOnScreenSaver(1200); // user set Time.
PLUGIN.setOffScreenSaver(); // off screen saver
When the video playback is complete, the screensaver is set on again.
Changing the Picture Settings¶
To change the picture settings in an application:
Add the following code in index.html.
<script type="text/javascript" src="$MANAGER_WIDGET/Common/API/CM_AVSetting.js"></script>
Call initialize method in the start code of widget.
CM_AVSetting.initAVSetting(param1, param2);
- param1: language code sent by manager App (for example, ko, en, en-GB.....)
- param2: callback function returning from picture setting popup window.
Add the following code to launch the picture setting popup window.
CM_AVSetting.showMenuPopup();
Call CM_AVSetting.hideTools(), when the content is finished. When the content is finished, OnRenderingComplete is called.
Note
In the BD system, do not call the 2 APIs. To distinguish this, check the CM_AVSetting.bBDPlayer flag to indicate whether the system is BD or TV. After calling CM_AVSetting.initAVSetting(), the flag is set ‘true’ or ‘false’.
- true: BD system
- false: TV system
- CM_AVSetting.showMenuPopup()
- CM_AVSetting.hideTools()
Picture Setting menu can be seen on the TV display.
Figure: Picture Setting menu







