Damian Mehers' Blog Evernote and Wearable devices. All opinions my own.

17Apr/102

Closed down MceFM

Click here to take survey to help me decide whether to create a new release of MceFM.

A couple of years ago I created a Windows Media Center addin called MceFM which let you listen to Last.fm within Media Center.

However Last.fm have changed their Application Programming Interface (API), and charge for access from Switzerland – leaving it unviable for me to continue…

Mcefm.com now redirects to this page.

Do check out BeebMC which lets you listen to BBC radio within Media Center.

Filed under: Media Center 2 Comments
21Dec/080

Small utility to generate summary of your Media Center recorded movies

I've created a small simple utility to create an HTML summary document listing all the movies I've recorded using Vista Media Center.

I did this to get a compact record which I can browse without firing up Media Center, and which I can print out to give to visitors that are not familiar with Media Center.

This is what the output looks like:

image

The links you can see are links to IMDB.

When you run the utility you can choose the directories it should look in, and the name of the output file:

image

The first checkbox lets you tell the utility to only output films that it has found duplicated -- I added this so that I could easily find films that I'd recorded twice by accident.  The second checkbox tells it to also output the locations of the media files so that you can see where they are.

This tool is free, unsupported, and is to be used entirely at your own riskYou can download it here.

You might also want to check out this other utility which you can use to download image files for your films so that the images appear in generated html and also in Windows Media Center.

Filed under: Media Center No Comments
5Nov/082

My PDC 2008 "Show Off" entry: Using .NET code injection to do the seemingly impossible

Having come third in the first event "Show Off" entry, for PDC 2005, I decided to submit an entry for the 2008 PDC.

I decided to base my entry on a .NET code injection blog article I'd written a couple of months ago.  I got a very positive response from the people running the contest, although on the night I didn't actually win.

I missed the showing of my entry because I was attending a dinner thrown by Microsoft Switzerland for the people that attended the PDC from Switzerland.

In any event, here is my entry: http://damianblog.com/DamianMehersPDC2008ShowOff.wmv

 

29Aug/080

MCML Scroll Button PageUp jumps to first item in list

In BeebMC when you select a channel, you are presented with the list of programs available to "Listen Again".  You can scroll up and down the list using a remote control, or by positioning your mouse just before the first item, or just after the last item.

I've had a request from someone that has a touch panel.  They have no easy way of scrolling through their list.  I've decided to add a couple of arrow buttons to let you page up/down through the list.  This was pretty easy.  I first added a couple of buttons to my Content:

<con:TouchButton Name="ScrollUpButton" Command="[ScrollUpCommand]"
                 Picture="resx://BeebMC/BeebMC.Resources/arrow_up_blue">
  <LayoutInput>
    <FormLayoutInput Top="Parent, 0.2" Left="Parent,0.05"/>
  </LayoutInput>
</con:TouchButton>

<con:TouchButton Name="ScrollDownButton" Command="[ScrollDownCommand]"
                 Picture="resx://BeebMC/BeebMC.Resources/arrow_down_blue">
  <LayoutInput>
    <FormLayoutInput Bottom="Parent, 0.80" Left="Parent,0.05"/>
  </LayoutInput>
</con:TouchButton>

When the user clicks the button, I page up or down the scroller:

<Changed Source="[ScrollUpCommand.Invoked]">
  <Actions>
    <Invoke Target="[ScrollingData.PageUp]"/>
  </Actions>
</Changed>


<Changed Source="[ScrollDownCommand.Invoked]">
  <Actions>
    <Invoke Target="[ScrollingData.PageDown]"/>
  </Actions>
</Changed>

