Cracking the Silverlight Streaming Direct Content Access Code

4/28/2008 4:09:00 PM

I'm working on my presentation for the Iowa Code Camp, and I once again bounced into something that has really frustrated me with Silverlight and Silverlight Streaming - getting access to SLS content directly, without having to go through one of the existing SLS options.  Basically, if I host mycoolvideo.wmv to SLS, I want to be able to get an URL to that media file and pass it to my Silverlight media element on my HTML page and have it play the video.

As it stands "out of the box", SLS provides four basic options for getting to your Silverlight application.  The first three assume you have loaded an actual Silverlight application (manifest, XAML, script, etc.) to SLS.  The first one, and the one I have used most often, is to place an iFrame in your web page.  Something along the lines of:

<iframe src="http://silverlight.services.live.com/invoke/29367/AlmostLive1/iframe.html" mce_src="http://silverlight.services.live.com/invoke/29367/AlmostLive1/iframe.html" scrolling="no" frameborder="0" style="width:500px; height:400px"></iframe>

Option 2 is using a Live Control.  I won't go over the exact steps here, but its a three step process that ends up putting a remotely hosted SLS player on your page.  The third option is to provide a download of  your application such as http://silverlight.services.live.com/invoke/29367/AlmostLive1/application.html.  This is really intended for a kind of "desktop install" sort of experience.

Of these three "application" options, iFrame is the easiest but it forces you to upload an entire Silverlight app for each video. The downside is that if you want to have a themed media player experience on your web site, you are at the mercy of the style the media player has in the uploaded application.  Sure, I could make sure I upload the same player that fits into my overall site design over and over again, but what happens if I want to change my design.  I would have to go back and update all of those applications in SLS.  A royal pain.

Recently, SLS added a fourth option.  This option allows you to upload only the media you want to host in SLS (currently limited to WMV only), and not an entire SLS application.  This, at first, sounded like the perfect solution until I realized that the only way to play that media was to use another iFrame solution, such as

<iframe src="http://silverlight.services.live.com/invoke/29367/DSMHHH/iframe.html" scrolling="no" frameborder="0" style="width:500px; height:375px"></iframe>

This iFrame will then play your video using a standard media player design as shown below:

slickthoughtproductions

Again, this is not what I want since I have no control over the style of the player.  What I really want, and based on some web searches it seems like quite a few others do as well, is a way to design my own Silverlight player, host that player directly into my web page, and then reference only media that is stored in the SLS cloud.  Essentially, I want to write:

mediaElement.Source = "streaming:/29367/DSMHHH/DSM-HHH.wmv

Now I can change the look and feel of my site without having to update SLS but I get all the other advantages of hosting my media in SLS. 

There is a partial solution to this problem already.  I will let you, dear reader, read all the gory details on your own.  Laurent Kempé has a very nice article on how to get this solution up and running and it actually served as the inspiration for the solution I have come up with.  The basic process is replacing the standard silverlight.js file that most Silverlight tools provide and use http://agappdom.net/h/silverlight.js.  This new script file provides a javascript function called createObjectEx that you use in place of the normal createObject when instantiating your Silverlight application on a given web page.  The big thing that createObjectEx provides is a way to pass in initialization parameters, most importantly one in the format of "streaming:/<acctid>/<appname>/<filename>".  This is resolved to a valid url (the actual media file url changes over time, preventing you from storing content urls and using them later) for your content and is then assigned your newly created Silverlight media element.  This works ok, but it's not as nice as being able to just ask SLS for a valid url and then assigning that url to your media element since it requires a new media element be created with each all to createObjectEx.

In what is one of the biggest oversights I can imagine, it seems that neither the Silverlight team or the SLS team thought anyone would want to be able to just get a streaming url for a piece of content directly.  The most obvious scenario would look something like 1) launch my Silverlight app, 2) query SLS for a list of applications and associated media files, 3) display that list to the user in my Silverlight control, 4) user picks an application, 5) ask SLS for a current http address for a given media file from that application, 6) assign that url to the Silverlight media player for playback.  Of course, even simpler would have been for the Silverlight media element to accept a Source property in the streaming:/<acctid>/<appname>/<filename> format and have it resolve a good url on its own.  Alas, neither option was provided.  Since SLS came out a bit after Silverlight 1.0 (IIRC), I can forgive the lack of support in the SL 1.0 media element.  I have no idea what the SLS guys were thinking.  Even more surprising, is that as of Silverlight 2 Beta 1, the media element has not been updated to support this scenario either.  Nor can I find any official statements saying it will be since folks have already started to repeat the same question but now with SL 2 as the player.

