Minianwendungen („Gadgets“) für die Windows Vista Sidebar entwickeln

Nachdem ich im Büro recht gute PC Lautsprecher, aber kein Radio habe, höre ich gerne Internet Radio. Auch bin ich oft in Amerika und höre dann gerne österreichische Radiosender von dort aus.

Mein Musikgeschmack ist ziemlich vielfältig, ich höre gerne Radio Stephansdom, gelegentlich Ö1, manchmal FM4, und auch oft Radio Wien. Ö3 höre ich selten bis gar nicht bzw. nur beim Autofahren.

Als ich davon hörte, dass es ein Ö3-Windows Sidebar Gadget gibt, wollte ich mir dieses dennoch herunterladen, weil ich wissen wollte, was dahinter steckt, Windows Sidebar Gadgets sollen ja so schön und relativ einfach zu entwickeln sein. Leider war das Sidebar Gadget damals vorübergehend nicht verfügbar, und ein paar Webcasts und Blog-Artikel später beschloss ich einfach ein eigenes Sidebar Gadget für österreichische Radiosender zu bauen, zumal mir Ö3 alleine ohnehin nicht reichen würde.

Files.pngEin Vista Gadget, auf Deutsch auch „Minianwendung“ bezeichnet, besteht im Wesentlichen aus einigen Files die im Verzeichnis C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows Sidebar\Gadgets abgelegt sind.

Das Unterverzeichnis ATRadio07.gadget enthält diese Files:

·        gadget.xml

·        atradio.html, atradio.css, atradio.js

·        flyout.html, flyout.css, flyout.js

·        settings.html

·        atradio.png, glass130.png, glass130c.png, logo.png

 

 

 

Das File gadget.xml, die Manifest-Datei, enthält alle Definitionen für das Gadget:

gadget.xml

<?xml version="1.0" encoding="utf-8"?>

<gadget>

  <name>ATRadio</name>

  <namespace>this.at.radio</namespace>

  <version>0.4</version>

  <author name="Christian Haberl">

    <info url="http://www.this.at" text="www.this.at" />

    <logo src="logo.png" />

  </author>

  <copyright>© 2007</copyright>

  <description>Radio aus Österreich.</description>

  <icons>

    <icon height="64" width="64" src="atradio.png" />

  </icons>

  <hosts>

    <host name="sidebar">

      <base type="HTML" apiVersion="1.0.0" src="atradio.html" />

      <permissions>full</permissions>

      <platform minPlatformVersion="1.0" />

      <defaultImage src="atradio.png" />

    </host>

  </hosts>

</gadget>

 

Wenn das Manifest File vorhanden ist, wird das Gadget auch automatisch im Vista Gadget Auswahlfenster angezeigt:

GadgetSelector.png

atradio.html ist der Hauptteil des Gadgets, den Javascript Code habe ich in atradio.js ausgelagert, die Styles in atradio.css.

PlainGadget.pngDer Aufbau ist recht einfach: Es gibt einen Button der gleichzeitig den Radiosender anzeigt und das Flyout öffnet um den Sender zu wechseln. Es gibt ein Windows Media Player Objekt und einen Start und einen Stop Button.

Im Wesentlichen wird das Windows Media Player Objekt mittels Javascript angesteuert, um die Radiostreams abzuspielen.

Wenn ein Stream gespielt wird, zeigt das Media Center Objekt eine Visualisierung, sonst wird der Status angezeigt, etwa „Buffering…“ oder „Error“.

Error bedeutet übrigens so gut wie immer, dass der Streaming Server überlastet ist, was bei Ö3 und Radio Wien an einem Arbeitstag unter Tags recht oft vorkommt, wenigstens in letzter Zeit.

atradio.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

 

<head>

<title>ATRadio</title>

<link rel="stylesheet" type="text/css" href="atradio.css">

<script src="atradio.js" language="javascript" type="text/javascript"></script>

</head>

 

<body id="gadgetContent" onload="SetupGadget();">

 

