Index: myththemes/MythCenter/music-ui.xml
===================================================================
--- myththemes/MythCenter/music-ui.xml	(revision 0)
+++ myththemes/MythCenter/music-ui.xml	(revision 0)
@@ -0,0 +1,156 @@
+<mythuitheme>
+
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>30</size>
+          <size:small>18</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="labels" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="display" face="Trebuchet MS">
+          <color>#ffffff</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <container name="edit_container">
+            <area>0,0,800,600</area>
+
+            <textarea name="title" draworder="1" align="center">
+                <area>0,15,800,50</area>
+                <font>title</font>
+                <value>Track Information</value>
+            </textarea>
+
+            <!--
+                    Labels
+            -->
+
+
+            <textarea name="station_label" draworder="1" align="right">
+                <area>15,70,170,30</area>
+                <font>labels</font>
+                <value>Station:</value>
+            </textarea>
+
+            <textarea name="channel_label" draworder="1" align="right">
+                <area>15,110,170,30</area>
+                <font>labels</font>
+                <value>Channel:</value>
+            </textarea>
+
+            <textarea name="url_label" draworder="1" align="right">
+                <area>15,150,170,30</area>
+                <font>labels</font>
+                <value>URL:</value>
+            </textarea>
+
+            <textarea name="metaformat_label" draworder="1" align="right">
+                <area>15,190,170,30</area>
+                <font>labels</font>
+                <value>Meta:</value>
+            </textarea>
+
+            <textarea name="genre_label" draworder="1" align="right">
+                <area>15,230,170,30</area>
+                <font>labels</font>
+                <value>Genre:</value>
+            </textarea>
+
+            <textarea name="rating_label" draworder="1" align="right">
+                <area>15,350,170,30</area>
+                <font>labels</font>
+                <value>Rating:</value>
+            </textarea>
+
+            <!--
+                    edits
+            -->
+
+            <remoteedit name="station_edit" draworder="1" align="left">
+                <area>195,70,525,35</area>
+                <font>display</font>
+            </remoteedit> 
+
+            <pushbutton name="searchstation_button" draworder="2">
+                <position>725,70</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <remoteedit name="channel_edit" draworder="1" align="left">
+                <area>195,110,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="url_edit" draworder="1" align="left">
+                <area>195,150,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="metaformat_edit" draworder="1" align="left">
+                <area>195,190,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="genre_edit" draworder="1" align="left">
+                <area>195,230,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <pushbutton name="searchgenre_button" draworder="2">
+                <position>725,230</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                <filename>mm_rating.png</filename>
+                <position>190,360</position>
+                <orientation>LeftToRight</orientation>
+            </repeatedimage>
+
+            <selector name="rating_button" draworder="0">
+                <area>420,350,30,30</area>
+                <font>display</font>
+                <image function="on" filename="mm_leftright_on.png"></image>
+                <image function="off" filename="mm_leftright_off.png"></image>
+                <image function="pushed" filename="mm_leftright_pushed.png"></image>
+            </selector>
+
+
+            <!--
+                    Push buttons
+            -->
+
+            <textbutton name="done_button" draworder="0">
+                <position>350,540</position>
+                <font>display</font>
+                <image function="on" filename="text_button_on.png"></image>
+                <image function="off" filename="text_button_off.png"></image>
+                <image function="pushed" filename="text_button_pushed.png"></image>
+            </textbutton>
+
+      </container>
+
+   </window>
+
+</mythuitheme>
Index: myththemes/Retro/music-ui.xml
===================================================================
--- myththemes/Retro/music-ui.xml	(revision 0)
+++ myththemes/Retro/music-ui.xml	(revision 0)
@@ -0,0 +1,156 @@
+<mythuitheme>
+
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>30</size>
+          <size:small>18</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="labels" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="display" face="Trebuchet MS">
+          <color>#ffffff</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <container name="edit_container">
+            <area>0,0,800,600</area>
+
+            <textarea name="title" draworder="1" align="center">
+                <area>0,15,800,50</area>
+                <font>title</font>
+                <value>Track Information</value>
+            </textarea>
+
+            <!--
+                    Labels
+            -->
+
+
+            <textarea name="station_label" draworder="1" align="right">
+                <area>15,70,170,30</area>
+                <font>labels</font>
+                <value>Station:</value>
+            </textarea>
+
+            <textarea name="channel_label" draworder="1" align="right">
+                <area>15,110,170,30</area>
+                <font>labels</font>
+                <value>Channel:</value>
+            </textarea>
+
+            <textarea name="url_label" draworder="1" align="right">
+                <area>15,150,170,30</area>
+                <font>labels</font>
+                <value>URL:</value>
+            </textarea>
+
+            <textarea name="metaformat_label" draworder="1" align="right">
+                <area>15,190,170,30</area>
+                <font>labels</font>
+                <value>Meta:</value>
+            </textarea>
+
+            <textarea name="genre_label" draworder="1" align="right">
+                <area>15,230,170,30</area>
+                <font>labels</font>
+                <value>Genre:</value>
+            </textarea>
+
+            <textarea name="rating_label" draworder="1" align="right">
+                <area>15,350,170,30</area>
+                <font>labels</font>
+                <value>Rating:</value>
+            </textarea>
+
+            <!--
+                    edits
+            -->
+
+            <remoteedit name="station_edit" draworder="1" align="left">
+                <area>195,70,525,35</area>
+                <font>display</font>
+            </remoteedit> 
+
+            <pushbutton name="searchstation_button" draworder="2">
+                <position>725,70</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <remoteedit name="channel_edit" draworder="1" align="left">
+                <area>195,110,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="url_edit" draworder="1" align="left">
+                <area>195,150,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="metaformat_edit" draworder="1" align="left">
+                <area>195,190,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="genre_edit" draworder="1" align="left">
+                <area>195,230,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <pushbutton name="searchgenre_button" draworder="2">
+                <position>725,230</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                <filename>mm_rating.png</filename>
+                <position>190,360</position>
+                <orientation>LeftToRight</orientation>
+            </repeatedimage>
+
+            <selector name="rating_button" draworder="0">
+                <area>420,350,30,30</area>
+                <font>display</font>
+                <image function="on" filename="mm_leftright_on.png"></image>
+                <image function="off" filename="mm_leftright_off.png"></image>
+                <image function="pushed" filename="mm_leftright_pushed.png"></image>
+            </selector>
+
+
+            <!--
+                    Push buttons
+            -->
+
+            <textbutton name="done_button" draworder="0">
+                <position>350,540</position>
+                <font>display</font>
+                <image function="on" filename="text_button_on.png"></image>
+                <image function="off" filename="text_button_off.png"></image>
+                <image function="pushed" filename="text_button_pushed.png"></image>
+            </textbutton>
+
+      </container>
+
+   </window>
+
+</mythuitheme>
Index: myththemes/MythCenter-wide/music-ui.xml
===================================================================
--- myththemes/MythCenter-wide/music-ui.xml	(revision 0)
+++ myththemes/MythCenter-wide/music-ui.xml	(revision 0)
@@ -0,0 +1,156 @@
+<mythuitheme>
+
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>30</size>
+          <size:small>18</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="labels" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="display" face="Trebuchet MS">
+          <color>#ffffff</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <container name="edit_container">
+
+         <area>0,0,1820,720 </area>
+
+             <!--
+                    Labels
+            -->
+
+             <textarea name="title" draworder="1" align="center">
+                 <area>0,30,1280,50</area>
+                 <font>title</font>
+                 <value>Track Information</value>
+             </textarea>
+
+             <textarea name="station_label" draworder="1" align="right">
+                 <area>15,100,170,30</area>
+                 <font>labels</font>
+                 <value>Station: </value>
+             </textarea>
+
+             <textarea name="channel_label" draworder="1" align="right">
+                 <area>15,150,170,30</area>
+                 <font>labels</font>
+                 <value>Channel: </value>
+             </textarea>
+
+             <textarea name="url_label" draworder="1" align="right">
+                 <area>15,200,170,30</area>
+                 <font>labels</font>
+                 <value>URL: </value>
+             </textarea>
+
+             <textarea name="metaformat_label" draworder="1" align="right">
+                 <area>15,250,170,30</area>
+                 <font>labels</font>
+                 <value>Meta Format: </value>
+             </textarea>
+
+             <textarea name="genre_label" draworder="1" align="right">
+                 <area>15,300,170,30</area>
+                 <font>labels</font>
+                 <value>Genre: </value>
+             </textarea>
+
+             <textarea name="rating_label" draworder="1" align="right">
+                 <area>15,350,170,30</area>
+                 <font>labels</font>
+                 <value>Rating: </value>
+             </textarea>
+
+             <!--
+                    edits
+            -->
+
+             <remoteedit name="station_edit" draworder="1" align="left">
+                 <area>200,100,880,35</area>
+                 <font>display</font>
+             </remoteedit> 
+
+             <pushbutton name="searchstation_button" draworder="2">
+                 <position>1100,100</position>
+                 <image function="on" filename="mm_blankbutton_on.png"> </image>
+                 <image function="off" filename="mm_blankbutton_off.png"> </image>
+                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <remoteedit name="channel_edit" draworder="1" align="left">
+                 <area>200,150,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="url_edit" draworder="1" align="left">
+                 <area>200,200,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="metaformat_edit" draworder="1" align="left">
+                 <area>200,250,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="genre_edit" draworder="1" align="left">
+                 <area>200,300,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <pushbutton name="searchgenre_button" draworder="2">
+                 <position>1100,300</position>
+                 <image function="on" filename="mm_blankbutton_on.png"> </image>
+                 <image function="off" filename="mm_blankbutton_off.png"> </image>
+                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                 <filename>mm_rating.png</filename>
+                 <position>200,360</position>
+                 <orientation>LeftToRight</orientation>
+             </repeatedimage>
+
+             <selector name="rating_button" draworder="0">
+                 <area>1100,350,30,30</area>
+                 <font>display</font>
+                 <image function="on" filename="mm_leftright_on.png"> </image>
+                 <image function="off" filename="mm_leftright_off.png"> </image>
+                 <image function="pushed" filename="mm_leftright_pushed.png"> </image>
+             </selector>
+
+
+             <!--
+                    Push buttons
+            -->
+
+             <textbutton name="done_button" draworder="0">
+                 <position>500,540</position>
+                 <font>display</font>
+                 <image function="on" filename="text_button_on.png"> </image>
+                 <image function="off" filename="text_button_off.png"> </image>
+                 <image function="pushed" filename="text_button_pushed.png"> </image>
+             </textbutton>
+
+      </container>
+
+   </window>
+
+</mythuitheme>
Index: mythtv/themes/G.A.N.T./music-ui.xml
===================================================================
--- mythtv/themes/G.A.N.T./music-ui.xml	(revision 12919)
+++ mythtv/themes/G.A.N.T./music-ui.xml	(working copy)
@@ -652,4 +652,154 @@
       </container>
    </window>
 
+  <window name="edit_radiometadata">
+
+        <font name="title" face="Arial">
+            <color>#ffff00</color>
+            <dropcolor>#000000</dropcolor>
+            <size>24</size>
+            <shadow>3,3</shadow>
+            <bold>yes</bold>
+        </font>
+
+        <font name="labels" face="Arial">
+            <color>#ffff00</color>
+            <dropcolor>#000000</dropcolor>
+            <size>18</size>
+            <shadow>3,3</shadow>
+            <bold>yes</bold>
+        </font>
+
+        <font name="display" face="Arial">
+            <color>#ffffff</color>
+            <dropcolor>#000000</dropcolor>
+            <size>18</size>
+            <shadow>3,3</shadow>
+            <bold>yes</bold>
+        </font>
+
+      <container name="edit_container">
+            <area>0,0,800,600</area>
+
+            <textarea name="title" draworder="1" align="center">
+                <area>0,15,800,50</area>
+                <font>title</font>
+                <value>Track Information</value>
+            </textarea>
+
+            <!--
+                    Labels
+            -->
+
+
+            <textarea name="station_label" draworder="1" align="right">
+                <area>15,70,170,30</area>
+                <font>labels</font>
+                <value>Station:</value>
+            </textarea>
+
+            <textarea name="channel_label" draworder="1" align="right">
+                <area>15,110,170,30</area>
+                <font>labels</font>
+                <value>Channel:</value>
+            </textarea>
+
+            <textarea name="url_label" draworder="1" align="right">
+                <area>15,150,170,30</area>
+                <font>labels</font>
+                <value>URL:</value>
+            </textarea>
+
+            <textarea name="metaformat_label" draworder="1" align="right">
+                <area>15,190,170,30</area>
+                <font>labels</font>
+                <value>Meta:</value>
+            </textarea>
+
+            <textarea name="genre_label" draworder="1" align="right">
+                <area>15,230,170,30</area>
+                <font>labels</font>
+                <value>Genre:</value>
+            </textarea>
+
+            <textarea name="rating_label" draworder="1" align="right">
+                <area>15,350,170,30</area>
+                <font>labels</font>
+                <value>Rating:</value>
+            </textarea>
+
+            <!--
+                    edits
+            -->
+
+            <remoteedit name="station_edit" draworder="1" align="left">
+                <area>195,70,525,35</area>
+                <font>display</font>
+            </remoteedit> 
+
+            <pushbutton name="searchstation_button" draworder="2">
+                <position>725,70</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <remoteedit name="channel_edit" draworder="1" align="left">
+                <area>195,110,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="url_edit" draworder="1" align="left">
+                <area>195,150,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="metaformat_edit" draworder="1" align="left">
+                <area>195,190,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <remoteedit name="genre_edit" draworder="1" align="left">
+                <area>195,230,525,35</area>
+                <font>display</font>
+            </remoteedit>
+
+            <pushbutton name="searchgenre_button" draworder="2">
+                <position>725,230</position>
+                <image function="on" filename="mm_blankbutton_on.png"></image>
+                <image function="off" filename="mm_blankbutton_off.png"></image>
+                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
+            </pushbutton>
+
+            <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                <filename>mm_rating.png</filename>
+                <position>190,360</position>
+                <orientation>LeftToRight</orientation>
+            </repeatedimage>
+
+            <selector name="rating_button" draworder="0">
+                <area>420,350,30,30</area>
+                <font>display</font>
+                <image function="on" filename="mm_leftright_on.png"></image>
+                <image function="off" filename="mm_leftright_off.png"></image>
+                <image function="pushed" filename="mm_leftright_pushed.png"></image>
+            </selector>
+
+
+            <!--
+                    Push buttons
+            -->
+
+            <textbutton name="done_button" draworder="0">
+                <position>350,540</position>
+                <font>display</font>
+                <image function="on" filename="text_button_on.png"></image>
+                <image function="off" filename="text_button_off.png"></image>
+                <image function="pushed" filename="text_button_pushed.png"></image>
+            </textbutton>
+
+      </container>
+
+   </window>
+
 </mythuitheme>
Index: mythtv/themes/blue/music-ui.xml
===================================================================
--- mythtv/themes/blue/music-ui.xml	(revision 12919)
+++ mythtv/themes/blue/music-ui.xml	(working copy)
@@ -585,5 +585,153 @@
 
       </container>
    </window>
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Arial">
+          <color>#ffff00 </color>
+          <dropcolor>#000000 </dropcolor>
+          <size>24 </size>
+          <shadow>3,3 </shadow>
+          <bold>yes </bold>
+      </font>
+
+      <font name="labels" face="Arial">
+          <color>#ffff00 </color>
+          <dropcolor>#000000 </dropcolor>
+          <size>18 </size>
+          <shadow>3,3 </shadow>
+          <bold>yes </bold>
+      </font>
+
+      <font name="display" face="Arial">
+          <color>#ffffff </color>
+          <dropcolor>#000000 </dropcolor>
+          <size>18 </size>
+          <shadow>3,3 </shadow>
+          <bold>yes </bold>
+      </font>
+
+       <container name="edit_container">
+             <area>0,0,800,600 </area>
+
+             <textarea name="title" draworder="1" align="center">
+                 <area>0,15,800,50 </area>
+                 <font>title </font>
+                 <value>Track Information </value>
+             </textarea>
+
+             <!--
+                    Labels
+            -->
+
+
+             <textarea name="station_label" draworder="1" align="right">
+                 <area>15,70,170,30 </area>
+                 <font>labels </font>
+                 <value>Station: </value>
+             </textarea>
+
+             <textarea name="channel_label" draworder="1" align="right">
+                 <area>15,110,170,30 </area>
+                 <font>labels </font>
+                 <value>Channel: </value>
+             </textarea>
+
+             <textarea name="url_label" draworder="1" align="right">
+                 <area>15,150,170,30 </area>
+                 <font>labels </font>
+                 <value>URL: </value>
+             </textarea>
+
+             <textarea name="metaformat_label" draworder="1" align="right">
+                 <area>15,190,170,30 </area>
+                 <font>labels </font>
+                 <value>Meta Format: </value>
+             </textarea>
+
+             <textarea name="genre_label" draworder="1" align="right">
+                 <area>15,230,170,30 </area>
+                 <font>labels </font>
+                 <value>Genre: </value>
+             </textarea>
+
+             <textarea name="rating_label" draworder="1" align="right">
+                 <area>15,350,170,30 </area>
+                 <font>labels </font>
+                 <value>Rating: </value>
+             </textarea>
+
+             <!--
+                    edits
+            -->
+
+             <remoteedit name="station_edit" draworder="1" align="left">
+                 <area>195,70,525,35 </area>
+                 <font>display </font>
+             </remoteedit> 
+
+             <pushbutton name="searchstation_button" draworder="2">
+                 <position>725,70 </position>
+                 <image function="on" filename="mm_blankbutton_on.png"> </image>
+                 <image function="off" filename="mm_blankbutton_off.png"> </image>
+                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <remoteedit name="channel_edit" draworder="1" align="left">
+                 <area>195,110,525,35 </area>
+                 <font>display </font>
+             </remoteedit>
+
+             <remoteedit name="url_edit" draworder="1" align="left">
+                 <area>195,150,525,35 </area>
+                 <font>display </font>
+             </remoteedit>
+
+             <remoteedit name="metaformat_edit" draworder="1" align="left">
+                 <area>195,190,525,35 </area>
+                 <font>display </font>
+             </remoteedit>
+
+             <remoteedit name="genre_edit" draworder="1" align="left">
+                 <area>195,230,525,35 </area>
+                 <font>display </font>
+             </remoteedit>
+
+             <pushbutton name="searchgenre_button" draworder="2">
+                 <position>725,230 </position>
+                 <image function="on" filename="mm_blankbutton_on.png"> </image>
+                 <image function="off" filename="mm_blankbutton_off.png"> </image>
+                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                 <filename>mm_rating.png </filename>
+                 <position>190,360 </position>
+                 <orientation>LeftToRight </orientation>
+             </repeatedimage>
+
+             <selector name="rating_button" draworder="0">
+                 <area>420,350,30,30 </area>
+                 <font>display </font>
+                 <image function="on" filename="mm_leftright_on.png"> </image>
+                 <image function="off" filename="mm_leftright_off.png"> </image>
+                 <image function="pushed" filename="mm_leftright_pushed.png"> </image>
+             </selector>
+
+
+             <!--
+                    Push buttons
+            -->
+
+             <textbutton name="done_button" draworder="0">
+                 <position>350,540 </position>
+                 <font>display </font>
+                 <image function="on" filename="text_button_on.png"> </image>
+                 <image function="off" filename="text_button_off.png"> </image>
+                 <image function="pushed" filename="text_button_pushed.png"> </image>
+             </textbutton>
+
+       </container>
+    </window>
   
 </mythuitheme>
Index: mythtv/libs/libmyth/output.cpp
===================================================================
--- mythtv/libs/libmyth/output.cpp	(revision 12919)
+++ mythtv/libs/libmyth/output.cpp	(working copy)
@@ -22,11 +22,8 @@
 
 
 void OutputListeners::error(const QString &e) {
-    QObject *object = firstListener();
-    while (object) {
-	QApplication::postEvent(object, new OutputEvent(e));
-	object = nextListener();
-    }
+    OutputEvent ev (e);
+    dispatch (ev);
 }
 
 void OutputListeners::addVisual(MythTV::Visual *v)
Index: mythtv/libs/libmyth/mythobservable.cpp
===================================================================
--- mythtv/libs/libmyth/mythobservable.cpp	(revision 12919)
+++ mythtv/libs/libmyth/mythobservable.cpp	(working copy)
@@ -12,48 +12,37 @@
 
 void MythObservable::addListener(QObject *listener)
 {
+    QMutexLocker locked (&mutex);
     if (m_listeners.find(listener) == -1)
         m_listeners.append(listener);
 }
 
 void MythObservable::removeListener(QObject *listener)
 {
+    QMutexLocker locked (&mutex);
     if (m_listeners.find(listener) != -1)
         m_listeners.remove(listener);
 }
 
-QObject* MythObservable::firstListener()
-{
-    return m_listeners.first();
-}
-
-QObject* MythObservable::nextListener()
-{
-    return m_listeners.next();
-}
-
-QPtrList<QObject> MythObservable::getListeners()
-{
-    return m_listeners;
-}
-
 void MythObservable::dispatch(MythEvent &event)
 {
-    QObject *listener = firstListener();
+    QMutexLocker locked (&mutex);
+    QObject *listener = m_listeners.first();
     while (listener)
     {
         QApplication::postEvent(listener, event.clone());
-        listener = nextListener();
+        listener = m_listeners.next();
     }
 }
 
 void MythObservable::dispatchNow(MythEvent &event)
 {
-    QObject *listener = firstListener();
+    QMutexLocker locked (&mutex);
+    QObject *listener = m_listeners.first();
     while (listener)
     {
         QApplication::sendEvent(listener, event.clone());
-        listener = nextListener();
+        listener = m_listeners.next();
     }
 }
 
Index: mythtv/libs/libmyth/mythobservable.h
===================================================================
--- mythtv/libs/libmyth/mythobservable.h	(revision 12919)
+++ mythtv/libs/libmyth/mythobservable.h	(working copy)
@@ -2,6 +2,7 @@
 #define MYTHOBSERVABLE_H_
 
 #include <qptrlist.h>
+#include <qmutex.h>
 #include "mythexp.h"
 #include "mythevent.h"
 
@@ -36,6 +37,8 @@
     the observers as listeners (ie. addListener), however,
     MythListenable just doesn't sound right, and fixing all the calls
     to addListener was too big a patch.
