Friday, March 2, 2007

 

Creating a Video Gadget (Soapbox Video Gadget)

Guide to create a Sidebar gadget for a video feed (soapbox @ msn.com)



Screenshot - top.jpg


Introduction


When I first started sidebar gadget development1, I found gadgets cool and innovative. But the more I learned about them, the more I realized their usefulness. Gadget development provides a whole new platform, becoming more productive in ways unimagined. On one hand, it's a mini web application which can host a web page, call a web service, and use AJAX to read an RSS feed. On the other, it can interact with the Windows API using the power of .NET languages. Wow! That's the best of both the worlds, wouldn't you agree?


The most important factor is the unique way in which a gadget provides the interaction between the web, Windows and users. In this example I try to utilize this, to give user the "Power of Videos" in the sidebar. Check the architecture below.


To hold your interest, here is how it looks in the sidebar.


Screenshot - 320x240withPic.jpg


About the gadget: The power of videos


Before I start, I must warn you: this gadget is quite addictive. Indulge yourself only during your spare time.


There are a number of RSS Feeds available from the Soapbox on MSN Video Beta page which give you the contents of the videos uploaded by the users. Some of the more interesting ones are: "Most Popular Videos", "Most Rated videos", and "Most Recent Videos". There is even a feed for "Videos By Tag" available, which we will use for searching videos. The idea behind this is to use those regularly updated feeds to show the user the video of his choice on the sidebar. If you want to know the basic barebones required to create a gadget, please refer to my first article Daily Dilbert . This article is actually an extension of that first article; I try to explain only those extra features which can be used to refine a gadget further:



  1. Create a gadget which can play a video in the flyout
  2. How to take care of paging when you read an XML file
  3. Settings and Search Videos
  4. Add video to "Favorite List"
  5. Create, read, update, & delete local XML file from Sidebar Gadget (new)
  6. Some tips in general

The architecture overview


Screenshot - Architecturenew.jpg


Let's Begin


Create a video Gadget


A standard XML file with the name Gadget.xml is required as shown below:


Screenshot - settingsxml.jpg


More information about this is available here


The name of the gadget is "Soapbox @ MSN" (as seen above). The namespace is basically to group more than one gadget (reserved for future use), which you can write in "mynamespace". MinPlatformVersion. Required. The expected value is "1.0."


"Permission" controls the amount of permission in the gadget. "Full" permission is required if you want to access a webpage through the gadget. With these two files you can deploy and test your gadget in the sidebar. The <permission> tags and <type> tags will be more flexible in future versions of gadget development.


Our Video Gadget will have these files


Gadget.xml An XML file defining the gadget properties, including name, icon and description
main.html the Main HTML page
main.js The core code/script for the gadget
Settings.html Exposes gadget settings for the user to change
Settings.js Script for the Settings file
soapboxFlyout.html The html file which will be used in Flyout window
Icon, images etc For use in the gadget selection box

You can get more details on these here


Get the RSS Feed


The Main.html file will have five elements ("DIV") to get the feed data: cell0, cell1 etc. We can show the videos as a list of five videos (below) or single video.


Screenshot - main.jpg Screenshot - listview.jpg


The Feed for the Gadget


We have the URL which we call using the MSXML2.XMLHTTP object. This is the core of the AJAX until the feed gets loaded. Here is the magical JavaScript for this:

