Good Morning!
My name is Clemens Vasters and I am Community Relations Program Manager for the Windows Communication Foundation at Microsoft Corporation.
[Boy, writing that feels a little strange. I guess I'll get used to it. ]
In the upcoming week I will be cleaning up the categories on my blog. So if you've subscribed to any of the category feeds directly, some of them may stop working.
As of tomorrow, the "official" blog address will be http://friends.newtelligence.net/clemensv as I won't be part of the newtelligence "staff" any longer. However, all existing subscriptions to the main RSS and Atom feeds will continue to work, because this blog isn't really going anywhere. I will see what it takes to get a mirror blog on blogs.msdn.comand whether I can use the dasBlog cross-blogging feature to push select content there.
Eventually, and once I am done reshuffling, I will explain the new category structure and actually encourage you to subscribe to select categories, because my blogging habits will change quite a bit in my new role at Microsoft. For instance, you'll see a lot more German here (which you might want to tune out of if you are not interested), I'll start a link-blog category and I will introduce some other categories that are not at all about technology.
Get the Goods (Yes, that page will get a cleanup once I've got the docs done)
There are roughly 24 hours left before I will switch jobs and become a "Microsoftie". My parting gift to the company I co-founded is this:
newtellivision is a framework and application for accessing live streaming television remotely. The use-case I had in mind building this is quite simple and is closely related to my current (and the new) job and the perspective of moving to the U.S. some time later this year: I want access to my local, German TV channels whenever I am traveling and I also want access to those channels when I've moved to Seattle. That's most important for two types of programs: sports and news. I care a lot more about Bundesliga football than for Major League baseball.
Consequently, newtellivision is focused on one thing: streaming video. And it comes with two user experiences that are a good choice for the above use-cases:
|
Windows Media Player
The Windows Media Player (WMP) experience (for WMP 9 and above), is the "television to go" version. It works from any Windows PC that has Windows Media Player 9/10 installed and that can connect via any network to a newtellivision server. There is no additional software that needs to be installed on the client. All interaction with the newtellivision server is hosted inside Media Player.
Features of the Windows Media Player experience in a nutshell:
Access to live streaming television, your video library, live web streams and video blogs from single user interface.
"Speed bar" across the top of the side panel allows manual adjustment of the backend streaming encoder settings to the available bandwidth with a single click.
Channels panel provides hover-over tooltips showing guide data for all channels for which guide data or content lists are available.
Guide data for current channel allow single-click scheduling of recordings if the newtelligence plug-in provider of the selected channel supports recording.
Direct playback access to shows that were recorded on the current channel.
|


|



|
|
Windows XP Media Center Edition
The Windows XP Media Center Edition (MCE) experience is built for the "home TV at home" experience. If you move to another country and your parents or your friends host the newtellivision server for you, you can access live streamed TV programs from far-far away and your recordings right within the usual MCE experience with your remote control. And from the very same experience you have access to video blogs and web streams such as DW TV, NASA TV and thousands of other live streaming TV sources, complete with support for XMLTV guide data.
Features of the Windows XP Media Center experience in a nutshell:
Remote control navigable, dynamic schedule grid for scheduling data modeled after the familiar Windows Media Center "Live TV" experience. Of course, the grid adjusts automatically as time progresses and shows you whether shows are scheduled for recording.
Detail information about individual upcoming shows including one-click scheduling of recordings.
Channel detail view with one click access to video blog content (the example on the right shows Microsoft's Channel 9) and recorded shows.
For the Windows Media Center experience you need a Windows Media Center PC as client.
|


|

|
|
The newtellivision Server and Framework
The newtellivision Server is built on the Windows Communication Foundation (WCF). The server can run either as an interactive application or as a Windows Service and the configuration wizard makes it very easy to switch between these modes. The server is extensible and comes with two plug-in providers that utilize the newtellivision Framework to provide access to live streaming video.
The "Beyond TV" provider encapsulates the SnapStream Media Beyond TV server engine and provides access to the recorder features, live TV streaming, the Beyond TV library and the electronic program guide (EPG).
The "Web Streams" provider provides access to public web streams and video blogs and has direct support for integrating EPG information in XMLTV format.
The newtellivision Framework and the Server application are available in source code for experimentation, improvement and, of course, for customizing the existing providers or for writing entirely new providers.
The server renders the list of available channels in OPML format and the electronic program guide data for each channel in RSS 2.0 format, each with the minimal set of extensions required to add vital information that is otherwise not representable in the core standard. Video playlists are rendered in Microsoft ASX 3.0 media metadata format. |


|


|
Requirements
For running newtellivision you need the following minimum server configuration:
Windows XP or Windows Server 2003 with the .NET Framework 2.0 and the WinFX 3.0 Runtime Components (Beta 2 CTP)
Furthermore, for the web stream provider:
An internet connection with sufficient bandwidth for viewing live video streams
Optionally, XMLTV for downloading guide data.
Optionally, a source for picking web stream links.
Furthermore, for the Beyond TV provider:
Beyond TV 4 by SnapStream Media (The demo version should work -- until expiration of course)
A Beyond TV compatible software encoder card for Windows Media streaming.
Beyond TV may run on a separate machine from the newtellivision Server machine. For this setup you MUST have a valid Beyond TV Link license (demo is ok while it lasts).
For accessing streams remotely and even though this is not enforced, you need a valid Beyond TV Link license as a CAL for each machine concurrently accessing the web streams.
Bandwidth, bandwidth, bandwidth -- upstream. I have 3000/512 DSL connection and I would still like it a bit quicker. It turns out that a sustained bitrate of ~400KBps yields a video quality that is good enough to watch sports remotely, but don't expect brilliant detail resolution. ~128KBps is plenty to watch news.
The Catch I
newtellivision is released in binary and source code form for any non-commercial purpose. In short, this is a "extend it for fun" application, but you can't derive any direct commercial gain from the application or any of its parts or from the operation of the application. If you'd like to use the application commercially, you must contact newtelligence to learn about your options. Commercial use expressly does not include teaching, speaking at conferences, writing books, etc.
The Catch II
This application is big, runs on beta software and is tested in only one or two server configurations to speak of. I have no idea whether anybody but me will get it to run. I've built it for myself only. It might burn your PC into a small pile of ash. I don't know what will happen. The app looks pretty polished, but evil lurks within. Consider it "alpha quality".
The Catch III
The server is, at this point, designed(!) not to scale to more than a single concurrent user. You can theoretically consume the streams from two or three clients concurrently, but there is only support for a single tuner and hence everyone has to watch the same live show. If someone switches the channel, all others view the channel too. The WMP experience is, in this case, nice enough to adjust the schedule panel to reflect the currently watched channel.
Future Direction
This is a demo app. I've written it just as that and for my personal use purposes. The reason for this public drop at this time is to avoid any troubles between me, newtelligence and Microsoft as I am making the transition between the firms. The license allows me to continue working on it privately, while all the commercialization rights up to this point rest with newtelligence. That said, I am not out of ideas. If you love writing (WCF) applications or are interested in the whole streaming TV story and you can live with the non-commercial constraint, I'd love to get you on board as an active contributor to the cause. Concretely, I'd be interested in investigating whether a provider for PPLive is feasible, whether the newtellivision server can tap the MCE streaming capabilities and whether a standalone provider can be built. See it as my next thing after DasBlog. It'll be a killer community project.
Availability
The code and the installable binaries sit here. Get them.
Meanwhile you might want to get (or go dig out) an old, used WinTV PCI card (those go on eBay for 10-15 US$) or equivalent (see SnapStream's site analog/without hardware encoding). One of my test configurations used a 1996 (!) WinTV PCI and it worked. Now my streaming source is a slightly newer Hauppauge WinTV card from 1999 that I bought on eBay.de for a whopping €10.70 a few months ago. Once you have that, it's useful to get Beyond TV configured and up and running for web streaming with the Beyond TV Web Admin interface (accessible with the "Live TV" link on the "Tasks" pane on the left of the web admin tool; http://localhost:8129 once you have installed Beyond TV). Once you get video there, you are good to go.
In the 5 years I've been "newtelligent" I have never been *really* late to an event. Not a single time. Admittedly,
- I've been an hour late to a talk in Kuala Lumpur because the schedule I received was wrong,
- I arrived in Vienna "just in time" and still intoxicated after missing the planned 05:20 (AM!) connection from Istanbul after a serious party night and being able to rebook to a later flight after rushing to the airport and still surrounded by a complete haze and only got to the venue about 5 minutes too late because the taxi driver didn't know where to go (I can't remember any content of the first session that day, but the attendees loved it),
- I got stuck in Heathrow one night due to bad weather on a connection to Dublin, but with an early enough connection to make it to the event in the morning.
And now ... on the very last event I am doing for newtelligence I got stranded in a hotel room at the Munich Airport and can't get to Istanbul for a workshop that's supposed to start at 9:30AM tomorrow. Istanbul is snowed in. Next opportunity to get out there is at 11AM tomorrow. Hope that works.
[Update: It did work, even though with an hour delay. In Istanbul, the chaos continued. 15-25 cm of snow per day in a city where "winter tires" are a rather unknown phenomenon are a bit problematic. I love Istanbul, but I am very happy to be back home this time.]
|
private void exitMenuItem Click(object sender, EventArgs
e)
{
if (runtime != null
&& runtime.Running)
{
ThreadPool.QueueUserWorkItem(delegate(object
obj)
{
runtime.Stop();
Invoke(new ThreadStart(delegate()
{
this.Close();
}));
});
}
else
{
this.Close();
}
}
|
Just caught myself coding this up. The shown method is a Windows Forms event
handler for a menu item. The task at hand is to check a local variable and if that’s
set to a specific value, to switch to a different thread and perform an action (that
particular job can’t be done on the Windows Forms STA thread), and to
close the form back on the main STA thread once that’s done. I colored
the executing threads; yellow is the Windows Forms STA thread, blue is an
arbitrary pool thread. Works brilliantly. Sick, eh?
[Mind that I am using ThreadStart just because it’s a
convenient void target(void) delegate]
Everyone seems to be giving some tech predictions for 2006 on their blog. Here's mine:
The DRM opposition will start turning DRM against the music and film industry and embrace DRM to create a distributed file-sharing protocol and applications that implements "lending between friends" and ensures that within a group of users, only as many copies of digital works can be played concurrently as the number of original, legally acquired media that have been contributed into a pool. The protocol will ensure that the media integrity is preserved insofar that no two people can, say, play different songs from the same album concurrently unless there are enough copies of the same album in the pool. Media players supporting this protocol will eventually be clever enough to prioritize and shuffle playlists in a way that the fewest possible media are required in the pool.
Since "lending" will later be found to be still too problematic from a legal standpoint, the physical media constituting the media pool will be put into a network of escrow services and acquiring a temporary DRM license to play a particular music album or video will automatically result in a "one-cent" sale transaction and transfer of ownership rights of the physical media for the period while the license is valid and therefore result in a legal digital copy of an legally owned media to be played.
In short: It'll remain interesting how the whole DRM situation develops. To me it seems consequential that the content consumer side will take a page out of the content provider side's book and use the same technology arsenal to try to achieve exactly opposite goals. I think there's a resonable chance that DRM will at least backfire for all digital media that's already published and out there. And that's quite a lot.
My stance on what's appropriate and inappropriate with respect to DRM is "undecided", even though I deplore Sony's rootkit DRM trickery. At the same time there must be a reasonable business model for entertainment media. Someone just has to find one that is sustainable and works. Luckily not my job.
Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8 If you are in a hurry and all you want is the code, scroll down to the bottom. ;) And for the few who are still reading: This is the 9th and final part of this series, which turned out to be a little bigger than planned. And if you read all parts up to here, you have meanwhile figured out that the extensions that I have presented here are not only about REST and POX but primarily a demonstration of how customizable Indigo is for your own needs. Indigo – Indigo was really a cool code-name. Just like many folks on the Indigo WCF team it’s a bit difficult for me to trade it for a clunky moniker like WCF, and it seems that this somewhat reflects public opinion. Much less am I inclined to really spell out “Windows Communication Foundation” in presentations, because that doesn’t exactly roll off the tongue like a poem, does it? But, hey, there’s always the namespace name and that doesn’t suck. “Service Model” is what everyone will be using in code. We don’t need three-letter acronyms, or do we? But I digress. I’ve explained a complete set of extensions that outfit the service model with the ability to receive and respond to HTTP requests with arbitrary payloads and without SOAP envelopes and do so by dispatching to request handlers (method) by matching the request URI and the HTTP method against metadata that we stick on the methods using attributes. And now I’ll show you the “Hello World!” for the extensions and about the simplest thing I can think of is a web server In fact, you already have the complete configuration file for this sample; I showed you that in Part 8. Since things like “Hello World!” are supposed to be simple and it’s ok not to make that a full coverage test case, we start with the following, very plain contract:
|
[ServiceContract, HttpMethodOperationSelector] interface IMyWebServer { [OperationContract, HttpMethod("GET", UriSuffix = "/*")] Message Get(Message msg); [OperationContract(Action = "*")] Message UnknownMessage(Message msg); } |
By now that should not need much explanation. The Get() method receives all HTTP GET requests on the URI suffix “/*” whereby “*” is a wildcard. In other words, all GET requests go to that method. The implementation of the service is pretty simple. I am implementing it as a singleton service that is constructed passing a directory name (path). The Get() implementation gets the URI of the incoming request from the To header of the message’s Headers collection. (Note that the HTTP transport maps the incoming request’s absolute URI to that header once the encoder has constructed the message.)
|
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class MyWebServer : IMyWebServer { string directory;
public MyWebServer(string directory) { this.directory = directory; }
public Message Get(Message msg) { string requestPath = msg.Headers.To.AbsolutePath; // get the path if (requestPath.Length > 0 && requestPath[0] == '/') { // if the path is just the "/", append "default.htm" as the // default page. if (requestPath.Substring(1).Length == 0) { requestPath = "/default.htm"; } // otherwise check whether a file by the requested name exists string filePath = Path.Combine(directory, requestPath.Substring(1).Replace('/', '\\')); if (File.Exists(filePath)) { // and return a file message return PoxMessages.CreateFileReplyMessage(filePath, PoxMessages.ReplyOptions.None); } } // if all fails, send a 404 return PoxMessages.CreateErrorMessage(HttpStatusCode.NotFound); }
public Message UnknownMessage(Message msg) { return PoxMessages.CreateErrorMessage(HttpStatusCode.NotImplemented); } } |
Once we’ve done a few checks on the URIs path portion, we construct a file path from the base directory and the URI path, check whether such a file exists, and if it does we create a message for that file and return it. If we can’t find the resulting file name, we construct a 404 “not found” error message. The UnknownMessage() method receives all requests with HTTP methods other than GET and appropriately returns a 501 “not implemented” message. Web server done. Well, ok. The actual messages are constructed in a helper class PoxMessages that aids in constructing the most common reply messages. The class is part of the extension assembly code you can download and therefore I just quote the relevant methods that are used above. We’ll start with PoxMessages.CreateErrorMessage(), because that its very simple:
|
public static Message CreateErrorMessage(System.Net.HttpStatusCode code) { Message reply = Message.CreateMessage("urn:reply"); HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty(); responseProperty.StatusCode = code; reply.Properties.Add(HttpResponseMessageProperty.Name, responseProperty); return reply; } |
We create a plain, empty service model Message, create an HttpResponseMessageProperty instance, set the StatusCode to the status code we want and add the property to the message. Return, done. The PoxMessages.CreateFileReplyMessage() method is a bit more complex, because it, well, involves opening files. I am not showing you the exact overload that’s used in the above example but the one that’s being delegated to:
|
public static Message CreateFileReplyMessage(string fileName, long rangeOffset, long rangeLength, ReplyOptions options) { string contentType = GetContentTypeFromFileName(fileName); try { FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); if (rangeOffset != -1 && rangeLength != -1) { SegmentStream segmentStream = new SegmentStream(fileStream, rangeOffset, rangeLength, true); return PoxMessages.CreateRawReplyMessage(segmentStream, contentType, rangeOffset, rangeLength, fileStream.Length, Path.GetFileName(fileName), options); } else { return PoxMessages.CreateRawReplyMessage(fileStream, contentType, Path.GetFileName(fileName), options); } } catch { return PoxMessages.CreateNotFoundMessage(); } } |
The implementation will first make a guess for the file’s content-type based on the file-name, which is a simple registry lookup with a fallback to application/octet-stream. Then it’ll try to open the file using a FileStream object. If that works – ignoring the special case with rangeOffset/rangeLength being set – we delegate to the CreateRawReplyMessage() method:
|
public static Message CreateRawReplyMessage(Stream stm, string contentType, long rangeOffset, long rangeLength, long totalLength, string streamName, ReplyOptions options) { PoxStreamedMessage reply = new PoxStreamedMessage(stm, 16384); HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty(); if ((options & ReplyOptions.ContentDisposition) == ReplyOptions.ContentDisposition) { responseProperty.Headers.Add("Content-Disposition", String.Format("Content-Disposition: attachment; filename=\"{0}\"", streamName)); } responseProperty.Headers.Add("Content-Type", contentType); if (rangeOffset != -1 && rangeLength != -1) { responseProperty.StatusCode = System.Net.HttpStatusCode.PartialContent; responseProperty.Headers.Add("Content-Range", String.Format("bytes {0}-{1}/{2}",rangeOffset,rangeOffset+rangeLength,totalLength)); responseProperty.Headers.Add("Content-Length", rangeLength.ToString()); } else { if ((options & ReplyOptions.AcceptRange) == ReplyOptions.AcceptRange) { responseProperty.Headers.Add("Content-Range", String.Format("bytes {0}-{1}/{2}", 0, totalLength-1, totalLength)); } responseProperty.Headers.Add("Content-Length", totalLength.ToString()); } if ((options & ReplyOptions.NoCache) == ReplyOptions.NoCache) { responseProperty.Headers.Add("Cache-Control", "no-cache"); responseProperty.Headers.Add("Expires", "-1"); } if ((options & ReplyOptions.AcceptRange) == ReplyOptions.AcceptRange) { responseProperty.Headers.Add("Accept-Ranges", "bytes"); } reply.Properties.Add(HttpResponseMessageProperty.Name, responseProperty); reply.Properties.Add(PoxEncoderMessageProperty.Name, new PoxEncoderMessageProperty(true)); return reply; } |
That method takes the stream, wraps it in our PoxStreamedMessage, sets all desired HTTP headers on the HttpResponseMessageProperty, adds the property to the message and lastly adds the PoxEncoderMessageProperty indicating that we want the encoder to operate in raw binary mode. However, all these helper methods are already part of the library and therefore the application code doesn’t really have to deal with all of that anymore. You just construct the fitting message, stick the content into it and return it. So now we have a service class and what’s left to do is to host it. For that we need a simple service host with a tiny little twist. Since the ServiceMetadataBehavior that typically gives you the WSDL file and the service information page would conflict with our direct interaction with HTTP, we need to switch it off. We do that by removing it from the list of behaviors before the service is initialized.
|
public class MyWebServerHost : ServiceHost { public MyWebServerHost(object instance) : base(instance) { }
protected override void OnInitialize() { Description.Behaviors.Remove<ServiceMetadataBehavior>(); base.OnInitialize(); } }
class Program { static void Main(string[] args) { string directoryName = args[0];
Console.WriteLine("LittleIndigoWebServer"); if (args.Length == 0) { Console.WriteLine("Usage: LittleIndigoWebServer.exe [root path]"); return; } if (!Directory.Exists(directoryName)) { Console.WriteLine("Directory '{0}' does not exist"); return; }
Console.WriteLine("Web server starting.");
using (MyWebServerHost host = new MyWebServerHost(new MyWebServer(directoryName))) { host.Open(); Console.WriteLine("Web Server running. Press ENTER to quit."); Console.ReadLine(); host.Close(); } } } |
The rest is just normal business for hosting and setting up a service model service in a console application. We read the first argument from the command-line and assume that’s a directory name. We verify that that is indeed so, construct the service host passing the singleton new’ed up with the directory name. We open the service host and we have the web server listening. The details for how the service is exposed on the network is the job of configuration and binding and, again, exhaustively explained in Part 8.
Below is the downloadable archive that contains two C# projects. Newtelligence.ServiceModelExtensions contains the extension set and LittleIndigoWebServer is the above demo app. The code complies and works with the WinFX November and December CTPs.
If you have installed Visual Studio on drive C: you should be able to run the sample immediately with F5, since the LittleIndigoWebServer project’s debugging settings pass the .NET SDK directory to the application on startup. So if you have that, start the server, and then browse http://localhost:8020/StartHere.htm you get this:

Otherwise, you can just start the server using any directory of your choosing, preferably one with HTML content.
And that’s it. I am happy that I’ve got all of the stuff out. This is probably the most documentation I’ve ever written in one stretch for some public giveaway infrastructure, but I am sure it’s worth it. I will follow up with more examples using these extensions. For instance I will show to use this for actual POX apps (the web server is just spitting out raw data, after all) using RSS, OPML and ASX. Stay tuned.
Oh, and … if you like this stuff I’d be happy about comments, questions, blog mentions and, first and foremost, public examples of other people using this stuff. License is BSD: Use as you like, risk is all yours, mention the creators. Enjoy.
Download: newtelligence-WCFExtensions-20060901.zip
[Note: I am preparing an update with client-side support and a few bugfixes right now. Should be available before or on 2006-01-16]
|