+    
+    All public methods in MythObservable are reentrant.
 */
 class MPUBLIC MythObservable
 {
@@ -61,57 +64,6 @@
     */
     void removeListener(QObject *listener);
 
-    /** \brief Begin iteration across listeners
-
-        If you simply need to iterate across the listeners, use \p
-        firstListener and \p nextListener to iterate across the
-        listeners. Ie. instead of 
-
-        \code
-        {
-            QPtrList<QObject> listeners = getListeners();
-            QObject *listener = listeners.first();
-            while (listener) {
-                // use listener...
-                listener = listeners.next();
-            }
-        }
-        \endcode
-
-        you can avoid the copy and just do
-
-        \code
-        {
-            QObject *listener = firstListener();
-            while (listener) {
-                // use listener...
-                listener = nextListener();
-            }
-        } 
-        \endcode
-
-        \returns pointer to the first listener, NULL if there are no listeners
-    */
-    QObject* firstListener();
-
-    /** \brief Continue iteration to the next listener
-
-        See firstListener. Returns NULL if there are no more listeners.
-
-        \returns pointer to the next listener, NULL if there are no more listeners
-    */
-    QObject* nextListener();
-
-    /** \brief Get a copy of the list of listener
-
-        If you need access to more than just iteration via
-        firstListener/nextListerner, you can call this to obtain a
-        QPtrList with all the listeners.
-
-        \returns a copy of the list of listener
-    */
-    QPtrList<QObject> getListeners(void);
-
     /** \brief Dispatch an event to all listeners 
                        
         Makes a copy of the event on the heap by calling
@@ -134,7 +86,9 @@
     void dispatchNow(MythEvent &event);
 
   private:
+
     QPtrList<QObject> m_listeners;
+    QMutex mutex;
 };
 
 #endif /* MYTHOBSERVABLE_H */
Index: mythplugins/mythmusic/mythmusic/playbackbox.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/playbackbox.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/playbackbox.cpp	(working copy)
@@ -19,14 +19,17 @@
 // MythMusic includes
 #include "metadata.h"
 #include "constants.h"
-#include "streaminput.h"
 #include "decoder.h"
 #include "playbackbox.h"
 #include "databasebox.h"
+#include "metaio.h"
 #include "mainvisual.h"
 #include "smartplaylist.h"
 #include "search.h"
+#include "decoderhandler.h"
 
+#define GET_REPO_ID(attrs) attrs->at(4)
+
 PlaybackBoxMusic::PlaybackBoxMusic(MythMainWindow *parent, QString window_name,
                                    QString theme_filename, 
                                    PlaylistsContainer *the_playlists,
@@ -36,9 +39,9 @@
 {
     //  A few internal variable defaults
  
-    input = NULL;
     output = NULL;
     decoder = NULL;
+    decoderHandler = NULL;
     mainvisual = NULL;
     visual_mode_timer = NULL;
     lcd_update_timer = NULL;
@@ -103,6 +106,10 @@
                 this, SLOT(hideVolume()));
     }
     
+    decoder_handler_progress_timer = new QTimer(this);
+    connect (decoder_handler_progress_timer, SIGNAL(timeout()), 
+             this, SLOT(operationProgressTimer()));
+    
     // Figure out the shuffle mode
 
     QString playmode = gContext->GetSetting("PlayMode", "none");
@@ -255,6 +262,14 @@
         gContext->SaveSetting("RepeatMode", "all");
     else
         gContext->SaveSetting("RepeatMode", "none");
+
+    if (decoderHandler) 
+    {
+        decoderHandler->removeListener(this);
+        decoderHandler->deleteLater();
+        decoderHandler = NULL;
+    }
+
     if (class LCD *lcd = LCD::Get())
         lcd->switchToTime();
 }
@@ -377,7 +392,7 @@
         }
         else if (action == "INFO")
             if (visualizer_status == 2) 
-                bannerToggle(curMeta);
+                bannerToggle(&displayMeta);
             else
                 showEditMetadataDialog();
         else
@@ -491,6 +506,32 @@
         MythThemedDialog::keyPressEvent(e);
 }
 
+/*note:temporary fix for radio - Ideally, I'd like the metadata repo
+  add it's own stuff to the menur entry 
+*/
+void PlaybackBoxMusic::addRadioMenuEntries() 
+{
+    GenericTree *node = music_tree_list->getCurrentNode();
+    IntVector *attrs = node->getAttributes();
+
+    if (GET_REPO_ID(attrs) != 1) 
+        return;
+
+    playlist_popup->addButton(tr("Add Radio"), this,
+                              SLOT(showAddRadioStationDialog()));
+
+    if (node->isSelectable()) 
+        playlist_popup->addButton(tr("Remove Radio"), this,
+                                  SLOT(showRemoveRadioStationDialog()));
+
+    QLabel *splitter = playlist_popup->addLabel(" ", MythPopupBox::Small);
+    splitter->setLineWidth(2);
+    splitter->setFrameShape(QFrame::HLine);
+    splitter->setFrameShadow(QFrame::Sunken);
+    splitter->setMaximumHeight((int) (5 * hmult));
+    splitter->setMaximumHeight((int) (5 * hmult));
+}
+
 void PlaybackBoxMusic::showMenu()
 {
     if (playlist_popup)
@@ -515,6 +556,8 @@
     splitter->setMaximumHeight((int) (5 * hmult));
     splitter->setMaximumHeight((int) (5 * hmult));
 
+    addRadioMenuEntries();
+
     playlist_popup->addButton(tr("Search"), this,
                               SLOT(showSearchDialog()));
     playlist_popup->addButton(tr("From CD"), this,
@@ -523,14 +566,19 @@
                               SLOT(allTracks()));
     if (curMeta)
     {
-        playlist_popup->addButton(tr("Tracks by current Artist"), this,
-                                  SLOT(byArtist()));
-        playlist_popup->addButton(tr("Tracks from current Album"), this,
-                                  SLOT(byAlbum()));
-        playlist_popup->addButton(tr("Tracks from current Genre"), this,
-                                  SLOT(byGenre()));
-        playlist_popup->addButton(tr("Tracks from current Year"), this,
+        /*note: temporary fix for radio. Ideally, I'd like the
+          metadata repo add it's own stuff to the menur entry. 
+        */
+        if (curMeta->Format()!="cast") {
+            playlist_popup->addButton(tr("Tracks by current Artist"), this,
+                                      SLOT(byArtist()));
+            playlist_popup->addButton(tr("Tracks from current Album"), this,
+                                      SLOT(byAlbum()));
+            playlist_popup->addButton(tr("Tracks from current Genre"), this,
+                                      SLOT(byGenre()));
+            playlist_popup->addButton(tr("Tracks from current Year"), this,
                                   SLOT(byYear()));
+        }
     }
     
     playlist_popup->ShowPopup(this, SLOT(closePlaylistPopup()));
@@ -585,6 +633,77 @@
     }
 }
 
+void PlaybackBoxMusic::decoderHandlerReady(void)
+{    
+    decoder = decoderHandler->getDecoder();
+
+    decoder->setOutput(output);
+    decoder->setBlockSize(globalBlockSize);
+    decoder->addListener(this);
+
+    currentTime = 0;
+
+    mainvisual->setDecoder(decoder);
+    mainvisual->setOutput(output);
+    
+    if (decoder->initialize()) 
+    {
+        if (output)
+             output->Reset();
+         
+        decoder->start();
+        
+        isplaying = true;
+        // hmm, it'd be more fair to only inc playcount and set lastplay 
+        // when it's played at least x seconds of the track.
+        curMeta->setLastPlay();
+        curMeta->incPlayCount();    
+    }
+}
+
+void PlaybackBoxMusic::decoderHandlerInfo(const QString &message, 
+                                          const QString &submessage)
+{
+    Metadata tmp (*curMeta);
+
+    tmp.setArtist(submessage);
+    tmp.setTitle(message);
+
+    updateTrackInfo(&tmp);
+}
+
+void PlaybackBoxMusic::decoderHandlerOperationStart(const QString &op)
+{
+    operation_name = op;
+    operation_progress_count = 0;
+    decoderHandlerInfo(operation_name, 
+                       QString().fill('.', operation_progress_count));
+    decoder_handler_progress_timer->start(1000);
+}
+
+void PlaybackBoxMusic::decoderHandlerOperationStop()
+{
+    Metadata tmp (*curMeta);
+    updateTrackInfo(&tmp);
+    decoder_handler_progress_timer->stop();
+}
+
+void PlaybackBoxMusic::operationProgressTimer()
+{
+    operation_progress_count++;
+    decoderHandlerInfo(operation_name, 
+                       QString().fill('.', operation_progress_count));
+
+    // You get ten seconds...
+    if (operation_progress_count >= 10) 
+    {
+        decoderHandler->stop();
+        MythPopupBox::showOkPopup(gContext->GetMainWindow(), 
+                                  statusString,
+                                  QString("Operation timed out"));
+    }
+}
+
 void PlaybackBoxMusic::showSearchDialog()
 {
    if (!playlist_popup)
@@ -604,6 +723,93 @@
     }
 }
 
+void PlaybackBoxMusic::showAddRadioStationDialog()
+{
+   if (!playlist_popup)
+        return;
+
+    closePlaylistPopup();
+
+    Metadata *meta = new Metadata ("http://", "", "", "", "");
+    meta->setArtist ("");
+    meta->setAlbum ("");
+    meta->setTitle ("");
+    meta->setGenre ("");
+    meta->setFormat("cast");
+
+    MythThemedDialog *dialog = meta->createEditorDialog ();
+    if (dialog->exec()) {
+        all_music->addRadioTrack(meta);
+        /*note:temporary fix for radio
+
+          This is a really really poor way or inserting something into
+          the tree. It'd be way nicer to have a object managing
+          classes of metadata which could then do this.
+          
+         */
+        for (int ii = 0; ii < playlist_tree->childCount(); ii++)
+        {
+            GenericTree *t_node = playlist_tree->getChildAt(ii);
+
+            if (!t_node)
+                continue;
+
+            IntVector *attrs = t_node->getAttributes();
+            if (attrs && attrs->size() > 4) 
+            {
+                int counter = t_node->childCount();
+                QString title = meta->FormatArtist ();
+                title += " ~ ";
+                title += meta->Title();
+                meta->setAlbum(title);
+                GenericTree *sub = t_node->addNode(title, meta->ID(), true);
+                sub->setAttribute(0, 1);
+                sub->setAttribute(1, counter);
+                sub->setAttribute(2, rand());
+                sub->setAttribute(3, rand());
+                sub->setAttribute(4, 1);
+                t_node->sortByString();
+                music_tree_list->refresh();
+                break;
+            }
+        }
+    }
+
+    delete dialog;
+}
+
+void PlaybackBoxMusic::showRemoveRadioStationDialog()
+{
+   if (!playlist_popup)
+        return;
+
+    closePlaylistPopup();
+
+    GenericTree *node = music_tree_list->getCurrentNode();
+    Metadata *meta = all_music->getRadioMetadata(node->getInt());
+    
+    bool answer = MythPopupBox::showOkCancelPopup(gContext->GetMainWindow(), 
+                                                  QString("Remove Station"),
+                                                  QString("Do you want to remove\n"
+                                                          "%1 ~ %2?").
+                                                  arg(meta->Artist()).
+                                                  arg(meta->Title()),
+                                                  true);
+
+    if (!answer)
+        return;
+
+    meta->removeFromDatabase();    
+    
+    // Move away from the current node...
+    if (!music_tree_list->moveUp(false)) 
+        music_tree_list->moveDown(false);
+
+    node->getParent()->removeNode(node);
+    music_tree_list->refresh();
+}
+
+
 void PlaybackBoxMusic::byArtist()
 {
     if (!playlist_popup || !curMeta)
@@ -880,32 +1086,52 @@
 
 void PlaybackBoxMusic::showEditMetadataDialog()
 {
-    if(!curMeta)
-    {
+    MythThemedDialog *editDialog = 0;
+    GenericTree *node = music_tree_list->getCurrentNode();
+    IntVector *attrs = node->getAttributes();
+    Metadata *editMeta = 0;
+
+    /* note: temporary fix for radio, get the appropriate editor for repo */
+    if (GET_REPO_ID(attrs) == 1) 
+        editMeta = all_music->getRadioMetadata(node->getInt());
+    else
+        editMeta = all_music->getMetadata(node->getInt());
+
+    if(!editMeta)
         return;
-    }
 
-    // store the current track metadata in case the track changes
-    // while we show the edit dialog
-    Metadata *editMeta = curMeta;
-    GenericTree *node = music_tree_list->getCurrentNode();
+    editDialog = editMeta->createEditorDialog ();
 
-    EditMetadataDialog editDialog(editMeta, gContext->GetMainWindow(),
-                      "edit_metadata", "music-", "edit metadata");
-    if (editDialog.exec())
+    if (editDialog->exec())
     {
         // update the metadata copy stored in all_music
-        if (all_music->updateMetadata(editMeta->ID(), editMeta))
+        if (all_music->updateMetadata(GET_REPO_ID(attrs), editMeta->ID(), editMeta))
         {
            // update the displayed track info
            if (node)
            {
-               bool errorFlag;
-               node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
+               /* note: temporary fix for radio, get the label editor for repo */
+               if (GET_REPO_ID(attrs) == 1) 
+               {
+                   QString title = editMeta->FormatArtist ();
+                   title += " ~ ";
+                   title += editMeta->FormatTitle();
+                   editMeta->setAlbum(title);
+                   node->setString(editMeta->Album());
+               }
+               else
+               {
+                   bool errorFlag;
+                   node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
+               }
+
+               node->getParent()->sortByString();
                music_tree_list->refresh();
            }
         }
     }
+
+    delete editDialog;
 }
 
 void PlaybackBoxMusic::checkForPlaylists()
@@ -1074,9 +1300,6 @@
         return;
     }
 
-    QUrl sourceurl(playfile);
-    QString sourcename(playfile);
-
     if (!output)
         openOutputDevice();
 
@@ -1086,58 +1309,11 @@
         return;
     }
 
-    if (!sourceurl.isLocalFile()) 
-    {
-        StreamInput streaminput(sourceurl);
-        streaminput.setup();
-        input = streaminput.socket();
-    } 
-    else
-        input = new QFile(playfile);
-    
-    if (decoder && !decoder->factory()->supports(sourcename))
-        decoder = 0;
+    if (!decoderHandler) 
+        setupDecoderHandler();
 
-    if (!decoder) 
-    {
-        decoder = Decoder::create(sourcename, input, output);
-
-        if (!decoder) 
-        {
-            printf("mythmusic: unsupported fileformat\n");
-            stopAll();
-            return;
-        }
-
-        decoder->setBlockSize(globalBlockSize);
-        decoder->addListener(this);
-    } 
-    else 
-    {
-        decoder->setInput(input);
-        decoder->setFilename(sourcename);
-        decoder->setOutput(output);
-    }
-
-    currentTime = 0;
-
-    mainvisual->setDecoder(decoder);
-    mainvisual->setOutput(output);
-    
-    if (decoder->initialize()) 
-    {
-        if (output)
-        {
-            output->Reset();
-        }
-
-        decoder->start();
-
-        bannerEnable(curMeta);
-        isplaying = true;
-        curMeta->setLastPlay();
-        curMeta->incPlayCount();    
-    }
+    decoderHandler->start(curMeta);
+    mainvisual->setMetadata(curMeta);
 }
 
 void PlaybackBoxMusic::visEnable()
@@ -1154,6 +1330,12 @@
     }
 }
 
+void PlaybackBoxMusic::bannerDisable()
+{
+    banner_timer->stop();
+    mainvisual->clearInformation();
+}
+
 void PlaybackBoxMusic::bannerEnable(QString text, int millis)
 {
     if (visualizer_status != 2) 
@@ -1165,8 +1347,23 @@
 
 void PlaybackBoxMusic::bannerEnable(Metadata *mdata)
 {
-    bannerEnable("\"" + mdata->Title() + "\"\n" +
-                        mdata->Artist() + " - " + mdata->Album(), 8000);
+    if (visualizer_status != 2) 
+        return;
+
+    banner_timer->start(8000);
+    QImage image;
+
+    QByteArray array = curMeta->AlbumArt ();
+    if (array.isEmpty()) {
+        MetaIO *tagger = decoder->doCreateTagger();
+        if (tagger) {
+            array = tagger->getAlbumArt(curMeta->Filename());
+            delete tagger;
+        }
+    }
+
+    image.loadFromData (array);
+    mainvisual->addInformation(image, mdata->FormatInfo());
 }
 
 void PlaybackBoxMusic::bannerToggle(Metadata *mdata) 
@@ -1177,12 +1374,6 @@
         bannerEnable(mdata);
 }
 
-void PlaybackBoxMusic::bannerDisable()
-{
-    banner_timer->stop();
-    mainvisual->addInformation("");
-}
-
 void PlaybackBoxMusic::CycleVisualizer()
 {
     QString new_visualizer;
@@ -1245,7 +1436,7 @@
     bannerEnable(tr("Visualization: ") + new_visualizer, 4000);
 }
 
-void PlaybackBoxMusic::setTrackOnLCD(Metadata *mdata)
+void PlaybackBoxMusic::setTrackOnLCD(const Metadata *mdata)
 {
     LCD *lcd = LCD::Get();
     if (!lcd)
@@ -1276,26 +1467,15 @@
 
 void PlaybackBoxMusic::stopDecoder(void)
 {
-    if (decoder && decoder->running()) 
-    {
-        decoder->lock();
-        decoder->stop();
-        decoder->unlock();
-    }
-
-    if (decoder) 
-    {
-        decoder->lock();
-        decoder->cond()->wakeAll();
-        decoder->unlock();
-    }
-
-    if (decoder)
-        decoder->wait();
+    printf ("PlaybackBoxMusic::stopDecoder\n");
+    decoderHandler->stop();
 }
 
 void PlaybackBoxMusic::stop(void)
 {
+    decoder_handler_progress_timer->stop();
+    stopDecoder();
+
     if (output)
     {
         output->Reset();
@@ -1306,14 +1486,9 @@
         }
     }
 
-    stopDecoder();
-
     mainvisual->setDecoder(0);
     mainvisual->setOutput(0);
 
-    delete input;
-    input = 0;
-
     QString time_string;
     int maxh = maxTime / 3600;
     int maxm = (maxTime / 60) % 60;
@@ -1394,10 +1569,15 @@
 
     isplaying = false;
 
-    if (repeatmode == REPEAT_TRACK)
-        play();
-    else 
-        next();
+    if (! decoderHandler->done()) 
+        decoderHandler->next();
+    else if (curMeta->Format() != "cast") 
+    {
+        if (repeatmode == REPEAT_TRACK)
+            play();
+        else 
+            next();
+    }
 }
 
 void PlaybackBoxMusic::seekforward()
@@ -1514,6 +1694,8 @@
         music_tree_list->setVisualOrdering(1);
     music_tree_list->refresh();
     
+    // refreshing the music_tree causes the LCD to get redrawn
+    // with the tree, so force back to the music screen.
     if (isplaying)
         setTrackOnLCD(curMeta);
 }
@@ -1808,11 +1990,17 @@
 
             break;
         }
+        case DecoderEvent::Decoding:
+        {
+            statusString = tr("Stream decoding.");
+            displayMeta = *curMeta;
+            break;
+        }
         case DecoderEvent::Stopped:
         {
             statusString = tr("Stream stopped.");
-
-            break;
+            if (mainvisual->getCurrentVisual() == "AlbumArt")
+                break;
         }
         case DecoderEvent::Finished:
         {
@@ -1836,6 +2024,54 @@
                                       .arg(*dxe->errorMessage()));
             break;
         }
+        case DecoderHandlerEvent::Ready:
+        {
+            decoderHandlerReady();
+            break;
+        }
+        case DecoderHandlerEvent::OperationStart:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            decoderHandlerOperationStart(*dxe->getMessage());
+            break;
+        }
+        case DecoderHandlerEvent::OperationStop:
+        {
+            decoderHandlerOperationStop();
+            break;
+        }
+        case DecoderHandlerEvent::Error:
+        {
+            statusString = tr("Input error.");
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            cerr << statusString << " " << *dxe->getMessage() << endl;
+            MythPopupBox::showOkPopup(gContext->GetMainWindow(), 
+                                      statusString,
+                                      QString("MythMusic has encountered the following error:\n%1")
+                                      .arg(*dxe->getMessage()));
+            stopAll();
+            break;
+        }
+        case DecoderHandlerEvent::Info:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            Metadata tmp (*curMeta);
+            tmp.setArtist("");
+            tmp.setTitle(*dxe->getMessage());            
+            updateTrackInfo(&tmp);
+            break;
+        }
+        case DecoderHandlerEvent::Meta:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            displayMeta = *dxe->getMetadata();
+            updateTrackInfo(&displayMeta);
+
+            if (visualizer_status > 0 && cycle_visualizer)
+                CycleVisualizer();
+
+            break;
+        }
     }
 
     QWidget::customEvent(event);
@@ -1869,6 +2105,7 @@
         album_text->SetText(mdata->Album());
     
     setTrackOnLCD(mdata);
+    bannerEnable(mdata);            
 }
 
 void PlaybackBoxMusic::openOutputDevice(void)
@@ -1891,6 +2128,12 @@
     output->addVisual(mainvisual);    
 }
 