function getRSS()
{
document.getElementById("mylogo").Title =
System.Gadget.Settings.
read("feedText");

try
{
error.style.visibility = "hidden";

loading.style.visibility = "visible";
rssObj = new ActiveXObject("Msxml2.XMLHTTP");
rssObj.open("GET",
System.Gadget.Settings.read("videoFeed") , true);
rssObj.onreadystatechange = function() {
if (rssObj.readyState = == 4)
{ if (rssObj.status == 200)
{ loading.innerText = "";
error.innerText = "";
error.style.visibility= "hidden";
loading.style.visibility = "hidden";
rssXML = rssObj.responseXML;
page = 0;
parseRSS();
if
(chkConn) { setInterval(getRSS, 60 * 60000);
loading.style.visibility = "hidden";
}
}
else
{
var chkConn;
loading.style.visibility = "visible";
chkConn = setInterval(getRSS, 30 * 60000);
}
} else {
loading.style.visibility = "visible";
}
}
rssObj.send(null);
}
catch(err)
{
loading.style.visibility = "hidden";
error.innerText = " Service not available";
error.style.visibility = "visible";
}
}

Service Not available


As you can see, if there is an error while connecting the feed or an error in the internet connection, we want to show the "Service not available" screen.


Screenshot - serviceNot.jpg


Parse the XML File


Here is the portion of the RSS Feed for a single item in the XML file and the corresponding "partial" ParseXML file to give you an idea of what we are doing with the feed.


Screenshot - feeddetail.jpg