I tested and all seemed to work well, however then I noticed that although paging down through the list worked fine, paging up was jumping straight to the first item in the list :-(

Eventually I discovered the problem.  When you click the Up button, the scroll area loses focus, and so it seems to lose track of where it was in the list.  I'm not sure why it works for the Down button. 

In any event, the solution was to add a rule to my TouchButton UI to essentially tell it not to grab focus:

<Default Target="[Input.KeyInteractive]" Value="false" />

Now it works and I am close to releasing version 2.1 of BeebMC, which should work with the Media Center TV Pack too.

Filed under: Media Center No Comments
31Jul/0810

BeebMC 2.0 released: Listen Again supported

I've just released a new version of BeebMC, adding support for "Listen Again":

image

image

image

If/when the BBC update their web site or RSS feeds then BeebMC will probably break -- leave a comment and I'll do what I can to fix it.  Many thanks to the BBC for making all this content available.

BeebMC is available here: http://www.beebmc.com/

The Channels.xml file under C:\ProgramData\Atadore\BeebMC has been extended to include the URL for each channel's RSS feed.  Feel free to edit it to add/remove/modify channels:

<Channel>
  <Description>BBC 1Xtra</Description>
  <PictureUrl>http://www.bbc.co.uk/iplayer/img/nav/radio/bbc_1xtra.gif</PictureUrl>
  <ChannelUrl>http://www.bbc.co.uk/iplayer/console/1xtra</ChannelUrl>
  <ProgramsRss>http://feeds.bbc.co.uk/iplayer/bbc_1xtra/list</ProgramsRss>
</Channel>

I'm going to try to add extender support next.  Subscribe to the "BeebMC" category in my blog to be notified of updates: http://damianblog.com/category/beebmc/

12Jul/089

Screencast: Creating a simple Media Center Application

I've had some demand for the source for my simple application that plays BBC Radio inside Media Center.

I thought it might be more valuable if I put together a screencast introducing how I created the application from scratch, although the source is below too.

I assume your are familiar with .NET and C#, and have perhaps browsed through the Media Center documentation a little.  If you are an experienced Media Center developer then move along, move along, there is nothing here to see.

The first screencast walks through the simple application that gets created for you when you create a new Media Center Application.  I show how it works, and how you can build, install, and run it.  It is available to be streamed or downloaded here: http://www.atadore.com/videos/MCBeeb.wmv

The second screencast starts when the first one leaves off, and shows how I modified the initially created application to play BBC Radio.  It is here: http://www.atadore.com/videos/MCBeeb2.wmv

The final project is here.  It isn't quite the same as the BeebMC application (it doesn't load the channels from an XML file, and has no error checking), but feel free to play around with it.

I recorded them in one go quite late on a Saturday night, and I stumbled a couple of times with the terminology - please forgive the occasional mistake.

Hope this is useful! Do let me know.

Filed under: Media Center 9 Comments
8Jul/0821

BeebMC – BBC Radio addin for Windows Media Center

I've developed a small  BBC Radio Media Center addin mainly for my own use, which I'm making available here for free for anyone else that wants it.