<input type="button" value="select station" id="stationname" onclick="toggleFlyout()"><br>

<div id="whichState">...</div>

<div id="mplayerdiv">

        <object id="Player" classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6">

                <param name="autoStart" value="False">

                <param name="SendPlayStateChangeEvents" value="True">

                <param name="SendErrorEvents" value="True">

                <param name="SendOpenStateChangeEvents" value="True">

                <param name="TransparentAtStart" value="True">

                <param name="uimode" value="none">

        </object>

</div>

<br>

<input type="button" name="BtnPlay" value="play" onclick="StartMeUp()" id="BtnPlay">

<input type="button" name="BtnStop" value="stop" onclick="ShutMeDown()" id="BtnStop">

 

</body>

</html>

atradio.js

function SetupGadget()

{

System.Gadget.Flyout.file = "flyout.html";

/*System.Gadget.settingsUI = "settings.html";*/

System.Gadget.Flyout.onShow = FlyoutShowing;

System.Gadget.Flyout.onHide = FlyoutHidden;

var iFlyoutTimerID;

Player.attachEvent("StatusChange", UpdateStateController);

if(System.Gadget.Settings.read("Station Name")!=""){

stationname.value=System.Gadget.Settings.read("Station Name");

Player.error.clearErrorQueue();

Player.URL=System.Gadget.Settings.read("Station URL");

}

}

 

function FlyoutShowing() {

iFlyoutTimerID = window.setInterval('UpdateStation();', 100);

}

 

function FlyoutHidden() {

window.clearInterval(iFlyoutTimerID)

}

 

function UpdateStation() {

if (System.Gadget.Flyout.show==true) {

       if (stationname.value!==System.Gadget.Settings.read("Station Name")) {

       if (System.Gadget.Settings.read("Station Name")!=="") {

       stationname.value=System.Gadget.Settings.read("Station Name");

       }

       Player.error.clearErrorQueue();

       Player.URL=System.Gadget.Settings.read("Station URL");

       StartMeUp ();             

       }      

       } else {

       UpdateStateController();

       window.ClearInterval(iFlyoutTimerID);      

       }

}

 

function StartMeUp () {

       Player.controls.play();

       UpdateStateController();

   }

 

function ShutMeDown () {

       Player.controls.stop(); 

       UpdateStateController();

   }

  

function toggleFlyout() {

    if (System.Gadget.Flyout.show==false) {

            System.Gadget.Flyout.show=true;

            UpdateStateController();        

    } else {

            System.Gadget.Flyout.show=false;

            UpdateStateController();

    }

}

 

function UpdateState(whichStateText, showHide) {

        if (showHide == "hide") {

                mplayerdiv.style.visibility='visible';

                whichState.style.visibility='hidden';

    }

    if (showHide == "show") {

                mplayerdiv.style.visibility='hidden';

                whichState.style.visibility='visible';

    }

    whichState.innerText = whichStateText;

}

 

function UpdateStateController() {

           if (Player.error.errorCount > 0) {

UpdateState("Error!", "show");
/* whichState.innerText = Player.error.item(Player.error.errorCount - 1).errorCode; */

                  return;

            }

        if (Player.playState==3){               

                  UpdateState("Playing...", "hide");

                  return; } else {

                  switch (Player.openState) {

                  case 10:

                  UpdateState("Connecting...", "show");

                  break;       

                  case 11:

                  UpdateState("Loading...", "show");

                  break;                 

                  case 12:

                  UpdateState("Opening...", "show");

                  break;

                  }

                  switch (Player.playState) {

                  case 1:

                  UpdateState("Stopped", "show");

                  break;       

                  case 6:

                  UpdateState("Buffering...", "show");

                  break;                 

                  case 7:

                  UpdateState("Waiting...", "show");

                  break;

                  case 8:

                  UpdateState("Media Ended", "show");

                  break;       

                  case 11:

                  UpdateState("Reconnected", "show");

                  break;                 

                  }

                }

}

settings.png