+void PlaybackBoxMusic::setupDecoderHandler() 
+{
+    decoderHandler = new DecoderHandler();
+    decoderHandler->addListener(this);
+}
+
 void PlaybackBoxMusic::handleTreeListSignals(int node_int, IntVector *attributes)
 {
     if (attributes->size() < 4)
@@ -1904,16 +2147,17 @@
     {
         //  It's a track
 
-        curMeta = all_music->getMetadata(node_int);
-        if (title_text)
-            title_text->SetText(curMeta->FormatTitle());
-        if (artist_text)
-            artist_text->SetText(curMeta->FormatArtist());
-        if (album_text)
-            album_text->SetText(curMeta->Album());
+        /*note: temporary fix for radio.
+          Ideally, I'd like for each metadata to carry a fifth
+          attribute, identifying the repository it came from, such as
+          CD, harddrive, iPod etc.
+         */
+        if (GET_REPO_ID(attributes) == 1) 
+            curMeta = all_music->getRadioMetadata(node_int);
+        else 
+            curMeta = all_music->getMetadata(node_int);
 
-        setTrackOnLCD(curMeta);
-
+        updateTrackInfo(curMeta);
         maxTime = curMeta->Length() / 1000;
 
         QString time_string;
Index: mythplugins/mythmusic/mythmusic/mythmusic.pro
===================================================================
--- mythplugins/mythmusic/mythmusic/mythmusic.pro	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/mythmusic.pro	(working copy)
@@ -38,6 +38,7 @@
 HEADERS += editmetadata.h smartplaylist.h search.h genres.h
 HEADERS += treebuilders.h importmusic.h directoryfinder.h
 HEADERS += filescanner.h
+HEADERS += shoutcast.h decoderhandler.h editradiometadata.h pls.h
 
 SOURCES += cddecoder.cpp cdrip.cpp decoder.cpp 
 SOURCES += flacdecoder.cpp flacencoder.cpp maddecoder.cpp main.cpp
@@ -53,4 +54,4 @@
 SOURCES += goom/zoom_filter_mmx.c goom/zoom_filter_xmmx.c goom/mythgoom.cpp
 SOURCES += avfdecoder.cpp editmetadata.cpp smartplaylist.cpp search.cpp
 SOURCES += treebuilders.cpp importmusic.cpp directoryfinder.cpp
-SOURCES += filescanner.cpp
+SOURCES += shoutcast.cpp decoderhandler.cpp editradiometadata.cpp pls.cpp 
Index: mythplugins/mythmusic/mythmusic/decoderhandler.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/decoderhandler.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/decoderhandler.cpp	(revision 0)
@@ -0,0 +1,541 @@
+#include "decoderhandler.h"
+#include "decoder.h"
+#include "metadata.h"
+#include "streaminput.h"
+#include "shoutcast.h"
+
+#include <qapplication.h>
+#include <qurl.h>
+#include <qurloperator.h>
+
+#include <mythtv/httpcomms.h>
+#include <mythtv/mythcontext.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+
+/**********************************************************************/
+
+DecoderHandlerEvent::DecoderHandlerEvent(const Metadata &m)
+    : MythEvent(Meta), m_msg(NULL), m_meta(NULL)
+{ 
+    m_meta = new Metadata(m);
+}
+
+DecoderHandlerEvent::~DecoderHandlerEvent()
+{
+    delete m_msg;
+    delete m_meta;
+}
+
+DecoderHandlerEvent* DecoderHandlerEvent::clone() 
+{ 
+    DecoderHandlerEvent *result = new DecoderHandlerEvent(*this);
+
+    if (m_msg)
+        result->m_msg = new QString(*m_msg);
+
+    if (m_meta)
+        result->m_meta = new Metadata(*m_meta);
+
+    return result;
+}
+
+/**********************************************************************/
+
+DecoderIOFactory::DecoderIOFactory(DecoderHandler *parent) 
+{
+    m_handler = parent;
+}
+
+DecoderIOFactory::~DecoderIOFactory()
+{
+}
+
+void DecoderIOFactory::doConnectDecoder(const QString &format) 
+{ 
+    m_handler->doOperationStop();
+    m_handler->doConnectDecoder(m_url, format); 
+}
+
+Decoder *DecoderIOFactory::getDecoder()
+{
+    return m_handler->getDecoder();
+}
+
+void DecoderIOFactory::doFailed(const QString &message) 
+{ 
+    m_handler->doOperationStop();
+    m_handler->doFailed(m_url, message); 
+}
+
+void DecoderIOFactory::doInfo(const QString &message) 
+{ 
+    m_handler->doInfo(message);
+}
+
+void DecoderIOFactory::doOperationStart(const QString &name)
+{
+    m_handler->doOperationStart(name);
+}
+
+void DecoderIOFactory::doOperationStop(void)
+{
+    m_handler->doOperationStop();
+}
+
+/**********************************************************************/
+
+DecoderIOFactoryFile::DecoderIOFactoryFile(DecoderHandler *parent) 
+    : DecoderIOFactory(parent), m_input (NULL)
+{
+}
+
+DecoderIOFactoryFile::~DecoderIOFactoryFile () 
+{
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryFile::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void 
+DecoderIOFactoryFile::start()
+{
+    QString sourcename = m_meta->Filename();    
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Opening Local File %1").arg(sourcename));
+    m_input = new QFile(sourcename);
+    doConnectDecoder(m_url);
+}
+
+
+/**********************************************************************/
+
+DecoderIOFactoryUrl::DecoderIOFactoryUrl(DecoderHandler *parent) : DecoderIOFactory(parent) 
+{
+    /* Yes, this is pretty lame, it doesn't throttle the download or
+       anything, but just blindly downloads the entire file. 
+       However, it supports whatever QUrlOperator supports and you should
+       see it as an example that you can enhance...
+    */
+    m_url_op = new QUrlOperator;
+    m_input = new QFile("/tmp/mythmusic.tmp");
+    m_input->open(IO_ReadOnly);
+    m_output = new QFile("/tmp/mythmusic.tmp");
+    m_output->open(IO_WriteOnly);
+    
+    connect(m_url_op, SIGNAL(finished(QNetworkOperation*)), 
+            this, SLOT(finished(QNetworkOperation*)));;
+    connect(m_url_op, SIGNAL(start(QNetworkOperation*)), 
+            this, SLOT(start(QNetworkOperation*)));;
+    connect(m_url_op, SIGNAL(data(const QByteArray&, QNetworkOperation*)), 
+            this, SLOT(data(const QByteArray&, QNetworkOperation*)));;
+}
+
+DecoderIOFactoryUrl::~DecoderIOFactoryUrl () 
+{
+    doClose();
+    
+    m_url_op->deleteLater();
+    m_output->remove();
+
+    delete m_output;
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryUrl::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void DecoderIOFactoryUrl::start()
+{
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Url %1").arg(m_url.toString()));
+
+    m_started = false;
+
+    doOperationStart("Fetching remote file");
+    
+    m_url_op->get(m_url);
+}
+
+void DecoderIOFactoryUrl::stop()
+{
+    doClose();
+}
+
+void DecoderIOFactoryUrl::finished(QNetworkOperation *op)
+{
+    m_output->close();
+
+    if (op->state() != QNetworkProtocol::StDone) 
+    {
+        doFailed("Cannot retrieve remote file.");
+        return;
+    }
+    
+    if (!m_started)
+        doStart();
+}
+
+void DecoderIOFactoryUrl::start(QNetworkOperation*)
+{
+}
+
+void DecoderIOFactoryUrl::data(const QByteArray & data, QNetworkOperation*)
+{
+    m_output->writeBlock(data);
+    if (!m_started && m_input->size () > DecoderIOFactory::DefaultPrebufferSize) 
+        doStart();
+}
+
+void DecoderIOFactoryUrl::doStart()
+{
+    doConnectDecoder(m_url);
+    m_started = true;
+}
+
+void DecoderIOFactoryUrl::doClose()
+{
+    if (m_input->isOpen())
+        m_input->close();
+    if (m_output->isOpen())
+        m_output->close();
+}
+
+/**********************************************************************/
+
+/* I've left this here since it used to exist... Don't even know what
+ * mqp is, much less how to test it... 
+ */
+
+DecoderIOFactoryMqp::DecoderIOFactoryMqp(DecoderHandler *parent) : DecoderIOFactory(parent) 
+{
+    m_input = NULL;
+}
+
+DecoderIOFactoryMqp::~DecoderIOFactoryMqp () 
+{
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryMqp::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void DecoderIOFactoryMqp::start()
+{
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Mqp %1").arg(m_url.toString()));
+    StreamInput streaminput(m_url);
+    streaminput.setup();
+    m_input = streaminput.socket();
+    doConnectDecoder(m_url);
+}
+
+void DecoderIOFactoryMqp::stop()
+{
+    m_input->close();
+}
+
+/**********************************************************************/
+
+DecoderHandler::DecoderHandler() 
+    : m_state (STOPPED), 
+      m_io_factory (NULL), 
+      m_decoder (NULL), 
+      m_op (false), 
+      m_redirects (0)
+{
+}
+
+DecoderHandler::~DecoderHandler() 
+{
+    stop();
+}
+
+void DecoderHandler::start(Metadata *mdata) 
+{
+    m_state = LOADING;
+
+    m_playlist.clear();
+    m_meta = mdata;
+    m_playlist_pos = -1;
+    m_redirects = 0;
+    
+    QUrl url(mdata->Filename());
+    bool result = createPlaylist(url);
+    
+    if (m_state == LOADING && result) 
+    {
+        for (int ii = 0; ii < m_playlist.size(); ii++) 
+            VERBOSE(VB_PLAYBACK, QString("Track %1 = %2").
+                    arg(ii).
+                    arg(m_playlist.get(ii)->File()));
+        next();
+    } else {
+        if (m_state != STOPPED) {
+            doFailed(url, "Could not get playlist");
+        }
+    }
+}
+
+void DecoderHandler::error(const QString &e) 
+{
+    QString *str = new QString(e.utf8());
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, str);
+    dispatch(ev);
+}
+
+bool DecoderHandler::done() 
+{
+    if (m_state == STOPPED)
+        return true;
+
+    if (m_playlist_pos + 1 >= m_playlist.size()) 
+    {
+        m_state = STOPPED;
+        return true;
+    }
+
+    return false;
+}
+
+bool DecoderHandler::next() 
+{
+    if (done())
+        return false;
+
+    m_playlist_pos++;
+
+    PlayListFileEntry *entry = m_playlist.get(m_playlist_pos);
+    QUrl url(entry->File());
+
+    VERBOSE(VB_PLAYBACK, QString("Now playing '%1'").arg(url.toString()));
+
+    deleteIOFactory();
+    createIOFactory(url);
+
+    if (! haveIOFactory()) 
+        return false;
+
+    getIOFactory()->addListener(this);
+    getIOFactory()->setUrl(url);
+    getIOFactory()->setMeta(m_meta);
+    getIOFactory()->start();
+    m_state = ACTIVE;
+
+    return true;
+}
+
+void DecoderHandler::stop()
+{
+    if (m_decoder) {
+        m_decoder->lock();
+        m_decoder->stop();
+        m_decoder->unlock();
+    }
+    if (m_decoder) {
+        m_decoder->lock();
+        m_decoder->cond()->wakeAll();
+        m_decoder->unlock();
+    }
+
+    if (m_decoder) {
+        m_decoder->wait();
+        delete m_decoder->input ();
+        m_decoder->setInput (NULL);
+    }
+
+    if (m_decoder)
+
+    deleteIOFactory ();
+    doOperationStop();
+
+    m_state = STOPPED;
+}
+
+void DecoderHandler::customEvent(QCustomEvent *qevent)
+{
+    if (class DecoderHandlerEvent *event = dynamic_cast<DecoderHandlerEvent*>(qevent)) {
+        // Proxy all DecoderHandlerEvents
+        return dispatch(*event);    
+    }
+}
+
+bool DecoderHandler::createPlaylist(const QUrl &url)
+{
+    if (url.fileName().right(4).lower() != ".pls")
+        return createPlaylistForSingleFile(url);
+    
+    if (url.isLocalFile()) 
+        return createPlaylistFromFile(url);
+    else
+        return createPlaylistFromRemoteUrl(url);
+
+    return false;
+}
+
+bool DecoderHandler::createPlaylistForSingleFile(const QUrl &url)
+{
+    PlayListFileEntry *entry = new PlayListFileEntry;
+
+    if (url.isLocalFile())
+        entry->setFile(url.dirPath()+'/'+url.fileName());
+    else
+        entry->setFile(url.toString());
+
+    m_playlist.add(entry);
+
+    return m_playlist.size() > 0;
+}
+
+bool DecoderHandler::createPlaylistFromFile(const QUrl &url)
+{
+    QFile f(url.fileName());
+    f.open(IO_ReadOnly);
+    QTextStream stream(&f);
+    
+    if (PlayListFile::parse(&m_playlist, &stream) < 0) 
+        return false;
+
+    return m_playlist.size() > 0;
+}
+
+bool DecoderHandler::createPlaylistFromRemoteUrl(const QUrl &url) 
+{
+    HttpComms comms;
+    QUrl _url(url);
+
+    VERBOSE(VB_NETWORK, QString("Retrieving playlist from %1").arg(_url.toString()));
+
+    doOperationStart("Retrieving playlist");
+    comms.request(_url, 10000, false);    
+    while(!comms.isDone()) 
+        qApp->processEvents(500);
+    doOperationStop();
+
+    VERBOSE(VB_NETWORK, QString("Retrieving playlist from %1 = %2 (%3)").
+            arg(_url.toString()).
+            arg(comms.getStatusCode ()).
+            arg(comms.isTimedout()?"timed out":"not timed out"));
+
+    if (comms.isTimedout()) 
+        return false;
+
+    if (comms.getStatusCode() == 302) 
+    {
+        QString redir = comms.getRedirectedURL();
+        m_redirects++;
+        if (m_redirects > MaxRedirects) {
+            VERBOSE(VB_IMPORTANT, QString ("Too many redirections."));
+            return false;
+        }
+        return createPlaylistFromRemoteUrl(redir);
+    }
+
+    if (comms.getStatusCode() != 200)
+        return false;
+
+    QBuffer buffer(comms.getRawData());
+    buffer.open(IO_ReadOnly);
+    QTextStream stream(&buffer);
+    
+    bool result = PlayListFile::parse(&m_playlist, &stream) > 0;
+    return result;
+}
+
+void DecoderHandler::doConnectDecoder(const QUrl &url, const QString &format) 
+{
+    if (m_decoder && !m_decoder->factory()->supports(format)) {
+        m_decoder = NULL;
+    }
+
+    if (! m_decoder) {
+        if ((m_decoder = Decoder::create(format, NULL, NULL)) == NULL) {
+            doFailed(url, "No decoder for this format");
+            return;
+        }
+    }
+
+    m_decoder->setInput(getIOFactory()->takeInput());
+    m_decoder->setFilename(url.toString());
+
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Ready);
+    dispatch(ev);
+}
+
+void DecoderHandler::doFailed(const QUrl &url, const QString &message)
+{
+    printf("mythmusic: unsupported fileformat: %s\n", url.toString().ascii ());
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, new QString (message));
+    dispatch(ev);
+}
+
+void DecoderHandler::doInfo(const QString &message)
+{
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Info, new QString (message));
+    dispatch(ev);
+}
+
+void DecoderHandler::doOperationStart(const QString &name)
+{
+    m_op = true;
+    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStart, new QString (name));
+    dispatch(ev);
+}
+
+void DecoderHandler::doOperationStop(void)
+{
+    if (!m_op)
+        return;
+
+    m_op = false;
+    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStop);
+    dispatch(ev);
+}
+
+void DecoderHandler::createIOFactory(const QUrl &url)
+{
+    PlayListFile::test();
+
+    if (haveIOFactory()) 
+        deleteIOFactory();
+
+    if (url.isLocalFile()) {
+        m_io_factory = new DecoderIOFactoryFile(this);
+    }
+    else
+    {
+        if (url.protocol() == "mqp" && !url.host().isNull()) 
+            m_io_factory = new DecoderIOFactoryMqp(this);
+        else if (m_meta && m_meta->Format() == "cast")
+            m_io_factory = new DecoderIOFactoryShoutCast(this);
+        else
+            m_io_factory = new DecoderIOFactoryUrl(this);
+    }
+}
+
+void DecoderHandler::deleteIOFactory(void)
+{
+    if (! haveIOFactory())
+        return;
+
+    if (m_state == ACTIVE)
+        m_io_factory->stop ();
+
+    //m_io_factory->removeListener(this);
+    //m_io_factory->disconnect();
+    //m_io_factory->deleteLater();
+    delete m_io_factory;
+    m_io_factory = NULL;
+}
Index: mythplugins/mythmusic/mythmusic/editradiometadata.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/editradiometadata.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/editradiometadata.cpp	(revision 0)
@@ -0,0 +1,356 @@
+#include <mythtv/mythcontext.h>
+#include <mythtv/mythdbcon.h>
+#include <qdir.h>
+#include "editradiometadata.h"
+#include "metadata.h"
+#include "decoder.h"
+#include "genres.h"
+
+EditRadioMetadataDialog::EditRadioMetadataDialog(Metadata *source_metadata,
+                                                 MythMainWindow *parent,
+                                                 QString window_name,
+                                                 QString theme_filename,
+                                                 const char* name)
+    :MythThemedDialog(parent, window_name, theme_filename, name)
+{
+    // make a copy so we can abandon changes
+    m_metadata = new Metadata(*source_metadata);
+    m_sourceMetadata = source_metadata;
+    wireUpTheme();
+    fillWidgets();
+    assignFirstFocus();
+}
+
+void EditRadioMetadataDialog::fillWidgets()
+{
+    if (station_edit)
+    {
+        station_edit->setText(m_metadata->Artist());
+    }
+
+    if (channel_edit)
+    {
+        channel_edit->setText(m_metadata->Title());
+    }
+
+    if (url_edit)
+    {
+        url_edit->setText(m_metadata->Filename());
+    }
+
+    if (metaformat_edit)
+    {
+        metaformat_edit->setText(m_metadata->CompilationArtist());
+    }
+
+    if (genre_edit)
+    {
+        genre_edit->setText(m_metadata->Genre());
+    }
+
+    if (rating_image)
+    {
+        rating_image->setRepeat(m_metadata->Rating());
+    }
+}
+
+void EditRadioMetadataDialog::incRating(bool up_or_down)
+{
+    if (up_or_down)
+        m_metadata->incRating();
+    else
+        m_metadata->decRating();
+
+    fillWidgets();
+}
+
+void EditRadioMetadataDialog::keyPressEvent(QKeyEvent *e)
+{
+    bool handled = false;
+
+    QStringList actions;
+    gContext->GetMainWindow()->TranslateKeyPress("Video", e, actions);
+
+    for (unsigned int i = 0; i < actions.size() && !handled; i++)
+    {
+        QString action = actions[i];
+        handled = true;
+
+        if (action == "UP")
+            nextPrevWidgetFocus(false);
+        else if (action == "DOWN")
+            nextPrevWidgetFocus(true);
+        else if (action == "LEFT")
+        {
+            if (getCurrentFocusWidget() == rating_button)
+            {
+                rating_button->push();
+                incRating(false);
+            }
+        }
+        else if (action == "RIGHT")
+        {
+            if (getCurrentFocusWidget() == rating_button)
+            {
+                rating_button->push();
+                incRating(true);
+            }
+        }
+        else if (action == "SELECT")
+        {
+            activateCurrent();
+        }
+        else if (action == "0")
+        {
+            if (done_button)
+                done_button->push();
+        }
+        else if (action == "1")
+        {
+        }
+        else
+            handled = false;
+    }
+
+    if (!handled)
+        MythThemedDialog::keyPressEvent(e);
+}
+
+void EditRadioMetadataDialog::wireUpTheme()
+{
+    station_edit = getUIRemoteEditType("station_edit");
+    if (station_edit)
+    {
+        station_edit->createEdit(this);
+        connect(station_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    channel_edit = getUIRemoteEditType("channel_edit");
+    if (channel_edit)
+    {
+        channel_edit->createEdit(this);
+        connect(channel_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    url_edit = getUIRemoteEditType("url_edit");
+    if (url_edit)
+    {
+        url_edit->createEdit(this);
+        connect(url_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    metaformat_edit = getUIRemoteEditType("metaformat_edit");
+    if (metaformat_edit)
+    {
+        metaformat_edit->createEdit(this);
+        connect(metaformat_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+         
+    genre_edit = getUIRemoteEditType("genre_edit");
+    if (genre_edit)
+    {
+        genre_edit->createEdit(this);
+        connect(genre_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+        
+    rating_image = getUIRepeatedImageType("rating_image");
+    
+    searchstation_button = getUIPushButtonType("searchstation_button");
+    if (searchstation_button)
+    {
+        connect(searchstation_button, SIGNAL(pushed()), this, SLOT(searchStation()));
+    }
+
+    searchgenre_button = getUIPushButtonType("searchgenre_button");
+    if (searchgenre_button)
+    {
+        connect(searchgenre_button, SIGNAL(pushed()), this, SLOT(searchGenre()));
+    }
+
+    done_button = getUITextButtonType("done_button");
+    if (done_button)
+    {
+        done_button->setText(tr("Done"));
+        connect(done_button, SIGNAL(pushed()), this, SLOT(showSaveMenu()));
+    }
+
+    rating_button = getUISelectorType("rating_button");
+    if (rating_button)
+    {
+        
+    }
+
+    buildFocusList();
+}
+
+void EditRadioMetadataDialog::editLostFocus()
+{
+    UIRemoteEditType *whichEditor = (UIRemoteEditType *) getCurrentFocusWidget();
+    
+    if (whichEditor == station_edit)
+    {
+        m_metadata->setArtist(station_edit->getText());
+    }
+    else if (whichEditor == channel_edit)
+    {
+        m_metadata->setTitle(channel_edit->getText());
+    }
+    else if (whichEditor == url_edit)
+    {
+        m_metadata->setFilename(url_edit->getText());
+    }
+    else if (whichEditor == metaformat_edit)
+    {
+        m_metadata->setCompilationArtist(metaformat_edit->getText());
+    }
+    else if (whichEditor == genre_edit)
+    {
+        m_metadata->setGenre(genre_edit->getText());
+    }
+}
+
+bool EditRadioMetadataDialog::showList(QString caption, QString &value)
+{
+    bool res = false;
+    
+    MythSearchDialog *searchDialog = new MythSearchDialog(gContext->GetMainWindow(), "");
+    searchDialog->setCaption(caption);
+    searchDialog->setSearchText(value);
+    searchDialog->setItems(searchList);
+    if (searchDialog->ExecPopup() == 0)
+    {
+        value = searchDialog->getResult();
+        res = true;
+    }
+    
+    delete searchDialog;
+    setActiveWindow();
+    
+    return res;     
+}
+
+void EditRadioMetadataDialog::fillSearchList(QString field, QString table)
+{
+    searchList.clear();
+    
+    QString querystr;
+    querystr = QString("SELECT DISTINCT %1 FROM %2 ORDER BY %3").
+        arg(field).arg(table).arg(field);
+         
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.exec(querystr);
+
+    if (query.isActive() && query.size())
+    {
+        while (query.next())
+        {
+            searchList << QString::fromUtf8(query.value(0).toString());
+        }
+    }         
+}
+    
+void EditRadioMetadataDialog::searchStation()
+{
+    QString s;
+    
+    fillSearchList("station", "music_radios");
+    
+    s = m_metadata->Artist();
+    if (showList(tr("Select a Station"), s))
+    {
+        m_metadata->setArtist(s);
+        fillWidgets();
+    }
+}
+
+void EditRadioMetadataDialog::searchGenre()
+{
+    QString s;
+
+    // load genre list
+    searchList.clear();
+    for (int x = 0; x < genre_table_size; x++)
+        searchList.push_back(QString(genre_table[x]));
+    searchList.sort();
+
+    s = m_metadata->Genre();
+    if (showList(tr("Select a Genre"), s))
+    {
+        m_metadata->setGenre(s);
+        fillWidgets();
+    }
+   
+}
+
+void EditRadioMetadataDialog::closeDialog()
+{
+    cancelPopup();
+    done(0);  
+}
+
+void EditRadioMetadataDialog::showSaveMenu()
+{
+    popup = new MythPopupBox(gContext->GetMainWindow(), "Menu");
+
+    QLabel *label = popup->addLabel(tr("Save Changes?"), MythPopupBox::Large, false);
+    label->setAlignment(Qt::AlignCenter | Qt::WordBreak);
+
+    QButton *topButton = popup->addButton(tr("Save"), this,
+                         SLOT(saveToDatabase()));
+    popup->addButton(tr("Exit/Do Not Save"), this,
+                         SLOT(closeDialog()));
+    popup->addButton(tr("Cancel"), this, SLOT(cancelPopup()));
+
+    popup->ShowPopup(this, SLOT(cancelPopup()));
+
+    topButton->setFocus();
+}
+
+void EditRadioMetadataDialog::cancelPopup()
+{
+  if (!popup)
+      return;
+
+  popup->hide();
+
+  delete popup;
+  popup = NULL;
+  setActiveWindow();
+}
+
+bool EditRadioMetadataDialog::verifyEntries()
+{
+    // TODO: check valid text in url and metaformat...
+    return true;
+}
+
+void EditRadioMetadataDialog::saveNewToDatabase()
+{
+    cancelPopup();
+
+    if (!verifyEntries()) 
+        return;
+
+    m_metadata->dumpToDatabase();        
+    *m_sourceMetadata = m_metadata;
+
+    done(1);
+}
+
+void EditRadioMetadataDialog::saveToDatabase()
+{
+    cancelPopup();
+
+    if (m_metadata->ID() == 0) 
+        return saveNewToDatabase();
+
+    m_metadata->dumpToDatabase();        
+    *m_sourceMetadata = m_metadata;
+
+    done(1);
+}
+
+EditRadioMetadataDialog::~EditRadioMetadataDialog()
+{
+    delete m_metadata;
+}
Index: mythplugins/mythmusic/mythmusic/decoder.h
===================================================================
--- mythplugins/mythmusic/mythmusic/decoder.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/decoder.h	(working copy)
@@ -37,8 +37,7 @@
 
     ~DecoderEvent()
     {
-        if (error_msg)
-            delete error_msg;
+        delete error_msg;
     }
 
     const QString *errorMessage() const { return error_msg; }
Index: mythplugins/mythmusic/mythmusic/visualize.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/visualize.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/visualize.cpp	(working copy)
@@ -11,14 +11,15 @@
 
 #include <cmath>
 #include "mainvisual.h"
+#include "metaio.h"
 #include "visualize.h"
 #include "inlines.h"
 #include "decoder.h"
+#include "metadata.h"
 
 #include <qpainter.h>
 #include <qpixmap.h>
 #include <qimage.h>
-#include <qdir.h>
 #include <qurl.h>
 
 #include <iostream>
@@ -327,6 +328,8 @@
         return true;
 
     if (filename != pParent->decoder()->getFilename()) {
+        //return true;
+
         QString curdir = QUrl(pParent->decoder()->getFilename()).dirPath();
         if (directory != curdir) {
             directory = curdir;
@@ -337,23 +340,16 @@
     return false;
 }
 
-QString AlbumArt::getImageFilename() 
+QByteArray AlbumArt::getImage() 
 {
-    QString result;
-    QString curfile = pParent->decoder()->getFilename();
-    QString curdir = QUrl(curfile).dirPath();
-    QString namefilter = gContext->GetSetting("AlbumArtFilter",
-                                              "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
-    // Get directory contents based on filter
-    QDir folder(curdir, namefilter, QDir::Name | QDir::IgnoreCase, 
-                QDir::Files | QDir::Hidden);
-    
-    if (folder.count())
-        result = folder[rand() % folder.count()];
-
-    result.prepend("/");
-    result.prepend(curdir);
-    
+    QByteArray result = pParent->getMetadata()->AlbumArt();
+    if (result.isEmpty()) {
+        MetaIO *tagger = pParent->decoder()->doCreateTagger();
+        if (tagger) {
+            result = tagger->getAlbumArt(pParent->decoder()->getFilename());
+            delete tagger;
+        }
+    }
     return result;
 }
 
@@ -365,21 +361,24 @@
     // If the directory has changed (new album) or the size, reload
     if (needsUpdate())
     {
-        QImage art(getImageFilename());
+        QImage art(getImage());
         if (art.isNull())
-        {
-            drawWarning(p, back, size, QObject::tr("?"));
-            cursize = size;
-            return true;
-        }
-        image = art.smoothScale(size, QImage::ScaleMin);
+            image.reset();
+        else
+            image = art.smoothScale(size, QImage::ScaleMin);
+
+        // Store the new filename
+        filename = pParent->decoder()->getFilename();
     }
 
     // Paint the image
     p->fillRect(0, 0, size.width(), size.height(), back);
-    p->drawPixmap((size.width() - image.width()) / 2,
-                  (size.height() - image.height()) / 2,
-                  image);
+    if (image.isNull())
+        drawWarning(p, back, size, QObject::tr("?"));
+    else
+        p->drawPixmap((size.width() - image.width()) / 2,
+                      (size.height() - image.height()) / 2,
+                      image);
     
     // Store our new size
     cursize = size;
@@ -458,6 +457,8 @@
 {
     number_of_squares = 16;
     fake_height = number_of_squares * analyzerBarWidth;
+    startHue = 220;
+    targetHue = 360;
 }
 
 Squares::~Squares()
@@ -471,6 +472,11 @@
     size = newsize;
 }
 
+bool Squares::process(VisualNode *node) {
+    rotateHues ();
+    return Spectrum::process (node);
+}
+
 void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h) 
 {
     double r, g, b, per;
@@ -502,9 +508,22 @@
     g = clamp(g, 255.0, 0.0);
     b = clamp(b, 255.0, 0.0);
 
-    p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
+    p->fillRect (x, y, w, h, QColor((int)r, (int)g, (int)b));
 }
 
+void Squares::rotateHues () {
+    targetHue ++;
+    if (targetHue >= 360)
+        targetHue = 0;
+
+    startHue ++;
+    if (startHue >= 360)
+        startHue = 0;
+
+    startColor = QColor (startHue, 255, 255, QColor::Hsv);
+    targetColor = QColor (targetHue, 255, 255, QColor::Hsv);
+}
+
 bool Squares::draw(QPainter *p, const QColor &back)
 {
     p->fillRect (0, 0, size.width (), size.height (), back);
Index: mythplugins/mythmusic/mythmusic/mainvisual.h
===================================================================
--- mythplugins/mythmusic/mythmusic/mainvisual.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/mainvisual.h	(working copy)
@@ -26,6 +26,7 @@
 class QTimer;
 class VisFactory;
 class InfoWidget;
+class Metadata;
 
 class VisualNode
 {
@@ -78,6 +79,8 @@
     VisualBase *visual() const { return vis; }
     void setVis( VisualBase *newvis );
     void setVisual( const QString &visualname );
+	void setMetadata (Metadata *m) { meta = m; }
+	const Metadata* getMetadata() const { return meta; }
 
     QString getCurrentVisual() const;
     QString getCurrentVisualDesc() const;
@@ -97,7 +100,9 @@
     void setFrameRate( int newfps );
     int frameRate() const { return fps; }
 
+    void addInformation(const QImage &img, const QString &);
     void addInformation(const QString &);
+	void clearInformation();
 
     static void registerVisFactory(VisFactory *);
     static VisualBase *createVis(const QString &name,
@@ -120,6 +125,7 @@
 
     QString current_visual_name;
     QStringList allowed_modes;
+	Metadata *meta;
 };
 
 class InfoWidget : public QWidget
@@ -128,7 +134,8 @@
 
 public:
     InfoWidget(QWidget *parent = 0);
-    void addInformation(const QString &);
+    void addInformation(const QString &, const QImage *img = 0);
+	void clearInformation();
     void paintEvent(QPaintEvent *);
 
 private:
Index: mythplugins/mythmusic/mythmusic/metadata.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/metadata.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metadata.cpp	(working copy)
@@ -3,6 +3,7 @@
 #include <qregexp.h> 
 #include <qdatetime.h>
 #include <qdir.h>
+#include <qurl.h>
 
 using namespace std;
 
@@ -11,10 +12,30 @@
 #include <mythtv/mythdbcon.h>
 
 #include "metadata.h"
+#include "editmetadata.h"
+#include "editradiometadata.h"
 #include "treebuilders.h"
 
 static QString thePrefix = "the ";
 
+QVariant queryField (const QString &v, const QString &table, 
+                     const QString &q, const QVariant &qv)
+{
+    ;
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare(QString("SELECT %1 FROM %2 WHERE %3 = :Q;").arg(v).arg(table).arg(q));
+    query.bindValue(":Q", qv);
+    
+    if (!query.exec()) {
+        MythContext::DBError(QString("Get %1 from %2 for %3 = %4").arg(v).arg(table).arg(q).arg(qv.toString()), query);           
+    } else if (query.isActive() && query.size() > 0)
+        while (query.next()) {
+            return query.value(0);
+        }                
+    return QVariant ();
+}
+
 bool operator==(const Metadata& a, const Metadata& b)
 {
     if (a.Filename() == b.Filename())
@@ -76,21 +97,43 @@
 
 int Metadata::compare(Metadata *other) 
 {
-    if (m_format == "cast") 
-    {
-        int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
-        
-        if (artist_cmp == 0) 
-            return Title().lower().localeAwareCompare(other->Title().lower());
-        
-        return artist_cmp;
-    } 
-    else 
-    {
-        return (Track() - other->Track());
-    }
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") 
+        return compare_cast(other);
+
+    return (Track() - other->Track());
 }
 
+int Metadata::compare_cast(Metadata *other) 
+{
+    int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
+    
+    if (artist_cmp == 0) 
+        return Title().lower().localeAwareCompare(other->Title().lower());
+    
+    return artist_cmp;
+}
+
+MythThemedDialog *Metadata::createEditorDialog(void) 
+{
+    /*note:temporary fix for radio*/
+    if (Format() == "cast")
+        return createEditorDialog_cast();
+
+    return new EditMetadataDialog(this, gContext->GetMainWindow(),
+                                  "edit_metadata", "music-", 
+                                  "edit metadata");
+    
+}
+
+MythThemedDialog *Metadata::createEditorDialog_cast(void) 
+{
+    return new EditRadioMetadataDialog(this, gContext->GetMainWindow(),
+                                       "edit_radiometadata", "music-", 
+                                       "edit metadata");
+    
+}
+
 bool Metadata::isInDatabase()
 {
     bool retval = false;
@@ -150,6 +193,12 @@
 
 void Metadata::dumpToDatabase()
 {
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") 
+        return dumpToDatabase_cast();
+
+    checkEmptyFields();
+
     QString sqlfilepath(m_filename);
     if (!sqlfilepath.contains("://"))
     {
@@ -157,18 +206,7 @@
     }
     QString sqldir = sqlfilepath.section( '/', 0, -2);
     QString sqlfilename = sqlfilepath.section( '/', -1 ) ;
-
-    if (m_artist == "")
-        m_artist = QObject::tr("Unknown Artist");
-    if (m_compilation_artist == "")
-        m_compilation_artist = m_artist; // This should be the same as Artist if blank.
-    if (m_album == "")
-        m_album = QObject::tr("Unknown Album");
-    if (m_title == "")
-        m_title = m_filename;
-    if (m_genre == "")
-        m_genre = QObject::tr("Unknown Genre");
-
+    
     MSqlQuery query(MSqlQuery::InitCon());
 
     if (m_directoryid < 0)
@@ -200,7 +238,7 @@
             m_directoryid = query.lastInsertId().toInt();
         }
     }
-
+    
     if (m_artistid < 0)
     {
         // Load the artist id
@@ -388,6 +426,60 @@
         m_id = query.lastInsertId().toInt();
 }
 
+void Metadata::dumpToDatabase_cast()
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT url FROM music_radios WHERE "
+                  "( ( station = :STATION ) AND "
+                  "( channel = :CHANNEL ) AND "
+                  "( format = :FORMAT ) AND "
+                  "( url = :URL ) ); ");
+    query.bindValue(":STATION", m_artist.utf8());
+    query.bindValue(":CHANNEL", m_title.utf8());
+    query.bindValue(":URL", m_filename);
+    query.bindValue(":FORMAT", m_format);
+    
+    if (query.exec() && query.isActive() && query.size() > 0)
+        return;
+    
+    QString strQuery;
+    if (m_id < 1) {
+        strQuery = "INSERT INTO music_radios "
+                   "(station,    channel,    url,        genre, "
+                   " metaformat, format,     rating            )"
+                   " VALUES "
+                   "(:STATION,     :CHANNEL,  :URL,     :GENRE,   "
+                  " :METAFORMAT,  :FORMAT,   :RATING );";
+    } else {
+        strQuery = "UPDATE music_radios          "
+                   "SET station   = :STATION,    "
+                   "    channel   = :CHANNEL,    "
+                   "    url       = :URL,        "
+                   "    genre     = :GENRE,      "
+                   "    metaformat= :METAFORMAT, "
+                   "    rating    = :RATING,     " 
+                   "    format    = :FORMAT      " 
+                   "WHERE intid = :ID;";
+    }
+    query.prepare(strQuery);
+    query.bindValue(":STATION", m_artist.utf8());
+    query.bindValue(":CHANNEL", m_title.utf8());
+    query.bindValue(":URL", m_filename);
+    query.bindValue(":GENRE", m_genre.utf8());
+    query.bindValue(":METAFORMAT", m_compilation_artist);
+    query.bindValue(":RATING", m_rating);
+    query.bindValue(":FORMAT", m_format);                      
+
+    if (m_id >= 1) {
+        query.bindValue(":ID", m_id);
+    }
+
+    query.exec();
+
+    if (m_id < 1 && query.isActive() && 1 == query.numRowsAffected())
+        setID(query.lastInsertId().toInt());
+}
+
 // Default values for formats
 // NB These will eventually be customizable....
 QString Metadata::m_formatnormalfileartist      = "ARTIST";
@@ -456,6 +548,17 @@
   return rv;
 }
 
+void Metadata::checkEmptyFields()
+{
+    if (m_artist.isEmpty())
+        m_artist = QObject::tr("Unknown Artist");
+    if (m_album.isEmpty())
+        m_album = QObject::tr("Unknown Album");
+    if (m_title.isEmpty())
+        m_title = QObject::tr("Unknown Title");
+    if (m_genre.isEmpty())
+        m_genre = QObject::tr("Unknown Genre");
+}
 
 inline void Metadata::setCompilationFormatting(bool cd)
 {
@@ -513,7 +616,58 @@
     return m_formattedtitle;
 }
 
+QString Metadata::FormatInfo()
+{       
+    QString result;
+    if (Format() == "cast") {
+        result = QString("\"%1\"\n"
+                         "%2\n"
+                         "Now playing on %3").
+            arg(FormatTitle()).
+            arg(FormatArtist()).
+            arg(Album());
+    } else {
+        result = QString("\"%1\"\n"
+                         "%2 - %3").
+        arg(FormatTitle()).
+            arg(FormatArtist()).
+            arg(Album());
+        
+        if (Year() > 0) 
+            result += " (" + QString::number (Year()) + ")";
+    }
 
+    return result;
+}
+
+void Metadata::removeFromDatabase()
+{
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") 
+        return removeFromDatabase_cast();
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("DELETE FROM musicmetadata "
+                  "WHERE intid = :ID;");
+    query.bindValue(":ID", m_id);
+
+    if (!query.exec())
+         MythContext::DBError("Remove musicmetadata", query);
+}
+
+void Metadata::removeFromDatabase_cast()
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("DELETE FROM music_radios "
+                  "WHERE intid = :ID;");
+    query.bindValue(":ID", m_id);
+
+    if (!query.exec())
+         MythContext::DBError("Remove music_radios", query);
+}
+
 void Metadata::setField(const QString &field, const QString &data)
 {
     if (field == "artist")
@@ -557,6 +711,13 @@
         *data = FormatTitle();
     else if (field == "genre")
         *data = m_genre;
+    else if (field == "x-cast-logourl") 
+    {
+        /*note:temporary fix for radio*/
+        if (Format() == "cast") {
+            *data = queryField ("logourl", "music_radios", "intid", m_id).toString ();
+        } 
+    }
     else
     {
         cerr << "metadata.o: Something asked me to return data about a field called " << field << endl ;
@@ -564,6 +725,36 @@
     }
 }
 
+QByteArray Metadata::AlbumArt() const {
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") {
+        QVariant v = queryField ("logocached", "music_radios", "intid", m_id);
+        if (v.isValid()) {
+            return v.toByteArray();
+        }
+    } else {
+    }
+
+    return QByteArray ();
+}
+
+void Metadata::setAlbumArt(const QByteArray &data) {
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("UPDATE music_radios          "
+                      "SET logocached = :LOGO       "
+                      "WHERE intid = :ID;");
+        query.bindValue(":LOGO", data);
+        if (m_id >= 1) {
+            query.bindValue(":ID", m_id);
+        }
+        
+        if (!query.exec())
+            MythContext::DBError("Store albumart", query);
+    }
+}
+
 void Metadata::decRating()
 {
     if (m_rating > 0)
@@ -681,13 +872,14 @@
     startLoading();
 
     m_all_music.setAutoDelete(true);
-
+    m_all_radios.setAutoDelete(true);
     m_last_listed = -1;
 }
 
 AllMusic::~AllMusic()
 {
     m_all_music.clear();
+    m_all_radios.clear();
 
     delete m_root_node;
 
@@ -742,7 +934,6 @@
 void AllMusic::resync()
 {
     m_done_loading = false;
-
     QString aquery = "SELECT music_songs.song_id, music_artists.artist_name, music_comp_artists.artist_name AS compilation_artist, "
                      "music_albums.album_name, music_songs.name, music_genres.genre, music_songs.year, "
                      "music_songs.track, music_songs.length, CONCAT(music_directories.path, '/', music_songs.filename) AS filename, "
@@ -757,7 +948,6 @@
                      "ORDER BY music_songs.song_id;";
 
     QString filename, artist, album, title;
-
     MSqlQuery query(MSqlQuery::InitCon());
     query.exec(aquery);
 
@@ -771,28 +961,16 @@
     {
         while (query.next())
         {
-            filename = QString::fromUtf8(query.value(9).toString());
+            QString filename = QString::fromUtf8(query.value(9).toString());
             if (!filename.contains("://"))
                 filename = m_startdir + filename;
 
-            artist = QString::fromUtf8(query.value(1).toString());
-            if (artist.isEmpty())
-                artist = QObject::tr("Unknown Artist");
-
-            album = QString::fromUtf8(query.value(3).toString());
-            if (album.isEmpty())
-                album = QObject::tr("Unknown Album");
-
-            title = QString::fromUtf8(query.value(4).toString());
-            if (title.isEmpty())
-                title = QObject::tr("Unknown Title");
-
             Metadata *temp = new Metadata(
                 filename,
-                artist,
+                QString::fromUtf8(query.value(1).toString()),
                 QString::fromUtf8(query.value(2).toString()),
-                album,
-                title,
+                QString::fromUtf8(query.value(3).toString()),
+                QString::fromUtf8(query.value(4).toString()),
                 QString::fromUtf8(query.value(5).toString()),
                 query.value(6).toInt(),
                 query.value(7).toInt(),
@@ -803,7 +981,6 @@
                 query.value(12).toString(), //lastplay
                 (query.value(13).toInt() > 0), //compilation
                 query.value(14).toString()); //format
-
             //  Don't delete temp, as PtrList now owns it
             m_all_music.append(temp);
 
@@ -851,9 +1028,57 @@
     //printTree();
     sortTree();
     //printTree();
+
+    resync_radios();
+
     m_done_loading = true;
 }
 
+/*note:temporary fix for radio*/
+void AllMusic::resync_radios(void)
+{
+    m_radio_map.clear();
+    m_all_radios.clear();
+
+    QString the_query = 
+        "SELECT "
+        "url, station, channel, genre, intid, rating, format, metaformat "
+        "FROM "
+        "music_radios ORDER BY station, channel";
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    
+    if (query.exec(the_query) && query.isActive() && query.size() > 0) {
+        while (query.next()) {
+            Metadata *md = new Metadata(QString::fromUtf8(query.value(0).toString()),
+                                        QString::fromUtf8(query.value(1).toString()),
+                                        "", "",
+                                        QString::fromUtf8(query.value(2).toString()),
+                                        QString::fromUtf8(query.value(3).toString()),
+                                        0, 0, 0,
+                                        query.value(4).toInt(), query.value(5).toInt());
+
+            // abuse...
+            md->setCompilationArtist(QString::fromUtf8(query.value(7).toString()));            
+            md->setFormat(query.value(6).toString());
+
+            addRadioTrack(md);
+        } 
+        m_all_radios.sort ();
+    }    
+}
+
+void AllMusic::addRadioTrack(Metadata *meta)
+{    
+    QString title = meta->FormatArtist ();
+    title += " ~ ";
+    title += meta->FormatTitle();
+    meta->setAlbum(title);
+
+    m_all_radios.append(meta);
+    m_radio_map[meta->ID()] = meta;
+}
+
 void AllMusic::sortTree()
 {
     m_root_node->sort();
@@ -897,9 +1122,64 @@
     delete builder;
 }
 
+/*note:temporary fix for radio*/
+#define RADIOS_PR_STATION 1
 void AllMusic::writeTree(GenericTree *tree_to_write_to)
 {
     m_root_node->writeTree(tree_to_write_to, 0);
+
+    /*note:temporary fix for radio*/
+    {
+        int rcounter = 0;
+        GenericTree *radio_root = tree_to_write_to->addNode(QObject::tr("All My Radios"), 0);
+        radio_root->setAttribute(0, 0);
+        radio_root->setAttribute(1, 0);
+        radio_root->setAttribute(2, 0);
+        radio_root->setAttribute(3, 0);
+        radio_root->setAttribute(4, 1);
+        
+#if RADIOS_PR_STATION
+        QMap<QString,GenericTree*> radio_stations;
+#endif
+        QPtrListIterator<Metadata> iterator(m_all_radios);
+        Metadata *mdata;
+
+        while ((mdata = iterator.current()) != 0)
+        {
+#if RADIOS_PR_STATION
+            if (radio_stations[mdata->Artist()] == 0) {
+                GenericTree *sub = 
+                    radio_root->addNode(mdata->FormatArtist(), false);
+                radio_stations[mdata->Artist()] = sub;
+                sub->setAttribute(0, 1);
+                sub->setAttribute(1, rcounter);
+                sub->setAttribute(2, rand());
+                sub->setAttribute(3, rand());
+                sub->setAttribute(4, 1);
+                rcounter++;
+            }
+#endif
+            QString title = mdata->Album();
+#if RADIOS_PR_STATION
+            title = mdata->Title();
+#endif
+                
+            GenericTree *insert_at = radio_root;
+#if RADIOS_PR_STATION 
+           insert_at = radio_stations[mdata->Artist()];
+#endif
+
+            GenericTree *sub = insert_at->addNode(title, mdata->ID (), true);
+            
+            sub->setAttribute(0, 1);
+            sub->setAttribute(1, rcounter);
+            sub->setAttribute(2, rand());
+            sub->setAttribute(3, rand());
+            sub->setAttribute(4, 1);
+            ++rcounter;
+            ++iterator;
+        }        
+    }
 }
 
 bool AllMusic::putYourselfOnTheListView(TreeCheckItem *where)
@@ -935,13 +1215,13 @@
     QString a_label = "";
     if(an_id > 0)
     {
-   
         if (!music_map.contains(an_id))
         {
             a_label = QString(QObject::tr("Missing database entry: %1")).arg(an_id);
             *error_flag = true;
             return a_label;
         }
+
       
         a_label += music_map[an_id]->FormatArtist();
         a_label += " ~ ";
@@ -1001,11 +1281,32 @@
     return NULL;
 }
 
-bool AllMusic::updateMetadata(int an_id, Metadata *the_track)
+
+Metadata* AllMusic::getRadioMetadata(int an_id)
 {
     if(an_id > 0)
     {
-        Metadata *mdata = getMetadata(an_id);
+        if (m_radio_map.contains(an_id))
+        {
+            return m_radio_map[an_id];    
+        }
+    }
+    return NULL;
+}
+
+bool AllMusic::updateMetadata(int repo_id, int an_id, Metadata *the_track)
+{
+    if(an_id > 0)
+    {
+        Metadata *mdata = NULL;
+
+        /* temporary fix for radios */
+        if(repo_id == 0) {
+            mdata = getMetadata(an_id);
+        } else if(repo_id == 1) {
+            mdata = getRadioMetadata(an_id);
+        }
+
         if (mdata)
         {
             *mdata = the_track;
Index: mythplugins/mythmusic/mythmusic/metaio.h
===================================================================
--- mythplugins/mythmusic/mythmusic/metaio.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metaio.h	(working copy)
@@ -16,6 +16,7 @@
     
     virtual bool write(Metadata* mdata, bool exclusive = false) = 0;
     virtual Metadata* read(QString filename) = 0;
+	virtual QByteArray getAlbumArt(const QString &filename);
 
     void readFromFilename(QString filename, QString &artist, QString &album, 
                           QString &title, QString &genre, int &tracknum);
Index: mythplugins/mythmusic/mythmusic/dbcheck.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/dbcheck.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/dbcheck.cpp	(working copy)
@@ -10,9 +10,379 @@
 #include "mythtv/mythdbcon.h"
 
 const QString currentDatabaseVersion = "1008";
+//----------------------------------------------------------------------------
+// The radio number is to seperate radios from the main. As long
+// as this patch isn't in the main svn trunk, it's a hassle to 
+// share the number. When and if in the svn trunk, use the next
+// db version number. This is a cut'n'paste of the code 
+// just to tweak to use another field in the settings table.
+const QString radioCurrentDatabaseVersion = "3";
+static void RadioUpdateDBVersionNumber(const QString &newnumber)
+{
+    MSqlQuery query(MSqlQuery::InitCon());
 
+    query.exec("DELETE FROM settings WHERE value='RadioMusicDBSchemaVer';");
+    query.exec(QString("INSERT INTO settings (value, data, hostname) "
+                          "VALUES ('RadioMusicDBSchemaVer', %1, NULL);")
+                         .arg(newnumber));
+}
+static void RadioPerformActualUpdate(const QString updates[], QString version,
+                                QString &dbver)
+{
+    VERBOSE(VB_IMPORTANT, QString("Upgrading to MythMusicRadio schema version ") + 
+            version);
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    int counter = 0;
+    QString thequery = updates[counter];
+
+    while (thequery != "")
+    {
+        query.exec(thequery);
+        counter++;
+        thequery = updates[counter];
+    }
+
+    RadioUpdateDBVersionNumber(version);
+    dbver = version;
+}
+static void RadioUpgrade() {
+    QString dbver = gContext->GetSetting("RadioMusicDBSchemaVer");
+    VERBOSE(VB_IMPORTANT, QString("RadioUpgrade dbversion='%1'").arg(dbver));
+    if (dbver == radioCurrentDatabaseVersion) return;
+    if (dbver == "") {
+        const QString updates[] = {
+            "DROP TABLE IF EXISTS music_radios;",
+            "CREATE TABLE music_radios ("
+            "    intid INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,"
+            "    station VARCHAR(128) NOT NULL,"
+            "    channel VARCHAR(128) NOT NULL,"
+            "    url VARCHAR(128) NOT NULL,"
+            "    logourl VARCHAR(128) NOT NULL,"
+            "    logocached BLOB,"
+            "    genre VARCHAR(128) NOT NULL,"
+            "    metaformat VARCHAR(128) NOT NULL,"
+            "    format VARCHAR(10) NOT NULL,"
+            "    rating INT UNSIGNED NOT NULL DEFAULT 5,"
+            "    INDEX (station),"
+            "    INDEX (channel)"
+            ");",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Space Station Soma\","
+            "    url=\"http://somafm.com/spacestation.pls\","
+            "    logourl=\"http://img.somafm.com/img/sss.jpg\","
+            "    genre=\"Electronica\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Doomed\","
+            "    url=\"http://somafm.com/doomed.pls\","
+            "    logourl=\"http://img.somafm.com/img/doomed.gif\","
+            "    genre=\"Darkwave\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Beatblender\","
+            "    url=\"http://somafm.com/beatblender.pls\","
+            "    logourl=\"http://img.somafm.com/img/blender.gif\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Cliqhop idm\","
+            "    url=\"http://somafm.com/cliqhop.pls\","
+            "    logourl=\"http://img.somafm.com/img/cliqhop.gif\","
+            "    genre=\"Dance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Indie Pop Rocks\","
+            "    url=\"http://somafm.com/indiepop.pls\","
+            "    logourl=\"http://img.somafm.com/img/indychick.jpg\","
+            "    genre=\"Indie\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Tag's Trance Trip\","
+            "    url=\"http://somafm.com/tagstrance.pls\","
+            "    logourl=\"http://img.somafm.com/img/tagstrancefract.jpg\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Drone Zone\","
+            "    url=\"http://somafm.com/dronezone.pls\","
+            "    logourl=\"http://img.somafm.com/img/DroneZoneBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Secret Agent\","
+            "    url=\"http://somafm.com/secretagent.pls\","
+            "    logourl=\"http://img.somafm.com/img/SecretAgentBox.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Groove Salad\","
+            "    url=\"http://somafm.com/groovesalad.pls\","
+            "    logourl=\"http://img.somafm.com/img/GrooveSaladBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Illinois Street Lounge\","
+            "    url=\"http://somafm.com/illstreet.pls\","
+            "    logourl=\"http://img.somafm.com/img/illstreet.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"trance\","
+            "    url=\"http://di.fm/mp3/trance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"vocaltrance\","
+            "    url=\"http://di.fm/mp3/vocaltrance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"chillout\","
+            "    url=\"http://di.fm/mp3/chillout.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Electronica\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"house\","
+            "    url=\"http://di.fm/mp3/house.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"harddance\","
+            "    url=\"http://di.fm/mp3/harddance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"eurodance\","
+            "    url=\"http://di.fm/mp3/eurodance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"progressive\","
+            "    url=\"http://di.fm/mp3/progressive.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"goapsy\","
+            "    url=\"http://di.fm/mp3/goapsy.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"hardcore\","
+            "    url=\"http://di.fm/mp3/hardcore.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"djmixes\","
+            "    url=\"http://di.fm/mp3/djmixes.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"lounge\","
+            "    url=\"http://di.fm/mp3/lounge.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"ambient\","
+            "    url=\"http://di.fm/mp3/ambient.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"drumandbass\","
+            "    url=\"http://di.fm/mp3/drumandbass.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"classictechno\","
+            "    url=\"http://di.fm/mp3/classictechno.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"breaks\","
+            "    url=\"http://di.fm/mp3/breaks.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"gabber\","
+            "    url=\"http://di.fm/mp3/gabber.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"classical\","
+            "    url=\"http://di.fm/mp3/classical.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Classical\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"newage\","
+            "    url=\"http://di.fm/mp3/newage.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Newage\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"guitar\","
+            "    url=\"http://di.fm/mp3/guitar.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Instrumental\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"smoothjazz\","
+            "    url=\"http://di.fm/mp3/smoothjazz.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"tophits\","
+            "    url=\"http://di.fm/mp3/tophits.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Pop\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"the80s\","
+            "    url=\"http://di.fm/mp3/the80s.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"rootsreggae\","
+            "    url=\"http://di.fm/mp3/rootsreggae.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Reggae\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"hit70s\","
+            "    url=\"http://di.fm/mp3/hit70s.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"country\","
+            "    url=\"http://di.fm/mp3/country.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Country\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"jazz\","
+            "    url=\"http://di.fm/mp3/jazz.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Jazz\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"salsa\","
+            "    url=\"http://di.fm/mp3/salsa.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Salsa\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            ""
+        };
+        //performActualUpdate(updates, "1008", dbver);
+        RadioPerformActualUpdate(updates, "1", dbver);
+    }
+    if (dbver == "1") {
+        const QString updates[] = {
+            "ALTER TABLE music_radios ADD logocached BLOB;",
+            ""
+        };
+        RadioPerformActualUpdate(updates, "2", dbver);
+    }
+}
+//----------------------------------------------------------------------------
+
 static bool UpdateDBVersionNumber(const QString &newnumber)
-{   
+{
+    MSqlQuery query(MSqlQuery::InitCon());
 
     if (!gContext->SaveSettingOnHost("MusicDBSchemaVer",newnumber,NULL))
     {   
@@ -67,6 +437,9 @@
 bool UpgradeMusicDatabaseSchema(void)
 {
     QString dbver = gContext->GetSetting("MusicDBSchemaVer");
+
+    //Once in the main trun, the radioupgrade code back in here...
+    RadioUpgrade();
     
     if (dbver == currentDatabaseVersion)
         return true;
@@ -351,7 +724,6 @@
             return false;
     }
 
-
     if (dbver == "1005")
     {
         const QString updates[] = {
@@ -501,7 +873,6 @@
 //"DROP TABLE musicmetadata;",
 //"DROP TABLE musicplaylist;",
 
-
     return true;
 }
 
Index: mythplugins/mythmusic/mythmusic/metaioid3v2.h
===================================================================
--- mythplugins/mythmusic/mythmusic/metaioid3v2.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metaioid3v2.h	(working copy)
@@ -20,6 +20,7 @@
     
     bool write(Metadata* mdata, bool exclusive = false);
     Metadata* read(QString filename);
+	QByteArray getAlbumArt(const QString &filename);
     
 private:
     int getTrackLength(QString filename);
Index: mythplugins/mythmusic/mythmusic/metadata.h
===================================================================
--- mythplugins/mythmusic/mythmusic/metadata.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metadata.h	(working copy)
@@ -40,11 +40,11 @@
                 m_changed = false;
                 m_show = true;
                 m_format = lformat;
-
                 m_directoryid = -1;
                 m_artistid = -1;
                 m_albumid = -1;
                 m_genreid = -1;
+				checkEmptyFields();
             }
 
     Metadata(const Metadata &other)
@@ -76,22 +76,23 @@
 
     Metadata& operator=(Metadata *rhs);
 
-    QString Artist() { return m_artist; }
+    QString Artist() const { return m_artist; }
     void setArtist(const QString &lartist) { m_artist = lartist; m_formattedartist = m_formattedtitle = ""; }
     
-    QString CompilationArtist() { return m_compilation_artist; }
+    QString CompilationArtist() const { return m_compilation_artist; }
     void setCompilationArtist(const QString &lcompilation_artist) { m_compilation_artist = lcompilation_artist; m_formattedartist = m_formattedtitle = ""; }
     
-    QString Album() { return m_album; }
+    QString Album() const { return m_album; }
     void setAlbum(const QString &lalbum) { m_album = lalbum; m_formattedartist = m_formattedtitle = ""; }
 
-    QString Title() { return m_title; }
+    QString Title() const { return m_title; }
     void setTitle(const QString &ltitle) { m_title = ltitle; }
 
     QString FormatArtist();
     QString FormatTitle();
+	QString FormatInfo();
 
-    QString Genre() { return m_genre; }
+    QString Genre() const { return m_genre; }
     void setGenre(const QString &lgenre) { m_genre = lgenre; }
 
     void setDirectoryId(int ldirectoryid) { m_directoryid = ldirectoryid; }
@@ -106,53 +107,60 @@
     int Year() { return m_year; }
     void setYear(int lyear) { m_year = lyear; }
 
-    int Track() { return m_tracknum; }
+    int Track() const { return m_tracknum; }
     void setTrack(int ltrack) { m_tracknum = ltrack; }
 
-    int Length() { return m_length; }
+    int Length() const { return m_length; }
     void setLength(int llength) { m_length = llength; }
 
-    int Playcount() { return m_playcount; }
+    int Playcount() const { return m_playcount; }
     void setPlaycount(int lplaycount) { m_playcount = lplaycount; }
 
-    unsigned int ID() { return m_id; }
+    unsigned int ID() const { return m_id; }
     void setID(int lid) { m_id = lid; }
-    
+
     QString Filename() const { return m_filename; }
-    void setFilename(QString &lfilename) { m_filename = lfilename; }
-    
+    void setFilename(const QString &lfilename) { m_filename = lfilename; }
+
     QString Format() const { return m_format; }
     void setFormat(const QString &lformat) { m_format = lformat; }
+
+	QByteArray AlbumArt() const;
+	void setAlbumArt(const QByteArray &data);
     
-    int Rating() { return m_rating; }
+    int Rating() const { return m_rating; }
+
     void decRating();
     void incRating();
     void setRating(int lrating) { m_rating = lrating; }
 
     double LastPlay();
-    QString LastPlayStr() { return m_lastplay; }
+    QString LastPlayStr() const { return m_lastplay; }
     void setLastPlay();
 
-    int PlayCount() { return m_playcount; }
+    int PlayCount() const { return m_playcount; }
     void incPlayCount();
 
-    bool isVisible() { return m_show; }
+    bool isVisible() const { return m_show; }
     void setVisible(bool visible) { m_show = visible; }
 
     // track is part of a compilation album
-    bool Compilation() { return m_compilation; }
+    bool Compilation() const { return m_compilation; }
     void setCompilation(bool state) { m_compilation = state; m_formattedartist = m_formattedtitle = ""; }
-    bool determineIfCompilation(bool m_cd = false);
+    bool determineIfCompilation(bool cd = false);
 
     bool isInDatabase(void);
     void dumpToDatabase(void);
+	void removeFromDatabase(void);
     void setField(const QString &field, const QString &data);
     void getField(const QString& field, QString *data);
     void persist();
     bool hasChanged(){return m_changed;}
     int compare (Metadata *other);
+
+    MythThemedDialog *createEditorDialog (void);
+
     static void setArtistAndTrackFormats();
-
     static void SetStartdir(const QString &dir);
 
     static QStringList fillFieldList(QString field);
@@ -160,6 +168,7 @@
   private:
     void setCompilationFormatting(bool cd = false);
     QString formatReplaceSymbols(const QString &format);
+	void checkEmptyFields(void);
 
     QString m_artist;
     QString m_compilation_artist;
@@ -200,6 +209,13 @@
     static QString m_formatcompilationfiletrack;
     static QString m_formatcompilationcdartist;
     static QString m_formatcompilationcdtrack;
+
+    /*note:temporary fix for radio. These should be split out by subclassing Metadata */
+    int compare_cast (Metadata *other);
+    MythThemedDialog *createEditorDialog_cast (void);
+    void dumpToDatabase_cast();
+    void updateDatabase_cast();
+    void removeFromDatabase_cast();
 };
 
 bool operator==(const Metadata& a, const Metadata& b);
@@ -301,13 +317,16 @@
 
     QString     getLabel(int an_id, bool *error_flag);
     Metadata*   getMetadata(int an_id);
-    bool        updateMetadata(int an_id, Metadata *the_track);
-    int         count() { return m_numPcs; }
-    int         countLoaded() { return m_numLoaded; } 
+    Metadata*   getRadioMetadata(int an_id); /*note:temporary fix for radio*/
+    bool        updateMetadata(int repo_id, int an_id, Metadata *the_track);
+    int         count() const { return m_numPcs; }
+    int         countLoaded() const { return m_numLoaded; } 
     void        save();
     bool        startLoading(void);
     void        resync();   //  After a CD rip, for example
+    void        resync_radios(void); /*note:temporary fix for radio*/
     void        clearCDData();
+    void        addRadioTrack(Metadata *the_track); /*note:temporary fix for radio*/
     void        addCDTrack(Metadata *the_track);
     bool        checkCDTrack(Metadata *the_track);
     bool        getCDMetadata(int m_the_track, Metadata *some_metadata);
@@ -329,6 +348,8 @@
   private:
   
     MetadataPtrList     m_all_music;
+    MetadataPtrList     m_all_radios; /*note:temporary fix for radio*/
+    QMap<int,Metadata*> m_radio_map;  /*note:temporary fix for radio*/
     MusicNode           *m_root_node;
     
     int m_numPcs;
Index: mythplugins/mythmusic/mythmusic/pls.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/pls.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/pls.cpp	(revision 0)
@@ -0,0 +1,406 @@
+/*
+  playlistfile (.pls) parser
+  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
+*/
+
+#include "pls.h"
+
+#include <qpair.h>
+#include <qvaluelist.h>
+#include <qmap.h>
+
+#include <assert.h>
+
+using namespace std;
+
+class CfgReader 
+{
+  public:
+    CfgReader() 
+    {
+    }
+    ~CfgReader() 
+    {
+    }
+
+    typedef QPair<QString,QString> KeyValue;
+    typedef QValueList<KeyValue> KeyValueList;
+    typedef QMap<QString, KeyValueList> ConfigMap;
+
+    void parse(const char *d, int l) 
+    {
+        const char *ptr = d;
+        int line = 1;
+        bool done = l <= 0;
+
+        QString current_section = "";
+        KeyValueList keyvals;
+
+        while(!done) 
+        {
+            switch(*ptr) 
+            {
+            case '\0':
+                done = true;
+                break;
+            case '#': 
+            {
+                char *end = strchr(ptr, '\n');
+                if (!end) done = true;
+                ptr = end;
+                break;
+            }
+            case '\n':
+                ptr ++;
+                line ++;
+                break;
+            case '[': 
+            {
+                ptr ++;
+                const char *nl = strchr(ptr, '\n');
+                const char *end = strchr(ptr, ']');
+
+                if (!nl) nl = d + l;
+
+                if (!end || nl < end) 
+                {
+                    fprintf(stderr, "Badly formatted section, line %d\n", line);
+                    done = true;
+                }
+
+                if (current_section.length() > 0) 
+                {
+                    cfg[current_section] = keyvals;
+                    keyvals = KeyValueList();
+                }
+
+                current_section = std::string(ptr, end - ptr);
+                if (current_section.length() == 0) 
+                {
+                    fprintf(stderr, "Badly formatted section, line %d\n", line);
+                    done = true;
+                }
+                ptr = end + 1;
+                break;
+            }
+            default:
+                if (current_section.length() > 0) 
+                {
+                    const char *eq = strchr(ptr, '=');
+                    const char *nl = strchr(ptr, '\n');
+
+                    if (!nl) nl = d + l;
+
+                    if (!eq || nl < eq) 
+                    {
+                        fprintf(stderr, "Badly formatted line %d\n", line);
+                        done = true;
+                    } 
+                    else 
+                    {
+                        QString key = string(ptr, eq - ptr);
+                        QString val = string(eq + 1, nl - eq - 1);
+                        keyvals.push_back(KeyValue(key, val));
+                        ptr = nl;
+                    }
+                } 
+                else 
+                {
+                    fprintf(stderr, "Badly formatted line %d\n", line);
+                    done = true;
+                }
+                break;
+            }
+
+            if (ptr - d == l) 
+                done = true;
+        }
+
+        if (current_section.length() > 0) 
+            cfg[current_section] = keyvals;
+    }
+
+    QValueList<QString> getSections(void) 
+    {
+        QValueList<QString> res;
+        for (ConfigMap::iterator it = cfg.begin(); it != cfg.end(); it++) 
+            res.push_back(it.key());
+        return res;
+    }
+
+    QValueList<QString> getKeys(const QString &section) 
+    {
+        KeyValueList keylist = cfg[section];
+        QValueList<QString> res;
+        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++)
+            res.push_back((*it).first);
+        return res; 
+    }
+
+    QString getStrVal(const QString &section, const QString &key, const QString &def="") 
+    {
+        KeyValueList keylist = cfg[section];
+        QString res = def;
+        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++) 
+        {
+            if ((*it).first == key) 
+            {
+                res =(*it).second;
+                break;
+            }
+        }
+        return res;
+    }
+
+    int getIntVal(const QString &section, const QString &key, int def=0) 
+    {
+        QString def_str;
+        def_str.setNum (def);
+        return getStrVal(section, key, def_str).toInt();
+    }
+    
+    // very simple unit test, only tests parsing a wellformed file...
+    static void test(void)
+    {
+        const char *sample1 =
+            "# Sample config file\n"
+            "\n"
+            "[foo]\n"
+            "key1=value1\n"
+            "key2=value2\n"
+            "key3=value3\n"
+            "\n"
+            "key4=value4\n"
+            "[bar]\n"
+            "key5=value5\n"
+            "key6=value5\n"
+            "key7=7";
+        
+        CfgReader cfg;
+        cfg.parse(sample1, strlen(sample1));
+        QValueList<QString> sections = cfg.getSections();
+        QValueList<QString> keys;
+        QValueList<QString>::iterator key_it;
+        QValueList<QString>::iterator section_it;
+        section_it = sections.begin(); 
+
+        assert((*section_it) == "bar");
+        keys = cfg.getKeys((*section_it));
+        key_it = keys.begin();
+        assert((*key_it) == "key5");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value5");
+        key_it++;
+        assert((*key_it) == "key6");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value5");
+        key_it++;
+        assert((*key_it) == "key7");
+        assert(cfg.getIntVal(*section_it, *key_it) == 7);
+        key_it++;
+        assert(key_it == keys.end());
+        section_it++;
+
+        assert((*section_it) == "foo");
+        keys = cfg.getKeys((*section_it));
+        key_it = keys.begin();
+        assert((*key_it) == "key1");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value1");
+        key_it++;
+        assert((*key_it) == "key2");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value2");
+        key_it++;
+        assert((*key_it) == "key3");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value3");
+        key_it++;
+        assert((*key_it) == "key4");
+        assert(cfg.getStrVal(*section_it, *key_it) == "value4");
+        key_it++;
+        assert(key_it == keys.end());
+        section_it++;
+
+        assert(section_it == sections.end());
+    }
+
+  private:
+    ConfigMap cfg;
+};
+
+/****************************************************************************/
+
+PlayListFile::PlayListFile() 
+{
+    entries.setAutoDelete(true);
+}
+
+PlayListFile::~PlayListFile() 
+{
+}
+
+int PlayListFile::parse(PlayListFile *pls, QTextStream *stream) 
+{
+    int parsed = 0;
+    QString d = stream->read();
+    CfgReader cfg;
+    cfg.parse(d.ascii(), d.length());    
+    
+    int num_entries = cfg.getIntVal("playlist", "numberofentries", -1);
+
+    // Some pls files have "numberofentries", some has "NumberOfEntries".
+    if (num_entries == -1) 
+        num_entries = cfg.getIntVal("playlist", "NumberOfEntries", -1);
+
+    for (int n = 1; n <= num_entries; n++) 
+    {
+        PlayListFileEntry *e = new PlayListFileEntry();
+        QString t_key = QString("Title%1").arg(n);
+        QString f_key = QString("File%1").arg(n);
+        QString l_key = QString("Length%1").arg(n);
+
+        e->setFile(cfg.getStrVal("playlist", f_key));
+        e->setTitle(cfg.getStrVal("playlist", t_key));
+        e->setLength(cfg.getIntVal("playlist", l_key));
+
+        pls->add(e);
+        parsed++;
+    }
+
+    return parsed;
+}
+
+void PlayListFile::test(void) 
+{
+    CfgReader::test();
+
+    {
+        // test reading an empty string
+        PlayListFile parser;
+        QString a1 = "";
+        QTextIStream aa (&a1);
+        assert (PlayListFile::parse (&parser, &aa) == 0);
+    }
+    {
+        // test reading an empty playlist
+        PlayListFile parser;
+        QString b1 = "[playlist]\n";
+        b1 += "numberofentries=0\n";
+        b1 += "version=2\n";
+        QTextIStream bb (&b1);
+        assert (parser.PlayListFile::parse(&parser, &bb) == 0);
+    }
+    {
+        // test reading a playlist w/1 item
+        PlayListFile parser;
+        QString c1 = "[playlist]\n";
+        c1 += "numberofentries=1\n";
+        c1 += "File1=file_1\n";
+        c1 += "Title1=title 1\n";
+        c1 += "Length1=1\n";
+        c1 += "version=2\n";
+        QTextIStream cc (&c1);
+        assert (PlayListFile::parse(&parser, &cc) == 1);
+        assert (parser.get (0)->File () == "file_1");
+        assert (parser.get (0)->Title () == "title 1");
+        assert (parser.get (0)->Length () == 1);
+        assert (parser.get (1) == 0);
+    }
+
+    {
+        // test reading a playlist w/1 item but more after first item
+        PlayListFile parser;
+        QString d1 = "[playlist]\n";
+        d1 += "numberofentries=1\n";
+        d1 += "File1=file_1\n";
+        d1 += "Title1=title 1\n";
+        d1 += "Length1=1\n";
+        d1 += "File2=file_2\n";
+        d1 += "Title2=title 2\n";
+        d1 += "Length2=2\n";
+        d1 += "File3=file_3\n";
+        d1 += "Title3=title 3\n";
+        d1 += "Length3=3\n";
+        d1 += "version=2\n";
+        QTextIStream dd (&d1);
+        assert (PlayListFile::parse(&parser, &dd) == 1);
+        assert (parser.get (0)->File () == "file_1");
+        assert (parser.get (0)->Title () == "title 1");
+        assert (parser.get (0)->Length () == 1);
+        assert (parser.get (1) == 0);
+    }
+
+    {
+        // test reading a playlist w/3 items
+        PlayListFile parser;
+        QString e1 = "[playlist]\n";
+        e1 += "numberofentries=3\n";
+        e1 += "File1=file_1\n";
+        e1 += "Title1=title 1\n";
+        e1 += "Length1=1\n";
+        e1 += "File2=file_2\n";
+        e1 += "Title2=title 2\n";
+        e1 += "Length2=2\n";
+        e1 += "File3=file_3\n";
+        e1 += "Title3=title 3\n";
+        e1 += "Length3=3\n";
+        e1 += "version=2\n";
+        QTextIStream ee (&e1);
+        assert (PlayListFile::parse(&parser, &ee) == 3);
+        assert (parser.get (0)->File () == "file_1");
+        assert (parser.get (0)->Title () == "title 1");
+        assert (parser.get (0)->Length () == 1);
+        assert (parser.get (1)->File () == "file_2");
+        assert (parser.get (1)->Title () == "title 2");
+        assert (parser.get (1)->Length () == 2);
+        assert (parser.get (2)->File () == "file_3");
+        assert (parser.get (2)->Title () == "title 3");
+        assert (parser.get (2)->Length () == 3);
+        assert (parser.get (3) == 0);
+    }
+
+    {
+        // test reading a playlist w/2 items but more after second
+        PlayListFile parser;
+        QString f1 = "[playlist]\n";
+        f1 += "numberofentries=2\n";
+        f1 += "File1=file_1\n";
+        f1 += "Title1=title 1\n";
+        f1 += "Length1=1\n";
+        f1 += "File2=file_2\n";
+        f1 += "Title2=title 2\n";
+        f1 += "Length2=2\n";
+        f1 += "File3=file_3\n";
+        f1 += "Title3=title 3\n";
+        f1 += "Length3=3\n";
+        f1 += "version=2\n";
+        QTextIStream ff (&f1);
+        assert (PlayListFile::parse(&parser, &ff) == 2);
+        assert (parser.get (0)->File () == "file_1");
+        assert (parser.get (0)->Title () == "title 1");
+        assert (parser.get (0)->Length () == 1);
+        assert (parser.get (1)->File () == "file_2");
+        assert (parser.get (1)->Title () == "title 2");
+        assert (parser.get (1)->Length () == 2);
+        assert (parser.get (2) == 0);
+    }
+    {
+        PlayListFile parser;
+        QString f1 = "[playlist]\n";
+        f1 += "NumberOfEntries=2\n";
+        f1 += "File1=http://64.236.34.97:80/stream/1035\n";
+        f1 += "Title1=D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!\n";
+        f1 += "Length1=-1\n";
+        f1 += "File2=http://64.236.34.97:5190/stream/1035\n";
+        f1 += "Title2=D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!\n";
+        f1 += "Length2=-1\n";
+        f1 += "Version=2\n";
+        QTextIStream ff (&f1);
+        assert (PlayListFile::parse(&parser, &ff) == 2);
+        assert (parser.size () == 2);
+        assert (parser.get (0)->File () == "http://64.236.34.97:80/stream/1035");
+        assert (parser.get (0)->Title () == "D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!");
+        assert (parser.get (0)->Length () == -1);
+        assert (parser.get (1)->File () == "http://64.236.34.97:5190/stream/1035");
+        assert (parser.get (1)->Title () == "D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!");
+        assert (parser.get (1)->Length () == -1);
+        assert (parser.get (2) == 0);
+    }
+}
+
Index: mythplugins/mythmusic/mythmusic/inlines.h
===================================================================
--- mythplugins/mythmusic/mythmusic/inlines.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/inlines.h	(working copy)
@@ -11,6 +11,64 @@
 
 // *fast* convenience functions
 
+static inline void smoothen_mono(short *in_data, short *out_data, int len, int new_len)
+{
+    int step = new_len/len;
+    int dii = 0;
+    for (int ii = 0; ii < len; ii++)            
+    {
+        out_data[dii] = in_data[ii];
+        
+        short smooth = 0;
+
+        if (ii < len-1) 
+            smooth = in_data[ii+1] - in_data[ii];
+
+        if (smooth)
+            smooth /= step;
+        
+        for (int cc = 1; cc < step; cc++) {
+            out_data[dii+cc] = out_data[dii+cc-1] + smooth;
+        }
+        
+        dii += step;
+    }
+}
+
+static inline void smoothen_stereo(short *l_in_data, short *l_out_data, 
+                                   short *r_in_data, short *r_out_data,
+                                   int len, int new_len)
+{
+    int step = new_len/len;
+    int dii = 0;
+    for (int ii = 0; ii < len; ii++)            
+    {
+        l_out_data[dii] = l_in_data[ii];
+        r_out_data[dii] = r_in_data[ii];
+        
+        short l_smooth = 0;
+        short r_smooth = 0;
+
+        if (ii < len-1) {
+            l_smooth = l_in_data[ii+1] - l_in_data[ii];
+            r_smooth = r_in_data[ii+1] - r_in_data[ii];
+        }
+
+        if (l_smooth)
+            l_smooth /= step;
+        
+        if (r_smooth)
+            r_smooth /= step;
+        
+        for (int cc = 1; cc < step; cc++) {
+            l_out_data[dii+cc] = l_out_data[dii+cc-1] + l_smooth;
+            r_out_data[dii+cc] = r_out_data[dii+cc-1] + r_smooth;
+        }
+        
+        dii += step;
+    }
+}
+
 static inline void stereo16_from_stereopcm8(register short *l,
 					    register short *r,
 					    register uchar *c,
Index: mythplugins/mythmusic/mythmusic/shoutcast.h
===================================================================
--- mythplugins/mythmusic/mythmusic/shoutcast.h	(revision 0)
+++ mythplugins/mythmusic/mythmusic/shoutcast.h	(revision 0)
@@ -0,0 +1,148 @@
+/*
+  Shoutcast decoder for MythTV.
+  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
+*/
+
+#ifndef SHOUTCAST_H_
+#define SHOUTCAST_H_
+
+#include "config.h"
+#include "decoder.h"
+#include "decoderhandler.h"
+
+#include <qobject.h>
+#include <qsocketdevice.h>
+#include <qbuffer.h>
+#include <qurl.h>
+#include <qdns.h>
+#include <qmutex.h>
+
+// debug, collect kb/s info in ShoutCastIODevice
+#include <sys/time.h>
+#include <time.h>
+
+
+class ShoutCastRequest;
+class ShoutCastResponse;
+class ShoutCastLogoGrabber;
+class ShoutCastBuffer;
+
+typedef QMap<QString,QString> ShoutCastMetaMap;
+
+class ShoutCastIODevice : public QObject, public QIODevice {
+    Q_OBJECT
+public:
+	enum State {         
+        NOT_CONNECTED,
+        RESOLVING,
+        CONNECTING,
+        CANT_RESOLVE,
+        CANT_CONNECT,
+		CONNECTED,
+        WRITING_HEADER,
+        READING_HEADER,
+		PLAYING,
+        STREAMING,
+        STREAMING_META,
+        STOPPED
+	};
+    static const char* stateString (const State &s);
+
+    ShoutCastIODevice ();
+    ~ShoutCastIODevice ();
+
+	void connectToUrl (const QUrl &url);
+    bool open(int);
+    void close();
+    void flush();
+
+    Q_ULONG size() const;
+    Offset at () const { return 0; }
+    bool at (Offset) { return false; }
+	Q_ULONG bytesAvailable () const;
+
+    Q_LONG readBlock(char *data, Q_ULONG sz);
+    Q_LONG writeBlock(const char *data, Q_ULONG sz);
+    
+    int getch();
+    int putch(int c);
+    int ungetch(int);
+
+    bool getResponse(ShoutCastResponse &response);
+
+signals:
+    void meta(const QString &metadata);
+    void changedState(ShoutCastIODevice::State newstate);
+
+private slots:
+	void socketHostFound ();
+	void socketConnected ();
+	void socketConnectionClosed ();
+	void socketReadyRead ();
+	void socketBytesWritten (int);
+	void socketError (int);
+
+private:
+    void switchToState(const State &s);
+    int parseHeader(const char *data, Q_ULONG len);
+    int parseStream(char *data, Q_ULONG maxlen);
+    bool parseMeta(void);
+	void doKbPerSecond(int bytes_read);
+	bool waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed = 1);
+
+	// Our tools
+    ShoutCastBuffer *m_buffer;
+	ShoutCastResponse *m_response;
+	int m_redirects;
+    QSocket *m_socket;
+
+	// Our scratchpad
+	QByteArray m_scratchpad;
+	Q_ULONG m_scratchpad_pos;
+
+	// Our state info
+	QUrl m_url;
+	Q_ULONG m_bytes_till_next_meta;
+	State m_state;
+	QString m_last_metadata;
+	struct timeval m_sample_tv;
+	uint m_bytes_downloaded;
+	bool m_response_gotten;
+
+	// 
+    QWaitCondition m_cond;
+};
+
+class DecoderIOFactoryShoutCast : public DecoderIOFactory 
+{
+    Q_OBJECT
+  public:
+	DecoderIOFactoryShoutCast(DecoderHandler *parent);
+    ~DecoderIOFactoryShoutCast();
+
+    void start();
+    void stop();
+    QIODevice *takeInput(void);
+
+  protected slots:
+	void periodicallyCheckResponse(void);
+	void periodicallyCheckBuffered(void);
+    void shoutcastMeta(const QString &metadata);
+    void shoutcastChangedState(ShoutCastIODevice::State newstate);
+
+  private:
+	void socketConnected(void);
+	void socketClosed(void);
+	void socketError(int);
+
+	int checkResponseOK();
+	
+	void makeIODevice();
+	void closeIODevice();
+    QTimer *m_timer;
+
+	ShoutCastIODevice *m_input;
+	ShoutCastLogoGrabber *m_logo_grabber;
+};
+
+#endif /* SHOUTCAST_H_ */
Index: mythplugins/mythmusic/mythmusic/pls.h
===================================================================
--- mythplugins/mythmusic/mythmusic/pls.h	(revision 0)
+++ mythplugins/mythmusic/mythmusic/pls.h	(revision 0)
@@ -0,0 +1,89 @@
+/*
+  playlistfile (.pls) parser
+  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
+*/
+
+#ifndef PLS_H_
+#define PLS_H_
+
+#include <qstring.h>
+#include <qbuffer.h>
+#include <qptrlist.h>
+#include <qtextstream.h>
+
+/** \brief Class for representing entries in a pls file 
+ */
+class PlayListFileEntry 
+{
+  public:
+    PlayListFileEntry() {}
+    ~PlayListFileEntry() {}
+
+    QString File(void) { return file; }
+    QString Title(void) { return title; }
+    int Length(void) { return length; }
+
+    void setFile(const QString &f) { file = f; }
+    void setTitle(const QString &t) { title = t; }
+    void setLength(int l) { length = l; }
+
+  private:  
+    QString file;
+    QString title;
+    int length;
+};
+
+/** \brief Class for containing the info of a pls file
+ */
+class PlayListFile 
+{
+public:
+    PlayListFile ();
+    ~PlayListFile ();
+
+    /** \brief Get the number of entries in the pls file 
+
+        This returns the number of actual parsed entries, not the
+        <tt>numberofentries</tt> field.
+
+        \returns the number of entries
+    */
+    int size(void) { return entries.count(); }
+
+    /** \brief Get a pls file entry
+        \param i which entry to get, between 0 and the value returned by calling \p PlayListParser::size
+        \returns a pointer to a \p PlayListEntry
+    */
+    PlayListFileEntry* get(int i) { return entries.at(i); }
+
+    /** \brief Version of the parsed pls file 
+
+        Returns the version number specified in the <tt>version</tt>
+        field of the pls file.
+
+        \returns the version number
+     */
+    int version(void) { return _version ; }
+
+    /** Add a entry to the playlist 
+        \param e a \p PlayListFileEntry object
+     */
+    void add(PlayListFileEntry *e) { entries.append(e); }
+
+    /** Clear out all the entries */
+    void clear(void) { entries.clear(); }
+
+    /** Perform internal unittest */
+    static void test(void);
+
+    /** \brief Parse a pls file.
+        \param stream the playlist file in a \p QTextStream
+        \returns the number of entries parsed 
+    */
+    static int parse(PlayListFile *pls, QTextStream *stream);
+  private:
+    QPtrList<PlayListFileEntry> entries;
+    int _version;
+};
+
+#endif /* PLS_H_ */
Index: mythplugins/mythmusic/mythmusic/playbackbox.h
===================================================================
--- mythplugins/mythmusic/mythmusic/playbackbox.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/playbackbox.h	(working copy)
@@ -12,11 +12,11 @@
 #include "mainvisual.h"
 #include "metadata.h"
 #include "playlist.h"
-#include "editmetadata.h"
 #include "databasebox.h"
 
 class Output;
 class Decoder;
+class DecoderHandler;
 
 class PlaybackBoxMusic : public MythThemedDialog
 {
@@ -79,6 +79,8 @@
 
     void occasionallyCheckCD();
 
+    void operationProgressTimer();
+
     // popup menu
     void showMenu();
     void closePlaylistPopup();
@@ -90,6 +92,8 @@
     void fromCD();
     void showSmartPlaylistDialog();
     void showSearchDialog();
+	void showAddRadioStationDialog();
+	void showRemoveRadioStationDialog();
     bool getInsertPLOptions(InsertPLOption &insertOption,
                             PlayPLOption &playOption, bool &bRemoveDups);
 
@@ -105,18 +109,29 @@
     void doUpdatePlaylist(QString whereClause);
     void CycleVisualizer(void);
     void updatePlaylistFromCD(void);
-    void setTrackOnLCD(Metadata *mdata);
+    void setTrackOnLCD(const Metadata *mdata);
     void updateTrackInfo(Metadata *mdata);
     void openOutputDevice(void);
+    void setupDecoderHandler(void);
     void postUpdate();
     void playFirstTrack();
     void bannerEnable(QString text, int millis);
     void bannerEnable(Metadata *mdata);
     void bannerToggle(Metadata *mdata);
+    void addRadioMenuEntries();
 
-    QIODevice *input;
+    void decoderHandlerReady(void);
+    void decoderHandlerInfo(const QString&, const QString&);
+    void decoderHandlerOperationStart(const QString &);
+    void decoderHandlerOperationStop();
+    
+    QString operation_name;
+    int operation_progress_count;
+    QTimer  *decoder_handler_progress_timer;
+
     AudioOutput *output;
     Decoder *decoder;
+    DecoderHandler *decoderHandler;
 
     QString playfile;
     QString statusString;
@@ -142,8 +157,8 @@
     int currentTime, maxTime;
 
     Metadata *curMeta;
+    Metadata displayMeta;
 
-
     unsigned int shufflemode;
     unsigned int repeatmode;
 
Index: mythplugins/mythmusic/mythmusic/mainvisual.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/mainvisual.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/mainvisual.cpp	(working copy)
@@ -270,6 +270,33 @@
     else
         len = 0;
 
+    if (len < 512)
+    {
+        short *new_l = 0;
+        short *new_r = 0;
+
+        if (c == 1)
+        {
+            new_l = new short[512];
+            smoothen_mono(l, new_l, len, 512);
+            delete[] l;
+            l = new_l;
+        }
+
+        if (c == 2)
+        {
+            new_l = new short[512];
+            new_r = new short[512];
+            smoothen_stereo(l, new_l, r, new_r, len, 512);
+            delete[] r;
+            delete[] l;
+            r = new_r;
+            l = new_l;
+        }
+
+        len = 512;
+    }
+
     nodes.append(new VisualNode(l, r, len, w));
 }
 
@@ -373,10 +400,18 @@
     visfactories->append(vis);
 }
 
+void MainVisual::addInformation(const QImage &img, const QString &new_info) {
+    info_widget->addInformation(new_info, &img);
+}
+
 void MainVisual::addInformation(const QString &new_info) {
     info_widget->addInformation(new_info);
 }
 
+void MainVisual::clearInformation() {
+    info_widget->clearInformation();
+}
+
 VisualBase *MainVisual::createVis(const QString &name, MainVisual *parent,
                                   long int winid)
 {
@@ -426,7 +461,7 @@
     hide();
 }
 
-void InfoWidget::addInformation(const QString &new_info) {
+void InfoWidget::addInformation(const QString &new_info, const QImage *img) {
     if (new_info == info)
         return;
     
@@ -440,28 +475,36 @@
     info_pixmap = QPixmap(width(), height()/*, pixmap.depth ()*/);
     QPainter p(&info_pixmap);
 
-    int indent = int(info_pixmap.width() * 0.02);
+    int text_indent = int(info_pixmap.width() * 0.02);
+    int img_indent = int(info_pixmap.width() * 0.01);
 
     p.fillRect(0, 0,
                info_pixmap.width(), info_pixmap.height(),
-               QColor ("darkblue"));
+               QColor (0x03, 0x27, 0x44));
 
 
     p.setFont(gContext->GetMediumFont());
 
     QFontMetrics fm(p.font());
-    int width = fm.width(info);
-    int height = fm.height() * (info.contains("\n") + 1);
-    int x = indent;
-    int y = indent;
+    int text_width = fm.width(info);
+    int text_height = fm.height() * (info.contains("\n") + 1);
+    int text_x = text_indent;
+    int text_y = text_indent;
 
+    if (img && !img->isNull()) {
+        text_x += info_pixmap.height();
+        int img_height = info_pixmap.height() - 2 * img_indent;
+        QRect rect(img_indent, img_indent, img_height, img_height);
+        p.drawImage(rect, img->smoothScale (rect.size(), QImage::ScaleMin));
+    }
+
     QString info_copy = info;
-    for (int offset = 0; offset < height; offset += fm.height()) {
+    for (int offset = 0; offset < text_height; offset += fm.height()) {
         QString l = info_copy.left(info_copy.find("\n"));
         p.setPen(Qt::black);
-        p.drawText(x + 2, y + offset + 2, width, height, Qt::AlignLeft, l);
+        p.drawText(text_x + 2, text_y + offset + 2, text_width, text_height, Qt::AlignLeft, l);
         p.setPen(Qt::white);
-        p.drawText(x, y + offset, width, height, Qt::AlignLeft, l);
+        p.drawText(text_x, text_y + offset, text_width, text_height, Qt::AlignLeft, l);
         info_copy.remove(0, l.length () + 1);
     }
 
@@ -469,6 +512,10 @@
     repaint();
 }
 
+void InfoWidget::clearInformation() {
+    addInformation("", 0);
+}
+
 void InfoWidget::paintEvent ( QPaintEvent * ) 
 {
     bitBlt(this, 0, 0, &info_pixmap); 
@@ -505,10 +552,10 @@
     long s, indexTo;
     double *magnitudesp = magnitudes.data();
     double valL, valR, tmpL, tmpR;
-    double index, step = 512.0 / size.width();
 
     if (node) {
-	index = 0;
+    double step = (node->length < 512 ? node->length : 512.0) / size.width();
+	double index = 0;
 	for ( i = 0; i < size.width(); i++) {
 	    indexTo = (int)(index + step);
             if (indexTo == (int)(index))
@@ -704,11 +751,10 @@
     double *magnitudesp = magnitudes.data();
     double val, tmp;
 
-    double index, step = 512.0 / size.width();
-
     if (node) 
     {
-        index = 0;
+        double step = (node->length < 512 ? node->length : 512.0) / size.width();
+        double index = 0;
         for ( i = 0; i < size.width(); i++) 
         {
             indexTo = (int)(index + step);
Index: mythplugins/mythmusic/mythmusic/decoderhandler.h
===================================================================
--- mythplugins/mythmusic/mythmusic/decoderhandler.h	(revision 0)
+++ mythplugins/mythmusic/mythmusic/decoderhandler.h	(revision 0)
@@ -0,0 +1,206 @@
+#ifndef DECODERHANDLER_H_
+#define DECODERHANDLER_H_
+
+#include <qobject.h>
+#include <qiodevice.h>
+#include <qfile.h>
+#include <qhttp.h>
+#include <qurl.h>
+
+#include <mythtv/mythobservable.h>
+
+#include "pls.h"
+
+class QUrl;
+class QUrlOperator;
+
+class Decoder;
+class Metadata;
+class DecoderIOFactory;
+class DecoderHandler;
+
+/** \brief Events sent by the \p DecoderHandler and it's helper classes.
+ */
+class DecoderHandlerEvent : public MythEvent
+{
+  public:
+    enum Type { Ready = (QEvent::User + 300), Meta, Info, OperationStart, OperationStop, Error};
+
+    DecoderHandlerEvent(Type t)
+        : MythEvent(t), m_msg(0), m_meta(0)
+    { ; }
+
+    DecoderHandlerEvent(Type t, QString *e)
+        : MythEvent(t), m_msg(e), m_meta(0)
+    { ; }
+
+    DecoderHandlerEvent(const Metadata &m);
+    ~DecoderHandlerEvent();
+
+    const QString *getMessage() const { return m_msg; }
+    const Metadata *getMetadata() const { return m_meta; }
+
+    virtual DecoderHandlerEvent *clone();
+
+private:
+    QString *m_msg;
+    Metadata *m_meta;
+};
+
+/** \brief Class for starting stream decoding.
+
+    This class handles starting the \p Decoder for the \p
+    PlaybackBox via DecoderIOFactorys. 
+
+    It operates on a playlist, either created with a single file, by
+    loading a .pls or downloading it, and for each entry creates an
+    appropriate DecoderIOFactory. The creator is simply a intermediate
+    class that translates the next URL in the playlist to
+    QIODevice. Ie. the DecoderIOFactoryFile just returns a QFile,
+    whereas the DecoderIOFactoryShoutcast returns a QSocket subclass,
+    where reads do the necessary translation of the shoutcast stream.
+ */
+class DecoderHandler : public QObject, public MythObservable
+{
+    Q_OBJECT
+    friend class DecoderIOFactory;
+  public:
+    typedef enum { 
+        ACTIVE,
+        LOADING,
+        STOPPED
+    } State;
+
+    DecoderHandler();
+    virtual ~DecoderHandler();
+
+    Decoder *getDecoder(void) { return m_decoder; }
+    void start(Metadata *mdata);
+
+    void stop(void);
+    void customEvent(QCustomEvent*);
+    bool done();
+    bool next(void);
+    void error(const QString &msg);
+
+  protected:
+    void doOperationStart(const QString &name);
+    void doOperationStop(void);
+    void doConnectDecoder(const QUrl &url, const QString &format);
+    void doFailed(const QUrl &url, const QString &message);
+    void doInfo(const QString &message);
+
+  private:  
+	int               m_state;
+    int               m_playlist_pos;
+    PlayListFile      m_playlist;
+    DecoderIOFactory *m_io_factory;
+    Decoder          *m_decoder;
+    Metadata         *m_meta;
+	bool              m_op;
+	uint              m_redirects;
+
+    static const uint MaxRedirects = 3;
+
+    bool createPlaylist(const QUrl &url);
+    bool createPlaylistForSingleFile(const QUrl &url);
+    bool createPlaylistFromFile(const QUrl &url);
+    bool createPlaylistFromRemoteUrl(const QUrl &url);
+
+	bool haveIOFactory(void) { return m_io_factory != 0; }
+    DecoderIOFactory *getIOFactory(void) { return m_io_factory; }
+	void createIOFactory(const QUrl &url);
+	void deleteIOFactory(void);
+};
+
+/** \brief The glue between the DecoderHandler and the Decoder
+	
+    The DecoderIOFactory is responsible for opening the QIODevice that
+    is given to the Decoder....
+ */
+class DecoderIOFactory : public QObject, public MythObservable
+{
+  public:
+    DecoderIOFactory(DecoderHandler *parent);
+    virtual ~DecoderIOFactory();
+
+    virtual void start() = 0;
+    virtual void stop() = 0;
+    virtual QIODevice *takeInput(void) = 0;
+
+    void setUrl (const QUrl &url) { m_url = url; }
+    void setMeta (Metadata *meta) { m_meta = meta; }
+
+    static const uint DefaultPrebufferSize = 128 * 1024;
+    static const uint MaxRedirects = 3;
+
+  protected:
+    void doConnectDecoder(const QString &format);
+    Decoder *getDecoder(void);
+    void doFailed(const QString &message);
+    void doInfo(const QString &message);
+    void doOperationStart(const QString &name);
+    void doOperationStop(void);
+
+    QUrl m_url;
+    Metadata *m_meta;
+
+  private:
+    DecoderHandler *m_handler;
+};
+
+class DecoderIOFactoryFile : public DecoderIOFactory 
+{
+    Q_OBJECT
+  public:
+    DecoderIOFactoryFile(DecoderHandler *parent);
+    ~DecoderIOFactoryFile();
+    void start();
+    void stop() {}
+    QIODevice *takeInput(void);
+
+  private:
+    QIODevice *m_input;
+};
+
+class DecoderIOFactoryUrl : public DecoderIOFactory 
+{
+    Q_OBJECT
+  public:
+    DecoderIOFactoryUrl(DecoderHandler *parent);
+    ~DecoderIOFactoryUrl();
+
+    void start();
+    void stop();
+    QIODevice *takeInput(void);
+
+  protected slots:
+    void finished(QNetworkOperation *op);
+    void start(QNetworkOperation *op);
+    void data(const QByteArray & data, QNetworkOperation * op);
+
+  private:
+    void doStart(void);
+    void doClose(void);
+
+    bool m_started;
+    QUrlOperator *m_url_op;
+    QFile *m_output;
+    QFile *m_input;
+};
+
+class DecoderIOFactoryMqp : public DecoderIOFactory 
+{
+    Q_OBJECT
+  public:
+    DecoderIOFactoryMqp(DecoderHandler *parent);
+    ~DecoderIOFactoryMqp();
+    void start();
+    void stop();
+    QIODevice *takeInput(void);
+
+  private:
+    QIODevice *m_input;
+};
+
+#endif /* DECODERHANDLER_H_ */
Index: mythplugins/mythmusic/mythmusic/editradiometadata.h
===================================================================
--- mythplugins/mythmusic/mythmusic/editradiometadata.h	(revision 0)
+++ mythplugins/mythmusic/mythmusic/editradiometadata.h	(revision 0)
@@ -0,0 +1,74 @@
+#ifndef EDITRADIOMETADATA_H_
+#define EDITRADIOMETADATA_H_
+
+#include <iostream>
+using namespace std;
+
+#include <mythtv/mythdialogs.h>
+
+#include "editmetadata.h"
+
+class UIPhoneEntry;
+
+class EditRadioMetadataDialog : public MythThemedDialog
+{
+
+  Q_OBJECT
+
+  public:
+
+    EditRadioMetadataDialog(Metadata *source_metadata,
+							MythMainWindow *parent,
+							QString window_name,
+							QString theme_filename,
+							const char* name = 0);
+    ~EditRadioMetadataDialog();
+
+    void keyPressEvent(QKeyEvent *e);
+    void wireUpTheme(void);
+    void fillWidgets(void);
+
+  public slots:
+
+    void closeDialog(void);
+    void searchStation(void);
+    void searchGenre(void);
+    void incRating(bool up_or_down);
+    void showSaveMenu(void);
+	bool verifyEntries();
+    void saveNewToDatabase();
+    void saveToDatabase();
+    void cancelPopup();
+    void editLostFocus();
+
+  private:
+
+    void fillSearchList(QString field, QString table);
+    bool showList(QString caption, QString &value);
+    
+    Metadata *m_metadata;
+	Metadata *m_sourceMetadata ;
+    MythPopupBox *popup;
+
+    //
+    //  GUI stuff
+    //
+
+    UIRemoteEditType    *station_edit;
+    UIRemoteEditType    *channel_edit;
+    UIRemoteEditType    *url_edit;
+    UIRemoteEditType    *metaformat_edit;
+    UIRemoteEditType    *genre_edit;
+
+    UIRepeatedImageType *rating_image;
+
+    UIPushButtonType    *searchstation_button;
+    UIPushButtonType    *searchgenre_button;
+    UIPushButtonType    *rating_button;
+    
+    UITextButtonType    *done_button;
+    
+    QStringList         searchList;
+};
+
+#endif
Index: mythplugins/mythmusic/mythmusic/shoutcast.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/shoutcast.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/shoutcast.cpp	(revision 0)
@@ -0,0 +1,998 @@
+/*
+  Shoutcast decoder for MythTV.
+  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
+ */
+
+#include <qapplication.h>
+#include <qregexp.h>
+#include <qsocket.h>
+#include <mythtv/mythcontext.h>
+#include <mythtv/httpcomms.h>
+#include "shoutcast.h"
+#include "metadata.h"
+#include <assert.h>
+
+/****************************************************************************/
+
+#define WAIT_FOR_MS 2600
+#define MAX_ALLOWED_META_SIZE 1024 * 100
+#define MAX_REDIRECTS 3
+
+#define to_string(a) case a: return #a
+
+const char* ShoutCastIODevice::stateString (const State &s) {
+    switch (s) {
+        to_string (NOT_CONNECTED);
+        to_string (RESOLVING);
+        to_string (CONNECTING);
+        to_string (CANT_RESOLVE);
+        to_string (CANT_CONNECT);
+		to_string (CONNECTED);
+        to_string (WRITING_HEADER);
+        to_string (READING_HEADER);
+		to_string (PLAYING);
+        to_string (STREAMING);
+        to_string (STREAMING_META);
+        to_string (STOPPED);
+    default:
+        return "unknown state";        
+    }
+}
+
+#undef to_string
+
+int moveFromByteArray(QByteArray &array, char *data, Q_ULONG maxlen) {
+    Q_ULONG consumed = array.size ();
+
+    if (consumed > maxlen)
+        consumed = maxlen;    
+    if (data)
+        memcpy (data, array.data(), consumed);   
+    if (consumed != array.size ())
+        memmove (array.data(), array.data() + consumed, array.size() - consumed);
+    array.resize(array.size() - consumed);
+
+    return consumed;
+}
+
+/****************************************************************************/
+
+/** \brief Class to download shoutcast station logos
+
+    Runs a HttpComms request in a thread until success/failure or
+    stop_transfer is called.
+ */
+class ShoutCastLogoGrabber : public QThread {
+public:
+    ShoutCastLogoGrabber(const Metadata *m) : m_meta(*m), m_stop(false) { }
+
+    void stop_transfer() { m_stop = true; }
+
+    void run() {
+        HttpComms *comms = new HttpComms;
+        // FIXME: use metadata::getField to access logourl without having 
+        // to add a LogoURL call..
+        QString urlstr;
+        m_meta.getField("x-cast-logourl", &urlstr);
+        QUrl url(urlstr);
+        comms->request(url, 10000, false);
+        while (!m_stop && !comms->isDone())
+            usleep(500);
+        if (m_stop)
+            return;
+        if (comms->isTimedout() || comms->getStatusCode() != 200) {
+            comms->deleteLater();
+            comms = NULL;
+            return;
+        }
+        m_meta.setAlbumArt(comms->getRawData());
+        comms->deleteLater();
+        comms = NULL;
+    }
+
+private:
+    Metadata m_meta;
+    bool m_stop;
+};
+
+/****************************************************************************/
+
+/** \brief A buffer class
+    The intent with this it can be replaced with may RingBuffer ?
+ */
+class ShoutCastBuffer {
+public:
+    ShoutCastBuffer () : m_pos (0), m_size (0) { }
+    ~ShoutCastBuffer () { }
+
+    Q_ULONG Read (char *data, Q_ULONG max) {
+        QMutexLocker holder (&m_mutex);
+        const QByteArray &next_buffer = m_buffers.front();
+        const char *next_data = next_buffer.data () + m_pos;
+        uint next_size = next_buffer.size () - m_pos;
+
+        if (max > m_size)
+            max = m_size;
+
+        if (max > next_size)
+            max = next_size;
+        
+        memcpy (data, next_data, max);
+
+        m_pos += max;
+        m_size -= max;
+        
+        if (max == next_size) {
+            m_pos = 0;
+            m_buffers.pop_front();
+        }
+
+        return max;
+    }
+
+    /** \brief Add data to the buffer
+        \param data the bytes to add, will be owned by the ShoutCastBuffer
+        \param sz the size of data
+    */
+    void Write (char *data, uint sz) {
+        if (sz == 0)
+            return;
+
+        QMutexLocker holder (&m_mutex);
+        QByteArray array (sz);
+        array.assign (data, sz);
+        m_buffers.push_back (array);
+        m_size += sz;
+    }
+
+    /** \brief Add data to the buffer
+        \param arr the byte array to add
+    */
+    void Write (QByteArray &array) {
+        if (array.size () == 0)
+            return;
+        QMutexLocker holder (&m_mutex);
+        m_buffers.push_back (array);
+        m_size += array.size ();
+    }
+
+    Q_ULONG ReadBufAvail () const { 
+        return m_size; 
+    }
+
+    static void selfTest () {
+        VERBOSE(VB_IMPORTANT, "Selftesting ShoutCastBuffer");
+        ShoutCastBuffer iobuf;
+        char *data_1 = strdup ("aaaaa");
+        char *data_2 = strdup ("bbbbb");
+        QByteArray arr (5);
+        arr.duplicate ("ccccc", 5);
+
+        iobuf.Write (data_1, strlen (data_1));
+        assert (iobuf.ReadBufAvail() == 5);
+        iobuf.Write (data_2, strlen (data_2));
+        assert (iobuf.ReadBufAvail() == 10);
+        iobuf.Write (arr);
+        assert (iobuf.ReadBufAvail() == 15);
+
+        char *data = (char*)alloca(iobuf.ReadBufAvail());
+        assert (iobuf.Read (data, 3) == 3);
+        assert (iobuf.ReadBufAvail() == 12);
+        assert (memcmp (data ,"aaa", 3) == 0);
+
+        assert (iobuf.Read (data, 3) == 2);
+        assert (iobuf.ReadBufAvail() == 10);
+        assert (memcmp (data ,"aa", 2) == 0);
+
+        assert (iobuf.Read (data, 10) == 5);
+        assert (iobuf.ReadBufAvail() == 5);
+        assert (memcmp (data ,"bbbbb", 5) == 0);
+
+        assert (iobuf.Read (data, 5) == 5);
+        assert (iobuf.ReadBufAvail() == 0);
+        assert (memcmp (data ,"ccccc", 5) == 0);
+    }
+
+private:
+    QValueList<QByteArray> m_buffers;
+    uint m_pos;
+    Q_ULONG m_size;
+    QMutex m_mutex;
+};
+
+
+/****************************************************************************/
+
+class ShoutCastRequest 
+{
+public:
+    ShoutCastRequest() { }
+    ShoutCastRequest(const QUrl &url) { setUrl(url); }
+    ~ShoutCastRequest() { }
+	const char *data(void) { return m_data.data(); }
+	uint size(void) { return m_data.size(); }
+	
+private:
+    void setUrl(const QUrl &url) {
+        QString hdr;
+        hdr = QString("GET %1 HTTP/1.1\r\n"
+                      "Host: %2\r\n"
+                      "User-Agent: mythmusic/svn\r\n"
+                      "Keep-Alive:\r\n"
+                      "Connection: TE, Keep-Alive\r\n"
+                      "TE: trailers\r\n"
+                      "icy-metadata:1\r\n"
+                      "\r\n").arg(url.path()).arg(url.host());
+        
+        m_data.duplicate(hdr.ascii(), hdr.length());
+    }
+
+    QByteArray m_data;
+};
+
+
+/****************************************************************************/
+
+
+class ShoutCastResponse 
+{
+  public:
+    ShoutCastResponse() { }
+    ~ShoutCastResponse() { }
+    
+    int getMetaint(void) { return getInt("icy-metaint"); }
+    int getBitrate(void) { return getInt("icy-br"); }
+    QString getGenre(void) { return getString("icy-genre"); }
+    QString getName(void) { return getString("icy-name"); }
+    int getStatus(void) { return getInt("status"); }
+    bool isICY(void) { return QString(m_data["protocol"]).left(3) == "ICY"; }
+    QString getContent(void) { return getString("content-type"); }
+    QString getLocation(void) { return getString("location"); }
+
+    QString getString(const QString &key) { return m_data[key]; }
+    int getInt(const QString &key) { return m_data[key].toInt(); }
+
+    int fillResponse(const char *data, int len);
+  private:
+    QMap<QString,QString> m_data;
+};
+
+/** \brief Consume bytes and parse shoutcast header
+    \returns number of bytes consumed
+*/
+int ShoutCastResponse::fillResponse(const char *s, int l) 
+{
+    QCString d(s, l);
+    int result = 0;
+    // check each line
+    for (;;) 
+    {
+        int pos = d.find("\r");
+
+        if (pos <= 0) 
+            break;
+
+        // Extract the line
+        QCString snip(d.data(), pos + 1);
+        d.remove(0, pos + 2);
+        result += pos + 2;
+        
+        if (snip.left(4) == "ICY ") 
+        {
+            int space = snip.find(' ');
+            m_data["protocol"] = "ICY";
+            QString tmp = snip.mid(space).simplifyWhiteSpace();
+            int second_space = tmp.find(' ');
+            if (second_space > 0) {
+                m_data["status"] = tmp.left(second_space);
+            } else {
+                m_data["status"] = tmp;
+            }
+        } 
+        else if (snip.left(7) == "HTTP/1.") 
+        {
+            int space = snip.find(' ');
+            m_data["protocol"] = snip.left(space);
+            QString tmp = snip.mid(space).simplifyWhiteSpace();
+            int second_space = tmp.find(' ');
+            if (second_space > 0) {
+                m_data["status"] = tmp.left(second_space);
+            } else {
+                m_data["status"] = tmp;
+            }
+        } 
+        else if (snip.left(9).lower() == "location:") 
+        {
+            m_data["location"] = snip.mid(9).stripWhiteSpace();
+        } 
+        else if (snip.left(13).lower() == "content-type:") 
+        {
+            m_data["content-type"] = snip.mid(13).stripWhiteSpace();
+        } 
+        else if (snip.left(4) == "icy-") 
+        {
+            int pos = snip.find(':');
+            QString key = snip.left(pos);
+            m_data[key.ascii()] = snip.mid(pos+1).stripWhiteSpace();
+        }
+    }
+
+    return result;
+}
+
+/****************************************************************************/
+
+class ShoutCastMetaParser
+{
+  public:
+	ShoutCastMetaParser() { }
+	~ShoutCastMetaParser() { }
+
+	void setMetaFormat(const QString &metaformat);
+	ShoutCastMetaMap parseMeta(QString meta);
+
+  private:
+	QString m_meta_format;
+	int m_meta_artist_pos;
+	int m_meta_title_pos;
+	int m_meta_album_pos;
+};
+
+void ShoutCastMetaParser::setMetaFormat(const QString &metaformat) 
+{
+/*
+  We support these metatags :
+  %a - artist
+  %t - track
+  %b - album
+  %r - random bytes
+ */
+    m_meta_format = metaformat;
+
+    m_meta_artist_pos = 0;
+    m_meta_title_pos = 0;
+    m_meta_album_pos = 0;
+
+    int assign_index = 1;
+    int pos = 0;
+
+    pos = m_meta_format.find("%", pos);
+    while (pos >= 0) {
+        pos++;
+        QChar ch = m_meta_format.at(pos);
+
+        if (ch == '%') {
+            pos++;
+        }
+        else if (ch == 'r' || ch == 'a' || ch == 'b' || ch == 't') 
+        {
+            if (ch == 'a')
+                m_meta_artist_pos = assign_index;
+            
+            if (ch == 'b')
+                m_meta_album_pos = assign_index;
+            
+            if (ch == 't')
+                m_meta_title_pos = assign_index;
+
+            assign_index++;
+        } else
+            fprintf(stderr, "CastDecoder: malformed metaformat '%s'\n", m_meta_format.ascii());
+
+        pos = m_meta_format.find("%", pos);
+    }
+    
+    m_meta_format.replace("%a", "(.*)");
+    m_meta_format.replace("%t", "(.*)");
+    m_meta_format.replace("%b", "(.*)");
+    m_meta_format.replace("%r", "(.*)");
+    m_meta_format.replace("%%", "%");
+}
+
+ShoutCastMetaMap ShoutCastMetaParser::parseMeta(QString meta)
+{
+    QCString metastring(meta);
+    ShoutCastMetaMap result;
+    int title_begin_pos = metastring.find("StreamTitle='");
+    int title_end_pos;
+
+    if (title_begin_pos >= 0) 
+    {
+        title_begin_pos += 13;
+        title_end_pos = metastring.find("';", title_begin_pos);
+        QCString title = metastring.mid(title_begin_pos, 
+                                        title_end_pos - title_begin_pos);
+        QRegExp rx;
+        rx.setPattern(m_meta_format);
+        if (rx.search(title) != -1)
+        {
+            VERBOSE(VB_PLAYBACK, QString("ShoutCast: Meta     : '%1'").
+                    arg(meta));
+            VERBOSE(VB_PLAYBACK, QString("ShoutCast: Parsed as: '%1' by '%2'").
+                    arg(rx.cap(m_meta_title_pos)).
+                    arg(rx.cap(m_meta_artist_pos)));
+            
+            if (m_meta_title_pos > 0) 
+                result["title"] = rx.cap(m_meta_title_pos);
+            
+            if (m_meta_artist_pos > 0) 
+                result["artist"] = rx.cap(m_meta_artist_pos);
+            
+            if (m_meta_album_pos > 0) 
+                result["album"] = rx.cap(m_meta_album_pos);
+        }
+    }
+
+    return result;
+}
+
+/****************************************************************************/
+
+ShoutCastIODevice::ShoutCastIODevice ()
+    :  m_redirects (0), 
+       m_scratchpad_pos (0),
+       m_state (NOT_CONNECTED)
+{ 
+    printf ("CTOR iodev=%p\n", dynamic_cast<QIODevice*>(this));
+    ShoutCastBuffer::selfTest();
+
+    m_buffer = new ShoutCastBuffer;
+    m_socket = new QSocket (this);
+    m_response = new ShoutCastResponse;
+
+    connect (m_socket, SIGNAL (hostFound ()), SLOT (socketHostFound ()));
+    connect (m_socket, SIGNAL (connected ()), SLOT (socketConnected ()));
+    connect (m_socket, SIGNAL (connectionClosed ()), SLOT (socketConnectionClosed ()));
+    connect (m_socket, SIGNAL (readyRead ()), SLOT (socketReadyRead ()));
+    connect (m_socket, SIGNAL (error (int)), SLOT (socketError (int)));
+
+    switchToState (NOT_CONNECTED);
+
+    setFlags (IO_Direct | IO_Async | IO_ReadOnly);
+
+    m_sample_tv.tv_sec = 0;
+    m_sample_tv.tv_usec = 0;
+}
+
+ShoutCastIODevice::~ShoutCastIODevice () { 
+    printf ("DTOR iodev=%p\n", dynamic_cast<QIODevice*>(this));
+    delete m_buffer;
+    delete m_response;
+    m_socket->disconnect (this);
+}
+
+void ShoutCastIODevice::connectToUrl (const QUrl &url)  { 
+    m_url = url;
+    switchToState (RESOLVING);
+    setMode(IO_ReadOnly);
+    setState(IO_Open);
+    return m_socket->connectToHost (m_url.host (), m_url.port ());
+}
+
+bool ShoutCastIODevice::open(int m) { 
+    return m_socket->open (m);
+}
+
+void ShoutCastIODevice::close() { 
+    return m_socket->close();
+}
+
+void ShoutCastIODevice::flush() { 
+    return m_socket->flush();
+}
+
+Q_ULONG ShoutCastIODevice::size() const { 
+    return m_buffer->ReadBufAvail(); 
+}
+
+bool ShoutCastIODevice::waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed) {
+    if (m_state != STOPPED && m_buffer->ReadBufAvail() < bytes_needed)
+        m_cond.wait (millisecs);
+    if (m_buffer->ReadBufAvail() < bytes_needed)
+        return false;
+    return true;
+}
+
+Q_LONG ShoutCastIODevice::readBlock(char *data, Q_ULONG maxlen) { 
+    int result = 0;
+
+    if (waitForData (WAIT_FOR_MS) == false) {
+        VERBOSE(VB_PLAYBACK, "Waited for in stream, got none");
+        switchToState(STOPPED);
+    }
+
+    if (m_state == STREAMING_META && parseMeta()) 
+        switchToState(STREAMING);
+    
+    if (m_state == STREAMING) 
+    {
+        if (waitForData (WAIT_FOR_MS) == false) {
+            VERBOSE(VB_PLAYBACK, "Waited for in stream again, got none");
+            switchToState(STOPPED);
+        } else {
+            result = parseStream(data, maxlen);
+            if (m_bytes_till_next_meta == 0) 
+                switchToState(STREAMING_META);
+        }
+    } 
+    
+    if (m_state != STOPPED) 
+        VERBOSE(VB_NETWORK, QString("ShoutCastIO2: %1 kb in buffer, btnm=%2/%3 state=%4, rb=%5/%6").
+                arg(m_buffer->ReadBufAvail () / 1024, 4).
+                arg(m_bytes_till_next_meta, 4).
+                arg(m_response->getMetaint()).
+                arg(stateString (m_state)).
+                arg(result).
+                arg(maxlen));
+    else 
+        VERBOSE(VB_NETWORK, QString("ShoutCastIO2: stopped"));
+    
+    return result;
+}
+Q_LONG ShoutCastIODevice::writeBlock(const char *data, Q_ULONG sz) { 
+    return m_socket->writeBlock (data, sz);
+}
+
+Q_ULONG ShoutCastIODevice::bytesAvailable () const {
+    return m_buffer->ReadBufAvail ();
+}
+
+int ShoutCastIODevice::getch() { 
+    assert (0);
+    return -1;
+}
+int ShoutCastIODevice::putch(int) { 
+    assert (0);
+    return -1;
+}
+int ShoutCastIODevice::ungetch(int) { 
+    assert (0);
+    return -1;
+}
+
+void ShoutCastIODevice::socketHostFound () {
+    VERBOSE(VB_NETWORK, "ShoutCastIO2: Host Found");
+    //disconnect (m_socket, SIGNAL (hostFound ()), this, 0);
+    switchToState (CONNECTING);
+}
+
+void ShoutCastIODevice::socketConnected () {
+    VERBOSE(VB_NETWORK, "ShoutCastIO2: Connected");
+    //disconnect (m_socket, SIGNAL (connected ()), this, 0);
+    switchToState (CONNECTED);
+
+    ShoutCastRequest request (m_url);
+    Q_ULONG written = m_socket->writeBlock (request.data (), request.size ());
+    VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Sending Request, %1 of %1 bytes").arg(written).arg(request.size()));
+
+    //disconnect (m_socket, SIGNAL (connected ()), this, 0);
+
+    if (written != request.size ()) {
+        m_scratchpad.duplicate (request.data () + written, request.size () - written);
+        m_scratchpad_pos = 0;
+        connect (m_socket, SIGNAL (bytesWritten (int)), SLOT (socketBytesWritten (int)));
+        switchToState (WRITING_HEADER);
+    } else {
+        switchToState (READING_HEADER);
+    }
+}
+
+void ShoutCastIODevice::socketConnectionClosed () {
+    VERBOSE(VB_NETWORK, "ShoutCastIO2: Connection Closed");
+    switchToState (STOPPED);
+}
+
+void ShoutCastIODevice::socketReadyRead () {
+    Q_ULONG sz = m_socket->bytesAvailable();
+    //VERBOSE(VB_IMPORTANT, QString ("ShoutCastIO2: %1 bytes readable").arg(sz));    
+
+    char *data = (char*)malloc (sz);
+    Q_ULONG actual_sz = m_socket->readBlock (data, sz);
+    if (actual_sz < sz)
+        data = (char*)realloc (data, actual_sz);
+
+    if (m_state == READING_HEADER) {
+        if (parseHeader (data, actual_sz) == 1) {
+            if (m_response->isICY () && m_response->getStatus () == 200) {
+                switchToState (PLAYING);
+                
+                m_response_gotten = true;
+                
+                // debug, collect kb/s info
+                gettimeofday (&m_sample_tv, NULL);
+                m_bytes_downloaded = 0;
+                m_bytes_till_next_meta = m_response->getMetaint();
+                
+                // whatever's left in the scratch pad, toss into m_buffer
+                free (data);
+                m_buffer->Write (m_scratchpad);
+                m_cond.wakeOne ();
+                
+                switchToState (STREAMING);
+            } else if (m_response->getStatus () == 302) {
+                if (++m_redirects > MAX_REDIRECTS) {
+                    VERBOSE (VB_NETWORK, QString ("Too many redirects"));
+                    switchToState (STOPPED);
+                } else {
+                    VERBOSE (VB_NETWORK, QString ("Redirect to %1").arg(m_response->getLocation()));
+                    connectToUrl(m_url);
+                }
+            } else {
+                VERBOSE (VB_NETWORK, QString ("Unknown response status %1").arg (m_response->getStatus ()));
+                switchToState (STOPPED);
+            }
+
+        }
+    } else {
+        m_buffer->Write (data, actual_sz);
+        m_cond.wakeOne ();
+        doKbPerSecond(actual_sz);
+    }
+}
+
+void ShoutCastIODevice::socketBytesWritten (int) {
+    Q_ULONG written = m_socket->writeBlock (m_scratchpad.data () + m_scratchpad_pos,
+                                            m_scratchpad.size () - m_scratchpad_pos);
+    VERBOSE(VB_NETWORK, QString ("ShoutCastIO: %1 bytes written").arg(written));
+
+    m_scratchpad_pos += written;
+    if (m_scratchpad_pos == m_scratchpad.size ()) {
+        m_scratchpad.truncate (0);
+        disconnect (m_socket, SIGNAL (bytesWritten (int)), this, 0);
+        switchToState (READING_HEADER);
+    }
+}
+
+void ShoutCastIODevice::socketError (int error) {
+    VERBOSE(VB_NETWORK, QString ("ShoutCastIO: Socket Error %1").arg(error));
+
+    switch (error) {
+    case QSocket::ErrConnectionRefused:
+        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Connection Refused");
+        switchToState(CANT_CONNECT);
+        break;
+    case QSocket::ErrHostNotFound:
+        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Host Not Found"); 
+        switchToState(CANT_RESOLVE);
+        break;
+    case QSocket::ErrSocketRead:
+        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Socket Read");
+        switchToState (STOPPED);
+        break;
+    }
+}
+
+void ShoutCastIODevice::switchToState(const State &state) 
+{
+    switch (state) 
+    {
+    case PLAYING:
+        VERBOSE(VB_PLAYBACK, QString ("Playing %1 (%2) at %3 kbps").
+                arg(m_response->getName()).
+                arg(m_response->getGenre()).
+                arg(m_response->getBitrate()));
+        break;
+    case STREAMING:
+        if (m_state == STREAMING_META)
+            m_bytes_till_next_meta = m_response->getMetaint();
+        break;
+    case STOPPED:
+        m_socket->close ();
+        m_cond.wakeAll ();
+        break;
+    default:
+        break;
+    }
+
+    m_state = state;
+    emit changedState(m_state);
+}
+
+int ShoutCastIODevice::parseHeader(const char *data, Q_ULONG len) {
+    // Pad the read data to the end of m_response_buf
+    int old_buf_size = m_scratchpad.size();
+    m_scratchpad.resize(old_buf_size + len);
+    memcpy(m_scratchpad.data() + old_buf_size, data, len);
+    
+    int consumed = m_response->fillResponse(m_scratchpad.data(), m_scratchpad.size());        
+    VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header, %1 bytes").arg(consumed));
+    {
+        QString tmp;
+        tmp.setAscii (m_scratchpad.data (), consumed);        
+        VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header %1").arg(tmp));
+    }
+    moveFromByteArray (m_scratchpad, 0, consumed);
+    
+    if (m_scratchpad.size() >= 2 && 
+        m_scratchpad[0] == '\r' && 
+        m_scratchpad[1] == '\n')
+    {
+        moveFromByteArray (m_scratchpad, 0, 2);        
+        return 1;
+    }
+    
+    return 0;
+}
+
+bool ShoutCastIODevice::getResponse(ShoutCastResponse &response) {
+    if (! m_response_gotten)
+        return false;
+
+    response = *m_response;
+    return true;
+}
+
+int ShoutCastIODevice::parseStream(char *data, Q_ULONG maxlen) {
+    if (maxlen > m_bytes_till_next_meta)
+        maxlen = m_bytes_till_next_meta;
+
+    int result = m_buffer->Read (data, maxlen);
+    m_bytes_till_next_meta -= result;
+
+    doKbPerSecond(result);
+    return result;
+}
+
+bool ShoutCastIODevice::parseMeta() {
+    char ch;
+    m_buffer->Read (&ch, 1);
+    Q_ULONG meta_size = 0;
+
+    if (ch < 0)
+        meta_size = 0x100 + ch;
+    else 
+        meta_size = ch;
+
+    meta_size *= 16;
+
+    if (meta_size == 0)
+        return true;
+
+    // FIXME: in case the stream is f*cked, we don't want to allocate too much 
+    if (meta_size > MAX_ALLOWED_META_SIZE) {
+        VERBOSE(VB_PLAYBACK, QString ("Error in stream, got a meta size of %1").arg(meta_size));
+        switchToState (STOPPED);
+        return false;
+    }
+
+    VERBOSE(VB_NETWORK, QString("ShoutCast: Reading %1 bytes of meta").arg(meta_size));
+
+    if (waitForData (WAIT_FOR_MS, meta_size) == false) {
+        VERBOSE(VB_PLAYBACK, "Stream seems to have stopped");
+        switchToState(STOPPED);
+        return false;
+    }
+
+    // Read in a loop until we have all of meta_size (but no more)
+    QByteArray metadata (meta_size);
+    Q_ULONG bytes_read = 0;
+    do {
+        Q_ULONG len = m_buffer->Read (metadata.data () + bytes_read, meta_size - bytes_read);
+        if (len <= 0) {
+            VERBOSE(VB_PLAYBACK, QString ("Error in metadata, expected %1 bytes of meta, got %1").
+                    arg(meta_size).arg(len));
+            switchToState(STOPPED);
+            return false;
+        }
+        bytes_read += len;
+    } while (bytes_read < meta_size);
+    m_bytes_downloaded += bytes_read;
+
+    // Avoid sending signals if the data hasn't changed
+    QString metadata_string (metadata);
+    if (m_last_metadata == metadata_string) 
+        return true;
+
+    m_last_metadata = metadata_string;
+    emit meta(metadata_string);
+
+    return true;
+}
+
+void ShoutCastIODevice::doKbPerSecond(int bytes_read)
+{
+    // debug, collect kb/s info
+    m_bytes_downloaded += bytes_read;
+    struct timeval tv = {0, 0};
+    gettimeofday(&tv, NULL);
+    int msecs =((tv.tv_sec * 1000000 + tv.tv_usec) - 
+                (m_sample_tv.tv_sec * 1000000 + m_sample_tv.tv_usec)) / 1000;
+
+    if (msecs > 5000) 
+    {
+        VERBOSE(VB_NETWORK, QString("ShoutCast: download speed, %1 kb in %2 s =  %3 kb/s").
+                arg(m_bytes_downloaded/1024.0, 1, 'f', 1).
+                arg(msecs/1000.0, 1, 'f', 1).
+                arg((m_bytes_downloaded/1024)/(msecs/1000.0), 3, 'f', 1));
+        m_sample_tv = tv;
+        m_bytes_downloaded = 0;
+    }
+}
+
+/****************************************************************************/
+
+DecoderIOFactoryShoutCast::DecoderIOFactoryShoutCast(DecoderHandler *parent) 
+    : DecoderIOFactory(parent), m_timer(NULL), m_input(NULL), m_logo_grabber(NULL)
+{
+    m_timer = new QTimer(this);
+}
+
+DecoderIOFactoryShoutCast::~DecoderIOFactoryShoutCast() 
+{
+    closeIODevice();
+    if (m_logo_grabber) {
+        m_logo_grabber->stop_transfer();
+        m_logo_grabber->wait();
+        delete m_logo_grabber;
+        m_logo_grabber = NULL;
+    }
+    delete m_logo_grabber;
+}
+
+QIODevice* DecoderIOFactoryShoutCast::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void DecoderIOFactoryShoutCast::makeIODevice() 
+{
+    closeIODevice();
+
+    m_input = new ShoutCastIODevice();
+
+    connect(m_input, SIGNAL(meta(const QString&)),
+            this,    SLOT(shoutcastMeta(const QString&)));
+    connect(m_input, SIGNAL(changedState(ShoutCastIODevice::State)),
+            this,    SLOT(shoutcastChangedState(ShoutCastIODevice::State)));
+}
+
+void DecoderIOFactoryShoutCast::closeIODevice() 
+{
+    if (m_input) {
+        m_input->disconnect();
+        if (m_input->isOpen()) {
+            m_input->close();
+        }
+        m_input->deleteLater();
+        m_input = NULL;
+        VERBOSE(VB_PLAYBACK, "DecoderIOFactoryShoutCast m_input is now 0");
+    }
+}
+
+
+void DecoderIOFactoryShoutCast::start()
+{
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactoryShoutCast %1").arg(m_url.toString()));
+    doOperationStart("Connecting");
+
+    makeIODevice();
+    m_input->connectToUrl(m_url);
+
+    m_logo_grabber = new ShoutCastLogoGrabber(m_meta);
+    m_logo_grabber->start();
+}
+
+void DecoderIOFactoryShoutCast::stop()
+{
+    if (m_timer) 
+        m_timer->disconnect ();
+
+    doOperationStop();
+
+    Metadata mdata(*m_meta);    
+    mdata.setTitle("Stopped");
+    mdata.setArtist("");
+    mdata.setLength(-1);
+    DecoderHandlerEvent ev(mdata);
+    dispatch(ev);
+
+    if (m_logo_grabber) {
+        m_logo_grabber->stop_transfer();
+        m_logo_grabber->wait();
+        delete m_logo_grabber;
+        m_logo_grabber = NULL;
+    }
+}
+
+void DecoderIOFactoryShoutCast::periodicallyCheckResponse(void)
+{
+    int res = checkResponseOK();        
+    if (res == 0) 
+    {
+        m_timer->stop();
+        m_timer->disconnect();
+        connect(m_timer, SIGNAL(timeout()),
+                this, SLOT(periodicallyCheckBuffered()));
+        m_timer->start(500);
+    }
+    else if (res < 0) 
+    {
+        m_timer->stop();
+        doFailed("Cannot parse this stream");
+    }
+}
+
+void DecoderIOFactoryShoutCast::periodicallyCheckBuffered(void)
+{
+    uint need = 32 * 1024;
+    VERBOSE(VB_NETWORK, QString("DecoderIOFactoryShoutCast: prebuffered %1/%2KB").
+            arg(m_input->bytesAvailable()/1024).arg(need/1024));
+    
+    if (m_input->bytesAvailable() < need)
+        return;
+    
+    doConnectDecoder("create-mp3-decoder.mp3");
+    m_timer->disconnect();
+    m_timer->stop();
+}
+
+void DecoderIOFactoryShoutCast::shoutcastMeta(const QString &metadata)
+{
+    ShoutCastMetaParser parser;
+    parser.setMetaFormat(m_meta->CompilationArtist());
+
+    ShoutCastMetaMap meta_map = parser.parseMeta(metadata);
+
+    Metadata mdata(*m_meta);
+    mdata.setTitle(meta_map["title"]);
+    mdata.setArtist(meta_map["artist"]);
+    mdata.setAlbum(m_meta->Album()); // meta_map["album"]
+    mdata.setLength(-1);
+
+    DecoderHandlerEvent ev(mdata);
+    dispatch(ev);
+}
+
+void DecoderIOFactoryShoutCast::shoutcastChangedState(ShoutCastIODevice::State state)
+{    
+    VERBOSE(VB_PLAYBACK, QString ("ShoutCast changed state to %1").arg(ShoutCastIODevice::stateString (state)));
+    if (state == ShoutCastIODevice::RESOLVING)
+        doOperationStart("Finding radio");
+    if (state == ShoutCastIODevice::CANT_RESOLVE)
+        doFailed (QObject::tr ("Cannot find radio.\nCheck the URL is correct."));
+
+    if (state == ShoutCastIODevice::CONNECTING)
+        doOperationStart("Connecting to radio");
+    if (state == ShoutCastIODevice::CANT_CONNECT)
+        doFailed (QObject::tr ("Cannot connect to radio.\nCheck the URL is correct."));
+    if (state == ShoutCastIODevice::CONNECTED) {
+        doOperationStart("Connected to radio");
+        m_timer->stop();
+        m_timer->disconnect();
+        connect(m_timer, SIGNAL(timeout()),
+                this, SLOT(periodicallyCheckResponse()));
+        m_timer->start(300);
+    }
+    if (state == ShoutCastIODevice::PLAYING) {
+        doOperationStart("Buffering");
+    }
+
+    if (state == ShoutCastIODevice::STOPPED)
+        stop();
+}
+
+int DecoderIOFactoryShoutCast::checkResponseOK()
+{
+    ShoutCastResponse response;
+
+    if (!m_input->getResponse(response))
+        return 0;
+
+    if (! response.isICY() && 
+        response.getStatus() == 302 && 
+        ! response.getLocation().isEmpty())
+    {
+        // restart with new location...
+        setUrl(response.getLocation());
+        start();
+        return 1;
+    }
+
+    if (! response.isICY() || response.getStatus() != 200) 
+        return -1;
+
+    return 0;
+}
+    
Index: mythplugins/mythmusic/mythmusic/metaio.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/metaio.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metaio.cpp	(working copy)
@@ -2,6 +2,8 @@
 
 #include "metaio.h"
 #include "metadata.h"
+#include <qdir.h>
+#include <qurl.h>
 #include <mythtv/mythcontext.h>
 
 //==========================================================================
@@ -105,3 +107,37 @@
 
     return retdata;
 }
+
+//==========================================================================
+/*!
+ * \brief Retrieve the albumart.
+ *
+ * The default implementation picks a random image file from the
+ * directory in which the file is.
+ *
+ * \param filename The filename to try and determin metadata for.
+ * \returns A QByteArray that can be passed to QImage or such.
+ */
+QByteArray MetaIO::getAlbumArt(const QString &filename)
+{
+    QString curdir = QUrl(filename).dirPath();
+    QString namefilter = gContext->GetSetting("AlbumArtFilter",
+                                              "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
+    // Get directory contents based on filter
+    QDir folder(curdir, namefilter, QDir::Name | QDir::IgnoreCase, 
+                QDir::Files | QDir::Hidden);
+    
+    if (!folder.count())
+        return QByteArray();
+    
+    QString result = folder[rand() % folder.count()];
+    result.prepend("/");
+    result.prepend(curdir);
+    
+    QFile file(result);
+    file.open(IO_ReadOnly);
+    if (!file.isOpen())
+        return QByteArray();
+
+    return file.readAll();
+}
Index: mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp	(working copy)
@@ -78,19 +78,28 @@
         return true;
 
     int numSamps = 512;
+    int step = 1;
+
     if (node->length < 512)
-        numSamps = node->length;
+        step = 512 / node->length;
 
     signed short int data[2][512];
    
     int i = 0;
-    for (i = 0; i < numSamps; i++)
+    for (i = 0; i < numSamps; i += step)
     {
         data[0][i] = node->left[i];
         if (node->right)
             data[1][i] = node->right[i];
         else
             data[1][i] = data[0][i];
+
+        if (step > 1) 
+            for (int j = 1; j < step; j++)
+            {
+                data[0][i+j] = data[0][i];
+                data[1][i+j] = data[1][i];
+            }
     }
 
     for (; i < 512; i++)
Index: mythplugins/mythmusic/mythmusic/visualize.h
===================================================================
--- mythplugins/mythmusic/mythmusic/visualize.h	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/visualize.h	(working copy)
@@ -94,7 +94,7 @@
     bool process(VisualNode *node = 0);
     bool draw(QPainter *p, const QColor &back = Qt::black);
     bool needsUpdate();
-    QString getImageFilename();
+    QByteArray getImage();
 
   private:
     QSize size, cursize;
@@ -142,14 +142,19 @@
     virtual ~Squares();
     
     void resize (const QSize &newsize);
+    bool process(VisualNode *node = 0);
     bool draw(QPainter *p, const QColor &back = Qt::black);
 
   private:
     void drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h);
+    void rotateHues ();
+
     QSize size;
     MainVisual *pParent;
     int fake_height;
     int number_of_squares;
+    int targetHue;
+    int startHue;
 };
 
 class SquaresFactory : public VisFactory
Index: mythplugins/mythmusic/mythmusic/metaioid3v2.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/metaioid3v2.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metaioid3v2.cpp	(working copy)
@@ -11,10 +11,19 @@
 #include "metadata.h"
 
 #include "metaio_libid3hack.h"
+#include <qurl.h>
+#include <mythtv/mythcontext.h>
 
+id3_file *openFile(const QString &filename, id3_file_mode mode) {
+    QUrl url(filename);    
+    if (!url.isLocalFile())
+        return NULL;
+    id3_file *f = id3_file_open(url.path().local8Bit(), mode);
+    if (!f)
+        f = id3_file_open(url.path().ascii(), mode);
+    return f;
+}
 
-#include <mythtv/mythcontext.h>
-
 //==========================================================================
 MetaIOID3v2::MetaIOID3v2(void)
     : MetaIO(".mp3")
@@ -43,13 +52,8 @@
     if (!mdata)
         return false;
 
-    id3_file* p_input = NULL;
-    
-    p_input = id3_file_open(mdata->Filename().local8Bit(), ID3_FILE_MODE_READWRITE);
+    id3_file* p_input = openFile(mdata->Filename().local8Bit(), ID3_FILE_MODE_READWRITE);
     if (!p_input)
-        p_input = id3_file_open(mdata->Filename().ascii(), ID3_FILE_MODE_READWRITE);
-  
-    if (!p_input)
       return false;
 
     // We don't like id3v1 tags... too limiting.
@@ -178,10 +182,7 @@
     bool compilation = false;
     id3_file *p_input = NULL;
     
-    p_input = id3_file_open(filename.local8Bit(), ID3_FILE_MODE_READONLY);
-    if (!p_input)
-        p_input = id3_file_open(filename.ascii(), ID3_FILE_MODE_READONLY);
-
+    p_input = openFile(filename, ID3_FILE_MODE_READONLY);
     if (p_input)
     {
         id3_tag *tag = id3_file_tag(p_input);
@@ -307,6 +308,8 @@
     
     while (loop_de_doo) 
     {
+        const unsigned char *prev_buf = 0;
+
         if (buflen < sizeof(buffer)) 
         {
             int bytes;
@@ -322,6 +325,15 @@
         {
             if (mad_header_decode(&header, &stream) == -1)
             {
+                // If we fail on the same buffer, we're not going to make
+                // any progress.
+                if (prev_buf == stream.buffer) 
+                {
+                    loop_de_doo = false;
+                    break;
+                }
+                prev_buf = stream.buffer;
+
                 if (!MAD_RECOVERABLE(stream.error))
                 {
                     break;
@@ -335,11 +347,13 @@
                     {
                         mad_stream_skip(&stream, tagsize);
                         s.st_size -= tagsize;
+                        prev_buf = 0;
                     }
                 }
             }
             else
             {
+                prev_buf = 0;
                 if(amount_checked == 0)
                 {
                     old_bitrate = header.bitrate;
@@ -596,3 +610,42 @@
 
     return true;
 }
+
+//==========================================================================
+/*!
+ * \brief Function to set an individual comment in an ID3v2 Tag
+ *
+ * \param pTag Pointer to a id3_file object
+ * \param pLabel The label of the comment you want
+ * \param value A reference to the data you want to write
+ * \param desc An optional description (frame type pLabel must support this)
+ * \returns Nothing
+ */
+QByteArray MetaIOID3v2::getAlbumArt(const QString &filename)
+{
+    id3_file *file = openFile(filename, ID3_FILE_MODE_READONLY);
+    if (!file)
+        return MetaIO::getAlbumArt(filename);
+
+    id3_tag *tag = id3_file_tag(file);
+    if (!tag) {
+        id3_file_close(file);
+        return MetaIO::getAlbumArt(filename);
+    }
+
+    struct id3_frame *p_frame = 0;
+    for (int i=0; NULL != (p_frame = id3_tag_findframe(tag, "APIC", i)); ++i) {
+        for (unsigned int f = 0; f < p_frame->nfields; ++f) {
+            id3_field *field = &(p_frame->fields[f]);
+            if (field->type == ID3_FIELD_TYPE_BINARYDATA) {
+                QByteArray data;
+                data.duplicate((char*)field->binary.data, (size_t)field->binary.length);
+                id3_file_close(file);
+                return data;
+            }
+        }
+    }
+
+    id3_file_close(file);
+    return MetaIO::getAlbumArt(filename);
+}
Index: mythplugins/mythmusic/mythmusic/maddecoder.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/maddecoder.cpp	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/maddecoder.cpp	(working copy)
@@ -279,6 +279,9 @@
 
 void MadDecoder::seek(double pos)
 {
+    if(!input()->isDirectAccess())
+        return;
+
     seekTime = pos;
 }
 
@@ -333,6 +336,8 @@
         dispatch(e);
     }
 
+    printf ("MAD iodev=%p\n", dynamic_cast<QIODevice*>(input()));
+
     while (! done && ! finish && ! derror) {
         lock();
 
@@ -423,12 +428,12 @@
 
     unlock();
 
+    deinit();
+
     {
         DecoderEvent e((DecoderEvent::Type) stat);
         dispatch(e);
     }
-
-    deinit();
 }
 
 static inline signed int scale(mad_fixed_t sample)
Index: mythplugins/mythmusic/mythmusic/metaio_libid3hack.c
===================================================================
--- mythplugins/mythmusic/mythmusic/metaio_libid3hack.c	(revision 12919)
+++ mythplugins/mythmusic/mythmusic/metaio_libid3hack.c	(working copy)
@@ -120,7 +120,8 @@
   if (-1 == fseek(file->iofile, location, SEEK_SET)
       || 1 != fwrite(data, length, 1, file->iofile))
     goto fail;
-  
+
+  /* fixme: on linux this can be done via sendfile for faster update */
   /* loop through reading and writing the data */
   if (overlap > 0) {
    