[I've since posted a new version of BeebMC here . It supports "Listen Again".   I recommend going to this posting to read about it and download it.]

There are a couple of restrictions:

  • Your need Real Player installed
  • You need version 3.5 of the .NET framework installed.  It is available here if you don't have it (or if you are not sure).
  • It will not work with Extenders, since the BBC streams using Real Audio.

I can't promise a lot of support, but reply here if you have problems using it and I'll help if I can.

Once installed you'll find it under the Music menu:

image

Click on a channel to play that channel:

image 

You can customize it by editing the Channels.xml file you will find under C:\ProgramData\Atadore\BeebMC. Add channels by adding a Channel element with the appropriate description, picture and URL for playing the channel.

image 

[I've since posted a new version of BeebMC here . It supports "Listen Again".   I recommend going to this posting to read about it and download it.]

2Jul/084

.NET Code Injection

In this post I'll show how you can inject your own .NET code into a process that is already running the .NET framework.

I needed to do this when developing for Windows Media Center because there is no official documented way to determine what page is currently being displayed in Media Center.

I wanted to know when the user is showing the Media Center "now playing" page.

Media Center addins run in a completely separate process to the main Media Center process, which is ehshell.exe.  Calls you make in your addin are remoted across to the Media Center ehshell.exe process.

The solution works using:

  • an "Injecter" C# class  which uses CreateRemoteThread to load a Bootstrap DLL into the ehshell.exe process
  • The "Bootstrap" C++ DLL which is injected into the ehshell.exe process uses the CLR Hosting API to attach to the default .NET domain, and then load an instance of the "Injectee" C# class
  • The "Injectee" C# class is loaded by the "Bootstrap" DLL and runs within the ehshell.exe process.  It uses reflection to subscribe to an event within the ehshell.exe process and then sends an interprocess message when the event fires

The "Injecter"

Using Reflector, I looked around at the ehshell.exe assembly, and related assemblies, and eventually discovered that there is a PageChanged event fired by the ServiceBus.UIFramework.PageBasedUCPService class:

image 

What is more, there is a static PageBasedUCPService.DefaultUCP method to return a PageBasedUCPService instance.

There are of course a couple of problems.  The first problem being that these classes are internal to the ehshell assembly,  the second issue being that these are all running in a totally separate process to my addin.

I searched a little, and came across this example showing how you could use the CreateRemoteThread from .NET to inject your own native code into a remote process.

Essentially it opens a handle to the remote process, finds the LoadLibrary system call's address in the remote process, allocates a chunk of memory in the process, and fills the memory with a LoadLibrary call to load a DLL that you specify, and then creates the thread in the remote process, passing the address of the chunk of memory as the starting point for the thread.

I recommend taking a look at the CRT call in the example.  I'll not be duplicating the code here.

To use the CRT call, I first find Media Center:

public static Process FindMediaCenterProcess() {
  // Could be more than one if an extender is running too
  foreach (Process process in Process.GetProcessesByName("ehshell")) {
    if (process.SessionId == Process.GetCurrentProcess().SessionId) {
      return process;
    }
  }
  return null;
}

I can then call the CRT method:

Process mediaCenterProcess = FindMediaCenterProcess();
IntPtr hwnd;
string error;
CRT(mediaCenterProcess, dllPath, out error, out hwnd);

The dllPath is the full path to my C++ DLL, in my case this was

Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
                                  @"\Atadore\MceFM\InjectBootstrap.dll"
 

The "Boostrap DLL"

The InjectBootstrap.dll is a pretty simple C++ library project.  The DllMain calls a bootstrap function when the process is loaded:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        Bootstrap();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
 
My bootstrap function attaches to the default .NET domain and loads an instance of one of my own classes:
 
#include "stdafx.h"
#include <stdio.h>
#include "objbase.h"
#include "MSCorEE.h"
#import "C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb" raw_interfaces_only
using namespace mscorlib;

void Bootstrap() {
    OutputDebugString(L"MceFM Bootstrap Started");

    CoInitializeEx(0, COINIT_MULTITHREADED );
    ICorRuntimeHost* pICorRuntimeHost = 0;
    HRESULT st = CoCreateInstance(CLSID_CorRuntimeHost, 0, CLSCTX_ALL,
        IID_ICorRuntimeHost, (void**)&pICorRuntimeHost);
    if(!pICorRuntimeHost) return; // Clean up and log errror ...

    HDOMAINENUM hEnum = NULL;
    pICorRuntimeHost->EnumDomains(&hEnum);

    if(!hEnum) return; // Clean up and log errror ...

    IUnknown* pUunk = 0;
    st = pICorRuntimeHost->NextDomain(hEnum, &pUunk);
    if(!pUunk) return; // Clean up and log errror ...

    _AppDomain * pCurDomain = NULL;
    st = pUunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCurDomain);
    if(!pCurDomain) return; // Clean up and log errror ...

    _bstr_t assemblyName =
        "Last, Version=1.1.0.0, Culture=neutral, PublicKeyToken=792d614cdf38e9ce";
    _bstr_t typeName = "MceFM.Last.Inject.Injectee";

    _ObjectHandle* pObjectHandle = 0;

    pCurDomain->CreateInstance(assemblyName, typeName, &pObjectHandle);

}
 

The "Injectee"

Finally we come to the Injectee class, which is loaded by the DLL, and runs inside the remote (ehshell.exe) process.  It is in a strongly named assembly which is stored in the GAC, so that the bootstrap call above can find it (note the strong name in the assembly name bstr).

I use a static constructor (which runs as soon as the class is loaded).  In this static constructor I subscribe to the event:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace MceFM.Last.Inject {
  public class Injectee {

    static Injectee() {
      try {
        Assembly ehshell = Assembly.Load("ehshell");

        // Get the PageBasedUCPService type
        Type pageBasedUCPServiceType =
          ehshell.GetType("ServiceBus.UIFramework.PageBasedUCPService");

        // Call static DefaultUCP method to get a PageBasedUCPService instance
        const BindingFlags bindingFlags = BindingFlags.DeclaredOnly |
          BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty;
        object[] args = new object[0];
        object defaultUCP = pageBasedUCPServiceType.InvokeMember("DefaultUCP",
          bindingFlags, null, null, args);

        // Get a reference to the PageChanged event
        EventInfo pageChangedEventInfo =
          pageBasedUCPServiceType.GetEvent("PageChanged");
        Type pageChangedDelegate = pageChangedEventInfo.EventHandlerType;

        // Get the MethodInfo for the method to be called when the event fires
        MethodInfo newPageHandlerMethodInfo =
          typeof(Injectee).GetMethod("NewPageHandler");

        // Build an array of types used for new method we shall dynamically emit
        Type pageType = ehshell.GetType("ServiceBus.UIFramework.Page");
        Type[] pageChangedDelegateParameters = new[] { pageType };

        // Need a dynamic method because we can't create a method that has
        // the Page type as a parameter
        DynamicMethod dynamicHandler = new DynamicMethod("", null,
          pageChangedDelegateParameters, typeof(Injectee));
        ILGenerator ilgen = dynamicHandler.GetILGenerator();
        ilgen.Emit(OpCodes.Nop);
        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Call, newPageHandlerMethodInfo);
        ilgen.Emit(OpCodes.Nop);
        ilgen.Emit(OpCodes.Ret);

        if(pageChangedDelegate != null) {
          // Subscribe to the event
          Delegate dEmitted = dynamicHandler.CreateDelegate(pageChangedDelegate);
          pageChangedEventInfo.GetAddMethod().Invoke(defaultUCP,
                                                     new object[] {dEmitted});
        }
      } catch(Exception ex) {
        Trace.TraceError("Unexpected error in Injectee initializer: {0}", ex);
        Trace.TraceError(ex.StackTrace);
      }
    }

    // Queue of new pages that have been navigated to.  Decouples event handler
    // from re-despatching of the event to the addin process that is told
    // of the new event.
    private static readonly Queue<string> newPages = new Queue<string>();

    // Indicates whether background thread that despatches events is running
    private static bool notifierActive;

    // Called when Media Center navigates to a new page
    public static void NewPageHandler(object page) {
      Trace.TraceInformation("New Page: " + page);
      lock(newPages) {
        if(!notifierActive) {
          Thread thread = new Thread(NewPageNotifier) {IsBackground = true};
          thread.Start();
          notifierActive = true;
        }
        newPages.Enqueue(page.GetType().FullName);
        Monitor.Pulse(newPages);
      }
    }

    // Thread that despatches events to addin process telling it that
    // a new page has been navigated to
    private static void NewPageNotifier() {
      while(true) {
        string page;

        lock(newPages) {
          while(newPages.Count == 0) {
            Monitor.Wait(newPages);
          }
          page = newPages.Dequeue();
        }

        try {
          // Use whatever interprocess notification mechanism you wish.
          // WCF could be good.  Here I use a simple web call.  My
          // addin has a web server in it to receive these calls.
          WebClient webClient = new WebClient();
          webClient.QueryString[Server.PAGE_QUERY_STRING] = page;
          webClient.DownloadString(Util.LocalBaseUrl
                                   + Server.MCE_PAGE_CHANGED_ACTION);
        } catch (Exception ex) {
          Trace.TraceError("Error while sending new page notification: {0}", ex);
        }
      }
#pragma warning disable FunctionNeverReturns
    }
#pragma warning restore FunctionNeverReturns
  }
}
 