I've been thinking of and tinkering with various hacks around this problem for a while but none of them worked they way I wanted or provided the simplicity I wanted.  I had really assumed that Silverlight 2 Beta 1 would solve this problem, and that was my thinking when I submitted the session abstract for the Code Camp.  Once I sat down to start building my content, it was very disappointing to find that I was SOL as far as getting the desired functionality I was expecting from SL 2.  Needless to say, it was a) very disappointing, b) the risk you take by assuming a feature will be in a Beta, and c) meant I was probably going to have to finally settle on some nasty, clunky hack that would belittle me in the eyes of the Code Camp attendees.  Certainly not what would be expected of the world's only self-proclaimed .NET Sex Symbol!

I figured I would give the ol' Internet one more surf to see if there was anything new on the topic before I got my hack on.  That is when I stumbled on Laurent's article.  The big "AHA" moment came when Laurent talked about how the createObjectEx actually returns a piece of javascript under the covers.  The returned javascript looks like this:

SLStreaming._StartApp("bl2", null
, {}, []
, [http://msbluelight-0.agappdom.net/e1/d/4065/8.w/63325188000/0.UZcUXMfJgIK0I0HcP-SQGzhvvVE/livewriterdemo.wmv]);

Well, well, well, there is a current and valid URL to the media I so desperately crave right there!  That made the light bulb click on.  You never see that bit of code when you check out your browser's source since its all handled under the covers but the foundation was there to get at the url I wanted.  I started digging into the silverlight.js file and figured out how createObjectEx went about it's business. After that, it became pretty easy to figure out what needed to be done.

The heart of the solution starts with recognizing that createObjectEx submits the streaming :/ parameter to services hosted at http://silverlight.services.live.com/invoke.  The GET request that returns the piece of javascript looks like this:

http://silverlight.services.live.com/invoke/local/starth.js?id=bl2&u=1209235530389&p0=/29367/DSMHHH/DSM-HHH.wmv

Let's break this down real quick so we know what we are looking at:

  • id=bl2 - To be honest, I am not really sure what this is.  The initial value will always be bl2 and in the silveright.js implementation the only part of the id that may change is the integer - bl is a constant.  Leaving it as bl2 doesn't seem to break anything.
  • u=1209235530389  -  u is equal to the total number of millseconds since the Unix Epoch (Jan 1, 1970).  I assume this is a way to randomize the generated URL across various servers for load balancing and also to give some kind of lifetime to the request.  and it lets you use the term Unix Epoch with friends and family.
  • p0=/29367/DSMHHH/DSM-HHH.wmv - this is the path to the content I am looking for.  Basically, my account ID (29367), application name (DSMHHH), and the media file I am looking for (DSM-HHH.wmv)

So basically, all I had to do was construct an xmlHttpRequest (javascript) or a WebClient (.NET) and pass in the appropriate URL, take the snippet of javascript that is returned to me, and RegEx it looking for my content URL.

javascript C#
function GetMediaUrl(AcctID, AppName,FileName)
    {
        var baseAddress = "http://silverlight.services.live.com/invoke/local/";
        var url = baseAddress +
                  "starth.js?id=bl2" +
                  "&u=" + (new Date().valueOf()) +
                  "&p0=/" + AcctID + "/" + AppName + "/" + FileName;
       
        if (window.XMLHttpRequest)
        {
           var oReq = new XMLHttpRequest();
           oReq.open("GET", url,false);
           oReq.send();
        }

        var r, re;                    
        re = new RegExp("http:.+\.wmv","i"); 
        r = oReq.responseText.match(re);

}

WebClient client = new WebClient();

client.BaseAddress = "http://silverlight.services.live.com/invoke/local/";

TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
double timestamp = t.TotalMilliseconds;
string milliseconds = timestamp.ToString().Split(new char[] { '.' })[0];
string myUri = string.Format("starth.js?id=bl2&u={0}&p0=/{1}/{2}/{3}", milliseconds, acctID, appID, mediaFile);
string response = client.DownloadString(myUri);

Match mediaMatch = Regex.Match(response, @"http:.+\.wmv");
string mediaUrl = mediaMatch.Value;

You could certainly add more error checking code, make the javascript async, and other improvements, but these examples get the point across.  Of course, neither of these examples will work for a hill of beans if you try and use them in your web page or Silverlight app!!!  Why?  Cross-site scripting limitations, my friends.  Tim Heuer posted about the fact the SLS does not have a cross domain policy file (that policy file would let the above code work like a champ) and wonders why anyone would want it?  Well, Tim (who's greatness and tech wizardary far surpasses my own), I want to get my media stream!!! ;-)  Without that SLS cross domain policy file, we are forced to take our solution to the server and essentially remote proxy calls from our client to the SLS service.  Not ideal and if you don't have the ability to run your own code on your host server then this solution won't work.  All the more reason to harp on the SLS folks to get a cross domain policy file in place!

So with that in mind, I will show you what I banged out (banged being the operative word) to be that remote server proxy.  Disclaimer:  This code is totally whipped out on the fly.  It absolutely is spaghetti code.  It's a way for me to show you how to get the job done.  You could (and probably should) come up with something cleaner, more intuitive, <pick your better approach description and insert here>.  Probably the first thing I would change would be to put the output into some kind of syndication format like RSS or ATOM.  You could then publish your "TV channel" and others could consume it and play it pretty easily.  The only thing I haven't figured out, and it could certainly be because I am not a RSS/ATOM guru, is how to account for that fact that one "item" (or in our case SLS application) can have more than one media file.  It is different from a podcast RSS feed where one item equals one enclosure equals one audio file.  I'll let some one else figure it out.

Below are the two functions that are the heart and soul of my solution. Note, you can get all of the code I used to build this sample here.

string ExecuteSLSRequest(string url, string acctID, string password)

    {
        if (acctID == null || password == null)
            throw new SecurityException("Missing AccountID and/or Password");

        HttpWebRequest httpRequest =
                (HttpWebRequest)HttpWebRequest.Create(SLSSecureRoot + acctID + "/" + url);
        byte[] userPass = Encoding.Default.GetBytes(acctID + ":" + password);
        string basic = "Basic " + Convert.ToBase64String(userPass);
        httpRequest.Headers["Authorization"] = basic;

        HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
        Stream httpStream = httpResponse.GetResponseStream();
        StreamReader streamReader = new StreamReader(httpStream);

        return streamReader.ReadToEnd();
    }

and

string GetMediaStream(string appname, string filename)

{
    TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
    double timestamp = t.TotalMilliseconds;
    string milliseconds = timestamp.ToString().Split(new char[] { '.' })[0];

    string targetUri = string.Format("invoke/local/starth.js?id=bl2&u={0}&p0=/{1}/{2}/{3}", milliseconds, SilverlightStreaming.Account.AccountID, appname, filename);

    HttpWebRequest httpRequest =
            (HttpWebRequest)HttpWebRequest.Create(SLSMediaServiceRoot + targetUri);

    HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
    Stream responseStream = httpResponse.GetResponseStream();
    StreamReader streamReader = new StreamReader(responseStream);

    string scriptResponse = streamReader.ReadToEnd();

    Match mediaMatch = Regex.Match(scriptResponse, @"http:.+\.wmv");
    string mediaUrl = mediaMatch.Value;
    return mediaUrl;
}

ExcecuteSLSRequest is used to do secure communication with SLS.  This provides access to the application list and the file list per application.  It is driven by the url you pass in, of which you will only be passing in two - empty (to get a list of applications) or application name, which will get you a list of all files in the application.  There is entire SLS REST API for managing your application, but I don't care about that right now. I just exposed an Applications property off of my helper class to fetch all of the applications and their associated media files.  The property uses ExecuteSLSRequest to build a list of applications.  GetMediaStream takes an appname and a filename and talks to SLS to find a current, valid http address for the given media file.  Pretty easy and useful in it's own right. I'm already thinking about how you could use this to redirect clients to download content directly from SLS - podcasts for example.  Can you say free streaming AND free storage!!!  I think you can.

As a simple example of using my helper class, take a look at SLSApplications.ashx below:

public class SLSApplicationsHandler : IHttpHandler {
    const double expireTime = 1;  // expressed in hours
    public void ProcessRequest (HttpContext context) {
        XElement slsXml = null;
        if (context.Cache["sls"] == null)
        {  
            SilverlightStreaming.Account.AccountID = "<yourAccountID>";
            SilverlightStreaming.Account.Password = "<yourAccountKey";

            Dictionary<string, SLSApplication> apps = SilverlightStreaming.Applications;

            slsXml = new XElement("ContentList",
                    from a in apps.Values
                    select new XElement("Content",
                        new XAttribute("Name", a.Name),
                            from m in a.Media
                            select new XElement("File",
                                new XAttribute("Name", m.Name),
                                new XAttribute("Size", m.Size))));

            context.Cache.Add("sls",
                slsXml,
                null,
                DateTime.Now.AddHours(expireTime),
                System.Web.Caching.Cache.NoSlidingExpiration,
                System.Web.Caching.CacheItemPriority.Normal,
                null);

        }
        else
            slsXml = (XElement)context.Cache["sls"];

        context.Response.ContentType = "text/xml";
        context.Response.Write(slsXml.ToString());
    }
    public bool IsReusable {
        get {
            return false;
        }
    }
}

It uses my SilverlightStreaming helper class to get the apps and files as XML from SLS and then uses LINQ to shape that data into the format I want.  I use the ASP.NET cache to store the result since it is unlike to change all that often.  The results are returned as XML like this:

xml

I have another file, SLSMediaStream.ashx, that returns a valid http url when you pass in a query string containing the appname and media file you want to access - http://somehost.com/SLSMediaStream.ashx?appname=xyz&filename=abc.wmv. The result is returned as a simple string (not XML).  The code is shown below:

public class SLSMediaStream : IHttpHandler {


    public void ProcessRequest (HttpContext context) {


        string app = context.Request.QueryString["appname"];
        string file = context.Request.QueryString["filename"];


        string httpStream = SilverlightStreaming.GetMediaStream(app,file);
        context.Response.ContentType = "text/plain";
        context.Response.Write(httpStream);


    }


    public bool IsReusable {
        get {
            return false;
        }
    }
}

You can check out a live version of SLSApplicaitons.ashx here (returns all of my Silverlight apps and files) and a Silverlight 2 Beta 1 "video player" here (this one is super lame - working on a better one for SlickthoughtTV and DeveloperMinute.com.  You could also do it in Silverlight 1.0 but I like SL 2 much better <grin/>).  I'm not a javascript guy at all so I didn't put together a javascript web page to work with the "API" and since you need some kind of play back capability anyway, I didn't see the point. You can get the source here.

Hope you found it useful.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Number Five is Alive! Well, Sort of...

4/28/2008 8:53:00 AM

clip_image001Yes, an old school movie reference for those of you old enough to remember that movie. No hints on the title - go figure it out for yourself if you don't already know. Let me just say, it was one of the last movies where Ally Sheedy was still "all that". What does that have to do with anything? Well, Microsoft has launched RoboChamps (www.robochamps.com), a simulated robotics league that is open to academics, hobbyists and developers from around the world.  From the official announcement...

RoboChamps is built on top of the Microsoft Robotics Developer Studio(MSRDS) 2008 CTP, and uses that product’s robust, physics enabled simulation environment to remove the barriers of entry that exist for many today. This simulated league provides individuals with immersive 3-d environments, simulated versions of robots, and compelling scenario-specific challenges where they can win real robots.  Environments range from a maze to the surface of the planet Mars to downtown driving to robot rescue to soccer.  The top four finalists will be flown to PDC, where the competition moves to the real world and participants apply their code to real robots.

It looks really cool and fun.  Even the web site is cool.  There are six different types of robot challenges you can choose from - navigate a maze, build a Mars Rover-style robot to explore alien worlds, drive through an urban setting, build a rescue bot, a bot that wrestles, or head-to-head tournament action.  Work a check out!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Slick Thoughts

New Spaghetti Code Podcast Available - Jon Stonecash on ORM

4/28/2008 4:51:17 AM

In this edition of Spaghetti Code, I sit down with Jon Stonecash and talk about Object Relational Mapping.  We start off with an overview for those new to ORM, and then start to look at the various solutions, challenges, and ways to get started.  We also chat about Microsoft's forthcoming Entity Framework and get Jon's opinion on how that effort is looking so far and what still remains to be done.  All in all a very informative interview - thanks Jon!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Headlines | SpaghettiCode

Powered by BlogEngine.NET 1.3.1.0
Theme by Mads Kristensen

About the author

Jeff Brand Jeff Brand

This is the personal web site of Jeff Brand, self-proclaimed .NET Sex Symbol and All-Around Good guy. Content from my presentations, blog, and links to other useful .NET information can all be found here.

E-mail me Send mail


Calendar

<<  April 2008  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar

Twitter Updates

XBOX
Live

Recent comments

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008

Sign in