Da ich die Sender über das Flyout ändere, habe ich das Settings-Fenster momentan auskommentiert.
Viele Gadgets verwenden es, daher möchte ich es hier auch kurz anschneiden:
Die Zeile System.Gadget.settingsUI = "settings.html"; würde bewirken, dass seitlich vom Gadget ein Settings-Button auftaucht, der das Gadget selbst verkleinert und settings.html anzeigt.

 

 

 

 

 

 

 

 

flyout.html ist das Flyout-Fenster, welches sich öffnet, wo man den Sender auswählen und damit umschalten kann. Nachdem der Sender ausgewählt wurde wird er mittels System.Gadget.Settings.write in die Gadget-spezifischen Variablen geschrieben.

GadgetFlyout.png

flyout.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

 

<head>

<title>ATRadio</title>

<link rel="stylesheet" type="text/css" href="flyout.css">

<script src="flyout.js" language="javascript" type="text/javascript"></script>

</head>

 

<body onload="Init();">

 

<select id="Station" multiple="multiple" onchange="WriteSettings();" onclick="WriteSettings();" name="Station">

<option value="mms://stream4.orf.at/oe1-wort">Ö1 Live</option>

<option value="mms://stream4.orf.at/oe1-news">Ö1 Inforadio</option>

<option value="mms://stream4.orf.at/radiowien_live">Radio Wien</option>

<option value="mms://stream2.orf.at/oe3_live">Ö3</option>

<option value="mms://stream1.orf.at/fm4_live">FM4</option>

<option value="http://www.kronehit.at:8081/stream.m3u">Krone Hit Radio</option>

<option value="http://www.energy.at/cont/energylive/nrjmedia.asx">Energy 104.2</option>

<option value="http://srvhost24.serverhosting.apa.net:8000/rsdstream128.m3u">Radio Stephansdom</option>

<option value="http://streamintern.orange.or.at/live3.m3u">Orange</option>

</select>

 

</body>

 

</html>

 

flyout.js

function Init()

 

{

Station.selectedIndex=System.Gadget.Settings.readString("Station Index");

}

  

   function WriteSettings ()

   {

       System.Gadget.Settings.write("Station Name",Station.options[Station.selectedIndex].text);

       System.Gadget.Settings.write("Station URL",Station.options[Station.selectedIndex].value);                 

       System.Gadget.Settings.writeString("Station Index",Station.selectedIndex);                 

       System.Gadget.Flyout.show=false;

   }

 

Da man ein Flyout nur vom Haupt-Gadget (atradio.htm) aus- und einklappen kann, ich aber wollte, dass es sich automatisch einklappt, sobald ein neuer Sender ausgewählt wurde, verwende ich - wie übrigens viele andere Gadget-Developer auch – „Polling“, um zu sehen ob sich im Flyout etwas geändert hat. Das ist etwas „dirty“, lässt sich aber meines Wissens nach nicht besser lösen. Ich polle natürlich nur solange das Flyout offen ist, ob es einen neuen Sender in den System.Gadget.Settings gibt, wenn ja, starte ich diesen, und schließe das Flyout. All das ist vom Haupt Gadget aus notwendig und nur mit Polling möglich, es lässt sich nicht vom Flyout auf das Hauptgadget zugreifen!

Zum Testen muss man das Gadget immer wieder zur Sidebar hinzufügen bzw. schließen. Außerdem empfiehlt es sich, Skriptdebugging einzuschalten, damit man Javascript Fehlermeldungen bekommt.

Skriptdebugging.png

So sieht nun das fertige Gadget in der Sidebar aus, mit ausgeklapptem Flyout:

Sidebar.png

Ziemlich genial funktioniert das Packaging des neuen Gadgets: Einfach alle Files zippen, und die Endung von .zip in .gadget umbenennen! Fertig!

Mein Gadget bekommt man unter http://www.this.at/ATRadio07.zip

Man kann mein Gadget auch schon bei http://vista.gallery.microsoft.com/ herunterladen und bewerten.

Würde mich auch über Feedback freuen!