The Result

In my web server running within my addin (which is notified by the "NewPageNotifier" method above), I keep track of which is the current page, and log information when the page changes:

 
Server notified of page change MediaCenter.Audio.AudioNowPlayingPage
Server notified of page change MediaCenter.Audio.AudioNowPlayingTracklistPage

All this means that if you are a Last.fm user and want to tell Last.fm that you love a track that is currently playing then you can press the right-button on your remote control on the "now playing" page using my MceFM addin.

I use low level system hooks to know when you press the right button, and the above technique to know that you are on the "now playing" page:

image

Word if warning.  If/When Media Center changes its internal structure this technique will completely fall over.  Use sparingly, entirely at your own risk,  to do the seemingly impossible.

12Jun/080

Using mdbg to debug at the MSIL level instead of assembly code.

Windows Media Center (MCE) lets you create addins to provide new functionality.  There are two kinds of addins -- normal addins, and background addins.  Background addins run continuously as soon as MCE starts up, and are limited in the user-interaction they provide.

One thing they are allowed to do is to pop up a dialog message to the user using the AddInHost.Current.MediaCenterEnvironment.Dialog(...) method call.

I was doing this in my background addin, but I was getting an error from MCE saying that I was not allowed to make that call.

Addins run in separate processes to the main MCE process, which is ehshell.exe, and the Dialog call is remoted into the ehshell.exe process.