function parseRSS(page)
{
rssItems = rssXML.getElementsByTagName("item");
rssTitle = null; rssAuthors = null; rssDescription = null;
rssLink = null;

rssTitle = escape(rssItems[i].firstChild.text);
rssLink =rssItems[i].getElementsByTagName("guid")[0]
.firstChild.nodeValue;
rssViews = rssItems[i].getElementsByTagName("vidAt:playCount")[0]
.firstChild.nodeValue;
rssDateUpdate = rssItems[i].getElementsByTagName("vidAt:pubDate")[0]
.firstChild.nodeValue;
rssRatingValue = rssItems[i].getElementsByTagName("vidAt:rating")[0]
.firstChild.nodeValue;
rssDuration = rssItems[i].getElementsByTagName("vidAt:duration")[0]
.firstChild.nodeValue;
rssVDescription= escape(rssItems[i].getElementsByTagName
("vidAt:description")[0].firstChild.nodeValue);
rssDescription=rssItems[i].getElementsByTagName("description")[0]
.firstChild
.nodeValue;
myTitle = unescape(rssTitle)
myTitle1 = myTitle.substr(0,16);
myRatingTip = "Rating: " + rssRatingValueFormated2 + ",
View: "
+ rssViews + ", Duration: " + rssDurationFormated ;

document.getElementById("cell" + (cell)).innerHTML =
"onclick="
showFlyout
(\'' + rssTitle + '\',\'' + rssVDescription + '\',
\'' + rssLink + '\',\'' + rssImage + '\',\'' + rssRImage + '\',\''
+ rssViews + '\',
\'' + rssDateUpdate + '\',\'' + rssDurationFormated + '\',
\'' + rssRatingValueFormated2 + '\');" >
}

Since we have the feed required, we can create a flyout from the video. This is the showFlyout function, which is created dynamically and is used to pass all the parameters from the item to the flyout page.

function showFlyout(sTitle,sDescription,sGuid, sImage, sRImage,sView,
sDateUpdate,sDuration,sRatingFormated)
{

if (System.Gadget.Settings.read("sTitle")==sTitle)
{
System.Gadget.Settings.write("sTitle", "myTitle");
hideFlyout();
}
else
{
System.Gadget.Settings.write("sTitle", sTitle);
System.Gadget.Settings.write("sView", sView);
System.Gadget.Settings.write("sDateUpdate", sDateUpdate);
System.Gadget.Settings.write("sGuid", sGuid);
System.Gadget.Settings.write("sDescription", sDescription);
System.Gadget.Settings.write("sImage", sImage);
System.Gadget.Settings.write("sRImage", sRImage);
System.Gadget.Settings.write("sRatingFormated",
sRatingFormated);
System.Gadget.Settings.write("sDuration", sDuration);
System.Gadget.Flyout.file = "soapboxFlyout.html";
System.Gadget.Flyout.show = true;
}
}

Other Settings for the Gadget


Apart from the settings which are set on the showFlyout function, there are other settings which can be managed by the user. The user can search under "Video" or select from one of the Video feeds.


Once we have all the settings, we Build the video object in the flyout window.

function BuildVideoObject()
{
try
{
if (System.Gadget.Settings.read("videoSize")
=="large")
{
Video_HtmlString = '‹OBJECT id="VIDEO"
width="640" height="480" ';
}
else
{
Video_HtmlString = '‹OBJECT id="Body1"
width="320" height="240" ';
}
Video_HtmlString += 'style="position:absolute; left:0;top:0;"';
Video_HtmlString += 'CLASSID="CLSID:6BF52A52-394A-11d3-B153-"';
Video_HtmlString += 'type="application/x-oleobject"
VIEWASTEXT›';
Video_HtmlString += '‹PARAM NAME="URL" VALUE="';
Video_HtmlString +=
"http:/soapbox.msn.com/StreamingUrl.aspx?vid="
+ System.Gadget.Settings.read("sGuid");
Video_HtmlString += '"›‹param NAME="stretchToFit"
VALUE="1"›';
Video_HtmlString += '‹param name="AutoSize"
value="1"›
PARAM NAME="SendPlayStateChangeEvents"
VALUE="True"›';
Video_HtmlString += '‹PARAM NAME="AutoStart"
VALUE="True"›';
Video_HtmlString += '‹PARAM name="uiMode"
value="none"›';
Video_HtmlString += '‹PARAM name="PlayCount"
value="9999"›';
Video_HtmlString += '‹/OBJECT›';
document.write(Video_HtmlString);
}
catch (err)
{
document.write("Problem with the machine's Windows Media
Player");
}
}

More information on the flyout is available here


Paging


Since XML files can have hundreds of items, we need to be able to page it. So we add a DIV item in the main.html file with the following:


Screenshot - tab.jpg

‹div id="tbar" title=""›
‹table width="100%" height="100%" border="0" cellpadding="2" cellspacing="0"
ID="Table2"›
‹tr align="center" valign="middle"›
‹td width="12" class="arrow" onclick="chPage(-999);" align="right"›
«‹/td›
‹td width="12" class="arrow" onclick="chPage(-1);" align="right"›
‹b›‹‹/b›‹/td›
‹td class="sub" id="pageNum"›1/1‹/td›
‹td width="12" class="arrow" onclick="chPage(+1);" align="left"›
‹b››‹/b›‹/td›
‹td width="12" class="arrow" onclick="chPage(+999);" align="left"›
»‹/td›
‹/tr›
‹/table›
‹/div›

A Change page function will Parse the content of items to that page.

function chPage(off)
{
try
{

if (System.Gadget.Settings.read("listview"))
{
if (rssItems.length < 100)
{
myval = Math.ceil (rssItems.length/5);
}
else
{myval = 20;}
}
else
{
if (rssItems.length < 100)
{
myval = rssItems.length;
}
else
{myval = 100;}
}


if (off === -999)
{
off = myval-page;
}
if (off === 999)
{
off = myval-page-1;
}

try
{
if (((page + off) > -1) && ((page + off) < myval))
{
page = page + off;
parseRSS(page);
} else if ((page + off) === myval)
{
page = 0;
parseRSS(page);
} else if ((page + off) === 0)
{
page = myval;
parseRSS(page);
}
}
catch (err)
{
}
}
catch (err)
{
}
}

Make you own Favorite Videos List


Screenshot - addtofavorite.jpg


Now with each video you will see a plus icon in the bottom, so you can add the video to your favorites list.


A portion of the code behind

function addtoFavorites(sGuid)
{

var variableName = "userprofile";
var mytext ="Testing";

try
{
var variableName = "userprofile";
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.load(System.Environment.getEnvironmentVariable
(variableName) + "\\Videos\\SoapBoxFavorites.xml");
xmlObj=xmlDoc.documentElement;
rssAddItems = xmlObj.getElementsByTagName("channel");
rssAddedItems= xmlObj.getElementsByTagName("item");
var varAlreadythere = false;


if (rssItems[i].getElementsByTagName("guid")[0]
.firstChild.nodeValue == sGuid)
{
if (!varAlreadythere)
{
rssAddItems[0].appendChild(rssItems[i]);
}
break;
}

xmlDoc.save(System.Environment.getEnvironmentVariable
(variableName) + "\\Videos\\SoapBoxFavorites.xml");

Screenshot - addedinfavorite.jpg


When you click on the "Add to Favorite" icon it will add the XML feed information for that video from the online feed to a local XML file.


%userprofile%/Video/soapboxfavorites.xml


Here are the new settings to view your favorite videos. Select "My Favorite Videos" to see the bookmarked videos.


Screenshot - newsetting.jpg


If any "My Favorites" are selected, the gadget will read from the local XML file.


A portion of the code behind

 if (System.Gadget.Settings.read("feedText").indexOf("My favorite") >=0)
{
try
{
var variableName = "userprofile";
var xmlDoc = new ActiveXObject("Msxml2.DOMDocument");
xmlDoc.load(System.Environment.getEnvironmentVariable(variableName)
+ "\\Videos\\SoapBoxFavorites.xml");
rssXML=xmlDoc;
parseRSS();
}

By default, the "My Favorite Video" list will show nine videos from the "Current Featured Feed".


Create the XML file for the first time from "Current Featured Feed".


A portion of the code behind

 var variableName = "userprofile"
var SoapboxFavoritesPath = System.Environment.getEnvironmentVariable
(variableName) + "\\Videos\\SoapBoxFavorites.xml";

rssObj = new ActiveXObject("Msxml2.XMLHTTP");
rssObj.open("GET",System.Gadget.Settings.read("videoFeed"),true);
rssObj.onreadystatechange = function()
{
if (rssObj.readyState === 4)
{
if (rssObj.status === 200)
{
try
{
System.Shell.itemFromPath(SoapboxFavoritesPath);
return;
}
catch (notFound)
{
var stream = new ActiveXObject("ADODB.Stream");
stream.Type = 1;
stream.Open();
stream.Write(rssObj.responseBody);
stream.SaveToFile(SoapboxFavoritesPath, 2);
stream.Close;
stream = null;
}

You can also remove the video from the Video list. Select My Favorite video, and then you can see the "Remove icon" after each video in the gadget.


Screenshot - removefromfavorites.jpg


The code behind Remove

function removefromFavorites(sGuid)
{

var variableName = "userprofile";
var mytext ="Testing";

xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.load(System.Environment.getEnvironmentVariable
(variableName) + "\\Videos\\SoapBoxFavorites.xml");
xmlObj=xmlDoc.documentElement;
rssRemoveItems = xmlObj.getElementsByTagName("item");

if (rssRemoveItems[i].getElementsByTagName("guid")
[0].firstChild.nodeValue==sGuid)
{
var lastNode=rssRemoveItems[i];
var delNode=xmlObj.firstChild.removeChild(lastNode);
xmlDoc.save(System.Environment.getEnvironmentVariable
(variableName) + "\\Videos\\SoapBoxFavorites.xml");
mytext = "Deleted and Saved";
break;
}
}

And we are done.


Tips in general



In Action


Soapbox Gadget in Action : Size: 400 X 360 in flash player


Screenshot - FLASH.jpg


References



  1. Innovate with Gadgets
  2. Creating a Simple Gadget: A Beginner's Guide
  3. Soapbox at MSN Videos
  4. MicrosoftGadgets.com sidebar development
  5. Windows setinterval
  6. Design concepts and usage pattern

Article History



And thanks

For coming so far! I hope you find this useful, and give me a comment if you do and take care.

Labels: , , ,