Using Reflector I could see that the call was eventually failing in MediaCenter.Extensibility.ExtensibilityAutomation.EnforceApplicationPermission(...) which was calling through to Microsoft.MediaCenter.Hosting.Infrastructure.RegisteredApps.IsBackgroundEntryPoint which looks like this:

public static bool IsBackgroundEntryPoint(ExtensibilityEntryPointInfo epi)
{
    return (0 == string.Compare(epi.Category, "Background", true,
CultureInfo.InvariantCulture));
}

Somehow I needed a way to use the debugger to see what the epi.Category value was when the call was failing.

The standard Visual Studio debugger just drops you through to x86 assembly code if it has no debugging symbols.  I needed a debugger which shows .NET IL instead.

This is where mdbg comes in. Mdbg is a command line debugger created by Mike Stall.

Once you have downloaded it and started running it, then you can use the 'a' command to list available processes, and then 'a <pid>' to attach to a specific process.

Once attached, you can set a breakpoint using 'br <module name>!<type>.<method name>", and then 'go' to resume:

image

Once the breakpoint is hit, you can use the 'print' command to display a parameter value:

image

There is also a GUI which can be invoked using 'load gui':

 image

6Jun/080

MceFM 1.0 Beta 6 released

I'm finally feeling as though I am getting very close to the first non-beta release of MceFM.

I decided to remove MceFM's Queue page (which was by all accounts rather ugly anyway) and just to use the standard Windows Media Center music related pages (Now Playing, and View Queue).

MSAS Fun

I needed to detect Media Center events in order to know when a song changes, etc.

The standard way to do this is using the Media State Aggregation Service (MSAS), and I got this working.  However it is rather flaky, and in the end I threw away the MSAS code I'd written, and took a different approach.  The code is here if anyone wants it.

I'm embedding an instance of the Windows Media Player (WMP) ActiveX Control in a hidden window attached to the MceFM background application.  The WMP control runs in "remote" mode, so that it reflects what is happening with the WMP instance used by Media Center.  Using this control I can detect when songs start playing.

Detecting Remote Control button pushes

I also went through a weird and wonderful journey with regards to allowing users mark songs as Loved or Banned in Last.fm.  Since I no longer have my own Queue page, I somehow needed to let the user press unused keys on the remote control when the Media Center "Now Playing" page was displayed.  Two things were needed -- how to detect when the key is pressed, and how to know when the Now Playing page is being displayed.

I initially decided to use the colored buttons that appear on my remote control (Red, Green, Yellow, Blue) which are used for teletext.  These buttons can't be intercepted using the normal techniques (low level system hooks), but can be detected using RegisterRawInputDevices and looking for RAWINPUT Windows Messages.  I got this working fine on my computer (RAWINPUT 91=Red, 92=Green, 93=Yellow, 94=Blue).

My idea was to let people press the Red button to Ban a song, and the Green button to Love a song.  I got this all working great.  Then I hit two issues.

The first issue is that the RegisterRawInputDevices mechanism didn't work at all for extenders.

The second issue is that only European Media Center remote controls appear to have the colored buttons.  D'oh.

I had to throw the whole approach overboard and start again from scratch.

I ended up letting you press the Left or Right arrow buttons on the remote control, which can be detected using low level system hooks, and do work on Extenders.  Whew.

Filed under: Media Center No Comments