Part of Slepp's ProjectsPastebinTURLImagebinFilebin
Feedback -- English French German Japanese
Create Upload Newest Tools Donate

Advertising

Miscellany
Friday, August 3rd, 2007 at 11:34:38pm UTC 

  1. Index: myththemes/MythCenter/music-ui.xml
  2. ===================================================================
  3. --- myththemes/MythCenter/music-ui.xml  (revision 0)
  4. +++ myththemes/MythCenter/music-ui.xml  (revision 0)
  5. @@ -0,0 +1,156 @@
  6. +<mythuitheme>
  7. +
  8. +   <window name="edit_radiometadata">
  9. +
  10. +      <font name="title" face="Trebuchet MS">
  11. +          <color>#ffff00</color>
  12. +          <dropcolor>#000000</dropcolor>
  13. +          <size>30</size>
  14. +          <size:small>18</size:small>
  15. +          <shadow>3,3</shadow>
  16. +          <bold>yes</bold>
  17. +      </font>
  18. +
  19. +      <font name="labels" face="Trebuchet MS">
  20. +          <color>#ffff00</color>
  21. +          <dropcolor>#000000</dropcolor>
  22. +          <size>18</size>
  23. +          <size:small>14</size:small>
  24. +          <shadow>3,3</shadow>
  25. +          <bold>yes</bold>
  26. +      </font>
  27. +
  28. +      <font name="display" face="Trebuchet MS">
  29. +          <color>#ffffff</color>
  30. +          <dropcolor>#000000</dropcolor>
  31. +          <size>18</size>
  32. +          <size:small>14</size:small>
  33. +          <shadow>3,3</shadow>
  34. +          <bold>yes</bold>
  35. +      </font>
  36. +
  37. +      <container name="edit_container">
  38. +            <area>0,0,800,600</area>
  39. +
  40. +            <textarea name="title" draworder="1" align="center">
  41. +                <area>0,15,800,50</area>
  42. +                <font>title</font>
  43. +                <value>Track Information</value>
  44. +            </textarea>
  45. +
  46. +            <!--
  47. +                    Labels
  48. +            -->
  49. +
  50. +
  51. +            <textarea name="station_label" draworder="1" align="right">
  52. +                <area>15,70,170,30</area>
  53. +                <font>labels</font>
  54. +                <value>Station:</value>
  55. +            </textarea>
  56. +
  57. +            <textarea name="channel_label" draworder="1" align="right">
  58. +                <area>15,110,170,30</area>
  59. +                <font>labels</font>
  60. +                <value>Channel:</value>
  61. +            </textarea>
  62. +
  63. +            <textarea name="url_label" draworder="1" align="right">
  64. +                <area>15,150,170,30</area>
  65. +                <font>labels</font>
  66. +                <value>URL:</value>
  67. +            </textarea>
  68. +
  69. +            <textarea name="metaformat_label" draworder="1" align="right">
  70. +                <area>15,190,170,30</area>
  71. +                <font>labels</font>
  72. +                <value>Meta:</value>
  73. +            </textarea>
  74. +
  75. +            <textarea name="genre_label" draworder="1" align="right">
  76. +                <area>15,230,170,30</area>
  77. +                <font>labels</font>
  78. +                <value>Genre:</value>
  79. +            </textarea>
  80. +
  81. +            <textarea name="rating_label" draworder="1" align="right">
  82. +                <area>15,350,170,30</area>
  83. +                <font>labels</font>
  84. +                <value>Rating:</value>
  85. +            </textarea>
  86. +
  87. +            <!--
  88. +                    edits
  89. +            -->
  90. +
  91. +            <remoteedit name="station_edit" draworder="1" align="left">
  92. +                <area>195,70,525,35</area>
  93. +                <font>display</font>
  94. +            </remoteedit>
  95. +
  96. +            <pushbutton name="searchstation_button" draworder="2">
  97. +                <position>725,70</position>
  98. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  99. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  100. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  101. +            </pushbutton>
  102. +
  103. +            <remoteedit name="channel_edit" draworder="1" align="left">
  104. +                <area>195,110,525,35</area>
  105. +                <font>display</font>
  106. +            </remoteedit>
  107. +
  108. +            <remoteedit name="url_edit" draworder="1" align="left">
  109. +                <area>195,150,525,35</area>
  110. +                <font>display</font>
  111. +            </remoteedit>
  112. +
  113. +            <remoteedit name="metaformat_edit" draworder="1" align="left">
  114. +                <area>195,190,525,35</area>
  115. +                <font>display</font>
  116. +            </remoteedit>
  117. +
  118. +            <remoteedit name="genre_edit" draworder="1" align="left">
  119. +                <area>195,230,525,35</area>
  120. +                <font>display</font>
  121. +            </remoteedit>
  122. +
  123. +            <pushbutton name="searchgenre_button" draworder="2">
  124. +                <position>725,230</position>
  125. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  126. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  127. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  128. +            </pushbutton>
  129. +
  130. +            <repeatedimage name="rating_image" draworder="1" fleximage="no">
  131. +                <filename>mm_rating.png</filename>
  132. +                <position>190,360</position>
  133. +                <orientation>LeftToRight</orientation>
  134. +            </repeatedimage>
  135. +
  136. +            <selector name="rating_button" draworder="0">
  137. +                <area>420,350,30,30</area>
  138. +                <font>display</font>
  139. +                <image function="on" filename="mm_leftright_on.png"></image>
  140. +                <image function="off" filename="mm_leftright_off.png"></image>
  141. +                <image function="pushed" filename="mm_leftright_pushed.png"></image>
  142. +            </selector>
  143. +
  144. +
  145. +            <!--
  146. +                    Push buttons
  147. +            -->
  148. +
  149. +            <textbutton name="done_button" draworder="0">
  150. +                <position>350,540</position>
  151. +                <font>display</font>
  152. +                <image function="on" filename="text_button_on.png"></image>
  153. +                <image function="off" filename="text_button_off.png"></image>
  154. +                <image function="pushed" filename="text_button_pushed.png"></image>
  155. +            </textbutton>
  156. +
  157. +      </container>
  158. +
  159. +   </window>
  160. +
  161. +</mythuitheme>
  162. Index: myththemes/Retro/music-ui.xml
  163. ===================================================================
  164. --- myththemes/Retro/music-ui.xml       (revision 0)
  165. +++ myththemes/Retro/music-ui.xml       (revision 0)
  166. @@ -0,0 +1,156 @@
  167. +<mythuitheme>
  168. +
  169. +   <window name="edit_radiometadata">
  170. +
  171. +      <font name="title" face="Trebuchet MS">
  172. +          <color>#ffff00</color>
  173. +          <dropcolor>#000000</dropcolor>
  174. +          <size>30</size>
  175. +          <size:small>18</size:small>
  176. +          <shadow>3,3</shadow>
  177. +          <bold>yes</bold>
  178. +      </font>
  179. +
  180. +      <font name="labels" face="Trebuchet MS">
  181. +          <color>#ffff00</color>
  182. +          <dropcolor>#000000</dropcolor>
  183. +          <size>18</size>
  184. +          <size:small>14</size:small>
  185. +          <shadow>3,3</shadow>
  186. +          <bold>yes</bold>
  187. +      </font>
  188. +
  189. +      <font name="display" face="Trebuchet MS">
  190. +          <color>#ffffff</color>
  191. +          <dropcolor>#000000</dropcolor>
  192. +          <size>18</size>
  193. +          <size:small>14</size:small>
  194. +          <shadow>3,3</shadow>
  195. +          <bold>yes</bold>
  196. +      </font>
  197. +
  198. +      <container name="edit_container">
  199. +            <area>0,0,800,600</area>
  200. +
  201. +            <textarea name="title" draworder="1" align="center">
  202. +                <area>0,15,800,50</area>
  203. +                <font>title</font>
  204. +                <value>Track Information</value>
  205. +            </textarea>
  206. +
  207. +            <!--
  208. +                    Labels
  209. +            -->
  210. +
  211. +
  212. +            <textarea name="station_label" draworder="1" align="right">
  213. +                <area>15,70,170,30</area>
  214. +                <font>labels</font>
  215. +                <value>Station:</value>
  216. +            </textarea>
  217. +
  218. +            <textarea name="channel_label" draworder="1" align="right">
  219. +                <area>15,110,170,30</area>
  220. +                <font>labels</font>
  221. +                <value>Channel:</value>
  222. +            </textarea>
  223. +
  224. +            <textarea name="url_label" draworder="1" align="right">
  225. +                <area>15,150,170,30</area>
  226. +                <font>labels</font>
  227. +                <value>URL:</value>
  228. +            </textarea>
  229. +
  230. +            <textarea name="metaformat_label" draworder="1" align="right">
  231. +                <area>15,190,170,30</area>
  232. +                <font>labels</font>
  233. +                <value>Meta:</value>
  234. +            </textarea>
  235. +
  236. +            <textarea name="genre_label" draworder="1" align="right">
  237. +                <area>15,230,170,30</area>
  238. +                <font>labels</font>
  239. +                <value>Genre:</value>
  240. +            </textarea>
  241. +
  242. +            <textarea name="rating_label" draworder="1" align="right">
  243. +                <area>15,350,170,30</area>
  244. +                <font>labels</font>
  245. +                <value>Rating:</value>
  246. +            </textarea>
  247. +
  248. +            <!--
  249. +                    edits
  250. +            -->
  251. +
  252. +            <remoteedit name="station_edit" draworder="1" align="left">
  253. +                <area>195,70,525,35</area>
  254. +                <font>display</font>
  255. +            </remoteedit>
  256. +
  257. +            <pushbutton name="searchstation_button" draworder="2">
  258. +                <position>725,70</position>
  259. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  260. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  261. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  262. +            </pushbutton>
  263. +
  264. +            <remoteedit name="channel_edit" draworder="1" align="left">
  265. +                <area>195,110,525,35</area>
  266. +                <font>display</font>
  267. +            </remoteedit>
  268. +
  269. +            <remoteedit name="url_edit" draworder="1" align="left">
  270. +                <area>195,150,525,35</area>
  271. +                <font>display</font>
  272. +            </remoteedit>
  273. +
  274. +            <remoteedit name="metaformat_edit" draworder="1" align="left">
  275. +                <area>195,190,525,35</area>
  276. +                <font>display</font>
  277. +            </remoteedit>
  278. +
  279. +            <remoteedit name="genre_edit" draworder="1" align="left">
  280. +                <area>195,230,525,35</area>
  281. +                <font>display</font>
  282. +            </remoteedit>
  283. +
  284. +            <pushbutton name="searchgenre_button" draworder="2">
  285. +                <position>725,230</position>
  286. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  287. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  288. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  289. +            </pushbutton>
  290. +
  291. +            <repeatedimage name="rating_image" draworder="1" fleximage="no">
  292. +                <filename>mm_rating.png</filename>
  293. +                <position>190,360</position>
  294. +                <orientation>LeftToRight</orientation>
  295. +            </repeatedimage>
  296. +
  297. +            <selector name="rating_button" draworder="0">
  298. +                <area>420,350,30,30</area>
  299. +                <font>display</font>
  300. +                <image function="on" filename="mm_leftright_on.png"></image>
  301. +                <image function="off" filename="mm_leftright_off.png"></image>
  302. +                <image function="pushed" filename="mm_leftright_pushed.png"></image>
  303. +            </selector>
  304. +
  305. +
  306. +            <!--
  307. +                    Push buttons
  308. +            -->
  309. +
  310. +            <textbutton name="done_button" draworder="0">
  311. +                <position>350,540</position>
  312. +                <font>display</font>
  313. +                <image function="on" filename="text_button_on.png"></image>
  314. +                <image function="off" filename="text_button_off.png"></image>
  315. +                <image function="pushed" filename="text_button_pushed.png"></image>
  316. +            </textbutton>
  317. +
  318. +      </container>
  319. +
  320. +   </window>
  321. +
  322. +</mythuitheme>
  323. Index: myththemes/MythCenter-wide/music-ui.xml
  324. ===================================================================
  325. --- myththemes/MythCenter-wide/music-ui.xml     (revision 0)
  326. +++ myththemes/MythCenter-wide/music-ui.xml     (revision 0)
  327. @@ -0,0 +1,156 @@
  328. +<mythuitheme>
  329. +
  330. +   <window name="edit_radiometadata">
  331. +
  332. +      <font name="title" face="Trebuchet MS">
  333. +          <color>#ffff00</color>
  334. +          <dropcolor>#000000</dropcolor>
  335. +          <size>30</size>
  336. +          <size:small>18</size:small>
  337. +          <shadow>3,3</shadow>
  338. +          <bold>yes</bold>
  339. +      </font>
  340. +
  341. +      <font name="labels" face="Trebuchet MS">
  342. +          <color>#ffff00</color>
  343. +          <dropcolor>#000000</dropcolor>
  344. +          <size>18</size>
  345. +          <size:small>14</size:small>
  346. +          <shadow>3,3</shadow>
  347. +          <bold>yes</bold>
  348. +      </font>
  349. +
  350. +      <font name="display" face="Trebuchet MS">
  351. +          <color>#ffffff</color>
  352. +          <dropcolor>#000000</dropcolor>
  353. +          <size>18</size>
  354. +          <size:small>14</size:small>
  355. +          <shadow>3,3</shadow>
  356. +          <bold>yes</bold>
  357. +      </font>
  358. +
  359. +      <container name="edit_container">
  360. +
  361. +         <area>0,0,1820,720 </area>
  362. +
  363. +             <!--
  364. +                    Labels
  365. +            -->
  366. +
  367. +             <textarea name="title" draworder="1" align="center">
  368. +                 <area>0,30,1280,50</area>
  369. +                 <font>title</font>
  370. +                 <value>Track Information</value>
  371. +             </textarea>
  372. +
  373. +             <textarea name="station_label" draworder="1" align="right">
  374. +                 <area>15,100,170,30</area>
  375. +                 <font>labels</font>
  376. +                 <value>Station: </value>
  377. +             </textarea>
  378. +
  379. +             <textarea name="channel_label" draworder="1" align="right">
  380. +                 <area>15,150,170,30</area>
  381. +                 <font>labels</font>
  382. +                 <value>Channel: </value>
  383. +             </textarea>
  384. +
  385. +             <textarea name="url_label" draworder="1" align="right">
  386. +                 <area>15,200,170,30</area>
  387. +                 <font>labels</font>
  388. +                 <value>URL: </value>
  389. +             </textarea>
  390. +
  391. +             <textarea name="metaformat_label" draworder="1" align="right">
  392. +                 <area>15,250,170,30</area>
  393. +                 <font>labels</font>
  394. +                 <value>Meta Format: </value>
  395. +             </textarea>
  396. +
  397. +             <textarea name="genre_label" draworder="1" align="right">
  398. +                 <area>15,300,170,30</area>
  399. +                 <font>labels</font>
  400. +                 <value>Genre: </value>
  401. +             </textarea>
  402. +
  403. +             <textarea name="rating_label" draworder="1" align="right">
  404. +                 <area>15,350,170,30</area>
  405. +                 <font>labels</font>
  406. +                 <value>Rating: </value>
  407. +             </textarea>
  408. +
  409. +             <!--
  410. +                    edits
  411. +            -->
  412. +
  413. +             <remoteedit name="station_edit" draworder="1" align="left">
  414. +                 <area>200,100,880,35</area>
  415. +                 <font>display</font>
  416. +             </remoteedit>
  417. +
  418. +             <pushbutton name="searchstation_button" draworder="2">
  419. +                 <position>1100,100</position>
  420. +                 <image function="on" filename="mm_blankbutton_on.png"> </image>
  421. +                 <image function="off" filename="mm_blankbutton_off.png"> </image>
  422. +                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
  423. +             </pushbutton>
  424. +
  425. +             <remoteedit name="channel_edit" draworder="1" align="left">
  426. +                 <area>200,150,880,35</area>
  427. +                 <font>display</font>
  428. +             </remoteedit>
  429. +
  430. +             <remoteedit name="url_edit" draworder="1" align="left">
  431. +                 <area>200,200,880,35</area>
  432. +                 <font>display</font>
  433. +             </remoteedit>
  434. +
  435. +             <remoteedit name="metaformat_edit" draworder="1" align="left">
  436. +                 <area>200,250,880,35</area>
  437. +                 <font>display</font>
  438. +             </remoteedit>
  439. +
  440. +             <remoteedit name="genre_edit" draworder="1" align="left">
  441. +                 <area>200,300,880,35</area>
  442. +                 <font>display</font>
  443. +             </remoteedit>
  444. +
  445. +             <pushbutton name="searchgenre_button" draworder="2">
  446. +                 <position>1100,300</position>
  447. +                 <image function="on" filename="mm_blankbutton_on.png"> </image>
  448. +                 <image function="off" filename="mm_blankbutton_off.png"> </image>
  449. +                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
  450. +             </pushbutton>
  451. +
  452. +             <repeatedimage name="rating_image" draworder="1" fleximage="no">
  453. +                 <filename>mm_rating.png</filename>
  454. +                 <position>200,360</position>
  455. +                 <orientation>LeftToRight</orientation>
  456. +             </repeatedimage>
  457. +
  458. +             <selector name="rating_button" draworder="0">
  459. +                 <area>1100,350,30,30</area>
  460. +                 <font>display</font>
  461. +                 <image function="on" filename="mm_leftright_on.png"> </image>
  462. +                 <image function="off" filename="mm_leftright_off.png"> </image>
  463. +                 <image function="pushed" filename="mm_leftright_pushed.png"> </image>
  464. +             </selector>
  465. +
  466. +
  467. +             <!--
  468. +                    Push buttons
  469. +            -->
  470. +
  471. +             <textbutton name="done_button" draworder="0">
  472. +                 <position>500,540</position>
  473. +                 <font>display</font>
  474. +                 <image function="on" filename="text_button_on.png"> </image>
  475. +                 <image function="off" filename="text_button_off.png"> </image>
  476. +                 <image function="pushed" filename="text_button_pushed.png"> </image>
  477. +             </textbutton>
  478. +
  479. +      </container>
  480. +
  481. +   </window>
  482. +
  483. +</mythuitheme>
  484. Index: mythtv/themes/G.A.N.T./music-ui.xml
  485. ===================================================================
  486. --- mythtv/themes/G.A.N.T./music-ui.xml (revision 13855)
  487. +++ mythtv/themes/G.A.N.T./music-ui.xml (working copy)
  488. @@ -652,4 +652,154 @@
  489.        </container>
  490.     </window>
  491.  
  492. +  <window name="edit_radiometadata">
  493. +
  494. +        <font name="title" face="Arial">
  495. +            <color>#ffff00</color>
  496. +            <dropcolor>#000000</dropcolor>
  497. +            <size>24</size>
  498. +            <shadow>3,3</shadow>
  499. +            <bold>yes</bold>
  500. +        </font>
  501. +
  502. +        <font name="labels" face="Arial">
  503. +            <color>#ffff00</color>
  504. +            <dropcolor>#000000</dropcolor>
  505. +            <size>18</size>
  506. +            <shadow>3,3</shadow>
  507. +            <bold>yes</bold>
  508. +        </font>
  509. +
  510. +        <font name="display" face="Arial">
  511. +            <color>#ffffff</color>
  512. +            <dropcolor>#000000</dropcolor>
  513. +            <size>18</size>
  514. +            <shadow>3,3</shadow>
  515. +            <bold>yes</bold>
  516. +        </font>
  517. +
  518. +      <container name="edit_container">
  519. +            <area>0,0,800,600</area>
  520. +
  521. +            <textarea name="title" draworder="1" align="center">
  522. +                <area>0,15,800,50</area>
  523. +                <font>title</font>
  524. +                <value>Track Information</value>
  525. +            </textarea>
  526. +
  527. +            <!--
  528. +                    Labels
  529. +            -->
  530. +
  531. +
  532. +            <textarea name="station_label" draworder="1" align="right">
  533. +                <area>15,70,170,30</area>
  534. +                <font>labels</font>
  535. +                <value>Station:</value>
  536. +            </textarea>
  537. +
  538. +            <textarea name="channel_label" draworder="1" align="right">
  539. +                <area>15,110,170,30</area>
  540. +                <font>labels</font>
  541. +                <value>Channel:</value>
  542. +            </textarea>
  543. +
  544. +            <textarea name="url_label" draworder="1" align="right">
  545. +                <area>15,150,170,30</area>
  546. +                <font>labels</font>
  547. +                <value>URL:</value>
  548. +            </textarea>
  549. +
  550. +            <textarea name="metaformat_label" draworder="1" align="right">
  551. +                <area>15,190,170,30</area>
  552. +                <font>labels</font>
  553. +                <value>Meta:</value>
  554. +            </textarea>
  555. +
  556. +            <textarea name="genre_label" draworder="1" align="right">
  557. +                <area>15,230,170,30</area>
  558. +                <font>labels</font>
  559. +                <value>Genre:</value>
  560. +            </textarea>
  561. +
  562. +            <textarea name="rating_label" draworder="1" align="right">
  563. +                <area>15,350,170,30</area>
  564. +                <font>labels</font>
  565. +                <value>Rating:</value>
  566. +            </textarea>
  567. +
  568. +            <!--
  569. +                    edits
  570. +            -->
  571. +
  572. +            <remoteedit name="station_edit" draworder="1" align="left">
  573. +                <area>195,70,525,35</area>
  574. +                <font>display</font>
  575. +            </remoteedit>
  576. +
  577. +            <pushbutton name="searchstation_button" draworder="2">
  578. +                <position>725,70</position>
  579. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  580. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  581. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  582. +            </pushbutton>
  583. +
  584. +            <remoteedit name="channel_edit" draworder="1" align="left">
  585. +                <area>195,110,525,35</area>
  586. +                <font>display</font>
  587. +            </remoteedit>
  588. +
  589. +            <remoteedit name="url_edit" draworder="1" align="left">
  590. +                <area>195,150,525,35</area>
  591. +                <font>display</font>
  592. +            </remoteedit>
  593. +
  594. +            <remoteedit name="metaformat_edit" draworder="1" align="left">
  595. +                <area>195,190,525,35</area>
  596. +                <font>display</font>
  597. +            </remoteedit>
  598. +
  599. +            <remoteedit name="genre_edit" draworder="1" align="left">
  600. +                <area>195,230,525,35</area>
  601. +                <font>display</font>
  602. +            </remoteedit>
  603. +
  604. +            <pushbutton name="searchgenre_button" draworder="2">
  605. +                <position>725,230</position>
  606. +                <image function="on" filename="mm_blankbutton_on.png"></image>
  607. +                <image function="off" filename="mm_blankbutton_off.png"></image>
  608. +                <image function="pushed" filename="mm_blankbutton_pushed.png"></image>
  609. +            </pushbutton>
  610. +
  611. +            <repeatedimage name="rating_image" draworder="1" fleximage="no">
  612. +                <filename>mm_rating.png</filename>
  613. +                <position>190,360</position>
  614. +                <orientation>LeftToRight</orientation>
  615. +            </repeatedimage>
  616. +
  617. +            <selector name="rating_button" draworder="0">
  618. +                <area>420,350,30,30</area>
  619. +                <font>display</font>
  620. +                <image function="on" filename="mm_leftright_on.png"></image>
  621. +                <image function="off" filename="mm_leftright_off.png"></image>
  622. +                <image function="pushed" filename="mm_leftright_pushed.png"></image>
  623. +            </selector>
  624. +
  625. +
  626. +            <!--
  627. +                    Push buttons
  628. +            -->
  629. +
  630. +            <textbutton name="done_button" draworder="0">
  631. +                <position>350,540</position>
  632. +                <font>display</font>
  633. +                <image function="on" filename="text_button_on.png"></image>
  634. +                <image function="off" filename="text_button_off.png"></image>
  635. +                <image function="pushed" filename="text_button_pushed.png"></image>
  636. +            </textbutton>
  637. +
  638. +      </container>
  639. +
  640. +   </window>
  641. +
  642.  </mythuitheme>
  643. Index: mythtv/themes/blue/music-ui.xml
  644. ===================================================================
  645. --- mythtv/themes/blue/music-ui.xml     (revision 13855)
  646. +++ mythtv/themes/blue/music-ui.xml     (working copy)
  647. @@ -585,5 +585,153 @@
  648.  
  649.        </container>
  650.     </window>
  651. +   <window name="edit_radiometadata">
  652. +
  653. +      <font name="title" face="Arial">
  654. +          <color>#ffff00 </color>
  655. +          <dropcolor>#000000 </dropcolor>
  656. +          <size>24 </size>
  657. +          <shadow>3,3 </shadow>
  658. +          <bold>yes </bold>
  659. +      </font>
  660. +
  661. +      <font name="labels" face="Arial">
  662. +          <color>#ffff00 </color>
  663. +          <dropcolor>#000000 </dropcolor>
  664. +          <size>18 </size>
  665. +          <shadow>3,3 </shadow>
  666. +          <bold>yes </bold>
  667. +      </font>
  668. +
  669. +      <font name="display" face="Arial">
  670. +          <color>#ffffff </color>
  671. +          <dropcolor>#000000 </dropcolor>
  672. +          <size>18 </size>
  673. +          <shadow>3,3 </shadow>
  674. +          <bold>yes </bold>
  675. +      </font>
  676. +
  677. +       <container name="edit_container">
  678. +             <area>0,0,800,600 </area>
  679. +
  680. +             <textarea name="title" draworder="1" align="center">
  681. +                 <area>0,15,800,50 </area>
  682. +                 <font>title </font>
  683. +                 <value>Track Information </value>
  684. +             </textarea>
  685. +
  686. +             <!--
  687. +                    Labels
  688. +            -->
  689. +
  690. +
  691. +             <textarea name="station_label" draworder="1" align="right">
  692. +                 <area>15,70,170,30 </area>
  693. +                 <font>labels </font>
  694. +                 <value>Station: </value>
  695. +             </textarea>
  696. +
  697. +             <textarea name="channel_label" draworder="1" align="right">
  698. +                 <area>15,110,170,30 </area>
  699. +                 <font>labels </font>
  700. +                 <value>Channel: </value>
  701. +             </textarea>
  702. +
  703. +             <textarea name="url_label" draworder="1" align="right">
  704. +                 <area>15,150,170,30 </area>
  705. +                 <font>labels </font>
  706. +                 <value>URL: </value>
  707. +             </textarea>
  708. +
  709. +             <textarea name="metaformat_label" draworder="1" align="right">
  710. +                 <area>15,190,170,30 </area>
  711. +                 <font>labels </font>
  712. +                 <value>Meta Format: </value>
  713. +             </textarea>
  714. +
  715. +             <textarea name="genre_label" draworder="1" align="right">
  716. +                 <area>15,230,170,30 </area>
  717. +                 <font>labels </font>
  718. +                 <value>Genre: </value>
  719. +             </textarea>
  720. +
  721. +             <textarea name="rating_label" draworder="1" align="right">
  722. +                 <area>15,350,170,30 </area>
  723. +                 <font>labels </font>
  724. +                 <value>Rating: </value>
  725. +             </textarea>
  726. +
  727. +             <!--
  728. +                    edits
  729. +            -->
  730. +
  731. +             <remoteedit name="station_edit" draworder="1" align="left">
  732. +                 <area>195,70,525,35 </area>
  733. +                 <font>display </font>
  734. +             </remoteedit>
  735. +
  736. +             <pushbutton name="searchstation_button" draworder="2">
  737. +                 <position>725,70 </position>
  738. +                 <image function="on" filename="mm_blankbutton_on.png"> </image>
  739. +                 <image function="off" filename="mm_blankbutton_off.png"> </image>
  740. +                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
  741. +             </pushbutton>
  742. +
  743. +             <remoteedit name="channel_edit" draworder="1" align="left">
  744. +                 <area>195,110,525,35 </area>
  745. +                 <font>display </font>
  746. +             </remoteedit>
  747. +
  748. +             <remoteedit name="url_edit" draworder="1" align="left">
  749. +                 <area>195,150,525,35 </area>
  750. +                 <font>display </font>
  751. +             </remoteedit>
  752. +
  753. +             <remoteedit name="metaformat_edit" draworder="1" align="left">
  754. +                 <area>195,190,525,35 </area>
  755. +                 <font>display </font>
  756. +             </remoteedit>
  757. +
  758. +             <remoteedit name="genre_edit" draworder="1" align="left">
  759. +                 <area>195,230,525,35 </area>
  760. +                 <font>display </font>
  761. +             </remoteedit>
  762. +
  763. +             <pushbutton name="searchgenre_button" draworder="2">
  764. +                 <position>725,230 </position>
  765. +                 <image function="on" filename="mm_blankbutton_on.png"> </image>
  766. +                 <image function="off" filename="mm_blankbutton_off.png"> </image>
  767. +                 <image function="pushed" filename="mm_blankbutton_pushed.png"> </image>
  768. +             </pushbutton>
  769. +
  770. +             <repeatedimage name="rating_image" draworder="1" fleximage="no">
  771. +                 <filename>mm_rating.png </filename>
  772. +                 <position>190,360 </position>
  773. +                 <orientation>LeftToRight </orientation>
  774. +             </repeatedimage>
  775. +
  776. +             <selector name="rating_button" draworder="0">
  777. +                 <area>420,350,30,30 </area>
  778. +                 <font>display </font>
  779. +                 <image function="on" filename="mm_leftright_on.png"> </image>
  780. +                 <image function="off" filename="mm_leftright_off.png"> </image>
  781. +                 <image function="pushed" filename="mm_leftright_pushed.png"> </image>
  782. +             </selector>
  783. +
  784. +
  785. +             <!--
  786. +                    Push buttons
  787. +            -->
  788. +
  789. +             <textbutton name="done_button" draworder="0">
  790. +                 <position>350,540 </position>
  791. +                 <font>display </font>
  792. +                 <image function="on" filename="text_button_on.png"> </image>
  793. +                 <image function="off" filename="text_button_off.png"> </image>
  794. +                 <image function="pushed" filename="text_button_pushed.png"> </image>
  795. +             </textbutton>
  796. +
  797. +       </container>
  798. +    </window>
  799.    
  800.  </mythuitheme>
  801. Index: mythtv/libs/libmyth/output.cpp
  802. ===================================================================
  803. --- mythtv/libs/libmyth/output.cpp      (revision 13855)
  804. +++ mythtv/libs/libmyth/output.cpp      (working copy)
  805. @@ -22,11 +22,8 @@
  806.  
  807.  
  808.  void OutputListeners::error(const QString &e) {
  809. -    QObject *object = firstListener();
  810. -    while (object) {
  811. -       QApplication::postEvent(object, new OutputEvent(e));
  812. -       object = nextListener();
  813. -    }
  814. +    OutputEvent ev (e);
  815. +    dispatch (ev);
  816.  }
  817.  
  818.  void OutputListeners::addVisual(MythTV::Visual *v)
  819. Index: mythplugins/mythmusic/mythmusic/playbackbox.cpp
  820. ===================================================================
  821. --- mythplugins/mythmusic/mythmusic/playbackbox.cpp     (revision 13855)
  822. +++ mythplugins/mythmusic/mythmusic/playbackbox.cpp     (working copy)
  823. @@ -19,15 +19,18 @@
  824.  // MythMusic includes
  825.  #include "metadata.h"
  826.  #include "constants.h"
  827. -#include "streaminput.h"
  828.  #include "decoder.h"
  829.  #include "cddecoder.h"
  830.  #include "playbackbox.h"
  831.  #include "databasebox.h"
  832. +#include "metaio.h"
  833.  #include "mainvisual.h"
  834.  #include "smartplaylist.h"
  835.  #include "search.h"
  836. +#include "decoderhandler.h"
  837.  
  838. +#define GET_REPO_ID(attrs) attrs->at(4)
  839. +
  840.  PlaybackBoxMusic::PlaybackBoxMusic(MythMainWindow *parent, QString window_name,
  841.                                     QString theme_filename,
  842.                                     PlaylistsContainer *the_playlists,
  843. @@ -38,9 +41,9 @@
  844.  {
  845.      //  A few internal variable defaults
  846.  
  847. -    input = NULL;
  848.      output = NULL;
  849.      decoder = NULL;
  850. +    decoderHandler = NULL;
  851.      mainvisual = NULL;
  852.      visual_mode_timer = NULL;
  853.      lcd_update_timer = NULL;
  854. @@ -112,6 +115,10 @@
  855.                  this, SLOT(hideVolume()));
  856.      }
  857.  
  858. +    decoder_handler_progress_timer = new QTimer(this);
  859. +    connect (decoder_handler_progress_timer, SIGNAL(timeout()),
  860. +             this, SLOT(operationProgressTimer()));
  861. +   
  862.      // Figure out the shuffle mode
  863.  
  864.      QString playmode = gContext->GetSetting("PlayMode", "none");
  865. @@ -278,6 +285,14 @@
  866.          gContext->SaveSetting("RepeatMode", "all");
  867.      else
  868.          gContext->SaveSetting("RepeatMode", "none");
  869. +
  870. +    if (decoderHandler)
  871. +    {
  872. +        decoderHandler->removeListener(this);
  873. +        decoderHandler->deleteLater();
  874. +        decoderHandler = NULL;
  875. +    }
  876. +
  877.      if (class LCD *lcd = LCD::Get())
  878.          lcd->switchToTime();
  879.  }
  880. @@ -420,7 +435,7 @@
  881.          }
  882.          else if (action == "INFO")
  883.              if (visualizer_status == 2)
  884. -                bannerToggle(curMeta);
  885. +                bannerToggle(&displayMeta);
  886.              else
  887.                  showEditMetadataDialog();
  888.          else
  889. @@ -565,6 +580,32 @@
  890.          MythThemedDialog::keyPressEvent(e);
  891.  }
  892.  
  893. +/*note:temporary fix for radio - Ideally, I'd like the metadata repo
  894. +  add it's own stuff to the menur entry
  895. +*/
  896. +void PlaybackBoxMusic::addRadioMenuEntries()
  897. +{
  898. +    GenericTree *node = music_tree_list->getCurrentNode();
  899. +    IntVector *attrs = node->getAttributes();
  900. +
  901. +    if (GET_REPO_ID(attrs) != 1)
  902. +        return;
  903. +
  904. +    playlist_popup->addButton(tr("Add Radio"), this,
  905. +                              SLOT(showAddRadioStationDialog()));
  906. +
  907. +    if (node->isSelectable())
  908. +        playlist_popup->addButton(tr("Remove Radio"), this,
  909. +                                  SLOT(showRemoveRadioStationDialog()));
  910. +
  911. +    QLabel *splitter = playlist_popup->addLabel(" ", MythPopupBox::Small);
  912. +    splitter->setLineWidth(2);
  913. +    splitter->setFrameShape(QFrame::HLine);
  914. +    splitter->setFrameShadow(QFrame::Sunken);
  915. +    splitter->setMaximumHeight((int) (5 * hmult));
  916. +    splitter->setMaximumHeight((int) (5 * hmult));
  917. +}
  918. +
  919.  void PlaybackBoxMusic::handlePush(QString buttonname)
  920.  {
  921.      if (m_pushedButton)
  922. @@ -611,6 +652,8 @@
  923.      splitter->setMaximumHeight((int) (5 * hmult));
  924.      splitter->setMaximumHeight((int) (5 * hmult));
  925.  
  926. +    addRadioMenuEntries();
  927. +
  928.      playlist_popup->addButton(tr("Search"), this,
  929.                                SLOT(showSearchDialog()));
  930.      playlist_popup->addButton(tr("From CD"), this,
  931. @@ -619,14 +662,19 @@
  932.                                SLOT(allTracks()));
  933.      if (curMeta)
  934.      {
  935. -        playlist_popup->addButton(tr("Tracks by current Artist"), this,
  936. -                                  SLOT(byArtist()));
  937. -        playlist_popup->addButton(tr("Tracks from current Album"), this,
  938. -                                  SLOT(byAlbum()));
  939. -        playlist_popup->addButton(tr("Tracks from current Genre"), this,
  940. -                                  SLOT(byGenre()));
  941. -        playlist_popup->addButton(tr("Tracks from current Year"), this,
  942. +        /*note: temporary fix for radio. Ideally, I'd like the
  943. +          metadata repo add it's own stuff to the menur entry.
  944. +        */
  945. +        if (curMeta->Format()!="cast") {
  946. +            playlist_popup->addButton(tr("Tracks by current Artist"), this,
  947. +                                      SLOT(byArtist()));
  948. +            playlist_popup->addButton(tr("Tracks from current Album"), this,
  949. +                                      SLOT(byAlbum()));
  950. +            playlist_popup->addButton(tr("Tracks from current Genre"), this,
  951. +                                      SLOT(byGenre()));
  952. +            playlist_popup->addButton(tr("Tracks from current Year"), this,
  953.                                    SLOT(byYear()));
  954. +        }
  955.      }
  956.      
  957.      playlist_popup->ShowPopup(this, SLOT(closePlaylistPopup()));
  958. @@ -681,6 +729,92 @@
  959.      }
  960.  }
  961.  
  962. +void PlaybackBoxMusic::decoderHandlerReady(void)
  963. +{   
  964. +    decoder = decoderHandler->getDecoder();
  965. +
  966. +    if (decoder->getFilename().contains("cda") == 1)
  967. +        dynamic_cast<CdDecoder*>(decoder)->setDevice(m_CDdevice);
  968. +   
  969. +    decoder->setOutput(output);
  970. +    decoder->setBlockSize(globalBlockSize);
  971. +    decoder->addListener(this);
  972. +
  973. +    currentTime = 0;
  974. +
  975. +    mainvisual->setDecoder(decoder);
  976. +    mainvisual->setOutput(output);
  977. +   
  978. +    if (decoder->initialize())
  979. +    {
  980. +        if (output)
  981. +             output->Reset();
  982. +         
  983. +        decoder->start();
  984. +       
  985. +        if (resumemode == RESUME_EXACT && gContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
  986. +        {
  987. +            seek(gContext->GetNumSetting("MusicBookmarkPosition", 0));
  988. +            gContext->SaveSetting("MusicBookmarkPosition", 0);
  989. +        }
  990. +
  991. +        isplaying = true;
  992. +
  993. +        // hmm, it'd be more fair to only inc playcount and set lastplay
  994. +        // when it's played at least x seconds of the track.
  995. +        curMeta->setLastPlay();
  996. +        curMeta->incPlayCount();   
  997. +
  998. +        bannerEnable(curMeta, show_album_art);
  999. +    } else {
  1000. +        VERBOSE(VB_PLAYBACK, QString ("Cannot initialise decoder for %1").
  1001. +                arg (decoder->getFilename ()));
  1002. +    }
  1003. +}
  1004. +
  1005. +void PlaybackBoxMusic::decoderHandlerInfo(const QString &message,
  1006. +                                          const QString &submessage)
  1007. +{
  1008. +    Metadata tmp (*curMeta);
  1009. +
  1010. +    tmp.setArtist(submessage);
  1011. +    tmp.setTitle(message);
  1012. +
  1013. +    updateTrackInfo(&tmp);
  1014. +}
  1015. +
  1016. +void PlaybackBoxMusic::decoderHandlerOperationStart(const QString &op)
  1017. +{
  1018. +    operation_name = op;
  1019. +    operation_progress_count = 0;
  1020. +    decoderHandlerInfo(operation_name,
  1021. +                       QString().fill('.', operation_progress_count));
  1022. +    decoder_handler_progress_timer->start(1000);
  1023. +}
  1024. +
  1025. +void PlaybackBoxMusic::decoderHandlerOperationStop()
  1026. +{
  1027. +    Metadata tmp (*curMeta);
  1028. +    updateTrackInfo(&tmp);
  1029. +    decoder_handler_progress_timer->stop();
  1030. +}
  1031. +
  1032. +void PlaybackBoxMusic::operationProgressTimer()
  1033. +{
  1034. +    operation_progress_count++;
  1035. +    decoderHandlerInfo(operation_name,
  1036. +                       QString().fill('.', operation_progress_count));
  1037. +
  1038. +    // You get ten seconds...
  1039. +    if (operation_progress_count >= 10)
  1040. +    {
  1041. +        decoderHandler->stop();
  1042. +        MythPopupBox::showOkPopup(gContext->GetMainWindow(),
  1043. +                                  statusString,
  1044. +                                  QString("Operation timed out"));
  1045. +    }
  1046. +}
  1047. +
  1048.  void PlaybackBoxMusic::showSearchDialog()
  1049.  {
  1050.     if (!playlist_popup)
  1051. @@ -700,6 +834,93 @@
  1052.      }
  1053.  }
  1054.  
  1055. +void PlaybackBoxMusic::showAddRadioStationDialog()
  1056. +{
  1057. +   if (!playlist_popup)
  1058. +        return;
  1059. +
  1060. +    closePlaylistPopup();
  1061. +
  1062. +    Metadata *meta = new Metadata ("http://", "", "", "", "");
  1063. +    meta->setArtist ("");
  1064. +    meta->setAlbum ("");
  1065. +    meta->setTitle ("");
  1066. +    meta->setGenre ("");
  1067. +    meta->setFormat("cast");
  1068. +
  1069. +    MythThemedDialog *dialog = meta->createEditorDialog ();
  1070. +    if (dialog->exec()) {
  1071. +        all_music->addRadioTrack(meta);
  1072. +        /*note:temporary fix for radio
  1073. +
  1074. +          This is a really really poor way or inserting something into
  1075. +          the tree. It'd be way nicer to have a object managing
  1076. +          classes of metadata which could then do this.
  1077. +         
  1078. +         */
  1079. +        for (int ii = 0; ii < playlist_tree->childCount(); ii++)
  1080. +        {
  1081. +            GenericTree *t_node = playlist_tree->getChildAt(ii);
  1082. +
  1083. +            if (!t_node)
  1084. +                continue;
  1085. +
  1086. +            IntVector *attrs = t_node->getAttributes();
  1087. +            if (attrs && attrs->size() > 4)
  1088. +            {
  1089. +                int counter = t_node->childCount();
  1090. +                QString title = meta->FormatArtist ();
  1091. +                title += " ~ ";
  1092. +                title += meta->Title();
  1093. +                meta->setAlbum(title);
  1094. +                GenericTree *sub = t_node->addNode(title, meta->ID(), true);
  1095. +                sub->setAttribute(0, 1);
  1096. +                sub->setAttribute(1, counter);
  1097. +                sub->setAttribute(2, rand());
  1098. +                sub->setAttribute(3, rand());
  1099. +                sub->setAttribute(4, 1);
  1100. +                t_node->sortByString();
  1101. +                music_tree_list->refresh();
  1102. +                break;
  1103. +            }
  1104. +        }
  1105. +    }
  1106. +
  1107. +    delete dialog;
  1108. +}
  1109. +
  1110. +void PlaybackBoxMusic::showRemoveRadioStationDialog()
  1111. +{
  1112. +   if (!playlist_popup)
  1113. +        return;
  1114. +
  1115. +    closePlaylistPopup();
  1116. +
  1117. +    GenericTree *node = music_tree_list->getCurrentNode();
  1118. +    Metadata *meta = all_music->getRadioMetadata(node->getInt());
  1119. +   
  1120. +    bool answer = MythPopupBox::showOkCancelPopup(gContext->GetMainWindow(),
  1121. +                                                  QString("Remove Station"),
  1122. +                                                  QString("Do you want to remove\n"
  1123. +                                                          "%1 ~ %2?").
  1124. +                                                  arg(meta->Artist()).
  1125. +                                                  arg(meta->Title()),
  1126. +                                                  true);
  1127. +
  1128. +    if (!answer)
  1129. +        return;
  1130. +
  1131. +    meta->removeFromDatabase();   
  1132. +   
  1133. +    // Move away from the current node...
  1134. +    if (!music_tree_list->moveUp(false))
  1135. +        music_tree_list->moveDown(false);
  1136. +
  1137. +    node->getParent()->removeNode(node);
  1138. +    music_tree_list->refresh();
  1139. +}
  1140. +
  1141. +
  1142.  void PlaybackBoxMusic::byArtist()
  1143.  {
  1144.      if (!playlist_popup || !curMeta)
  1145. @@ -979,31 +1200,49 @@
  1146.  
  1147.  void PlaybackBoxMusic::showEditMetadataDialog()
  1148.  {
  1149. -    if (!curMeta)
  1150. -    {
  1151. +    MythThemedDialog *editDialog = 0;
  1152. +    GenericTree *node = music_tree_list->getCurrentNode();
  1153. +    IntVector *attrs = node->getAttributes();
  1154. +    Metadata *editMeta = 0;
  1155. +
  1156. +    /* note: temporary fix for radio, get the appropriate editor for repo */
  1157. +    if (GET_REPO_ID(attrs) == 1)
  1158. +        editMeta = all_music->getRadioMetadata(node->getInt());
  1159. +    else
  1160. +        editMeta = all_music->getMetadata(node->getInt());
  1161. +
  1162. +    if(!editMeta)
  1163.          return;
  1164. -    }
  1165.  
  1166. -    // store the current track metadata in case the track changes
  1167. -    // while we show the edit dialog
  1168. -    Metadata *editMeta = curMeta;
  1169. -    GenericTree *node = music_tree_list->getCurrentNode();
  1170. +    editDialog = editMeta->createEditorDialog ();
  1171.  
  1172. -    EditMetadataDialog editDialog(editMeta, gContext->GetMainWindow(),
  1173. -                      "edit_metadata", "music-", "edit metadata");
  1174. -    if (editDialog.exec())
  1175. +    if (editDialog->exec())
  1176.      {
  1177.          // update the metadata copy stored in all_music
  1178. -        if (all_music->updateMetadata(editMeta->ID(), editMeta))
  1179. +        if (all_music->updateMetadata(GET_REPO_ID(attrs), editMeta->ID(), editMeta))
  1180.          {
  1181.             // update the displayed track info
  1182.             if (node)
  1183.             {
  1184. -               bool errorFlag;
  1185. -               node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
  1186. +               /* note: temporary fix for radio, get the label editor for repo */
  1187. +               if (GET_REPO_ID(attrs) == 1)
  1188. +               {
  1189. +                   QString title = editMeta->FormatArtist ();
  1190. +                   title += " ~ ";
  1191. +                   title += editMeta->FormatTitle();
  1192. +                   editMeta->setAlbum(title);
  1193. +                   node->setString(editMeta->Album());
  1194. +               }
  1195. +               else
  1196. +               {
  1197. +                   bool errorFlag;
  1198. +                   node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
  1199. +               }
  1200. +
  1201. +               node->getParent()->sortByString();
  1202.                 music_tree_list->refresh();
  1203.  
  1204. -               // make sure the track hasn't changed
  1205. +              // make sure the track hasn't changed
  1206.                 if (curMeta->ID() == editMeta->ID())
  1207.                 {
  1208.                      *curMeta = editMeta;
  1209. @@ -1019,6 +1258,8 @@
  1210.             }
  1211.          }
  1212.      }
  1213. +
  1214. +    delete editDialog;
  1215.  }
  1216.  
  1217.  void PlaybackBoxMusic::checkForPlaylists()
  1218. @@ -1202,9 +1443,6 @@
  1219.          return;
  1220.      }
  1221.  
  1222. -    QUrl sourceurl(playfile);
  1223. -    QString sourcename(playfile);
  1224. -
  1225.      if (!output)
  1226.          openOutputDevice();
  1227.  
  1228. @@ -1214,70 +1452,11 @@
  1229.          return;
  1230.      }
  1231.  
  1232. -    if (!sourceurl.isLocalFile())
  1233. -    {
  1234. -        StreamInput streaminput(sourceurl);
  1235. -        streaminput.setup();
  1236. -        input = streaminput.socket();
  1237. -    }
  1238. -    else
  1239. -        input = new QFile(playfile);
  1240. -   
  1241. -    if (decoder && !decoder->factory()->supports(sourcename))
  1242. -    {
  1243. -        decoder->removeListener(this);
  1244. -        decoder = 0;
  1245. -    }
  1246. +    if (!decoderHandler)
  1247. +        setupDecoderHandler();
  1248.  
  1249. -    if (!decoder)
  1250. -    {
  1251. -        decoder = Decoder::create(sourcename, input, output);
  1252. -
  1253. -        if (!decoder)
  1254. -        {
  1255. -            printf("mythmusic: unsupported fileformat\n");
  1256. -            stopAll();
  1257. -            return;
  1258. -        }
  1259. -        if (sourcename.contains("cda") == 1)
  1260. -            dynamic_cast<CdDecoder*>(decoder)->setDevice(m_CDdevice);
  1261. -
  1262. -        decoder->setBlockSize(globalBlockSize);
  1263. -        decoder->addListener(this);
  1264. -    }
  1265. -    else
  1266. -    {
  1267. -        decoder->setInput(input);
  1268. -        decoder->setFilename(sourcename);
  1269. -        decoder->setOutput(output);
  1270. -    }
  1271. -
  1272. -    currentTime = 0;
  1273. -
  1274. -    mainvisual->setDecoder(decoder);
  1275. -    mainvisual->setOutput(output);
  1276.      mainvisual->setMetadata(curMeta);
  1277. -
  1278. -    if (decoder->initialize())
  1279. -    {
  1280. -        if (output)
  1281. -        {
  1282. -            output->Reset();
  1283. -        }
  1284. -
  1285. -        decoder->start();
  1286. -
  1287. -        if (resumemode == RESUME_EXACT && gContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
  1288. -        {
  1289. -            seek(gContext->GetNumSetting("MusicBookmarkPosition", 0));
  1290. -            gContext->SaveSetting("MusicBookmarkPosition", 0);
  1291. -        }
  1292. -
  1293. -        bannerEnable(curMeta, show_album_art);
  1294. -        isplaying = true;
  1295. -        curMeta->setLastPlay();
  1296. -        curMeta->incPlayCount();
  1297. -    }
  1298. +    decoderHandler->start(curMeta);
  1299.  }
  1300.  
  1301.  void PlaybackBoxMusic::visEnable()
  1302. @@ -1358,7 +1537,7 @@
  1303.  
  1304.  }
  1305.  
  1306. -void PlaybackBoxMusic::setTrackOnLCD(Metadata *mdata)
  1307. +void PlaybackBoxMusic::setTrackOnLCD(const Metadata *mdata)
  1308.  {
  1309.      LCD *lcd = LCD::Get();
  1310.      if (!lcd)
  1311. @@ -1389,22 +1568,13 @@
  1312.  
  1313.  void PlaybackBoxMusic::stopDecoder(void)
  1314.  {
  1315. -    if (decoder && decoder->running())
  1316. -        decoder->stop();
  1317. -
  1318. -    if (decoder)
  1319. -    {
  1320. -        decoder->lock();
  1321. -        decoder->cond()->wakeAll();
  1322. -        decoder->unlock();
  1323. -    }
  1324. -
  1325. -    if (decoder)
  1326. -        decoder->wait();
  1327. +    if (decoderHandler)
  1328. +        decoderHandler->stop();
  1329.  }
  1330.  
  1331.  void PlaybackBoxMusic::stop(void)
  1332.  {
  1333. +    decoder_handler_progress_timer->stop();
  1334.      stopDecoder();
  1335.  
  1336.      if (output)
  1337. @@ -1420,9 +1590,6 @@
  1338.      mainvisual->setOutput(0);
  1339.      mainvisual->deleteMetadata();
  1340.  
  1341. -    delete input;
  1342. -    input = 0;
  1343. -
  1344.      QString time_string;
  1345.      int maxh = maxTime / 3600;
  1346.      int maxm = (maxTime / 60) % 60;
  1347. @@ -1503,10 +1670,15 @@
  1348.  
  1349.      isplaying = false;
  1350.  
  1351. -    if (repeatmode == REPEAT_TRACK)
  1352. -        play();
  1353. -    else
  1354. -        next();
  1355. +    if (! decoderHandler->done())
  1356. +        decoderHandler->next();
  1357. +    else if (curMeta->Format() != "cast")
  1358. +    {
  1359. +        if (repeatmode == REPEAT_TRACK)
  1360. +            play();
  1361. +        else
  1362. +            next();
  1363. +    }
  1364.  }
  1365.  
  1366.  void PlaybackBoxMusic::seekforward()
  1367. @@ -1623,6 +1795,8 @@
  1368.          music_tree_list->setVisualOrdering(1);
  1369.      music_tree_list->refresh();
  1370.  
  1371. +    // refreshing the music_tree causes the LCD to get redrawn
  1372. +    // with the tree, so force back to the music screen.
  1373.      if (isplaying)
  1374.          setTrackOnLCD(curMeta);
  1375.  }
  1376. @@ -1978,10 +2152,15 @@
  1377.  
  1378.              break;
  1379.          }
  1380. +        case DecoderEvent::Decoding:
  1381. +        {
  1382. +            statusString = tr("Stream decoding.");
  1383. +            displayMeta = *curMeta;
  1384. +            break;
  1385. +        }
  1386.          case DecoderEvent::Stopped:
  1387.          {
  1388.              statusString = tr("Stream stopped.");
  1389. -
  1390.              break;
  1391.          }
  1392.          case DecoderEvent::Finished:
  1393. @@ -2007,6 +2186,54 @@
  1394.                                        .arg(*dxe->errorMessage()));
  1395.              break;
  1396.          }
  1397. +        case DecoderHandlerEvent::Ready:
  1398. +        {
  1399. +            decoderHandlerReady();
  1400. +            break;
  1401. +        }
  1402. +        case DecoderHandlerEvent::OperationStart:
  1403. +        {
  1404. +            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
  1405. +            decoderHandlerOperationStart(*dxe->getMessage());
  1406. +            break;
  1407. +        }
  1408. +        case DecoderHandlerEvent::OperationStop:
  1409. +        {
  1410. +            decoderHandlerOperationStop();
  1411. +            break;
  1412. +        }
  1413. +        case DecoderHandlerEvent::Error:
  1414. +        {
  1415. +            statusString = tr("Input error.");
  1416. +            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
  1417. +            VERBOSE(VB_IMPORTANT, QString ("%1 %2").arg (statusString).arg(*dxe->getMessage()));
  1418. +            MythPopupBox::showOkPopup(gContext->GetMainWindow(),
  1419. +                                      statusString,
  1420. +                                      QString("MythMusic has encountered the following error:\n%1")
  1421. +                                      .arg(*dxe->getMessage()));
  1422. +            stopAll();
  1423. +            break;
  1424. +        }
  1425. +        case DecoderHandlerEvent::Info:
  1426. +        {
  1427. +            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
  1428. +            Metadata tmp (*curMeta);
  1429. +            tmp.setArtist("");
  1430. +            tmp.setTitle(*dxe->getMessage());           
  1431. +            updateTrackInfo(&tmp);
  1432. +            break;
  1433. +        }
  1434. +        case DecoderHandlerEvent::Meta:
  1435. +        {
  1436. +            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
  1437. +            displayMeta = *dxe->getMetadata();
  1438. +            updateTrackInfo(&displayMeta);
  1439. +
  1440. +            if (visualizer_status > 0 && cycle_visualizer)
  1441. +                CycleVisualizer();
  1442. +
  1443. +            break;
  1444. +        }
  1445.      }
  1446.  
  1447.      QWidget::customEvent(event);
  1448. @@ -2040,6 +2267,7 @@
  1449.          album_text->SetText(mdata->Album());
  1450.  
  1451.      setTrackOnLCD(mdata);
  1452. +    bannerEnable(mdata);           
  1453.  }
  1454.  
  1455.  void PlaybackBoxMusic::openOutputDevice(void)
  1456. @@ -2062,6 +2290,12 @@
  1457.      output->addVisual(mainvisual);   
  1458.  }
  1459.  
  1460. +void PlaybackBoxMusic::setupDecoderHandler()
  1461. +{
  1462. +    decoderHandler = new DecoderHandler();
  1463. +    decoderHandler->addListener(this);
  1464. +}
  1465. +
  1466.  void PlaybackBoxMusic::handleTreeListSignals(int node_int, IntVector *attributes)
  1467.  {
  1468.      if (attributes->size() < 4)
  1469. @@ -2075,16 +2309,17 @@
  1470.      {
  1471.          //  It's a track
  1472.  
  1473. -        curMeta = all_music->getMetadata(node_int);
  1474. -        if (title_text)
  1475. -            title_text->SetText(curMeta->FormatTitle());
  1476. -        if (artist_text)
  1477. -            artist_text->SetText(curMeta->FormatArtist());
  1478. -        if (album_text)
  1479. -            album_text->SetText(curMeta->Album());
  1480. +        /*note: temporary fix for radio.
  1481. +          Ideally, I'd like for each metadata to carry a fifth
  1482. +          attribute, identifying the repository it came from, such as
  1483. +          CD, harddrive, iPod etc.
  1484. +         */
  1485. +        if (GET_REPO_ID(attributes) == 1)
  1486. +            curMeta = all_music->getRadioMetadata(node_int);
  1487. +        else
  1488. +            curMeta = all_music->getMetadata(node_int);
  1489.  
  1490. -        setTrackOnLCD(curMeta);
  1491. -
  1492. +        updateTrackInfo(curMeta);
  1493.          maxTime = curMeta->Length() / 1000;
  1494.  
  1495.          QString time_string;
  1496. Index: mythplugins/mythmusic/mythmusic/mythmusic.pro
  1497. ===================================================================
  1498. --- mythplugins/mythmusic/mythmusic/mythmusic.pro       (revision 13855)
  1499. +++ mythplugins/mythmusic/mythmusic/mythmusic.pro       (working copy)
  1500. @@ -40,6 +40,7 @@
  1501.  HEADERS += editmetadata.h smartplaylist.h search.h genres.h
  1502.  HEADERS += treebuilders.h importmusic.h directoryfinder.h
  1503.  HEADERS += filescanner.h libvisualplugin.h
  1504. +HEADERS += shoutcast.h decoderhandler.h editradiometadata.h pls.h
  1505.  
  1506.  SOURCES += cddecoder.cpp cdrip.cpp decoder.cpp
  1507.  SOURCES += flacdecoder.cpp flacencoder.cpp maddecoder.cpp main.cpp
  1508. @@ -56,6 +57,7 @@
  1509.  SOURCES += avfdecoder.cpp editmetadata.cpp smartplaylist.cpp search.cpp
  1510.  SOURCES += treebuilders.cpp importmusic.cpp directoryfinder.cpp
  1511.  SOURCES += filescanner.cpp libvisualplugin.cpp
  1512. +SOURCES += shoutcast.cpp decoderhandler.cpp editradiometadata.cpp pls.cpp
  1513.  
  1514.  macx {
  1515.      SOURCES -= cddecoder.cpp
  1516. @@ -67,3 +69,4 @@
  1517.      #QMAKE_LFLAGS += -flat_namespace -undefined suppress
  1518.      QMAKE_LFLAGS += -flat_namespace -undefined error
  1519.  }
  1520. +
  1521. Index: mythplugins/mythmusic/mythmusic/decoderhandler.cpp
  1522. ===================================================================
  1523. --- mythplugins/mythmusic/mythmusic/decoderhandler.cpp  (revision 0)
  1524. +++ mythplugins/mythmusic/mythmusic/decoderhandler.cpp  (revision 0)
  1525. @@ -0,0 +1,539 @@
  1526. +#include "decoderhandler.h"
  1527. +#include "decoder.h"
  1528. +#include "metadata.h"
  1529. +#include "streaminput.h"
  1530. +#include "shoutcast.h"
  1531. +
  1532. +#include <qapplication.h>
  1533. +#include <qurl.h>
  1534. +#include <qurloperator.h>
  1535. +
  1536. +#include <mythtv/httpcomms.h>
  1537. +#include <mythtv/mythcontext.h>
  1538. +
  1539. +#include <unistd.h>
  1540. +#include <stdio.h>
  1541. +#include <assert.h>
  1542. +
  1543. +/**********************************************************************/
  1544. +
  1545. +DecoderHandlerEvent::DecoderHandlerEvent(const Metadata &m)
  1546. +    : MythEvent(Meta), m_msg(NULL), m_meta(NULL)
  1547. +{
  1548. +    m_meta = new Metadata(m);
  1549. +}
  1550. +
  1551. +DecoderHandlerEvent::~DecoderHandlerEvent()
  1552. +{
  1553. +    delete m_msg;
  1554. +    delete m_meta;
  1555. +}
  1556. +
  1557. +DecoderHandlerEvent* DecoderHandlerEvent::clone()
  1558. +{
  1559. +    DecoderHandlerEvent *result = new DecoderHandlerEvent(*this);
  1560. +
  1561. +    if (m_msg)
  1562. +        result->m_msg = new QString(*m_msg);
  1563. +
  1564. +    if (m_meta)
  1565. +        result->m_meta = new Metadata(*m_meta);
  1566. +
  1567. +    return result;
  1568. +}
  1569. +
  1570. +/**********************************************************************/
  1571. +
  1572. +DecoderIOFactory::DecoderIOFactory(DecoderHandler *parent)
  1573. +{
  1574. +    m_handler = parent;
  1575. +}
  1576. +
  1577. +DecoderIOFactory::~DecoderIOFactory()
  1578. +{
  1579. +}
  1580. +
  1581. +void DecoderIOFactory::doConnectDecoder(const QString &format)
  1582. +{
  1583. +    m_handler->doOperationStop();
  1584. +    m_handler->doConnectDecoder(m_url, format);
  1585. +}
  1586. +
  1587. +Decoder *DecoderIOFactory::getDecoder()
  1588. +{
  1589. +    return m_handler->getDecoder();
  1590. +}
  1591. +
  1592. +void DecoderIOFactory::doFailed(const QString &message)
  1593. +{
  1594. +    m_handler->doOperationStop();
  1595. +    m_handler->doFailed(m_url, message);
  1596. +}
  1597. +
  1598. +void DecoderIOFactory::doInfo(const QString &message)
  1599. +{
  1600. +    m_handler->doInfo(message);
  1601. +}
  1602. +
  1603. +void DecoderIOFactory::doOperationStart(const QString &name)
  1604. +{
  1605. +    m_handler->doOperationStart(name);
  1606. +}
  1607. +
  1608. +void DecoderIOFactory::doOperationStop(void)
  1609. +{
  1610. +    m_handler->doOperationStop();
  1611. +}
  1612. +
  1613. +/**********************************************************************/
  1614. +
  1615. +DecoderIOFactoryFile::DecoderIOFactoryFile(DecoderHandler *parent)
  1616. +    : DecoderIOFactory(parent), m_input (NULL)
  1617. +{
  1618. +}
  1619. +
  1620. +DecoderIOFactoryFile::~DecoderIOFactoryFile ()
  1621. +{
  1622. +    delete m_input;
  1623. +}
  1624. +
  1625. +QIODevice* DecoderIOFactoryFile::takeInput ()
  1626. +{
  1627. +    QIODevice *result = m_input;
  1628. +    m_input = NULL;
  1629. +    return result;
  1630. +}
  1631. +
  1632. +void
  1633. +DecoderIOFactoryFile::start()
  1634. +{
  1635. +    QString sourcename = m_meta->Filename();   
  1636. +    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Opening Local File %1").arg(sourcename));
  1637. +    m_input = new QFile(sourcename);
  1638. +    doConnectDecoder(m_url);
  1639. +}
  1640. +
  1641. +
  1642. +/**********************************************************************/
  1643. +
  1644. +DecoderIOFactoryUrl::DecoderIOFactoryUrl(DecoderHandler *parent) : DecoderIOFactory(parent)
  1645. +{
  1646. +    /* Yes, this is pretty lame, it doesn't throttle the download or
  1647. +       anything, but just blindly downloads the entire file.
  1648. +       However, it supports whatever QUrlOperator supports and you should
  1649. +       see it as an example that you can enhance...
  1650. +    */
  1651. +    m_url_op = new QUrlOperator;
  1652. +    m_input = new QFile("/tmp/mythmusic.tmp");
  1653. +    m_input->open(IO_ReadOnly);
  1654. +    m_output = new QFile("/tmp/mythmusic.tmp");
  1655. +    m_output->open(IO_WriteOnly);
  1656. +   
  1657. +    connect(m_url_op, SIGNAL(finished(QNetworkOperation*)),
  1658. +            this, SLOT(finished(QNetworkOperation*)));;
  1659. +    connect(m_url_op, SIGNAL(start(QNetworkOperation*)),
  1660. +            this, SLOT(start(QNetworkOperation*)));;
  1661. +    connect(m_url_op, SIGNAL(data(const QByteArray&, QNetworkOperation*)),
  1662. +            this, SLOT(data(const QByteArray&, QNetworkOperation*)));;
  1663. +}
  1664. +
  1665. +DecoderIOFactoryUrl::~DecoderIOFactoryUrl ()
  1666. +{
  1667. +    doClose();
  1668. +   
  1669. +    m_url_op->deleteLater();
  1670. +    m_output->remove();
  1671. +
  1672. +    delete m_output;
  1673. +    delete m_input;
  1674. +}
  1675. +
  1676. +QIODevice* DecoderIOFactoryUrl::takeInput ()
  1677. +{
  1678. +    QIODevice *result = m_input;
  1679. +    m_input = NULL;
  1680. +    return result;
  1681. +}
  1682. +
  1683. +void DecoderIOFactoryUrl::start()
  1684. +{
  1685. +    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Url %1").arg(m_url.toString()));
  1686. +
  1687. +    m_started = false;
  1688. +
  1689. +    doOperationStart("Fetching remote file");
  1690. +   
  1691. +    m_url_op->get(m_url);
  1692. +}
  1693. +
  1694. +void DecoderIOFactoryUrl::stop()
  1695. +{
  1696. +    doClose();
  1697. +}
  1698. +
  1699. +void DecoderIOFactoryUrl::finished(QNetworkOperation *op)
  1700. +{
  1701. +    m_output->close();
  1702. +
  1703. +    if (op->state() != QNetworkProtocol::StDone)
  1704. +    {
  1705. +        doFailed("Cannot retrieve remote file.");
  1706. +        return;
  1707. +    }
  1708. +   
  1709. +    if (!m_started)
  1710. +        doStart();
  1711. +}
  1712. +
  1713. +void DecoderIOFactoryUrl::start(QNetworkOperation*)
  1714. +{
  1715. +}
  1716. +
  1717. +void DecoderIOFactoryUrl::data(const QByteArray & data, QNetworkOperation*)
  1718. +{
  1719. +    m_output->writeBlock(data);
  1720. +    if (!m_started && m_input->size () > DecoderIOFactory::DefaultPrebufferSize)
  1721. +        doStart();
  1722. +}
  1723. +
  1724. +void DecoderIOFactoryUrl::doStart()
  1725. +{
  1726. +    doConnectDecoder(m_url);
  1727. +    m_started = true;
  1728. +}
  1729. +
  1730. +void DecoderIOFactoryUrl::doClose()
  1731. +{
  1732. +    if (m_input->isOpen())
  1733. +        m_input->close();
  1734. +    if (m_output->isOpen())
  1735. +        m_output->close();
  1736. +}
  1737. +
  1738. +/**********************************************************************/
  1739. +
  1740. +/* I've left this here since it used to exist... Don't even know what
  1741. + * mqp is, much less how to test it...
  1742. + */
  1743. +
  1744. +DecoderIOFactoryMqp::DecoderIOFactoryMqp(DecoderHandler *parent) : DecoderIOFactory(parent)
  1745. +{
  1746. +    m_input = NULL;
  1747. +}
  1748. +
  1749. +DecoderIOFactoryMqp::~DecoderIOFactoryMqp ()
  1750. +{
  1751. +    delete m_input;
  1752. +}
  1753. +
  1754. +QIODevice* DecoderIOFactoryMqp::takeInput ()
  1755. +{
  1756. +    QIODevice *result = m_input;
  1757. +    m_input = NULL;
  1758. +    return result;
  1759. +}
  1760. +
  1761. +void DecoderIOFactoryMqp::start()
  1762. +{
  1763. +    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Mqp %1").arg(m_url.toString()));
  1764. +    StreamInput streaminput(m_url);
  1765. +    streaminput.setup();
  1766. +    m_input = streaminput.socket();
  1767. +    doConnectDecoder(m_url);
  1768. +}
  1769. +
  1770. +void DecoderIOFactoryMqp::stop()
  1771. +{
  1772. +    m_input->close();
  1773. +}
  1774. +
  1775. +/**********************************************************************/
  1776. +
  1777. +DecoderHandler::DecoderHandler()
  1778. +    : m_state (STOPPED),
  1779. +      m_io_factory (NULL),
  1780. +      m_decoder (NULL),
  1781. +      m_op (false),
  1782. +      m_redirects (0)
  1783. +{
  1784. +}
  1785. +
  1786. +DecoderHandler::~DecoderHandler()
  1787. +{
  1788. +    stop();
  1789. +}
  1790. +
  1791. +void DecoderHandler::start(Metadata *mdata)
  1792. +{
  1793. +    m_state = LOADING;
  1794. +
  1795. +    m_playlist.clear();
  1796. +    m_meta = mdata;
  1797. +    m_playlist_pos = -1;
  1798. +    m_redirects = 0;
  1799. +   
  1800. +    QUrl url(mdata->Filename());
  1801. +    bool result = createPlaylist(url);
  1802. +   
  1803. +    if (m_state == LOADING && result)
  1804. +    {
  1805. +        for (int ii = 0; ii < m_playlist.size(); ii++)
  1806. +            VERBOSE(VB_PLAYBACK, QString("Track %1 = %2").
  1807. +                    arg(ii).
  1808. +                    arg(m_playlist.get(ii)->File()));
  1809. +        next();
  1810. +    } else {
  1811. +        if (m_state != STOPPED) {
  1812. +            doFailed(url, "Could not get playlist");
  1813. +        }
  1814. +    }
  1815. +}
  1816. +
  1817. +void DecoderHandler::error(const QString &e)
  1818. +{
  1819. +    QString *str = new QString(e.utf8());
  1820. +    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, str);
  1821. +    dispatch(ev);
  1822. +}
  1823. +
  1824. +bool DecoderHandler::done()
  1825. +{
  1826. +    if (m_state == STOPPED)
  1827. +        return true;
  1828. +
  1829. +    if (m_playlist_pos + 1 >= m_playlist.size())
  1830. +    {
  1831. +        m_state = STOPPED;
  1832. +        return true;
  1833. +    }
  1834. +
  1835. +    return false;
  1836. +}
  1837. +
  1838. +bool DecoderHandler::next()
  1839. +{
  1840. +    if (done())
  1841. +        return false;
  1842. +
  1843. +    m_playlist_pos++;
  1844. +
  1845. +    PlayListFileEntry *entry = m_playlist.get(m_playlist_pos);
  1846. +    QUrl url(entry->File());
  1847. +
  1848. +    VERBOSE(VB_PLAYBACK, QString("Now playing '%1'").arg(url.toString()));
  1849. +
  1850. +    deleteIOFactory();
  1851. +    createIOFactory(url);
  1852. +
  1853. +    if (! haveIOFactory())
  1854. +        return false;
  1855. +
  1856. +    getIOFactory()->addListener(this);
  1857. +    getIOFactory()->setUrl(url);
  1858. +    getIOFactory()->setMeta(m_meta);
  1859. +    getIOFactory()->start();
  1860. +    m_state = ACTIVE;
  1861. +
  1862. +    return true;
  1863. +}
  1864. +
  1865. +void DecoderHandler::stop()
  1866. +{
  1867. +    if (m_decoder) {
  1868. +        m_decoder->lock();
  1869. +        m_decoder->stop();
  1870. +        m_decoder->unlock();
  1871. +    }
  1872. +    if (m_decoder) {
  1873. +        m_decoder->lock();
  1874. +        m_decoder->cond()->wakeAll();
  1875. +        m_decoder->unlock();
  1876. +    }
  1877. +
  1878. +    if (m_decoder) {
  1879. +        m_decoder->wait();
  1880. +        delete m_decoder->input ();
  1881. +        m_decoder->setInput (NULL);
  1882. +    }
  1883. +
  1884. +    deleteIOFactory ();
  1885. +    doOperationStop();
  1886. +
  1887. +    m_state = STOPPED;
  1888. +}
  1889. +
  1890. +void DecoderHandler::customEvent(QCustomEvent *qevent)
  1891. +{
  1892. +    if (class DecoderHandlerEvent *event = dynamic_cast<DecoderHandlerEvent*>(qevent)) {
  1893. +        // Proxy all DecoderHandlerEvents
  1894. +        return dispatch(*event);   
  1895. +    }
  1896. +}
  1897. +
  1898. +bool DecoderHandler::createPlaylist(const QUrl &url)
  1899. +{
  1900. +    QString extension = url.fileName().right(4).lower();
  1901. +    VERBOSE (VB_NETWORK, QString ("File %1 has extension %2").arg (url.fileName()).arg(extension));
  1902. +    if (extension == ".pls" || extension == ".m3u")
  1903. +        if (url.isLocalFile())
  1904. +            return createPlaylistFromFile(url);
  1905. +        else
  1906. +            return createPlaylistFromRemoteUrl(url);
  1907. +   
  1908. +    return createPlaylistForSingleFile(url);
  1909. +}
  1910. +
  1911. +bool DecoderHandler::createPlaylistForSingleFile(const QUrl &url)
  1912. +{
  1913. +    PlayListFileEntry *entry = new PlayListFileEntry;
  1914. +
  1915. +    if (url.isLocalFile())
  1916. +        entry->setFile(url.dirPath()+'/'+url.fileName());
  1917. +    else
  1918. +        entry->setFile(url.toString());
  1919. +
  1920. +    m_playlist.add(entry);
  1921. +
  1922. +    return m_playlist.size() > 0;
  1923. +}
  1924. +
  1925. +bool DecoderHandler::createPlaylistFromFile(const QUrl &url)
  1926. +{
  1927. +    QFile f(url.fileName());
  1928. +    f.open(IO_ReadOnly);
  1929. +    QTextStream stream(&f);
  1930. +   
  1931. +    if (PlayListFile::parse(&m_playlist, &stream) < 0)
  1932. +        return false;
  1933. +
  1934. +    return m_playlist.size() > 0;
  1935. +}
  1936. +
  1937. +bool DecoderHandler::createPlaylistFromRemoteUrl(const QUrl &url)
  1938. +{
  1939. +    HttpComms comms;
  1940. +    QUrl _url(url);
  1941. +
  1942. +    VERBOSE(VB_NETWORK, QString("Retrieving playlist from %1").arg(_url.toString()));
  1943. +
  1944. +    doOperationStart("Retrieving playlist");
  1945. +    comms.request(_url, 10000, false);   
  1946. +    while(!comms.isDone())
  1947. +        qApp->processEvents(500);
  1948. +    doOperationStop();
  1949. +
  1950. +    VERBOSE(VB_NETWORK, QString("Retrieving playlist from %1 = %2 (%3)").
  1951. +            arg(_url.toString()).
  1952. +            arg(comms.getStatusCode ()).
  1953. +            arg(comms.isTimedout()?"timed out":"not timed out"));
  1954. +
  1955. +    if (comms.isTimedout())
  1956. +        return false;
  1957. +
  1958. +    if (comms.getStatusCode() == 302)
  1959. +    {
  1960. +        QString redir = comms.getRedirectedURL();
  1961. +        m_redirects++;
  1962. +        if (m_redirects > MaxRedirects) {
  1963. +            VERBOSE(VB_IMPORTANT, QString ("Too many redirections."));
  1964. +            return false;
  1965. +        }
  1966. +        return createPlaylistFromRemoteUrl(redir);
  1967. +    }
  1968. +
  1969. +    if (comms.getStatusCode() != 200)
  1970. +        return false;
  1971. +
  1972. +    QBuffer buffer(comms.getRawData());
  1973. +    buffer.open(IO_ReadOnly);
  1974. +    QTextStream stream(&buffer);
  1975. +   
  1976. +    bool result = PlayListFile::parse(&m_playlist, &stream) > 0;
  1977. +    return result;
  1978. +}
  1979. +
  1980. +void DecoderHandler::doConnectDecoder(const QUrl &url, const QString &format)
  1981. +{
  1982. +    if (m_decoder && !m_decoder->factory()->supports(format)) {
  1983. +        m_decoder = NULL;
  1984. +    }
  1985. +
  1986. +    if (! m_decoder) {
  1987. +        if ((m_decoder = Decoder::create(format, NULL, NULL)) == NULL) {
  1988. +            doFailed(url, "No decoder for this format");
  1989. +            return;
  1990. +        }
  1991. +    }
  1992. +
  1993. +    m_decoder->setInput(getIOFactory()->takeInput());
  1994. +    m_decoder->setFilename(url.toString());
  1995. +
  1996. +    DecoderHandlerEvent ev(DecoderHandlerEvent::Ready);
  1997. +    dispatch(ev);
  1998. +}
  1999. +
  2000. +void DecoderHandler::doFailed(const QUrl &url, const QString &message)
  2001. +{
  2002. +    printf("mythmusic: unsupported fileformat: %s\n", url.toString().ascii ());
  2003. +    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, new QString (message));
  2004. +    dispatch(ev);
  2005. +}
  2006. +
  2007. +void DecoderHandler::doInfo(const QString &message)
  2008. +{
  2009. +    DecoderHandlerEvent ev(DecoderHandlerEvent::Info, new QString (message));
  2010. +    dispatch(ev);
  2011. +}
  2012. +
  2013. +void DecoderHandler::doOperationStart(const QString &name)
  2014. +{
  2015. +    m_op = true;
  2016. +    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStart, new QString (name));
  2017. +    dispatch(ev);
  2018. +}
  2019. +
  2020. +void DecoderHandler::doOperationStop(void)
  2021. +{
  2022. +    if (!m_op)
  2023. +        return;
  2024. +
  2025. +    m_op = false;
  2026. +    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStop);
  2027. +    dispatch(ev);
  2028. +}
  2029. +
  2030. +void DecoderHandler::createIOFactory(const QUrl &url)
  2031. +{
  2032. +    PlayListFile::test();
  2033. +
  2034. +    if (haveIOFactory())
  2035. +        deleteIOFactory();
  2036. +
  2037. +    if (url.isLocalFile()) {
  2038. +        m_io_factory = new DecoderIOFactoryFile(this);
  2039. +    }
  2040. +    else
  2041. +    {
  2042. +        if (url.protocol() == "mqp" && !url.host().isNull())
  2043. +            m_io_factory = new DecoderIOFactoryMqp(this);
  2044. +        else if (m_meta && m_meta->Format() == "cast")
  2045. +            m_io_factory = new DecoderIOFactoryShoutCast(this);
  2046. +        else
  2047. +            m_io_factory = new DecoderIOFactoryUrl(this);
  2048. +    }
  2049. +}
  2050. +
  2051. +void DecoderHandler::deleteIOFactory(void)
  2052. +{
  2053. +    if (! haveIOFactory())
  2054. +        return;
  2055. +
  2056. +    if (m_state == ACTIVE)
  2057. +        m_io_factory->stop ();
  2058. +
  2059. +    //m_io_factory->removeListener(this);
  2060. +    //m_io_factory->disconnect();
  2061. +    //m_io_factory->deleteLater();
  2062. +    delete m_io_factory;
  2063. +    m_io_factory = NULL;
  2064. +}
  2065. Index: mythplugins/mythmusic/mythmusic/editradiometadata.cpp
  2066. ===================================================================
  2067. --- mythplugins/mythmusic/mythmusic/editradiometadata.cpp       (revision 0)
  2068. +++ mythplugins/mythmusic/mythmusic/editradiometadata.cpp       (revision 0)
  2069. @@ -0,0 +1,356 @@
  2070. +#include <mythtv/mythcontext.h>
  2071. +#include <mythtv/mythdbcon.h>
  2072. +#include <qdir.h>
  2073. +#include "editradiometadata.h"
  2074. +#include "metadata.h"
  2075. +#include "decoder.h"
  2076. +#include "genres.h"
  2077. +
  2078. +EditRadioMetadataDialog::EditRadioMetadataDialog(Metadata *source_metadata,
  2079. +                                                 MythMainWindow *parent,
  2080. +                                                 QString window_name,
  2081. +                                                 QString theme_filename,
  2082. +                                                 const char* name)
  2083. +    :MythThemedDialog(parent, window_name, theme_filename, name)
  2084. +{
  2085. +    // make a copy so we can abandon changes
  2086. +    m_metadata = new Metadata(*source_metadata);
  2087. +    m_sourceMetadata = source_metadata;
  2088. +    wireUpTheme();
  2089. +    fillWidgets();
  2090. +    assignFirstFocus();
  2091. +}
  2092. +
  2093. +void EditRadioMetadataDialog::fillWidgets()
  2094. +{
  2095. +    if (station_edit)
  2096. +    {
  2097. +        station_edit->setText(m_metadata->Artist());
  2098. +    }
  2099. +
  2100. +    if (channel_edit)
  2101. +    {
  2102. +        channel_edit->setText(m_metadata->Title());
  2103. +    }
  2104. +
  2105. +    if (url_edit)
  2106. +    {
  2107. +        url_edit->setText(m_metadata->Filename());
  2108. +    }
  2109. +
  2110. +    if (metaformat_edit)
  2111. +    {
  2112. +        metaformat_edit->setText(m_metadata->CompilationArtist());
  2113. +    }
  2114. +
  2115. +    if (genre_edit)
  2116. +    {
  2117. +        genre_edit->setText(m_metadata->Genre());
  2118. +    }
  2119. +
  2120. +    if (rating_image)
  2121. +    {
  2122. +        rating_image->setRepeat(m_metadata->Rating());
  2123. +    }
  2124. +}
  2125. +
  2126. +void EditRadioMetadataDialog::incRating(bool up_or_down)
  2127. +{
  2128. +    if (up_or_down)
  2129. +        m_metadata->incRating();
  2130. +    else
  2131. +        m_metadata->decRating();
  2132. +
  2133. +    fillWidgets();
  2134. +}
  2135. +
  2136. +void EditRadioMetadataDialog::keyPressEvent(QKeyEvent *e)
  2137. +{
  2138. +    bool handled = false;
  2139. +
  2140. +    QStringList actions;
  2141. +    gContext->GetMainWindow()->TranslateKeyPress("Video", e, actions);
  2142. +
  2143. +    for (unsigned int i = 0; i < actions.size() && !handled; i++)
  2144. +    {
  2145. +        QString action = actions[i];
  2146. +        handled = true;
  2147. +
  2148. +        if (action == "UP")
  2149. +            nextPrevWidgetFocus(false);
  2150. +        else if (action == "DOWN")
  2151. +            nextPrevWidgetFocus(true);
  2152. +        else if (action == "LEFT")
  2153. +        {
  2154. +            if (getCurrentFocusWidget() == rating_button)
  2155. +            {
  2156. +                rating_button->push();
  2157. +                incRating(false);
  2158. +            }
  2159. +        }
  2160. +        else if (action == "RIGHT")
  2161. +        {
  2162. +            if (getCurrentFocusWidget() == rating_button)
  2163. +            {
  2164. +                rating_button->push();
  2165. +                incRating(true);
  2166. +            }
  2167. +        }
  2168. +        else if (action == "SELECT")
  2169. +        {
  2170. +            activateCurrent();
  2171. +        }
  2172. +        else if (action == "0")
  2173. +        {
  2174. +            if (done_button)
  2175. +                done_button->push();
  2176. +        }
  2177. +        else if (action == "1")
  2178. +        {
  2179. +        }
  2180. +        else
  2181. +            handled = false;
  2182. +    }
  2183. +
  2184. +    if (!handled)
  2185. +        MythThemedDialog::keyPressEvent(e);
  2186. +}
  2187. +
  2188. +void EditRadioMetadataDialog::wireUpTheme()
  2189. +{
  2190. +    station_edit = getUIRemoteEditType("station_edit");
  2191. +    if (station_edit)
  2192. +    {
  2193. +        station_edit->createEdit(this);
  2194. +        connect(station_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
  2195. +    }
  2196. +   
  2197. +    channel_edit = getUIRemoteEditType("channel_edit");
  2198. +    if (channel_edit)
  2199. +    {
  2200. +        channel_edit->createEdit(this);
  2201. +        connect(channel_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
  2202. +    }
  2203. +   
  2204. +    url_edit = getUIRemoteEditType("url_edit");
  2205. +    if (url_edit)
  2206. +    {
  2207. +        url_edit->createEdit(this);
  2208. +        connect(url_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
  2209. +    }
  2210. +   
  2211. +    metaformat_edit = getUIRemoteEditType("metaformat_edit");
  2212. +    if (metaformat_edit)
  2213. +    {
  2214. +        metaformat_edit->createEdit(this);
  2215. +        connect(metaformat_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
  2216. +    }
  2217. +         
  2218. +    genre_edit = getUIRemoteEditType("genre_edit");
  2219. +    if (genre_edit)
  2220. +    {
  2221. +        genre_edit->createEdit(this);
  2222. +        connect(genre_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
  2223. +    }
  2224. +       
  2225. +    rating_image = getUIRepeatedImageType("rating_image");
  2226. +   
  2227. +    searchstation_button = getUIPushButtonType("searchstation_button");
  2228. +    if (searchstation_button)
  2229. +    {
  2230. +        connect(searchstation_button, SIGNAL(pushed()), this, SLOT(searchStation()));
  2231. +    }
  2232. +
  2233. +    searchgenre_button = getUIPushButtonType("searchgenre_button");
  2234. +    if (searchgenre_button)
  2235. +    {
  2236. +        connect(searchgenre_button, SIGNAL(pushed()), this, SLOT(searchGenre()));
  2237. +    }
  2238. +
  2239. +    done_button = getUITextButtonType("done_button");
  2240. +    if (done_button)
  2241. +    {
  2242. +        done_button->setText(tr("Done"));
  2243. +        connect(done_button, SIGNAL(pushed()), this, SLOT(showSaveMenu()));
  2244. +    }
  2245. +
  2246. +    rating_button = getUISelectorType("rating_button");
  2247. +    if (rating_button)
  2248. +    {
  2249. +       
  2250. +    }
  2251. +
  2252. +    buildFocusList();
  2253. +}
  2254. +
  2255. +void EditRadioMetadataDialog::editLostFocus()
  2256. +{
  2257. +    UIRemoteEditType *whichEditor = (UIRemoteEditType *) getCurrentFocusWidget();
  2258. +   
  2259. +    if (whichEditor == station_edit)
  2260. +    {
  2261. +        m_metadata->setArtist(station_edit->getText());
  2262. +    }
  2263. +    else if (whichEditor == channel_edit)
  2264. +    {
  2265. +        m_metadata->setTitle(channel_edit->getText());
  2266. +    }
  2267. +    else if (whichEditor == url_edit)
  2268. +    {
  2269. +        m_metadata->setFilename(url_edit->getText());
  2270. +    }
  2271. +    else if (whichEditor == metaformat_edit)
  2272. +    {
  2273. +        m_metadata->setCompilationArtist(metaformat_edit->getText());
  2274. +    }
  2275. +    else if (whichEditor == genre_edit)
  2276. +    {
  2277. +        m_metadata->setGenre(genre_edit->getText());
  2278. +    }
  2279. +}
  2280. +
  2281. +bool EditRadioMetadataDialog::showList(QString caption, QString &value)
  2282. +{
  2283. +    bool res = false;
  2284. +   
  2285. +    MythSearchDialog *searchDialog = new MythSearchDialog(gContext->GetMainWindow(), "");
  2286. +    searchDialog->setCaption(caption);
  2287. +    searchDialog->setSearchText(value);
  2288. +    searchDialog->setItems(searchList);
  2289. +    if (searchDialog->ExecPopup() == 0)
  2290. +    {
  2291. +        value = searchDialog->getResult();
  2292. +        res = true;
  2293. +    }
  2294. +   
  2295. +    delete searchDialog;
  2296. +    setActiveWindow();
  2297. +   
  2298. +    return res;     
  2299. +}
  2300. +
  2301. +void EditRadioMetadataDialog::fillSearchList(QString field, QString table)
  2302. +{
  2303. +    searchList.clear();
  2304. +   
  2305. +    QString querystr;
  2306. +    querystr = QString("SELECT DISTINCT %1 FROM %2 ORDER BY %3").
  2307. +        arg(field).arg(table).arg(field);
  2308. +         
  2309. +    MSqlQuery query(MSqlQuery::InitCon());
  2310. +    query.exec(querystr);
  2311. +
  2312. +    if (query.isActive() && query.size())
  2313. +    {
  2314. +        while (query.next())
  2315. +        {
  2316. +            searchList << QString::fromUtf8(query.value(0).toString());
  2317. +        }
  2318. +    }         
  2319. +}
  2320. +   
  2321. +void EditRadioMetadataDialog::searchStation()
  2322. +{
  2323. +    QString s;
  2324. +   
  2325. +    fillSearchList("station", "music_radios");
  2326. +   
  2327. +    s = m_metadata->Artist();
  2328. +    if (showList(tr("Select a Station"), s))
  2329. +    {
  2330. +        m_metadata->setArtist(s);
  2331. +        fillWidgets();
  2332. +    }
  2333. +}
  2334. +
  2335. +void EditRadioMetadataDialog::searchGenre()
  2336. +{
  2337. +    QString s;
  2338. +
  2339. +    // load genre list
  2340. +    searchList.clear();
  2341. +    for (int x = 0; x < genre_table_size; x++)
  2342. +        searchList.push_back(QString(genre_table[x]));
  2343. +    searchList.sort();
  2344. +
  2345. +    s = m_metadata->Genre();
  2346. +    if (showList(tr("Select a Genre"), s))
  2347. +    {
  2348. +        m_metadata->setGenre(s);
  2349. +        fillWidgets();
  2350. +    }
  2351. +   
  2352. +}
  2353. +
  2354. +void EditRadioMetadataDialog::closeDialog()
  2355. +{
  2356. +    cancelPopup();
  2357. +    done(0)
  2358. +}
  2359. +
  2360. +void EditRadioMetadataDialog::showSaveMenu()
  2361. +{
  2362. +    popup = new MythPopupBox(gContext->GetMainWindow(), "Menu");
  2363. +
  2364. +    QLabel *label = popup->addLabel(tr("Save Changes?"), MythPopupBox::Large, false);
  2365. +    label->setAlignment(Qt::AlignCenter | Qt::WordBreak);
  2366. +
  2367. +    QButton *topButton = popup->addButton(tr("Save"), this,
  2368. +                         SLOT(saveToDatabase()));
  2369. +    popup->addButton(tr("Exit/Do Not Save"), this,
  2370. +                         SLOT(closeDialog()));
  2371. +    popup->addButton(tr("Cancel"), this, SLOT(cancelPopup()));
  2372. +
  2373. +    popup->ShowPopup(this, SLOT(cancelPopup()));
  2374. +
  2375. +    topButton->setFocus();
  2376. +}
  2377. +
  2378. +void EditRadioMetadataDialog::cancelPopup()
  2379. +{
  2380. +  if (!popup)
  2381. +      return;
  2382. +
  2383. +  popup->hide();
  2384. +
  2385. +  delete popup;
  2386. +  popup = NULL;
  2387. +  setActiveWindow();
  2388. +}
  2389. +
  2390. +bool EditRadioMetadataDialog::verifyEntries()
  2391. +{
  2392. +    // TODO: check valid text in url and metaformat...
  2393. +    return true;
  2394. +}
  2395. +
  2396. +void EditRadioMetadataDialog::saveNewToDatabase()
  2397. +{
  2398. +    cancelPopup();
  2399. +
  2400. +    if (!verifyEntries())
  2401. +        return;
  2402. +
  2403. +    m_metadata->dumpToDatabase();       
  2404. +    *m_sourceMetadata = m_metadata;
  2405. +
  2406. +    done(1);
  2407. +}
  2408. +
  2409. +void EditRadioMetadataDialog::saveToDatabase()
  2410. +{
  2411. +    cancelPopup();
  2412. +
  2413. +    if (m_metadata->ID() == 0)
  2414. +        return saveNewToDatabase();
  2415. +
  2416. +    m_metadata->dumpToDatabase();       
  2417. +    *m_sourceMetadata = m_metadata;
  2418. +
  2419. +    done(1);
  2420. +}
  2421. +
  2422. +EditRadioMetadataDialog::~EditRadioMetadataDialog()
  2423. +{
  2424. +    delete m_metadata;
  2425. +}
  2426. Index: mythplugins/mythmusic/mythmusic/decoder.h
  2427. ===================================================================
  2428. --- mythplugins/mythmusic/mythmusic/decoder.h   (revision 13855)
  2429. +++ mythplugins/mythmusic/mythmusic/decoder.h   (working copy)
  2430. @@ -37,8 +37,7 @@
  2431.  
  2432.      ~DecoderEvent()
  2433.      {
  2434. -        if (error_msg)
  2435. -            delete error_msg;
  2436. +        delete error_msg;
  2437.      }
  2438.  
  2439.      const QString *errorMessage() const { return error_msg; }
  2440. Index: mythplugins/mythmusic/mythmusic/aacdecoder.cpp
  2441. ===================================================================
  2442. --- mythplugins/mythmusic/mythmusic/aacdecoder.cpp      (revision 13855)
  2443. +++ mythplugins/mythmusic/mythmusic/aacdecoder.cpp      (working copy)
  2444. @@ -175,17 +175,14 @@
  2445.          }
  2446.      }
  2447.  
  2448. -
  2449.      //
  2450.      //  See if we can seek
  2451.      //
  2452. -
  2453.      if (!input()->at(0))
  2454.      {
  2455. -      error("couldn't seek in input");
  2456. +        error("couldn't seek in input");
  2457.          return false;
  2458.      }
  2459. -   
  2460.  
  2461.      //
  2462.      //  figure out if it's an mp4 file (aac in a mp4 wrapper a la Apple) or
  2463. @@ -352,9 +349,9 @@
  2464.  
  2465.      // If max_bitrate == avg_bitrate, then file is fixed bitrate
  2466.      if (mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) ==
  2467. -       mp4ff_get_max_bitrate(mp4_input_file, aac_track_number))
  2468. +        mp4ff_get_max_bitrate(mp4_input_file, aac_track_number))
  2469.      {
  2470. -       bitrate = mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) / 1000;
  2471. +        bitrate = mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) / 1000;
  2472.      }
  2473.      
  2474.      //
  2475. @@ -501,7 +498,7 @@
  2476.  
  2477.              if (output())
  2478.              {
  2479. -              output()->Drain();
  2480. +                output()->Drain();
  2481.              }
  2482.  
  2483.              done = TRUE;
  2484. @@ -542,7 +539,7 @@
  2485.                                                     );
  2486.              
  2487.                  sample_count = frame_info.samples;
  2488. -
  2489. +               
  2490.                  //
  2491.                  //  Munge the samples into the "right" format and send them
  2492.                  //  to the output (after checking we're not going to exceed
  2493. @@ -571,24 +568,24 @@
  2494.                      output_bytes += sample_count * 2;
  2495.                      if (output())
  2496.                      {
  2497. -                     // If source is VBR, bitrate == 0
  2498. -                     if (bitrate)
  2499. -                     {
  2500. -                         output()->SetSourceBitrate(bitrate);
  2501. -                     }
  2502. +                        // If source is VBR, bitrate == 0
  2503. +                        if (bitrate)
  2504. +                        {
  2505. +                            output()->SetSourceBitrate(bitrate);
  2506. +                        }
  2507.                          else
  2508.                          {
  2509. -                         output()->SetSourceBitrate(
  2510. -                            (int) ((float) (frame_info.bytesconsumed * 8) /
  2511. -                                   (frame_info.samples /
  2512. -                                    frame_info.num_front_channels)
  2513. -                            * frame_info.samplerate) / 1000);
  2514. -                     }
  2515. -
  2516. +                            output()->SetSourceBitrate(
  2517. +                                (int) ((float) (frame_info.bytesconsumed * 8) /
  2518. +                                       (frame_info.samples /
  2519. +                                        frame_info.num_front_channels)
  2520. +                                       * frame_info.samplerate) / 1000);
  2521. +                        }
  2522. +                       
  2523.                          flush();
  2524.                      }
  2525.                  }
  2526. -           
  2527. +               
  2528.                  if (buffer)
  2529.                  {
  2530.                      free(buffer);
  2531. Index: mythplugins/mythmusic/mythmusic/visualize.cpp
  2532. ===================================================================
  2533. --- mythplugins/mythmusic/mythmusic/visualize.cpp       (revision 13855)
  2534. +++ mythplugins/mythmusic/mythmusic/visualize.cpp       (working copy)
  2535. @@ -17,7 +17,6 @@
  2536.  #include <qpainter.h>
  2537.  #include <qpixmap.h>
  2538.  #include <qimage.h>
  2539. -#include <qdir.h>
  2540.  #include <qurl.h>
  2541.  
  2542.  // mythtv
  2543. @@ -447,7 +446,7 @@
  2544.          (void)pluginName;
  2545.          return new AlbumArt(parent);
  2546.      }
  2547. -}AlbumArtFactory;
  2548. +} AlbumArtFactory;
  2549.  
  2550.  Blank::Blank()
  2551.      : VisualBase(true)
  2552. @@ -506,6 +505,8 @@
  2553.  {
  2554.      number_of_squares = 16;
  2555.      fake_height = number_of_squares * analyzerBarWidth;
  2556. +    startHue = 220;
  2557. +    targetHue = 360;
  2558.  }
  2559.  
  2560.  Squares::~Squares()
  2561. @@ -519,6 +520,11 @@
  2562.      size = newsize;
  2563.  }
  2564.  
  2565. +bool Squares::process(VisualNode *node) {
  2566. +    rotateHues ();
  2567. +    return Spectrum::process (node);
  2568. +}
  2569. +
  2570.  void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h)
  2571.  {
  2572.      double r, g, b, per;
  2573. @@ -550,9 +556,22 @@
  2574.      g = clamp(g, 255.0, 0.0);
  2575.      b = clamp(b, 255.0, 0.0);
  2576.  
  2577. -    p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
  2578. +    p->fillRect (x, y, w, h, QColor((int)r, (int)g, (int)b));
  2579.  }
  2580.  
  2581. +void Squares::rotateHues () {
  2582. +    targetHue ++;
  2583. +    if (targetHue >= 360)
  2584. +        targetHue = 0;
  2585. +
  2586. +    startHue ++;
  2587. +    if (startHue >= 360)
  2588. +        startHue = 0;
  2589. +
  2590. +    startColor = QColor (startHue, 255, 255, QColor::Hsv);
  2591. +    targetColor = QColor (targetHue, 255, 255, QColor::Hsv);
  2592. +}
  2593. +
  2594.  bool Squares::draw(QPainter *p, const QColor &back)
  2595.  {
  2596.      p->fillRect (0, 0, size.width (), size.height (), back);
  2597. Index: mythplugins/mythmusic/mythmusic/mainvisual.h
  2598. ===================================================================
  2599. --- mythplugins/mythmusic/mythmusic/mainvisual.h        (revision 13855)
  2600. +++ mythplugins/mythmusic/mythmusic/mainvisual.h        (working copy)
  2601. @@ -73,6 +73,7 @@
  2602.  {
  2603.    public:
  2604.      VisFactory() {m_pNextVisFactory = g_pVisFactories; g_pVisFactories = this;}
  2605. +       virtual ~VisFactory() { }
  2606.      const VisFactory* next() const {return m_pNextVisFactory;}
  2607.      virtual const QString &name(void) const = 0;
  2608.      virtual VisualBase* create(MainVisual *parent, long int winid,
  2609. @@ -114,6 +115,7 @@
  2610.      void showBanner(Metadata *meta, bool fullScreen, int visMode, int showTime = 8000);
  2611.      void hideBanner();
  2612.      bool bannerIsShowing(void) {return bannerTimer->isActive(); }
  2613. +       void clearInformation();
  2614.  
  2615.      static QStringList Visualizations();
  2616.  
  2617. @@ -157,6 +159,7 @@
  2618.      QString info;
  2619.      QPixmap info_pixmap;
  2620.      QRect   displayRect;
  2621. +       QColor  color;
  2622.  };
  2623.  
  2624.  class StereoScope : public VisualBase
  2625. Index: mythplugins/mythmusic/mythmusic/metadata.cpp
  2626. ===================================================================
  2627. --- mythplugins/mythmusic/mythmusic/metadata.cpp        (revision 13855)
  2628. +++ mythplugins/mythmusic/mythmusic/metadata.cpp        (working copy)
  2629. @@ -3,6 +3,7 @@
  2630.  #include <qregexp.h>
  2631.  #include <qdatetime.h>
  2632.  #include <qdir.h>
  2633. +#include <qurl.h>
  2634.  
  2635.  using namespace std;
  2636.  
  2637. @@ -11,10 +12,30 @@
  2638.  #include <mythtv/mythdbcon.h>
  2639.  
  2640.  #include "metadata.h"
  2641. +#include "editmetadata.h"
  2642. +#include "editradiometadata.h"
  2643.  #include "treebuilders.h"
  2644.  
  2645.  static QString thePrefix = "the ";
  2646.  
  2647. +QVariant queryField (const QString &v, const QString &table,
  2648. +                     const QString &q, const QVariant &qv)
  2649. +{
  2650. +    ;
  2651. +   
  2652. +    MSqlQuery query(MSqlQuery::InitCon());
  2653. +    query.prepare(QString("SELECT %1 FROM %2 WHERE %3 = :Q;").arg(v).arg(table).arg(q));
  2654. +    query.bindValue(":Q", qv);
  2655. +   
  2656. +    if (!query.exec()) {
  2657. +        MythContext::DBError(QString("Get %1 from %2 for %3 = %4").arg(v).arg(table).arg(q).arg(qv.toString()), query);           
  2658. +    } else if (query.isActive() && query.size() > 0)
  2659. +        while (query.next()) {
  2660. +            return query.value(0);
  2661. +        }               
  2662. +    return QVariant ();
  2663. +}
  2664. +
  2665.  bool operator==(const Metadata& a, const Metadata& b)
  2666.  {
  2667.      if (a.Filename() == b.Filename())
  2668. @@ -78,12 +99,7 @@
  2669.  {
  2670.      if (m_format == "cast")
  2671.      {
  2672. -        int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
  2673. -
  2674. -        if (artist_cmp == 0)
  2675. -            return Title().lower().localeAwareCompare(other->Title().lower());
  2676. -
  2677. -        return artist_cmp;
  2678. +        return compare_cast(other);
  2679.      }
  2680.      else
  2681.      {
  2682. @@ -96,6 +112,36 @@
  2683.      }
  2684.  }
  2685.  
  2686. +int Metadata::compare_cast(Metadata *other)
  2687. +{
  2688. +    int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
  2689. +   
  2690. +    if (artist_cmp == 0)
  2691. +        return Title().lower().localeAwareCompare(other->Title().lower());
  2692. +   
  2693. +    return artist_cmp;
  2694. +}
  2695. +
  2696. +MythThemedDialog *Metadata::createEditorDialog(void)
  2697. +{
  2698. +    /*note:temporary fix for radio*/
  2699. +    if (Format() == "cast")
  2700. +        return createEditorDialog_cast();
  2701. +
  2702. +    return new EditMetadataDialog(this, gContext->GetMainWindow(),
  2703. +                                  "edit_metadata", "music-",
  2704. +                                  "edit metadata");
  2705. +   
  2706. +}
  2707. +
  2708. +MythThemedDialog *Metadata::createEditorDialog_cast(void)
  2709. +{
  2710. +    return new EditRadioMetadataDialog(this, gContext->GetMainWindow(),
  2711. +                                       "edit_radiometadata", "music-",
  2712. +                                       "edit metadata");
  2713. +   
  2714. +}
  2715. +
  2716.  bool Metadata::isInDatabase()
  2717.  {
  2718.      bool retval = false;
  2719. @@ -155,6 +201,10 @@
  2720.  
  2721.  void Metadata::dumpToDatabase()
  2722.  {
  2723. +    /*note:temporary fix for radio*/
  2724. +    if (Format() == "cast")
  2725. +        return dumpToDatabase_cast();
  2726. +
  2727.      QString sqlfilepath(m_filename);
  2728.      if (!sqlfilepath.contains("://"))
  2729.      {
  2730. @@ -200,7 +250,7 @@
  2731.              m_directoryid = query.lastInsertId().toInt();
  2732.          }
  2733.      }
  2734. -
  2735. +   
  2736.      if (m_artistid < 0)
  2737.      {
  2738.          // Load the artist id
  2739. @@ -426,6 +476,60 @@
  2740.      }
  2741.  }
  2742.  
  2743. +void Metadata::dumpToDatabase_cast()
  2744. +{
  2745. +    MSqlQuery query(MSqlQuery::InitCon());
  2746. +    query.prepare("SELECT url FROM music_radios WHERE "
  2747. +                  "( ( station = :STATION ) AND "
  2748. +                  "( channel = :CHANNEL ) AND "
  2749. +                  "( format = :FORMAT ) AND "
  2750. +                  "( url = :URL ) ); ");
  2751. +    query.bindValue(":STATION", m_artist.utf8());
  2752. +    query.bindValue(":CHANNEL", m_title.utf8());
  2753. +    query.bindValue(":URL", m_filename);
  2754. +    query.bindValue(":FORMAT", m_format);
  2755. +   
  2756. +    if (query.exec() && query.isActive() && query.size() > 0)
  2757. +        return;
  2758. +   
  2759. +    QString strQuery;
  2760. +    if (m_id < 1) {
  2761. +        strQuery = "INSERT INTO music_radios "
  2762. +                   "(station,    channel,    url,        genre, "
  2763. +                   " metaformat, format,     rating            )"
  2764. +                   " VALUES "
  2765. +                   "(:STATION,     :CHANNEL,  :URL,     :GENRE,   "
  2766. +                  " :METAFORMAT,  :FORMAT,   :RATING );";
  2767. +    } else {
  2768. +        strQuery = "UPDATE music_radios          "
  2769. +                   "SET station   = :STATION,    "
  2770. +                   "    channel   = :CHANNEL,    "
  2771. +                   "    url       = :URL,        "
  2772. +                   "    genre     = :GENRE,      "
  2773. +                   "    metaformat= :METAFORMAT, "
  2774. +                   "    rating    = :RATING,     "
  2775. +                   "    format    = :FORMAT      "
  2776. +                   "WHERE intid = :ID;";
  2777. +    }
  2778. +    query.prepare(strQuery);
  2779. +    query.bindValue(":STATION", m_artist.utf8());
  2780. +    query.bindValue(":CHANNEL", m_title.utf8());
  2781. +    query.bindValue(":URL", m_filename);
  2782. +    query.bindValue(":GENRE", m_genre.utf8());
  2783. +    query.bindValue(":METAFORMAT", m_compilation_artist);
  2784. +    query.bindValue(":RATING", m_rating);
  2785. +    query.bindValue(":FORMAT", m_format);                     
  2786. +
  2787. +    if (m_id >= 1) {
  2788. +        query.bindValue(":ID", m_id);
  2789. +    }
  2790. +
  2791. +    query.exec();
  2792. +
  2793. +    if (m_id < 1 && query.isActive() && 1 == query.numRowsAffected())
  2794. +        setID(query.lastInsertId().toInt());
  2795. +}
  2796. +
  2797.  // Default values for formats
  2798.  // NB These will eventually be customizable....
  2799.  QString Metadata::m_formatnormalfileartist      = "ARTIST";
  2800. @@ -506,7 +610,6 @@
  2801.          m_title = m_filename;
  2802.      if (m_genre == "")
  2803.          m_genre = QObject::tr("Unknown Genre");
  2804. -
  2805.  }
  2806.  
  2807.  inline void Metadata::setCompilationFormatting(bool cd)
  2808. @@ -565,7 +668,58 @@
  2809.      return m_formattedtitle;
  2810.  }
  2811.  
  2812. +QString Metadata::FormatInfo()
  2813. +{       
  2814. +    QString result;
  2815. +    if (Format() == "cast") {
  2816. +        result = QString("\"%1\"\n"
  2817. +                         "%2\n"
  2818. +                         "Now playing on %3").
  2819. +            arg(FormatTitle()).
  2820. +            arg(FormatArtist()).
  2821. +            arg(Album());
  2822. +    } else {
  2823. +        result = QString("\"%1\"\n"
  2824. +                         "%2 - %3").
  2825. +        arg(FormatTitle()).
  2826. +            arg(FormatArtist()).
  2827. +            arg(Album());
  2828. +       
  2829. +        if (Year() > 0)
  2830. +            result += " (" + QString::number (Year()) + ")";
  2831. +    }
  2832.  
  2833. +    return result;
  2834. +}
  2835. +
  2836. +void Metadata::removeFromDatabase()
  2837. +{
  2838. +    /*note:temporary fix for radio*/
  2839. +    if (Format() == "cast")
  2840. +        return removeFromDatabase_cast();
  2841. +
  2842. +    MSqlQuery query(MSqlQuery::InitCon());
  2843. +
  2844. +    query.prepare("DELETE FROM musicmetadata "
  2845. +                  "WHERE intid = :ID;");
  2846. +    query.bindValue(":ID", m_id);
  2847. +
  2848. +    if (!query.exec())
  2849. +         MythContext::DBError("Remove musicmetadata", query);
  2850. +}
  2851. +
  2852. +void Metadata::removeFromDatabase_cast()
  2853. +{
  2854. +    MSqlQuery query(MSqlQuery::InitCon());
  2855. +
  2856. +    query.prepare("DELETE FROM music_radios "
  2857. +                  "WHERE intid = :ID;");
  2858. +    query.bindValue(":ID", m_id);
  2859. +
  2860. +    if (!query.exec())
  2861. +         MythContext::DBError("Remove music_radios", query);
  2862. +}
  2863. +
  2864.  void Metadata::setField(const QString &field, const QString &data)
  2865.  {
  2866.      if (field == "artist")
  2867. @@ -610,6 +764,13 @@
  2868.          *data = FormatTitle();
  2869.      else if (field == "genre")
  2870.          *data = m_genre;
  2871. +    else if (field == "x-cast-logourl")
  2872. +    {
  2873. +        /*note:temporary fix for radio*/
  2874. +        if (Format() == "cast") {
  2875. +            *data = queryField ("logourl", "music_radios", "intid", m_id).toString ();
  2876. +        }
  2877. +    }
  2878.      else
  2879.      {
  2880.          VERBOSE(VB_IMPORTANT, QString("Something asked me to return data "
  2881. @@ -618,6 +779,23 @@
  2882.      }
  2883.  }
  2884.  
  2885. +void Metadata::setAlbumArt(const QByteArray &data) {
  2886. +    /*note:temporary fix for radio*/
  2887. +    if (Format() == "cast") {
  2888. +        MSqlQuery query(MSqlQuery::InitCon());
  2889. +        query.prepare("UPDATE music_radios          "
  2890. +                      "SET logocached = :LOGO       "
  2891. +                      "WHERE intid = :ID;");
  2892. +        query.bindValue(":LOGO", data);
  2893. +        if (m_id >= 1) {
  2894. +            query.bindValue(":ID", m_id);
  2895. +        }
  2896. +       
  2897. +        if (!query.exec())
  2898. +            MythContext::DBError("Store albumart", query);
  2899. +    }
  2900. +}
  2901. +
  2902.  void Metadata::decRating()
  2903.  {
  2904.      if (m_rating > 0)
  2905. @@ -712,6 +890,13 @@
  2906.  
  2907.      QImage image;
  2908.  
  2909. +    if (Format() == "cast") {
  2910. +        QVariant v = queryField ("logocached", "music_radios", "intid", m_id);
  2911. +        if (v.isValid()) {
  2912. +            image = QImage (v.toByteArray());
  2913. +        }
  2914. +    }
  2915. +    else
  2916.      if (albumArt.isImageAvailable(type))
  2917.      {
  2918.          AlbumArtImage albumart_image = albumArt.getImage(type);
  2919. @@ -759,13 +944,14 @@
  2920.      startLoading();
  2921.  
  2922.      m_all_music.setAutoDelete(true);
  2923. -
  2924. +    m_all_radios.setAutoDelete(true);
  2925.      m_last_listed = -1;
  2926.  }
  2927.  
  2928.  AllMusic::~AllMusic()
  2929.  {
  2930.      m_all_music.clear();
  2931. +    m_all_radios.clear();
  2932.  
  2933.      delete m_root_node;
  2934.  
  2935. @@ -820,7 +1006,6 @@
  2936.  void AllMusic::resync()
  2937.  {
  2938.      m_done_loading = false;
  2939. -
  2940.      QString aquery = "SELECT music_songs.song_id, music_artists.artist_name, music_comp_artists.artist_name AS compilation_artist, "
  2941.                       "music_albums.album_name, music_songs.name, music_genres.genre, music_songs.year, "
  2942.                       "music_songs.track, music_songs.length, CONCAT_WS('/', "
  2943. @@ -836,7 +1021,6 @@
  2944.                       "ORDER BY music_songs.song_id;";
  2945.  
  2946.      QString filename, artist, album, title;
  2947. -
  2948.      MSqlQuery query(MSqlQuery::InitCon());
  2949.      query.exec(aquery);
  2950.  
  2951. @@ -850,28 +1034,16 @@
  2952.      {
  2953.          while (query.next())
  2954.          {
  2955. -            filename = QString::fromUtf8(query.value(9).toString());
  2956. +            QString filename = QString::fromUtf8(query.value(9).toString());
  2957.              if (!filename.contains("://"))
  2958.                  filename = m_startdir + filename;
  2959.  
  2960. -            artist = QString::fromUtf8(query.value(1).toString());
  2961. -            if (artist.isEmpty())
  2962. -                artist = QObject::tr("Unknown Artist");
  2963. -
  2964. -            album = QString::fromUtf8(query.value(3).toString());
  2965. -            if (album.isEmpty())
  2966. -                album = QObject::tr("Unknown Album");
  2967. -
  2968. -            title = QString::fromUtf8(query.value(4).toString());
  2969. -            if (title.isEmpty())
  2970. -                title = QObject::tr("Unknown Title");
  2971. -
  2972.              Metadata *temp = new Metadata(
  2973.                  filename,
  2974. -                artist,
  2975. +                QString::fromUtf8(query.value(1).toString()),
  2976.                  QString::fromUtf8(query.value(2).toString()),
  2977. -                album,
  2978. -                title,
  2979. +                QString::fromUtf8(query.value(3).toString()),
  2980. +                QString::fromUtf8(query.value(4).toString()),
  2981.                  QString::fromUtf8(query.value(5).toString()),
  2982.                  query.value(6).toInt(),
  2983.                  query.value(7).toInt(),
  2984. @@ -882,7 +1054,6 @@
  2985.                  query.value(12).toString(), //lastplay
  2986.                  (query.value(13).toInt() > 0), //compilation
  2987.                  query.value(14).toString()); //format
  2988. -
  2989.              //  Don't delete temp, as PtrList now owns it
  2990.              m_all_music.append(temp);
  2991.  
  2992. @@ -931,9 +1102,57 @@
  2993.      //printTree();
  2994.      sortTree();
  2995.      //printTree();
  2996. +
  2997. +    resync_radios();
  2998. +
  2999.      m_done_loading = true;
  3000.  }
  3001.  
  3002. +/*note:temporary fix for radio*/
  3003. +void AllMusic::resync_radios(void)
  3004. +{
  3005. +    m_radio_map.clear();
  3006. +    m_all_radios.clear();
  3007. +
  3008. +    QString the_query =
  3009. +        "SELECT "
  3010. +        "url, station, channel, genre, intid, rating, format, metaformat "
  3011. +        "FROM "
  3012. +        "music_radios ORDER BY station, channel";
  3013. +   
  3014. +    MSqlQuery query(MSqlQuery::InitCon());
  3015. +   
  3016. +    if (query.exec(the_query) && query.isActive() && query.size() > 0) {
  3017. +        while (query.next()) {
  3018. +            Metadata *md = new Metadata(QString::fromUtf8(query.value(0).toString()),
  3019. +                                        QString::fromUtf8(query.value(1).toString()),
  3020. +                                        "", "",
  3021. +                                        QString::fromUtf8(query.value(2).toString()),
  3022. +                                        QString::fromUtf8(query.value(3).toString()),
  3023. +                                        0, 0, 0,
  3024. +                                        query.value(4).toInt(), query.value(5).toInt());
  3025. +
  3026. +            // abuse...
  3027. +            md->setCompilationArtist(QString::fromUtf8(query.value(7).toString()));           
  3028. +            md->setFormat(query.value(6).toString());
  3029. +
  3030. +            addRadioTrack(md);
  3031. +        }
  3032. +        m_all_radios.sort ();
  3033. +    }   
  3034. +}
  3035. +
  3036. +void AllMusic::addRadioTrack(Metadata *meta)
  3037. +{   
  3038. +    QString title = meta->FormatArtist ();
  3039. +    title += " ~ ";
  3040. +    title += meta->FormatTitle();
  3041. +    meta->setAlbum(title);
  3042. +
  3043. +    m_all_radios.append(meta);
  3044. +    m_radio_map[meta->ID()] = meta;
  3045. +}
  3046. +
  3047.  void AllMusic::sortTree()
  3048.  {
  3049.      m_root_node->sort();
  3050. @@ -977,9 +1196,64 @@
  3051.      delete builder;
  3052.  }
  3053.  
  3054. +/*note:temporary fix for radio*/
  3055. +#define RADIOS_PR_STATION 1
  3056.  void AllMusic::writeTree(GenericTree *tree_to_write_to)
  3057.  {
  3058.      m_root_node->writeTree(tree_to_write_to, 0);
  3059. +
  3060. +    /*note:temporary fix for radio*/
  3061. +    {
  3062. +        int rcounter = 0;
  3063. +        GenericTree *radio_root = tree_to_write_to->addNode(QObject::tr("All My Radios"), 0);
  3064. +        radio_root->setAttribute(0, 0);
  3065. +        radio_root->setAttribute(1, 0);
  3066. +        radio_root->setAttribute(2, 0);
  3067. +        radio_root->setAttribute(3, 0);
  3068. +        radio_root->setAttribute(4, 1);
  3069. +       
  3070. +#if RADIOS_PR_STATION
  3071. +        QMap<QString,GenericTree*> radio_stations;
  3072. +#endif
  3073. +        QPtrListIterator<Metadata> iterator(m_all_radios);
  3074. +        Metadata *mdata;
  3075. +
  3076. +        while ((mdata = iterator.current()) != 0)
  3077. +        {
  3078. +#if RADIOS_PR_STATION
  3079. +            if (radio_stations[mdata->Artist()] == 0) {
  3080. +                GenericTree *sub =
  3081. +                    radio_root->addNode(mdata->FormatArtist(), false);
  3082. +                radio_stations[mdata->Artist()] = sub;
  3083. +                sub->setAttribute(0, 1);
  3084. +                sub->setAttribute(1, rcounter);
  3085. +                sub->setAttribute(2, rand());
  3086. +                sub->setAttribute(3, rand());
  3087. +                sub->setAttribute(4, 1);
  3088. +                rcounter++;
  3089. +            }
  3090. +#endif
  3091. +            QString title = mdata->Album();
  3092. +#if RADIOS_PR_STATION
  3093. +            title = mdata->Title();
  3094. +#endif
  3095. +               
  3096. +            GenericTree *insert_at = radio_root;
  3097. +#if RADIOS_PR_STATION
  3098. +           insert_at = radio_stations[mdata->Artist()];
  3099. +#endif
  3100. +
  3101. +            GenericTree *sub = insert_at->addNode(title, mdata->ID (), true);
  3102. +           
  3103. +            sub->setAttribute(0, 1);
  3104. +            sub->setAttribute(1, rcounter);
  3105. +            sub->setAttribute(2, rand());
  3106. +            sub->setAttribute(3, rand());
  3107. +            sub->setAttribute(4, 1);
  3108. +            ++rcounter;
  3109. +            ++iterator;
  3110. +        }       
  3111. +    }
  3112.  }
  3113.  
  3114.  bool AllMusic::putYourselfOnTheListView(TreeCheckItem *where)
  3115. @@ -1015,13 +1289,13 @@
  3116.      QString a_label = "";
  3117.      if(an_id > 0)
  3118.      {
  3119. -   
  3120.          if (!music_map.contains(an_id))
  3121.          {
  3122.              a_label = QString(QObject::tr("Missing database entry: %1")).arg(an_id);
  3123.              *error_flag = true;
  3124.              return a_label;
  3125.          }
  3126. +
  3127.        
  3128.          a_label += music_map[an_id]->FormatArtist();
  3129.          a_label += " ~ ";
  3130. @@ -1082,11 +1356,32 @@
  3131.      return NULL;
  3132.  }
  3133.  
  3134. -bool AllMusic::updateMetadata(int an_id, Metadata *the_track)
  3135. +
  3136. +Metadata* AllMusic::getRadioMetadata(int an_id)
  3137.  {
  3138.      if(an_id > 0)
  3139.      {
  3140. -        Metadata *mdata = getMetadata(an_id);
  3141. +        if (m_radio_map.contains(an_id))
  3142. +        {
  3143. +            return m_radio_map[an_id];   
  3144. +        }
  3145. +    }
  3146. +    return NULL;
  3147. +}
  3148. +
  3149. +bool AllMusic::updateMetadata(int repo_id, int an_id, Metadata *the_track)
  3150. +{
  3151. +    if(an_id > 0)
  3152. +    {
  3153. +        Metadata *mdata = NULL;
  3154. +
  3155. +        /* temporary fix for radios */
  3156. +        if(repo_id == 0) {
  3157. +            mdata = getMetadata(an_id);
  3158. +        } else if(repo_id == 1) {
  3159. +            mdata = getRadioMetadata(an_id);
  3160. +        }
  3161. +
  3162.          if (mdata)
  3163.          {
  3164.              *mdata = the_track;
  3165. @@ -1412,8 +1707,8 @@
  3166.      : m_parent(metadata)
  3167.  {
  3168.      m_imageList.setAutoDelete(true);
  3169. -
  3170. -    findImages();
  3171. +    if (m_parent)
  3172. +        findImages();
  3173.  }
  3174.  
  3175.  void AlbumArtImages::findImages(void)
  3176. Index: mythplugins/mythmusic/mythmusic/metaio.h
  3177. ===================================================================
  3178. --- mythplugins/mythmusic/mythmusic/metaio.h    (revision 13855)
  3179. +++ mythplugins/mythmusic/mythmusic/metaio.h    (working copy)
  3180. @@ -16,6 +16,7 @@
  3181.      
  3182.      virtual bool write(Metadata* mdata, bool exclusive = false) = 0;
  3183.      virtual Metadata* read(QString filename) = 0;
  3184. +       virtual QByteArray getAlbumArt(const QString &filename);
  3185.  
  3186.      void readFromFilename(QString filename, QString &artist, QString &album,
  3187.                            QString &title, QString &genre, int &tracknum);
  3188. Index: mythplugins/mythmusic/mythmusic/dbcheck.cpp
  3189. ===================================================================
  3190. --- mythplugins/mythmusic/mythmusic/dbcheck.cpp (revision 13855)
  3191. +++ mythplugins/mythmusic/mythmusic/dbcheck.cpp (working copy)
  3192. @@ -10,9 +10,379 @@
  3193.  #include "mythtv/mythdbcon.h"
  3194.  
  3195.  const QString currentDatabaseVersion = "1013";
  3196. +//----------------------------------------------------------------------------
  3197. +// The radio number is to seperate radios from the main. As long
  3198. +// as this patch isn't in the main svn trunk, it's a hassle to
  3199. +// share the number. When and if in the svn trunk, use the next
  3200. +// db version number. This is a cut'n'paste of the code
  3201. +// just to tweak to use another field in the settings table.
  3202. +const QString radioCurrentDatabaseVersion = "3";
  3203. +static void RadioUpdateDBVersionNumber(const QString &newnumber)
  3204. +{
  3205. +    MSqlQuery query(MSqlQuery::InitCon());
  3206.  
  3207. +    query.exec("DELETE FROM settings WHERE value='RadioMusicDBSchemaVer';");
  3208. +    query.exec(QString("INSERT INTO settings (value, data, hostname) "
  3209. +                          "VALUES ('RadioMusicDBSchemaVer', %1, NULL);")
  3210. +                         .arg(newnumber));
  3211. +}
  3212. +static void RadioPerformActualUpdate(const QString updates[], QString version,
  3213. +                                QString &dbver)
  3214. +{
  3215. +    VERBOSE(VB_IMPORTANT, QString("Upgrading to MythMusicRadio schema version ") +
  3216. +            version);
  3217. +
  3218. +    MSqlQuery query(MSqlQuery::InitCon());
  3219. +
  3220. +    int counter = 0;
  3221. +    QString thequery = updates[counter];
  3222. +
  3223. +    while (thequery != "")
  3224. +    {
  3225. +        query.exec(thequery);
  3226. +        counter++;
  3227. +        thequery = updates[counter];
  3228. +    }
  3229. +
  3230. +    RadioUpdateDBVersionNumber(version);
  3231. +    dbver = version;
  3232. +}
  3233. +static void RadioUpgrade() {
  3234. +    QString dbver = gContext->GetSetting("RadioMusicDBSchemaVer");
  3235. +    VERBOSE(VB_IMPORTANT, QString("RadioUpgrade dbversion='%1'").arg(dbver));
  3236. +    if (dbver == radioCurrentDatabaseVersion) return;
  3237. +    if (dbver == "") {
  3238. +        const QString updates[] = {
  3239. +            "DROP TABLE IF EXISTS music_radios;",
  3240. +            "CREATE TABLE music_radios ("
  3241. +            "    intid INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,"
  3242. +            "    station VARCHAR(128) NOT NULL,"
  3243. +            "    channel VARCHAR(128) NOT NULL,"
  3244. +            "    url VARCHAR(128) NOT NULL,"
  3245. +            "    logourl VARCHAR(128) NOT NULL,"
  3246. +            "    logocached BLOB,"
  3247. +            "    genre VARCHAR(128) NOT NULL,"
  3248. +            "    metaformat VARCHAR(128) NOT NULL,"
  3249. +            "    format VARCHAR(10) NOT NULL,"
  3250. +            "    rating INT UNSIGNED NOT NULL DEFAULT 5,"
  3251. +            "    INDEX (station),"
  3252. +            "    INDEX (channel)"
  3253. +            ");",
  3254. +       
  3255. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3256. +            "    channel = \"Space Station Soma\","
  3257. +            "    url=\"http://somafm.com/spacestation.pls\","
  3258. +            "    logourl=\"http://img.somafm.com/img/sss.jpg\","
  3259. +            "    genre=\"Electronica\","
  3260. +            "    metaformat=\"%a - %t\","
  3261. +            "    format=\"cast\";",
  3262. +       
  3263. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3264. +            "    channel = \"Doomed\","
  3265. +            "    url=\"http://somafm.com/doomed.pls\","
  3266. +            "    logourl=\"http://img.somafm.com/img/doomed.gif\","
  3267. +            "    genre=\"Darkwave\","
  3268. +            "    metaformat=\"%a - %t\","
  3269. +            "    format=\"cast\";",
  3270. +       
  3271. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3272. +            "    channel = \"Beatblender\","
  3273. +            "    url=\"http://somafm.com/beatblender.pls\","
  3274. +            "    logourl=\"http://img.somafm.com/img/blender.gif\","
  3275. +            "    genre=\"Ambient\","
  3276. +            "    metaformat=\"%a - %t\","
  3277. +            "    format=\"cast\";",
  3278. +       
  3279. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3280. +            "    channel = \"Cliqhop idm\","
  3281. +            "    url=\"http://somafm.com/cliqhop.pls\","
  3282. +            "    logourl=\"http://img.somafm.com/img/cliqhop.gif\","
  3283. +            "    genre=\"Dance\","
  3284. +            "    metaformat=\"%a - %t\","
  3285. +            "    format=\"cast\";",
  3286. +       
  3287. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3288. +            "    channel = \"Indie Pop Rocks\","
  3289. +            "    url=\"http://somafm.com/indiepop.pls\","
  3290. +            "    logourl=\"http://img.somafm.com/img/indychick.jpg\","
  3291. +            "    genre=\"Indie\","
  3292. +            "    metaformat=\"%a - %t\","
  3293. +            "    format=\"cast\";",
  3294. +       
  3295. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3296. +            "    channel = \"Tag's Trance Trip\","
  3297. +            "    url=\"http://somafm.com/tagstrance.pls\","
  3298. +            "    logourl=\"http://img.somafm.com/img/tagstrancefract.jpg\","
  3299. +            "    genre=\"Trance\","
  3300. +            "    metaformat=\"%a - %t\","
  3301. +            "    format=\"cast\";",
  3302. +       
  3303. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3304. +            "    channel = \"Drone Zone\","
  3305. +            "    url=\"http://somafm.com/dronezone.pls\","
  3306. +            "    logourl=\"http://img.somafm.com/img/DroneZoneBox.jpg\","
  3307. +            "    genre=\"Ambient\","
  3308. +            "    metaformat=\"%a - %t\","
  3309. +            "    format=\"cast\";",
  3310. +
  3311. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3312. +            "    channel = \"Secret Agent\","
  3313. +            "    url=\"http://somafm.com/secretagent.pls\","
  3314. +            "    logourl=\"http://img.somafm.com/img/SecretAgentBox.jpg\","
  3315. +            "    genre=\"Lounge\","
  3316. +            "    metaformat=\"%a - %t\","
  3317. +            "    format=\"cast\";",
  3318. +       
  3319. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3320. +            "    channel = \"Groove Salad\","
  3321. +            "    url=\"http://somafm.com/groovesalad.pls\","
  3322. +            "    logourl=\"http://img.somafm.com/img/GrooveSaladBox.jpg\","
  3323. +            "    genre=\"Ambient\","
  3324. +            "    metaformat=\"%a - %t\","
  3325. +            "    format=\"cast\";",
  3326. +
  3327. +            "INSERT INTO music_radios SET station = \"SomaFM\", "
  3328. +            "    channel = \"Illinois Street Lounge\","
  3329. +            "    url=\"http://somafm.com/illstreet.pls\","
  3330. +            "    logourl=\"http://img.somafm.com/img/illstreet.jpg\","
  3331. +            "    genre=\"Lounge\","
  3332. +            "    metaformat=\"%a - %t\","
  3333. +            "    format=\"cast\";",
  3334. +       
  3335. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3336. +            "    channel = \"trance\","
  3337. +            "    url=\"http://di.fm/mp3/trance.pls\","
  3338. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3339. +            "    genre=\"Trance\","
  3340. +            "    metaformat=\"%t - %a\","
  3341. +            "    format=\"cast\";",
  3342. +       
  3343. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3344. +            "    channel = \"vocaltrance\","
  3345. +            "    url=\"http://di.fm/mp3/vocaltrance.pls\","
  3346. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3347. +            "    genre=\"Trance\","
  3348. +            "    metaformat=\"%t - %a\","
  3349. +            "    format=\"cast\";",
  3350. +       
  3351. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3352. +            "    channel = \"chillout\","
  3353. +            "    url=\"http://di.fm/mp3/chillout.pls\","
  3354. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3355. +            "    genre=\"Electronica\","
  3356. +            "    metaformat=\"%t - %a\","
  3357. +            "    format=\"cast\";",
  3358. +       
  3359. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3360. +            "    channel = \"house\","
  3361. +            "    url=\"http://di.fm/mp3/house.pls\","
  3362. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3363. +            "    genre=\"Techno\","
  3364. +            "    metaformat=\"%t - %a\","
  3365. +            "    format=\"cast\";",
  3366. +       
  3367. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3368. +            "    channel = \"harddance\","
  3369. +            "    url=\"http://di.fm/mp3/harddance.pls\","
  3370. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3371. +            "    genre=\"Techno\","
  3372. +            "    metaformat=\"%t - %a\","
  3373. +            "    format=\"cast\";",
  3374. +       
  3375. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3376. +            "    channel = \"eurodance\","
  3377. +            "    url=\"http://di.fm/mp3/eurodance.pls\","
  3378. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3379. +            "    genre=\"Techno\","
  3380. +            "    metaformat=\"%t - %a\","
  3381. +            "    format=\"cast\";",
  3382. +       
  3383. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3384. +            "    channel = \"progressive\","
  3385. +            "    url=\"http://di.fm/mp3/progressive.pls\","
  3386. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3387. +            "    genre=\"Techno\","
  3388. +            "    metaformat=\"%t - %a\","
  3389. +            "    format=\"cast\";",
  3390. +       
  3391. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3392. +            "    channel = \"goapsy\","
  3393. +            "    url=\"http://di.fm/mp3/goapsy.pls\","
  3394. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3395. +            "    genre=\"Techno\","
  3396. +            "    metaformat=\"%t - %a\","
  3397. +            "    format=\"cast\";",
  3398. +       
  3399. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3400. +            "    channel = \"hardcore\","
  3401. +            "    url=\"http://di.fm/mp3/hardcore.pls\","
  3402. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3403. +            "    genre=\"\","
  3404. +            "    metaformat=\"%t - %a\","
  3405. +            "    format=\"cast\";",
  3406. +       
  3407. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3408. +            "    channel = \"djmixes\","
  3409. +            "    url=\"http://di.fm/mp3/djmixes.pls\","
  3410. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3411. +            "    genre=\"\","
  3412. +            "    metaformat=\"%t - %a\","
  3413. +            "    format=\"cast\";",
  3414. +       
  3415. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3416. +            "    channel = \"lounge\","
  3417. +            "    url=\"http://di.fm/mp3/lounge.pls\","
  3418. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3419. +            "    genre=\"Lounge\","
  3420. +            "    metaformat=\"%t - %a\","
  3421. +            "    format=\"cast\";",
  3422. +       
  3423. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3424. +            "    channel = \"ambient\","
  3425. +            "    url=\"http://di.fm/mp3/ambient.pls\","
  3426. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3427. +            "    genre=\"\","
  3428. +            "    metaformat=\"%t - %a\","
  3429. +            "    format=\"cast\";",
  3430. +       
  3431. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3432. +            "    channel = \"drumandbass\","
  3433. +            "    url=\"http://di.fm/mp3/drumandbass.pls\","
  3434. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3435. +            "    genre=\"Techno\","
  3436. +            "    metaformat=\"%t - %a\","
  3437. +            "    format=\"cast\";",
  3438. +       
  3439. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3440. +            "    channel = \"classictechno\","
  3441. +            "    url=\"http://di.fm/mp3/classictechno.pls\","
  3442. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3443. +            "    genre=\"Techno\","
  3444. +            "    metaformat=\"%t - %a\","
  3445. +            "    format=\"cast\";",
  3446. +       
  3447. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3448. +            "    channel = \"breaks\","
  3449. +            "    url=\"http://di.fm/mp3/breaks.pls\","
  3450. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3451. +            "    genre=\"Techno\","
  3452. +            "    metaformat=\"%t - %a\","
  3453. +            "    format=\"cast\";",
  3454. +
  3455. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3456. +            "    channel = \"gabber\","
  3457. +            "    url=\"http://di.fm/mp3/gabber.pls\","
  3458. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3459. +            "    genre=\"Techno\","
  3460. +            "    metaformat=\"%t - %a\","
  3461. +            "    format=\"cast\";",
  3462. +       
  3463. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3464. +            "    channel = \"classical\","
  3465. +            "    url=\"http://di.fm/mp3/classical.pls\","
  3466. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3467. +            "    genre=\"Classical\","
  3468. +            "    metaformat=\"%t - %a\","
  3469. +            "    format=\"cast\";",
  3470. +       
  3471. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3472. +            "    channel = \"newage\","
  3473. +            "    url=\"http://di.fm/mp3/newage.pls\","
  3474. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3475. +            "    genre=\"Newage\","
  3476. +            "    metaformat=\"%t - %a\","
  3477. +            "    format=\"cast\";",
  3478. +
  3479. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3480. +            "    channel = \"guitar\","
  3481. +            "    url=\"http://di.fm/mp3/guitar.pls\","
  3482. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3483. +            "    genre=\"Instrumental\","
  3484. +            "    metaformat=\"%t - %a\","
  3485. +            "    format=\"cast\";",
  3486. +
  3487. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3488. +            "    channel = \"smoothjazz\","
  3489. +            "    url=\"http://di.fm/mp3/smoothjazz.pls\","
  3490. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3491. +            "    genre=\"\","
  3492. +            "    metaformat=\"%t - %a\","
  3493. +            "    format=\"cast\";",
  3494. +
  3495. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3496. +            "    channel = \"tophits\","
  3497. +            "    url=\"http://di.fm/mp3/tophits.pls\","
  3498. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3499. +            "    genre=\"Pop\","
  3500. +            "    metaformat=\"%t - %a\","
  3501. +            "    format=\"cast\";",
  3502. +
  3503. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3504. +            "    channel = \"the80s\","
  3505. +            "    url=\"http://di.fm/mp3/the80s.pls\","
  3506. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3507. +            "    genre=\"\","
  3508. +            "    metaformat=\"%t - %a\","
  3509. +            "    format=\"cast\";",
  3510. +
  3511. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3512. +            "    channel = \"rootsreggae\","
  3513. +            "    url=\"http://di.fm/mp3/rootsreggae.pls\","
  3514. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3515. +            "    genre=\"Reggae\","
  3516. +            "    metaformat=\"%t - %a\","
  3517. +            "    format=\"cast\";",
  3518. +
  3519. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3520. +            "    channel = \"hit70s\","
  3521. +            "    url=\"http://di.fm/mp3/hit70s.pls\","
  3522. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3523. +            "    genre=\"\","
  3524. +            "    metaformat=\"%t - %a\","
  3525. +            "    format=\"cast\";",
  3526. +
  3527. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3528. +            "    channel = \"country\","
  3529. +            "    url=\"http://di.fm/mp3/country.pls\","
  3530. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3531. +            "    genre=\"Country\","
  3532. +            "    metaformat=\"%t - %a\","
  3533. +            "    format=\"cast\";",
  3534. +
  3535. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3536. +            "    channel = \"jazz\","
  3537. +            "    url=\"http://di.fm/mp3/jazz.pls\","
  3538. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3539. +            "    genre=\"Jazz\","
  3540. +            "    metaformat=\"%t - %a\","
  3541. +            "    format=\"cast\";",
  3542. +
  3543. +            "INSERT INTO music_radios SET station = \"d.i.\", "
  3544. +            "    channel = \"salsa\","
  3545. +            "    url=\"http://di.fm/mp3/salsa.pls\","
  3546. +            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
  3547. +            "    genre=\"Salsa\","
  3548. +            "    metaformat=\"%t - %a\","
  3549. +            "    format=\"cast\";",
  3550. +
  3551. +            ""
  3552. +        };
  3553. +        //performActualUpdate(updates, "1008", dbver);
  3554. +        RadioPerformActualUpdate(updates, "1", dbver);
  3555. +    }
  3556. +    if (dbver == "1") {
  3557. +        const QString updates[] = {
  3558. +            "ALTER TABLE music_radios ADD logocached BLOB;",
  3559. +            ""
  3560. +        };
  3561. +        RadioPerformActualUpdate(updates, "2", dbver);
  3562. +    }
  3563. +}
  3564. +//----------------------------------------------------------------------------
  3565. +
  3566.  static bool UpdateDBVersionNumber(const QString &newnumber)
  3567. -{   
  3568. +{
  3569. +    MSqlQuery query(MSqlQuery::InitCon());
  3570.  
  3571.      if (!gContext->SaveSettingOnHost("MusicDBSchemaVer",newnumber,NULL))
  3572.      {   
  3573. @@ -67,6 +437,9 @@
  3574.  bool UpgradeMusicDatabaseSchema(void)
  3575.  {
  3576.      QString dbver = gContext->GetSetting("MusicDBSchemaVer");
  3577. +
  3578. +    //Once in the main trun, the radioupgrade code back in here...
  3579. +    RadioUpgrade();
  3580.      
  3581.      if (dbver == currentDatabaseVersion)
  3582.          return true;
  3583. @@ -351,7 +724,6 @@
  3584.              return false;
  3585.      }
  3586.  
  3587. -
  3588.      if (dbver == "1005")
  3589.      {
  3590.          const QString updates[] = {
  3591. @@ -617,6 +989,5 @@
  3592.  //"DROP TABLE musicmetadata;",
  3593.  //"DROP TABLE musicplaylist;",
  3594.  
  3595. -
  3596.      return true;
  3597.  }
  3598. Index: mythplugins/mythmusic/mythmusic/metadata.h
  3599. ===================================================================
  3600. --- mythplugins/mythmusic/mythmusic/metadata.h  (revision 13855)
  3601. +++ mythplugins/mythmusic/mythmusic/metadata.h  (working copy)
  3602. @@ -108,8 +108,9 @@
  3603.  
  3604.      QString FormatArtist();
  3605.      QString FormatTitle();
  3606. +       QString FormatInfo();
  3607.  
  3608. -    QString Genre() { return m_genre; }
  3609. +    QString Genre() const { return m_genre; }
  3610.      void setGenre(const QString &lgenre) { m_genre = lgenre; }
  3611.  
  3612.      void setDirectoryId(int ldirectoryid) { m_directoryid = ldirectoryid; }
  3613. @@ -145,6 +146,8 @@
  3614.      QString Format() const { return m_format; }
  3615.      void setFormat(const QString &lformat) { m_format = lformat; }
  3616.  
  3617. +       void setAlbumArt(const QByteArray &data);
  3618. +
  3619.      int Rating() const { return m_rating; }
  3620.      void decRating();
  3621.      void incRating();
  3622. @@ -172,13 +175,16 @@
  3623.  
  3624.      bool isInDatabase(void);
  3625.      void dumpToDatabase(void);
  3626. +       void removeFromDatabase(void);
  3627.      void setField(const QString &field, const QString &data);
  3628.      void getField(const QString& field, QString *data);
  3629.      void persist();
  3630.      bool hasChanged() {return m_changed;}
  3631.      int compare (Metadata *other);
  3632. +
  3633. +    MythThemedDialog *createEditorDialog (void);
  3634. +
  3635.      static void setArtistAndTrackFormats();
  3636. -
  3637.      static void SetStartdir(const QString &dir);
  3638.      static QString GetStartdir() { return m_startdir; }
  3639.  
  3640. @@ -231,6 +237,13 @@
  3641.      static QString m_formatcompilationfiletrack;
  3642.      static QString m_formatcompilationcdartist;
  3643.      static QString m_formatcompilationcdtrack;
  3644. +
  3645. +    /*note:temporary fix for radio. These should be split out by subclassing Metadata */
  3646. +    int compare_cast (Metadata *other);
  3647. +    MythThemedDialog *createEditorDialog_cast (void);
  3648. +    void dumpToDatabase_cast();
  3649. +    void updateDatabase_cast();
  3650. +    void removeFromDatabase_cast();
  3651.  };
  3652.  
  3653.  bool operator==(const Metadata& a, const Metadata& b);
  3654. @@ -332,13 +345,16 @@
  3655.  
  3656.      QString     getLabel(int an_id, bool *error_flag);
  3657.      Metadata*   getMetadata(int an_id);
  3658. -    bool        updateMetadata(int an_id, Metadata *the_track);
  3659. +    Metadata*   getRadioMetadata(int an_id); /*note:temporary fix for radio*/
  3660. +    bool        updateMetadata(int repo_id, int an_id, Metadata *the_track);
  3661.      int         count() const { return m_numPcs; }
  3662.      int         countLoaded() const { return m_numLoaded; }
  3663.      void        save();
  3664.      bool        startLoading(void);
  3665.      void        resync();   //  After a CD rip, for example
  3666. +    void        resync_radios(void); /*note:temporary fix for radio*/
  3667.      void        clearCDData();
  3668. +    void        addRadioTrack(Metadata *the_track); /*note:temporary fix for radio*/
  3669.      void        addCDTrack(Metadata *the_track);
  3670.      bool        checkCDTrack(Metadata *the_track);
  3671.      bool        getCDMetadata(int m_the_track, Metadata *some_metadata);
  3672. @@ -361,6 +377,8 @@
  3673.    private:
  3674.    
  3675.      MetadataPtrList     m_all_music;
  3676. +    MetadataPtrList     m_all_radios; /*note:temporary fix for radio*/
  3677. +    QMap<int,Metadata*> m_radio_map;  /*note:temporary fix for radio*/
  3678.      MusicNode           *m_root_node;
  3679.      
  3680.      int m_numPcs;
  3681. Index: mythplugins/mythmusic/mythmusic/pls.cpp
  3682. ===================================================================
  3683. --- mythplugins/mythmusic/mythmusic/pls.cpp     (revision 0)
  3684. +++ mythplugins/mythmusic/mythmusic/pls.cpp     (revision 0)
  3685. @@ -0,0 +1,406 @@
  3686. +/*
  3687. +  playlistfile (.pls) parser
  3688. +  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
  3689. +*/
  3690. +
  3691. +#include "pls.h"
  3692. +
  3693. +#include <qpair.h>
  3694. +#include <qvaluelist.h>
  3695. +#include <qmap.h>
  3696. +
  3697. +#include <assert.h>
  3698. +
  3699. +using namespace std;
  3700. +
  3701. +class CfgReader
  3702. +{
  3703. +  public:
  3704. +    CfgReader()
  3705. +    {
  3706. +    }
  3707. +    ~CfgReader()
  3708. +    {
  3709. +    }
  3710. +
  3711. +    typedef QPair<QString,QString> KeyValue;
  3712. +    typedef QValueList<KeyValue> KeyValueList;
  3713. +    typedef QMap<QString, KeyValueList> ConfigMap;
  3714. +
  3715. +    void parse(const char *d, int l)
  3716. +    {
  3717. +        const char *ptr = d;
  3718. +        int line = 1;
  3719. +        bool done = l <= 0;
  3720. +
  3721. +        QString current_section = "";
  3722. +        KeyValueList keyvals;
  3723. +
  3724. +        while(!done)
  3725. +        {
  3726. +            switch(*ptr)
  3727. +            {
  3728. +            case '\0':
  3729. +                done = true;
  3730. +                break;
  3731. +            case '#':
  3732. +            {
  3733. +                char *end = strchr(ptr, '\n');
  3734. +                if (!end) done = true;
  3735. +                ptr = end;
  3736. +                break;
  3737. +            }
  3738. +            case '\n':
  3739. +                ptr ++;
  3740. +                line ++;
  3741. +                break;
  3742. +            case '[':
  3743. +            {
  3744. +                ptr ++;
  3745. +                const char *nl = strchr(ptr, '\n');
  3746. +                const char *end = strchr(ptr, ']');
  3747. +
  3748. +                if (!nl) nl = d + l;
  3749. +
  3750. +                if (!end || nl < end)
  3751. +                {
  3752. +                    fprintf(stderr, "Badly formatted section, line %d\n", line);
  3753. +                    done = true;
  3754. +                }
  3755. +
  3756. +                if (current_section.length() > 0)
  3757. +                {
  3758. +                    cfg[current_section] = keyvals;
  3759. +                    keyvals = KeyValueList();
  3760. +                }
  3761. +
  3762. +                current_section = std::string(ptr, end - ptr);
  3763. +                if (current_section.length() == 0)
  3764. +                {
  3765. +                    fprintf(stderr, "Badly formatted section, line %d\n", line);
  3766. +                    done = true;
  3767. +                }
  3768. +                ptr = end + 1;
  3769. +                break;
  3770. +            }
  3771. +            default:
  3772. +                if (current_section.length() > 0)
  3773. +                {
  3774. +                    const char *eq = strchr(ptr, '=');
  3775. +                    const char *nl = strchr(ptr, '\n');
  3776. +
  3777. +                    if (!nl) nl = d + l;
  3778. +
  3779. +                    if (!eq || nl < eq)
  3780. +                    {
  3781. +                        fprintf(stderr, "Badly formatted line %d\n", line);
  3782. +                        done = true;
  3783. +                    }
  3784. +                    else
  3785. +                    {
  3786. +                        QString key = string(ptr, eq - ptr);
  3787. +                        QString val = string(eq + 1, nl - eq - 1);
  3788. +                        keyvals.push_back(KeyValue(key, val));
  3789. +                        ptr = nl;
  3790. +                    }
  3791. +                }
  3792. +                else
  3793. +                {
  3794. +                    fprintf(stderr, "Badly formatted line %d\n", line);
  3795. +                    done = true;
  3796. +                }
  3797. +                break;
  3798. +            }
  3799. +
  3800. +            if (ptr - d == l)
  3801. +                done = true;
  3802. +        }
  3803. +
  3804. +        if (current_section.length() > 0)
  3805. +            cfg[current_section] = keyvals;
  3806. +    }
  3807. +
  3808. +    QValueList<QString> getSections(void)
  3809. +    {
  3810. +        QValueList<QString> res;
  3811. +        for (ConfigMap::iterator it = cfg.begin(); it != cfg.end(); it++)
  3812. +            res.push_back(it.key());
  3813. +        return res;
  3814. +    }
  3815. +
  3816. +    QValueList<QString> getKeys(const QString &section)
  3817. +    {
  3818. +        KeyValueList keylist = cfg[section];
  3819. +        QValueList<QString> res;
  3820. +        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++)
  3821. +            res.push_back((*it).first);
  3822. +        return res;
  3823. +    }
  3824. +
  3825. +    QString getStrVal(const QString &section, const QString &key, const QString &def="")
  3826. +    {
  3827. +        KeyValueList keylist = cfg[section];
  3828. +        QString res = def;
  3829. +        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++)
  3830. +        {
  3831. +            if ((*it).first == key)
  3832. +            {
  3833. +                res =(*it).second;
  3834. +                break;
  3835. +            }
  3836. +        }
  3837. +        return res;
  3838. +    }
  3839. +
  3840. +    int getIntVal(const QString &section, const QString &key, int def=0)
  3841. +    {
  3842. +        QString def_str;
  3843. +        def_str.setNum (def);
  3844. +        return getStrVal(section, key, def_str).toInt();
  3845. +    }
  3846. +   
  3847. +    // very simple unit test, only tests parsing a wellformed file...
  3848. +    static void test(void)
  3849. +    {
  3850. +        const char *sample1 =
  3851. +            "# Sample config file\n"
  3852. +            "\n"
  3853. +            "[foo]\n"
  3854. +            "key1=value1\n"
  3855. +            "key2=value2\n"
  3856. +            "key3=value3\n"
  3857. +            "\n"
  3858. +            "key4=value4\n"
  3859. +            "[bar]\n"
  3860. +            "key5=value5\n"
  3861. +            "key6=value5\n"
  3862. +            "key7=7";
  3863. +       
  3864. +        CfgReader cfg;
  3865. +        cfg.parse(sample1, strlen(sample1));
  3866. +        QValueList<QString> sections = cfg.getSections();
  3867. +        QValueList<QString> keys;
  3868. +        QValueList<QString>::iterator key_it;
  3869. +        QValueList<QString>::iterator section_it;
  3870. +        section_it = sections.begin();
  3871. +
  3872. +        assert((*section_it) == "bar");
  3873. +        keys = cfg.getKeys((*section_it));
  3874. +        key_it = keys.begin();
  3875. +        assert((*key_it) == "key5");
  3876. +        assert(cfg.getStrVal(*section_it, *key_it) == "value5");
  3877. +        key_it++;
  3878. +        assert((*key_it) == "key6");
  3879. +        assert(cfg.getStrVal(*section_it, *key_it) == "value5");
  3880. +        key_it++;
  3881. +        assert((*key_it) == "key7");
  3882. +        assert(cfg.getIntVal(*section_it, *key_it) == 7);
  3883. +        key_it++;
  3884. +        assert(key_it == keys.end());
  3885. +        section_it++;
  3886. +
  3887. +        assert((*section_it) == "foo");
  3888. +        keys = cfg.getKeys((*section_it));
  3889. +        key_it = keys.begin();
  3890. +        assert((*key_it) == "key1");
  3891. +        assert(cfg.getStrVal(*section_it, *key_it) == "value1");
  3892. +        key_it++;
  3893. +        assert((*key_it) == "key2");
  3894. +        assert(cfg.getStrVal(*section_it, *key_it) == "value2");
  3895. +        key_it++;
  3896. +        assert((*key_it) == "key3");
  3897. +        assert(cfg.getStrVal(*section_it, *key_it) == "value3");
  3898. +        key_it++;
  3899. +        assert((*key_it) == "key4");
  3900. +        assert(cfg.getStrVal(*section_it, *key_it) == "value4");
  3901. +        key_it++;
  3902. +        assert(key_it == keys.end());
  3903. +        section_it++;
  3904. +
  3905. +        assert(section_it == sections.end());
  3906. +    }
  3907. +
  3908. +  private:
  3909. +    ConfigMap cfg;
  3910. +};
  3911. +
  3912. +/****************************************************************************/
  3913. +
  3914. +PlayListFile::PlayListFile()
  3915. +{
  3916. +    entries.setAutoDelete(true);
  3917. +}
  3918. +
  3919. +PlayListFile::~PlayListFile()
  3920. +{
  3921. +}
  3922. +
  3923. +int PlayListFile::parse(PlayListFile *pls, QTextStream *stream)
  3924. +{
  3925. +    int parsed = 0;
  3926. +    QString d = stream->read();
  3927. +    CfgReader cfg;
  3928. +    cfg.parse(d.ascii(), d.length());   
  3929. +   
  3930. +    int num_entries = cfg.getIntVal("playlist", "numberofentries", -1);
  3931. +
  3932. +    // Some pls files have "numberofentries", some has "NumberOfEntries".
  3933. +    if (num_entries == -1)
  3934. +        num_entries = cfg.getIntVal("playlist", "NumberOfEntries", -1);
  3935. +
  3936. +    for (int n = 1; n <= num_entries; n++)
  3937. +    {
  3938. +        PlayListFileEntry *e = new PlayListFileEntry();
  3939. +        QString t_key = QString("Title%1").arg(n);
  3940. +        QString f_key = QString("File%1").arg(n);
  3941. +        QString l_key = QString("Length%1").arg(n);
  3942. +
  3943. +        e->setFile(cfg.getStrVal("playlist", f_key));
  3944. +        e->setTitle(cfg.getStrVal("playlist", t_key));
  3945. +        e->setLength(cfg.getIntVal("playlist", l_key));
  3946. +
  3947. +        pls->add(e);
  3948. +        parsed++;
  3949. +    }
  3950. +
  3951. +    return parsed;
  3952. +}
  3953. +
  3954. +void PlayListFile::test(void)
  3955. +{
  3956. +    CfgReader::test();
  3957. +
  3958. +    {
  3959. +        // test reading an empty string
  3960. +        PlayListFile parser;
  3961. +        QString a1 = "";
  3962. +        QTextIStream aa (&a1);
  3963. +        assert (PlayListFile::parse (&parser, &aa) == 0);
  3964. +    }
  3965. +    {
  3966. +        // test reading an empty playlist
  3967. +        PlayListFile parser;
  3968. +        QString b1 = "[playlist]\n";
  3969. +        b1 += "numberofentries=0\n";
  3970. +        b1 += "version=2\n";
  3971. +        QTextIStream bb (&b1);
  3972. +        assert (parser.PlayListFile::parse(&parser, &bb) == 0);
  3973. +    }
  3974. +    {
  3975. +        // test reading a playlist w/1 item
  3976. +        PlayListFile parser;
  3977. +        QString c1 = "[playlist]\n";
  3978. +        c1 += "numberofentries=1\n";
  3979. +        c1 += "File1=file_1\n";
  3980. +        c1 += "Title1=title 1\n";
  3981. +        c1 += "Length1=1\n";
  3982. +        c1 += "version=2\n";
  3983. +        QTextIStream cc (&c1);
  3984. +        assert (PlayListFile::parse(&parser, &cc) == 1);
  3985. +        assert (parser.get (0)->File () == "file_1");
  3986. +        assert (parser.get (0)->Title () == "title 1");
  3987. +        assert (parser.get (0)->Length () == 1);
  3988. +        assert (parser.get (1) == 0);
  3989. +    }
  3990. +
  3991. +    {
  3992. +        // test reading a playlist w/1 item but more after first item
  3993. +        PlayListFile parser;
  3994. +        QString d1 = "[playlist]\n";
  3995. +        d1 += "numberofentries=1\n";
  3996. +        d1 += "File1=file_1\n";
  3997. +        d1 += "Title1=title 1\n";
  3998. +        d1 += "Length1=1\n";
  3999. +        d1 += "File2=file_2\n";
  4000. +        d1 += "Title2=title 2\n";
  4001. +        d1 += "Length2=2\n";
  4002. +        d1 += "File3=file_3\n";
  4003. +        d1 += "Title3=title 3\n";
  4004. +        d1 += "Length3=3\n";
  4005. +        d1 += "version=2\n";
  4006. +        QTextIStream dd (&d1);
  4007. +        assert (PlayListFile::parse(&parser, &dd) == 1);
  4008. +        assert (parser.get (0)->File () == "file_1");
  4009. +        assert (parser.get (0)->Title () == "title 1");
  4010. +        assert (parser.get (0)->Length () == 1);
  4011. +        assert (parser.get (1) == 0);
  4012. +    }
  4013. +
  4014. +    {
  4015. +        // test reading a playlist w/3 items
  4016. +        PlayListFile parser;
  4017. +        QString e1 = "[playlist]\n";
  4018. +        e1 += "numberofentries=3\n";
  4019. +        e1 += "File1=file_1\n";
  4020. +        e1 += "Title1=title 1\n";
  4021. +        e1 += "Length1=1\n";
  4022. +        e1 += "File2=file_2\n";
  4023. +        e1 += "Title2=title 2\n";
  4024. +        e1 += "Length2=2\n";
  4025. +        e1 += "File3=file_3\n";
  4026. +        e1 += "Title3=title 3\n";
  4027. +        e1 += "Length3=3\n";
  4028. +        e1 += "version=2\n";
  4029. +        QTextIStream ee (&e1);
  4030. +        assert (PlayListFile::parse(&parser, &ee) == 3);
  4031. +        assert (parser.get (0)->File () == "file_1");
  4032. +        assert (parser.get (0)->Title () == "title 1");
  4033. +        assert (parser.get (0)->Length () == 1);
  4034. +        assert (parser.get (1)->File () == "file_2");
  4035. +        assert (parser.get (1)->Title () == "title 2");
  4036. +        assert (parser.get (1)->Length () == 2);
  4037. +        assert (parser.get (2)->File () == "file_3");
  4038. +        assert (parser.get (2)->Title () == "title 3");
  4039. +        assert (parser.get (2)->Length () == 3);
  4040. +        assert (parser.get (3) == 0);
  4041. +    }
  4042. +
  4043. +    {
  4044. +        // test reading a playlist w/2 items but more after second
  4045. +        PlayListFile parser;
  4046. +        QString f1 = "[playlist]\n";
  4047. +        f1 += "numberofentries=2\n";
  4048. +        f1 += "File1=file_1\n";
  4049. +        f1 += "Title1=title 1\n";
  4050. +        f1 += "Length1=1\n";
  4051. +        f1 += "File2=file_2\n";
  4052. +        f1 += "Title2=title 2\n";
  4053. +        f1 += "Length2=2\n";
  4054. +        f1 += "File3=file_3\n";
  4055. +        f1 += "Title3=title 3\n";
  4056. +        f1 += "Length3=3\n";
  4057. +        f1 += "version=2\n";
  4058. +        QTextIStream ff (&f1);
  4059. +        assert (PlayListFile::parse(&parser, &ff) == 2);
  4060. +        assert (parser.get (0)->File () == "file_1");
  4061. +        assert (parser.get (0)->Title () == "title 1");
  4062. +        assert (parser.get (0)->Length () == 1);
  4063. +        assert (parser.get (1)->File () == "file_2");
  4064. +        assert (parser.get (1)->Title () == "title 2");
  4065. +        assert (parser.get (1)->Length () == 2);
  4066. +        assert (parser.get (2) == 0);
  4067. +    }
  4068. +    {
  4069. +        PlayListFile parser;
  4070. +        QString f1 = "[playlist]\n";
  4071. +        f1 += "NumberOfEntries=2\n";
  4072. +        f1 += "File1=http://64.236.34.97:80/stream/1035\n";
  4073. +        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";
  4074. +        f1 += "Length1=-1\n";
  4075. +        f1 += "File2=http://64.236.34.97:5190/stream/1035\n";
  4076. +        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";
  4077. +        f1 += "Length2=-1\n";
  4078. +        f1 += "Version=2\n";
  4079. +        QTextIStream ff (&f1);
  4080. +        assert (PlayListFile::parse(&parser, &ff) == 2);
  4081. +        assert (parser.size () == 2);
  4082. +        assert (parser.get (0)->File () == "http://64.236.34.97:80/stream/1035");
  4083. +        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!");
  4084. +        assert (parser.get (0)->Length () == -1);
  4085. +        assert (parser.get (1)->File () == "http://64.236.34.97:5190/stream/1035");
  4086. +        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!");
  4087. +        assert (parser.get (1)->Length () == -1);
  4088. +        assert (parser.get (2) == 0);
  4089. +    }
  4090. +}
  4091. +
  4092. Index: mythplugins/mythmusic/mythmusic/inlines.h
  4093. ===================================================================
  4094. --- mythplugins/mythmusic/mythmusic/inlines.h   (revision 13855)
  4095. +++ mythplugins/mythmusic/mythmusic/inlines.h   (working copy)
  4096. @@ -11,6 +11,64 @@
  4097.  
  4098.  // *fast* convenience functions
  4099.  
  4100. +static inline void smoothen_mono(short *in_data, short *out_data, int len, int new_len)
  4101. +{
  4102. +    int step = new_len/len;
  4103. +    int dii = 0;
  4104. +    for (int ii = 0; ii < len; ii++)           
  4105. +    {
  4106. +        out_data[dii] = in_data[ii];
  4107. +       
  4108. +        short smooth = 0;
  4109. +
  4110. +        if (ii < len-1)
  4111. +            smooth = in_data[ii+1] - in_data[ii];
  4112. +
  4113. +        if (smooth)
  4114. +            smooth /= step;
  4115. +       
  4116. +        for (int cc = 1; cc < step; cc++) {
  4117. +            out_data[dii+cc] = out_data[dii+cc-1] + smooth;
  4118. +        }
  4119. +       
  4120. +        dii += step;
  4121. +    }
  4122. +}
  4123. +
  4124. +static inline void smoothen_stereo(short *l_in_data, short *l_out_data,
  4125. +                                   short *r_in_data, short *r_out_data,
  4126. +                                   int len, int new_len)
  4127. +{
  4128. +    int step = new_len/len;
  4129. +    int dii = 0;
  4130. +    for (int ii = 0; ii < len; ii++)           
  4131. +    {
  4132. +        l_out_data[dii] = l_in_data[ii];
  4133. +        r_out_data[dii] = r_in_data[ii];
  4134. +       
  4135. +        short l_smooth = 0;
  4136. +        short r_smooth = 0;
  4137. +
  4138. +        if (ii < len-1) {
  4139. +            l_smooth = l_in_data[ii+1] - l_in_data[ii];
  4140. +            r_smooth = r_in_data[ii+1] - r_in_data[ii];
  4141. +        }
  4142. +
  4143. +        if (l_smooth)
  4144. +            l_smooth /= step;
  4145. +       
  4146. +        if (r_smooth)
  4147. +            r_smooth /= step;
  4148. +       
  4149. +        for (int cc = 1; cc < step; cc++) {
  4150. +            l_out_data[dii+cc] = l_out_data[dii+cc-1] + l_smooth;
  4151. +            r_out_data[dii+cc] = r_out_data[dii+cc-1] + r_smooth;
  4152. +        }
  4153. +       
  4154. +        dii += step;
  4155. +    }
  4156. +}
  4157. +
  4158.  static inline void stereo16_from_stereopcm8(register short *l,
  4159.                                         register short *r,
  4160.                                         register uchar *c,
  4161. Index: mythplugins/mythmusic/mythmusic/shoutcast.h
  4162. ===================================================================
  4163. --- mythplugins/mythmusic/mythmusic/shoutcast.h (revision 0)
  4164. +++ mythplugins/mythmusic/mythmusic/shoutcast.h (revision 0)
  4165. @@ -0,0 +1,153 @@
  4166. +/*
  4167. +  Shoutcast decoder for MythTV.
  4168. +  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
  4169. +*/
  4170. +
  4171. +#ifndef SHOUTCAST_H_
  4172. +#define SHOUTCAST_H_
  4173. +
  4174. +#include "config.h"
  4175. +#include "decoder.h"
  4176. +#include "decoderhandler.h"
  4177. +
  4178. +#include <qobject.h>
  4179. +#include <qsocketdevice.h>
  4180. +#include <qbuffer.h>
  4181. +#include <qurl.h>
  4182. +#include <qdns.h>
  4183. +#include <qmutex.h>
  4184. +
  4185. +#include <mythtv/httpcomms.h>
  4186. +
  4187. +#include <sys/time.h>
  4188. +#include <time.h>
  4189. +
  4190. +
  4191. +class ShoutCastRequest;
  4192. +class ShoutCastResponse;
  4193. +class ShoutCastLogoGrabber;
  4194. +class ShoutCastBuffer;
  4195. +
  4196. +typedef QMap<QString,QString> ShoutCastMetaMap;
  4197. +
  4198. +class ShoutCastIODevice : public QObject, public QIODevice {
  4199. +    Q_OBJECT
  4200. +public:
  4201. +       enum State {         
  4202. +        NOT_CONNECTED,
  4203. +        RESOLVING,
  4204. +        CONNECTING,
  4205. +        CANT_RESOLVE,
  4206. +        CANT_CONNECT,
  4207. +              CONNECTED,
  4208. +        WRITING_HEADER,
  4209. +        READING_HEADER,
  4210. +              PLAYING,
  4211. +        STREAMING,
  4212. +        STREAMING_META,
  4213. +        STOPPED
  4214. +       };
  4215. +    static const char* stateString (const State &s);
  4216. +
  4217. +    ShoutCastIODevice ();
  4218. +    ~ShoutCastIODevice ();
  4219. +
  4220. +       void connectToUrl (const QUrl &url);
  4221. +    bool open(int);
  4222. +    void close();
  4223. +    void flush();
  4224. +
  4225. +    Q_ULONG size() const;
  4226. +    Offset at () const { return 0; }
  4227. +    bool at (Offset) { return false; }
  4228. +       Q_ULONG bytesAvailable () const;
  4229. +
  4230. +    Q_LONG readBlock(char *data, Q_ULONG sz);
  4231. +    Q_LONG writeBlock(const char *data, Q_ULONG sz);
  4232. +   
  4233. +    int getch();
  4234. +    int putch(int c);
  4235. +    int ungetch(int);
  4236. +
  4237. +    bool getResponse(ShoutCastResponse &response);
  4238. +
  4239. +signals:
  4240. +    void meta(const QString &metadata);
  4241. +    void changedState(ShoutCastIODevice::State newstate);
  4242. +
  4243. +private slots:
  4244. +       void socketHostFound ();
  4245. +       void socketConnected ();
  4246. +       void socketConnectionClosed ();
  4247. +       void socketReadyRead ();
  4248. +       void socketBytesWritten (int);
  4249. +       void socketError (int);
  4250. +
  4251. +private:
  4252. +    void switchToState(const State &s);
  4253. +    int parseHeader(const char *data, Q_ULONG len);
  4254. +    int parseStream(char *data, Q_ULONG maxlen);
  4255. +    bool parseMeta(void);
  4256. +       void doKbPerSecond(int bytes_read);
  4257. +       bool waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed = 1);
  4258. +
  4259. +       // Our tools
  4260. +    ShoutCastBuffer *m_buffer;
  4261. +       ShoutCastResponse *m_response;
  4262. +       int m_redirects;
  4263. +    QSocket *m_socket;
  4264. +
  4265. +       // Our scratchpad
  4266. +       QByteArray m_scratchpad;
  4267. +       Q_ULONG m_scratchpad_pos;
  4268. +
  4269. +       // Our state info
  4270. +       QUrl m_url;
  4271. +       Q_ULONG m_bytes_till_next_meta;
  4272. +       State m_state;
  4273. +       QString m_last_metadata;
  4274. +       struct timeval m_sample_tv;
  4275. +       uint m_bytes_downloaded;
  4276. +       bool m_response_gotten;
  4277. +
  4278. +       //
  4279. +    QWaitCondition m_cond;
  4280. +};
  4281. +
  4282. +class DecoderIOFactoryShoutCast : public DecoderIOFactory
  4283. +{
  4284. +    Q_OBJECT
  4285. +  public:
  4286. +       DecoderIOFactoryShoutCast(DecoderHandler *parent);
  4287. +    ~DecoderIOFactoryShoutCast();
  4288. +
  4289. +    void start();
  4290. +    void stop();
  4291. +    QIODevice *takeInput(void);       
  4292. +
  4293. +  protected slots:
  4294. +       void periodicallyCheckResponse(void);
  4295. +       void periodicallyCheckBuffered(void);
  4296. +    void shoutcastMeta(const QString &metadata);
  4297. +    void shoutcastChangedState(ShoutCastIODevice::State newstate);
  4298. +       void checkLogoGrabber ();
  4299. +
  4300. +  private:
  4301. +       void socketConnected(void);
  4302. +       void socketClosed(void);
  4303. +       void socketError(int);
  4304. +
  4305. +       int checkResponseOK();
  4306. +       
  4307. +       void makeIODevice();
  4308. +       void closeIODevice();
  4309. +    QTimer *m_timer;
  4310. +
  4311. +       ShoutCastIODevice *m_input;
  4312. +       uint m_prebuffer;
  4313. +       
  4314. +       HttpComms m_logo_grabber;
  4315. +    QTimer *m_logo_grabber_timer;
  4316. +};
  4317. +
  4318. +#endif /* SHOUTCAST_H_ */
  4319. Index: mythplugins/mythmusic/mythmusic/pls.h
  4320. ===================================================================
  4321. --- mythplugins/mythmusic/mythmusic/pls.h       (revision 0)
  4322. +++ mythplugins/mythmusic/mythmusic/pls.h       (revision 0)
  4323. @@ -0,0 +1,89 @@
  4324. +/*
  4325. +  playlistfile (.pls) parser
  4326. +  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
  4327. +*/
  4328. +
  4329. +#ifndef PLS_H_
  4330. +#define PLS_H_
  4331. +
  4332. +#include <qstring.h>
  4333. +#include <qbuffer.h>
  4334. +#include <qptrlist.h>
  4335. +#include <qtextstream.h>
  4336. +
  4337. +/** \brief Class for representing entries in a pls file
  4338. + */
  4339. +class PlayListFileEntry
  4340. +{
  4341. +  public:
  4342. +    PlayListFileEntry() {}
  4343. +    ~PlayListFileEntry() {}
  4344. +
  4345. +    QString File(void) { return file; }
  4346. +    QString Title(void) { return title; }
  4347. +    int Length(void) { return length; }
  4348. +
  4349. +    void setFile(const QString &f) { file = f; }
  4350. +    void setTitle(const QString &t) { title = t; }
  4351. +    void setLength(int l) { length = l; }
  4352. +
  4353. +  private: 
  4354. +    QString file;
  4355. +    QString title;
  4356. +    int length;
  4357. +};
  4358. +
  4359. +/** \brief Class for containing the info of a pls file
  4360. + */
  4361. +class PlayListFile
  4362. +{
  4363. +public:
  4364. +    PlayListFile ();
  4365. +    ~PlayListFile ();
  4366. +
  4367. +    /** \brief Get the number of entries in the pls file
  4368. +
  4369. +        This returns the number of actual parsed entries, not the
  4370. +        <tt>numberofentries</tt> field.
  4371. +
  4372. +        \returns the number of entries
  4373. +    */
  4374. +    int size(void) { return entries.count(); }
  4375. +
  4376. +    /** \brief Get a pls file entry
  4377. +        \param i which entry to get, between 0 and the value returned by calling \p PlayListParser::size
  4378. +        \returns a pointer to a \p PlayListEntry
  4379. +    */
  4380. +    PlayListFileEntry* get(int i) { return entries.at(i); }
  4381. +
  4382. +    /** \brief Version of the parsed pls file
  4383. +
  4384. +        Returns the version number specified in the <tt>version</tt>
  4385. +        field of the pls file.
  4386. +
  4387. +        \returns the version number
  4388. +     */
  4389. +    int version(void) { return _version ; }
  4390. +
  4391. +    /** Add a entry to the playlist
  4392. +        \param e a \p PlayListFileEntry object
  4393. +     */
  4394. +    void add(PlayListFileEntry *e) { entries.append(e); }
  4395. +
  4396. +    /** Clear out all the entries */
  4397. +    void clear(void) { entries.clear(); }
  4398. +
  4399. +    /** Perform internal unittest */
  4400. +    static void test(void);
  4401. +
  4402. +    /** \brief Parse a pls file.
  4403. +        \param stream the playlist file in a \p QTextStream
  4404. +        \returns the number of entries parsed
  4405. +    */
  4406. +    static int parse(PlayListFile *pls, QTextStream *stream);
  4407. +  private:
  4408. +    QPtrList<PlayListFileEntry> entries;
  4409. +    int _version;
  4410. +};
  4411. +
  4412. +#endif /* PLS_H_ */
  4413. Index: mythplugins/mythmusic/mythmusic/playbackbox.h
  4414. ===================================================================
  4415. --- mythplugins/mythmusic/mythmusic/playbackbox.h       (revision 13855)
  4416. +++ mythplugins/mythmusic/mythmusic/playbackbox.h       (working copy)
  4417. @@ -12,11 +12,11 @@
  4418.  #include "mainvisual.h"
  4419.  #include "metadata.h"
  4420.  #include "playlist.h"
  4421. -#include "editmetadata.h"
  4422.  #include "databasebox.h"
  4423.  
  4424.  class Output;
  4425.  class Decoder;
  4426. +class DecoderHandler;
  4427.  
  4428.  class PlaybackBoxMusic : public MythThemedDialog
  4429.  {
  4430. @@ -81,6 +81,8 @@
  4431.  
  4432.      void occasionallyCheckCD();
  4433.  
  4434. +    void operationProgressTimer();
  4435. +
  4436.      // popup menu
  4437.      void showMenu();
  4438.      void closePlaylistPopup();
  4439. @@ -92,6 +94,8 @@
  4440.      void fromCD();
  4441.      void showSmartPlaylistDialog();
  4442.      void showSearchDialog();
  4443. +       void showAddRadioStationDialog();
  4444. +       void showRemoveRadioStationDialog();
  4445.      bool getInsertPLOptions(InsertPLOption &insertOption,
  4446.                              PlayPLOption &playOption, bool &bRemoveDups);
  4447.  
  4448. @@ -107,9 +111,10 @@
  4449.      void doUpdatePlaylist(QString whereClause);
  4450.      void CycleVisualizer(void);
  4451.      void updatePlaylistFromCD(void);
  4452. -    void setTrackOnLCD(Metadata *mdata);
  4453. +    void setTrackOnLCD(const Metadata *mdata);
  4454.      void updateTrackInfo(Metadata *mdata);
  4455.      void openOutputDevice(void);
  4456. +    void setupDecoderHandler(void);
  4457.      void postUpdate();
  4458.      void playFirstTrack();
  4459.      void bannerEnable(QString text, int millis);
  4460. @@ -117,12 +122,22 @@
  4461.      void bannerToggle(Metadata *mdata);
  4462.      void savePosition(uint position);
  4463.      void restorePosition(void);
  4464. +    void addRadioMenuEntries();
  4465.  
  4466. +    void decoderHandlerReady(void);
  4467. +    void decoderHandlerInfo(const QString&, const QString&);
  4468. +    void decoderHandlerOperationStart(const QString &);
  4469. +    void decoderHandlerOperationStop();
  4470. +   
  4471. +    QString operation_name;
  4472. +    int operation_progress_count;
  4473. +    QTimer  *decoder_handler_progress_timer;
  4474. +
  4475.      void pushButton(UIPushButtonType *button);
  4476.  
  4477. -    QIODevice *input;
  4478.      AudioOutput *output;
  4479.      Decoder *decoder;
  4480. +    DecoderHandler *decoderHandler;
  4481.  
  4482.      QString playfile;
  4483.      QString statusString;
  4484. @@ -156,8 +171,8 @@
  4485.      bool scrollingDown;
  4486.  
  4487.      Metadata *curMeta;
  4488. +    Metadata displayMeta;
  4489.  
  4490. -
  4491.      unsigned int shufflemode;
  4492.      unsigned int repeatmode;
  4493.      unsigned int resumemode;
  4494. Index: mythplugins/mythmusic/mythmusic/mainvisual.cpp
  4495. ===================================================================
  4496. --- mythplugins/mythmusic/mythmusic/mainvisual.cpp      (revision 13855)
  4497. +++ mythplugins/mythmusic/mythmusic/mainvisual.cpp      (working copy)
  4498. @@ -34,7 +34,6 @@
  4499.  // fast inlines
  4500.  #include "inlines.h"
  4501.  
  4502. -
  4503.  using namespace std;
  4504.  
  4505.  VisFactory* VisFactory::g_pVisFactories = 0;
  4506. @@ -203,6 +202,33 @@
  4507.      else
  4508.          len = 0;
  4509.  
  4510. +    if (len < 512)
  4511. +    {
  4512. +        short *new_l = 0;
  4513. +        short *new_r = 0;
  4514. +
  4515. +        if (c == 1)
  4516. +        {
  4517. +            new_l = new short[512];
  4518. +            smoothen_mono(l, new_l, len, 512);
  4519. +            delete[] l;
  4520. +            l = new_l;
  4521. +        }
  4522. +
  4523. +        if (c == 2)
  4524. +        {
  4525. +            new_l = new short[512];
  4526. +            new_r = new short[512];
  4527. +            smoothen_stereo(l, new_l, r, new_r, len, 512);
  4528. +            delete[] r;
  4529. +            delete[] l;
  4530. +            r = new_r;
  4531. +            l = new_l;
  4532. +        }
  4533. +
  4534. +        len = 512;
  4535. +    }
  4536. +
  4537.      nodes.append(new VisualNode(l, r, len, w));
  4538.  }
  4539.  
  4540. @@ -342,7 +368,7 @@
  4541.  }
  4542.  
  4543.  InfoWidget::InfoWidget(QWidget *parent)
  4544. -    : QWidget( parent)
  4545. +    : QWidget( parent), color (0x03, 0x27, 0x44)
  4546.  {
  4547.      hide();
  4548.  }
  4549. @@ -416,11 +442,11 @@
  4550.          y += displayRect.y();
  4551.          // only show the text box if the visualiser is actually fullscreen
  4552.          if (visMode == 2)
  4553. -            p.fillRect(displayRect, QColor ("darkblue"));
  4554. +            p.fillRect(displayRect, color);
  4555.      }
  4556.      else
  4557.      {
  4558. -        p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), QColor ("darkblue"));
  4559. +        p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), color);
  4560.  
  4561.          if (! albumArt.isNull())
  4562.          {
  4563. @@ -480,7 +506,7 @@
  4564.      int x = indent;
  4565.      int y = indent;
  4566.  
  4567. -    p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), QColor ("darkblue"));
  4568. +    p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), color);
  4569.  
  4570.      QString info_copy = info;
  4571.      for (int offset = 0; offset < textHeight; offset += fm.height())
  4572. @@ -533,10 +559,10 @@
  4573.      long s, indexTo;
  4574.      double *magnitudesp = magnitudes.data();
  4575.      double valL, valR, tmpL, tmpR;
  4576. -    double index, step = 512.0 / size.width();
  4577.  
  4578.      if (node) {
  4579. -       index = 0;
  4580. +    double step = (node->length < 512 ? node->length : 512.0) / size.width();
  4581. +       double index = 0;
  4582.         for ( i = 0; i < size.width(); i++) {
  4583.             indexTo = (int)(index + step);
  4584.              if (indexTo == (int)(index))
  4585. @@ -732,11 +758,10 @@
  4586.      double *magnitudesp = magnitudes.data();
  4587.      double val, tmp;
  4588.  
  4589. -    double index, step = 512.0 / size.width();
  4590. -
  4591.      if (node)
  4592.      {
  4593. -        index = 0;
  4594. +        double step = (node->length < 512 ? node->length : 512.0) / size.width();
  4595. +        double index = 0;
  4596.          for ( i = 0; i < size.width(); i++)
  4597.          {
  4598.              indexTo = (int)(index + step);
  4599. Index: mythplugins/mythmusic/mythmusic/decoderhandler.h
  4600. ===================================================================
  4601. --- mythplugins/mythmusic/mythmusic/decoderhandler.h    (revision 0)
  4602. +++ mythplugins/mythmusic/mythmusic/decoderhandler.h    (revision 0)
  4603. @@ -0,0 +1,206 @@
  4604. +#ifndef DECODERHANDLER_H_
  4605. +#define DECODERHANDLER_H_
  4606. +
  4607. +#include <qobject.h>
  4608. +#include <qiodevice.h>
  4609. +#include <qfile.h>
  4610. +#include <qhttp.h>
  4611. +#include <qurl.h>
  4612. +
  4613. +#include <mythtv/mythobservable.h>
  4614. +
  4615. +#include "pls.h"
  4616. +
  4617. +class QUrl;
  4618. +class QUrlOperator;
  4619. +
  4620. +class Decoder;
  4621. +class Metadata;
  4622. +class DecoderIOFactory;
  4623. +class DecoderHandler;
  4624. +
  4625. +/** \brief Events sent by the \p DecoderHandler and it's helper classes.
  4626. + */
  4627. +class DecoderHandlerEvent : public MythEvent
  4628. +{
  4629. +  public:
  4630. +    enum Type { Ready = (QEvent::User + 300), Meta, Info, OperationStart, OperationStop, Error};
  4631. +
  4632. +    DecoderHandlerEvent(Type t)
  4633. +        : MythEvent(t), m_msg(0), m_meta(0)
  4634. +    { ; }
  4635. +
  4636. +    DecoderHandlerEvent(Type t, QString *e)
  4637. +        : MythEvent(t), m_msg(e), m_meta(0)
  4638. +    { ; }
  4639. +
  4640. +    DecoderHandlerEvent(const Metadata &m);
  4641. +    ~DecoderHandlerEvent();
  4642. +
  4643. +    const QString *getMessage() const { return m_msg; }
  4644. +    const Metadata *getMetadata() const { return m_meta; }
  4645. +
  4646. +    virtual DecoderHandlerEvent *clone();
  4647. +
  4648. +private:
  4649. +    QString *m_msg;
  4650. +    Metadata *m_meta;
  4651. +};
  4652. +
  4653. +/** \brief Class for starting stream decoding.
  4654. +
  4655. +    This class handles starting the \p Decoder for the \p
  4656. +    PlaybackBox via DecoderIOFactorys.
  4657. +
  4658. +    It operates on a playlist, either created with a single file, by
  4659. +    loading a .pls or downloading it, and for each entry creates an
  4660. +    appropriate DecoderIOFactory. The creator is simply a intermediate
  4661. +    class that translates the next URL in the playlist to
  4662. +    QIODevice. Ie. the DecoderIOFactoryFile just returns a QFile,
  4663. +    whereas the DecoderIOFactoryShoutcast returns a QSocket subclass,
  4664. +    where reads do the necessary translation of the shoutcast stream.
  4665. + */
  4666. +class DecoderHandler : public QObject, public MythObservable
  4667. +{
  4668. +    Q_OBJECT
  4669. +    friend class DecoderIOFactory;
  4670. +  public:
  4671. +    typedef enum {
  4672. +        ACTIVE,
  4673. +        LOADING,
  4674. +        STOPPED
  4675. +    } State;
  4676. +
  4677. +    DecoderHandler();
  4678. +    virtual ~DecoderHandler();
  4679. +
  4680. +    Decoder *getDecoder(void) { return m_decoder; }
  4681. +    void start(Metadata *mdata);
  4682. +
  4683. +    void stop(void);
  4684. +    void customEvent(QCustomEvent*);
  4685. +    bool done();
  4686. +    bool next(void);
  4687. +    void error(const QString &msg);
  4688. +
  4689. +  protected:
  4690. +    void doOperationStart(const QString &name);
  4691. +    void doOperationStop(void);
  4692. +    void doConnectDecoder(const QUrl &url, const QString &format);
  4693. +    void doFailed(const QUrl &url, const QString &message);
  4694. +    void doInfo(const QString &message);
  4695. +
  4696. +  private: 
  4697. +       int               m_state;
  4698. +    int               m_playlist_pos;
  4699. +    PlayListFile      m_playlist;
  4700. +    DecoderIOFactory *m_io_factory;
  4701. +    Decoder          *m_decoder;
  4702. +    Metadata         *m_meta;
  4703. +       bool              m_op;
  4704. +       uint              m_redirects;
  4705. +
  4706. +    static const uint MaxRedirects = 3;
  4707. +
  4708. +    bool createPlaylist(const QUrl &url);
  4709. +    bool createPlaylistForSingleFile(const QUrl &url);
  4710. +    bool createPlaylistFromFile(const QUrl &url);
  4711. +    bool createPlaylistFromRemoteUrl(const QUrl &url);
  4712. +
  4713. +       bool haveIOFactory(void) { return m_io_factory != 0; }
  4714. +    DecoderIOFactory *getIOFactory(void) { return m_io_factory; }
  4715. +       void createIOFactory(const QUrl &url);
  4716. +       void deleteIOFactory(void);
  4717. +};
  4718. +
  4719. +/** \brief The glue between the DecoderHandler and the Decoder
  4720. +       
  4721. +    The DecoderIOFactory is responsible for opening the QIODevice that
  4722. +    is given to the Decoder....
  4723. + */
  4724. +class DecoderIOFactory : public QObject, public MythObservable
  4725. +{
  4726. +  public:
  4727. +    DecoderIOFactory(DecoderHandler *parent);
  4728. +    virtual ~DecoderIOFactory();
  4729. +
  4730. +    virtual void start() = 0;
  4731. +    virtual void stop() = 0;
  4732. +    virtual QIODevice *takeInput(void) = 0;
  4733. +
  4734. +    void setUrl (const QUrl &url) { m_url = url; }
  4735. +    void setMeta (Metadata *meta) { m_meta = meta; }
  4736. +
  4737. +    static const uint DefaultPrebufferSize = 128 * 1024;
  4738. +    static const uint MaxRedirects = 3;
  4739. +
  4740. +  protected:
  4741. +    void doConnectDecoder(const QString &format);
  4742. +    Decoder *getDecoder(void);
  4743. +    void doFailed(const QString &message);
  4744. +    void doInfo(const QString &message);
  4745. +    void doOperationStart(const QString &name);
  4746. +    void doOperationStop(void);
  4747. +
  4748. +    QUrl m_url;
  4749. +    Metadata *m_meta;
  4750. +
  4751. +  private:
  4752. +    DecoderHandler *m_handler;
  4753. +};
  4754. +
  4755. +class DecoderIOFactoryFile : public DecoderIOFactory
  4756. +{
  4757. +    Q_OBJECT
  4758. +  public:
  4759. +    DecoderIOFactoryFile(DecoderHandler *parent);
  4760. +    ~DecoderIOFactoryFile();
  4761. +    void start();
  4762. +    void stop() {}
  4763. +    QIODevice *takeInput(void);
  4764. +
  4765. +  private:
  4766. +    QIODevice *m_input;
  4767. +};
  4768. +
  4769. +class DecoderIOFactoryUrl : public DecoderIOFactory
  4770. +{
  4771. +    Q_OBJECT
  4772. +  public:
  4773. +    DecoderIOFactoryUrl(DecoderHandler *parent);
  4774. +    ~DecoderIOFactoryUrl();
  4775. +
  4776. +    void start();
  4777. +    void stop();
  4778. +    QIODevice *takeInput(void);
  4779. +
  4780. +  protected slots:
  4781. +    void finished(QNetworkOperation *op);
  4782. +    void start(QNetworkOperation *op);
  4783. +    void data(const QByteArray & data, QNetworkOperation * op);
  4784. +
  4785. +  private:
  4786. +    void doStart(void);
  4787. +    void doClose(void);
  4788. +
  4789. +    bool m_started;
  4790. +    QUrlOperator *m_url_op;
  4791. +    QFile *m_output;
  4792. +    QFile *m_input;
  4793. +};
  4794. +
  4795. +class DecoderIOFactoryMqp : public DecoderIOFactory
  4796. +{
  4797. +    Q_OBJECT
  4798. +  public:
  4799. +    DecoderIOFactoryMqp(DecoderHandler *parent);
  4800. +    ~DecoderIOFactoryMqp();
  4801. +    void start();
  4802. +    void stop();
  4803. +    QIODevice *takeInput(void);
  4804. +
  4805. +  private:
  4806. +    QIODevice *m_input;
  4807. +};
  4808. +
  4809. +#endif /* DECODERHANDLER_H_ */
  4810. Index: mythplugins/mythmusic/mythmusic/editradiometadata.h
  4811. ===================================================================
  4812. --- mythplugins/mythmusic/mythmusic/editradiometadata.h (revision 0)
  4813. +++ mythplugins/mythmusic/mythmusic/editradiometadata.h (revision 0)
  4814. @@ -0,0 +1,74 @@
  4815. +#ifndef EDITRADIOMETADATA_H_
  4816. +#define EDITRADIOMETADATA_H_
  4817. +
  4818. +#include <iostream>
  4819. +using namespace std;
  4820. +
  4821. +#include <mythtv/mythdialogs.h>
  4822. +
  4823. +#include "editmetadata.h"
  4824. +
  4825. +class UIPhoneEntry;
  4826. +
  4827. +class EditRadioMetadataDialog : public MythThemedDialog
  4828. +{
  4829. +
  4830. +  Q_OBJECT
  4831. +
  4832. +  public:
  4833. +
  4834. +    EditRadioMetadataDialog(Metadata *source_metadata,
  4835. +                                                 MythMainWindow *parent,
  4836. +                                                 QString window_name,
  4837. +                                                 QString theme_filename,
  4838. +                                                 const char* name = 0);
  4839. +    ~EditRadioMetadataDialog();
  4840. +
  4841. +    void keyPressEvent(QKeyEvent *e);
  4842. +    void wireUpTheme(void);
  4843. +    void fillWidgets(void);
  4844. +
  4845. +  public slots:
  4846. +
  4847. +    void closeDialog(void);
  4848. +    void searchStation(void);
  4849. +    void searchGenre(void);
  4850. +    void incRating(bool up_or_down);
  4851. +    void showSaveMenu(void);
  4852. +       bool verifyEntries();
  4853. +    void saveNewToDatabase();
  4854. +    void saveToDatabase();
  4855. +    void cancelPopup();
  4856. +    void editLostFocus();
  4857. +
  4858. +  private:
  4859. +
  4860. +    void fillSearchList(QString field, QString table);
  4861. +    bool showList(QString caption, QString &value);
  4862. +   
  4863. +    Metadata *m_metadata;
  4864. +       Metadata *m_sourceMetadata ;
  4865. +    MythPopupBox *popup;
  4866. +
  4867. +    //
  4868. +    //  GUI stuff
  4869. +    //
  4870. +
  4871. +    UIRemoteEditType    *station_edit;
  4872. +    UIRemoteEditType    *channel_edit;
  4873. +    UIRemoteEditType    *url_edit;
  4874. +    UIRemoteEditType    *metaformat_edit;
  4875. +    UIRemoteEditType    *genre_edit;
  4876. +
  4877. +    UIRepeatedImageType *rating_image;
  4878. +
  4879. +    UIPushButtonType    *searchstation_button;
  4880. +    UIPushButtonType    *searchgenre_button;
  4881. +    UIPushButtonType    *rating_button;
  4882. +   
  4883. +    UITextButtonType    *done_button;
  4884. +   
  4885. +    QStringList         searchList;
  4886. +};
  4887. +
  4888. +#endif
  4889. Index: mythplugins/mythmusic/mythmusic/shoutcast.cpp
  4890. ===================================================================
  4891. --- mythplugins/mythmusic/mythmusic/shoutcast.cpp       (revision 0)
  4892. +++ mythplugins/mythmusic/mythmusic/shoutcast.cpp       (revision 0)
  4893. @@ -0,0 +1,1142 @@
  4894. +/*
  4895. +  Shoutcast decoder for MythTV.
  4896. +  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
  4897. + */
  4898. +
  4899. +#include <qapplication.h>
  4900. +#include <qregexp.h>
  4901. +#include <qsocket.h>
  4902. +#include <mythtv/mythcontext.h>
  4903. +#include "shoutcast.h"
  4904. +#include "metadata.h"
  4905. +#include <assert.h>
  4906. +#include <algorithm>
  4907. +
  4908. +/****************************************************************************/
  4909. +
  4910. +#define WAIT_FOR_MS 1500
  4911. +#define MAX_ALLOWED_META_SIZE 1024 * 100
  4912. +#define MAX_REDIRECTS 3
  4913. +#define PREBUFFER_SECS 5
  4914. +
  4915. +/****************************************************************************/
  4916. +
  4917. +struct timeval& operator-= (struct timeval &lhs, const struct timeval &rhs) {
  4918. +    lhs.tv_sec -= rhs.tv_sec;
  4919. +    if (lhs.tv_usec >= rhs.tv_usec) {
  4920. +        lhs.tv_usec -= rhs.tv_usec;
  4921. +    } else {
  4922. +        lhs.tv_sec --;
  4923. +        lhs.tv_usec = 1000000 - (rhs.tv_usec - lhs.tv_usec);
  4924. +    }
  4925. +    return lhs;
  4926. +}
  4927. +
  4928. +bool operator!=(const struct timeval &lhs, const struct timeval &rhs) {
  4929. +    if (lhs.tv_sec == rhs.tv_sec)
  4930. +        return lhs.tv_usec != rhs.tv_usec;
  4931. +    return true;   
  4932. +}
  4933. +
  4934. +bool operator==(const struct timeval &lhs, const struct timeval &rhs) {
  4935. +    if (lhs.tv_sec == rhs.tv_sec)
  4936. +        return lhs.tv_usec == rhs.tv_usec;
  4937. +    return false;
  4938. +}
  4939. +
  4940. +bool operator<(const struct timeval &lhs, const struct timeval &rhs) {
  4941. +    if (lhs.tv_sec == rhs.tv_sec)
  4942. +        return lhs.tv_usec < rhs.tv_usec;
  4943. +    return lhs.tv_sec < rhs.tv_sec;
  4944. +}
  4945. +
  4946. +bool operator<=(const struct timeval &lhs, const struct timeval &rhs) {
  4947. +    return lhs == rhs || lhs < rhs;
  4948. +}
  4949. +
  4950. +bool operator>(const struct timeval &lhs, const struct timeval &rhs) {
  4951. +    if (lhs.tv_sec == rhs.tv_sec)
  4952. +        return lhs.tv_usec > rhs.tv_usec;
  4953. +    return lhs.tv_sec > rhs.tv_sec;
  4954. +}
  4955. +
  4956. +bool operator>=(const struct timeval &lhs, const struct timeval &rhs) {
  4957. +    return lhs == rhs || lhs > rhs;
  4958. +}
  4959. +
  4960. +void test_timeval_operators () {
  4961. +    struct timeval a, b;
  4962. +    a.tv_sec = 1174197435;
  4963. +    a.tv_usec = 279159;
  4964. +    b.tv_sec = 1174197435;
  4965. +    b.tv_usec = 208796;
  4966. +   
  4967. +    assert (a == a);
  4968. +    assert (b == b);
  4969. +   
  4970. +    assert (a != b);
  4971. +   
  4972. +    assert (a > b);
  4973. +    assert (a >= b);
  4974. +    assert (a >= a);
  4975. +   
  4976. +    assert (b < a);
  4977. +    assert (b <= a);
  4978. +    assert (b <= b);
  4979. +   
  4980. +    a -= b;
  4981. +    assert (a.tv_sec == 0);
  4982. +    assert (a.tv_usec == 70363);
  4983. +   
  4984. +    a.tv_sec = 1174197735;
  4985. +    a.tv_usec = 40493;
  4986. +    b.tv_sec = 1174197735;
  4987. +    b.tv_usec = 29043;
  4988. +    a -= b;
  4989. +    assert (a.tv_sec == 0);
  4990. +    assert (a.tv_usec == 11450);
  4991. +}
  4992. +
  4993. +/****************************************************************************/
  4994. +
  4995. +const char* ShoutCastIODevice::stateString (const State &s) {
  4996. +#define TO_STRING(a) case a: return #a
  4997. +    switch (s) {
  4998. +        TO_STRING (NOT_CONNECTED);
  4999. +        TO_STRING (RESOLVING);
  5000. +        TO_STRING (CONNECTING);
  5001. +        TO_STRING (CANT_RESOLVE);
  5002. +        TO_STRING (CANT_CONNECT);
  5003. +              TO_STRING (CONNECTED);
  5004. +        TO_STRING (WRITING_HEADER);
  5005. +        TO_STRING (READING_HEADER);
  5006. +              TO_STRING (PLAYING);
  5007. +        TO_STRING (STREAMING);
  5008. +        TO_STRING (STREAMING_META);
  5009. +        TO_STRING (STOPPED);
  5010. +    default:
  5011. +        return "unknown state";       
  5012. +    }
  5013. +#undef TO_STRING
  5014. +}
  5015. +
  5016. +int moveFromByteArray(QByteArray &array, char *data, Q_ULONG maxlen) {
  5017. +    Q_ULONG consumed = array.size ();
  5018. +
  5019. +    if (consumed > maxlen)
  5020. +        consumed = maxlen;   
  5021. +    if (data)
  5022. +        memcpy (data, array.data(), consumed);   
  5023. +    if (consumed != array.size ())
  5024. +        memmove (array.data(), array.data() + consumed, array.size() - consumed);
  5025. +    array.resize(array.size() - consumed);
  5026. +
  5027. +    return consumed;
  5028. +}
  5029. +
  5030. +/****************************************************************************/
  5031. +
  5032. +/** \brief Class to download shoutcast station logos
  5033. +
  5034. +    Runs a HttpComms request in a thread until success/failure or
  5035. +    stop_transfer is called.
  5036. + */
  5037. +class ShoutCastLogoGrabber : public QThread {
  5038. +public:
  5039. +    ShoutCastLogoGrabber(const Metadata *m) : m_meta(*m), m_stop(false) { }
  5040. +
  5041. +    void stop_transfer() {
  5042. +        m_stop = true;
  5043. +    }
  5044. +
  5045. +    void run() {
  5046. +        HttpComms *comms = new HttpComms;
  5047. +
  5048. +        QString urlstr;
  5049. +        m_meta.getField("x-cast-logourl", &urlstr);
  5050. +
  5051. +        QUrl url(urlstr);
  5052. +        comms->request(url, 10000, false);
  5053. +
  5054. +        while (!m_stop && !comms->isDone())
  5055. +            usleep(500);
  5056. +
  5057. +        if (m_stop)
  5058. +            return;
  5059. +
  5060. +        if (comms->isTimedout() || comms->getStatusCode() != 200) {
  5061. +            comms->deleteLater();
  5062. +            comms = NULL;
  5063. +            return;
  5064. +        }
  5065. +
  5066. +        m_meta.setAlbumArt(comms->getRawData());
  5067. +        comms->deleteLater();
  5068. +        comms = NULL;
  5069. +    }
  5070. +
  5071. +private:
  5072. +    Metadata m_meta;
  5073. +    bool m_stop;
  5074. +};
  5075. +
  5076. +/****************************************************************************/
  5077. +
  5078. +/** \brief A buffer class
  5079. +    The intent with this it can be replaced with may RingBuffer ?
  5080. + */
  5081. +class ShoutCastBuffer {
  5082. +public:
  5083. +    ShoutCastBuffer () : m_pos (0), m_size (0) { }
  5084. +    ~ShoutCastBuffer () { }
  5085. +
  5086. +    Q_ULONG Read (char *data, Q_ULONG max) {
  5087. +        QMutexLocker holder (&m_socket_mutex);
  5088. +        const QByteArray &next_buffer = m_buffers.front();
  5089. +        const char *next_data = next_buffer.data () + m_pos;
  5090. +        uint next_size = next_buffer.size () - m_pos;
  5091. +
  5092. +        if (max > m_size)
  5093. +            max = m_size;
  5094. +
  5095. +        if (max > next_size)
  5096. +            max = next_size;
  5097. +       
  5098. +        memcpy (data, next_data, max);
  5099. +
  5100. +        m_pos += max;
  5101. +        m_size -= max;
  5102. +       
  5103. +        if (max == next_size) {
  5104. +            m_pos = 0;
  5105. +            m_buffers.pop_front();
  5106. +        }
  5107. +
  5108. +        return max;
  5109. +    }
  5110. +
  5111. +    /** \brief Add data to the buffer
  5112. +        \param data the bytes to add, will be owned by the ShoutCastBuffer
  5113. +        \param sz the size of data
  5114. +    */
  5115. +    void Write (char *data, uint sz) {
  5116. +        if (sz == 0)
  5117. +            return;
  5118. +
  5119. +        QMutexLocker holder (&m_socket_mutex);
  5120. +        QByteArray array (sz);
  5121. +        array.assign (data, sz);
  5122. +        m_buffers.push_back (array);
  5123. +        m_size += sz;
  5124. +    }
  5125. +
  5126. +    /** \brief Add data to the buffer
  5127. +        \param arr the byte array to add
  5128. +    */
  5129. +    void Write (QByteArray &array) {
  5130. +        if (array.size () == 0)
  5131. +            return;
  5132. +        QMutexLocker holder (&m_socket_mutex);
  5133. +        m_buffers.push_back (array);
  5134. +        m_size += array.size ();
  5135. +    }
  5136. +
  5137. +    Q_ULONG ReadBufAvail () const {
  5138. +        return m_size;
  5139. +    }
  5140. +
  5141. +    static void selfTest () {
  5142. +        VERBOSE(VB_IMPORTANT, "Selftesting ShoutCastBuffer");
  5143. +        ShoutCastBuffer iobuf;
  5144. +        char *data_1 = strdup ("aaaaa");
  5145. +        char *data_2 = strdup ("bbbbb");
  5146. +        QByteArray arr (5);
  5147. +        arr.duplicate ("ccccc", 5);
  5148. +
  5149. +        iobuf.Write (data_1, strlen (data_1));
  5150. +        assert (iobuf.ReadBufAvail() == 5);
  5151. +        iobuf.Write (data_2, strlen (data_2));
  5152. +        assert (iobuf.ReadBufAvail() == 10);
  5153. +        iobuf.Write (arr);
  5154. +        assert (iobuf.ReadBufAvail() == 15);
  5155. +
  5156. +        char *data = (char*)alloca(iobuf.ReadBufAvail());
  5157. +        assert (iobuf.Read (data, 3) == 3);
  5158. +        assert (iobuf.ReadBufAvail() == 12);
  5159. +        assert (memcmp (data ,"aaa", 3) == 0);
  5160. +
  5161. +        assert (iobuf.Read (data, 3) == 2);
  5162. +        assert (iobuf.ReadBufAvail() == 10);
  5163. +        assert (memcmp (data ,"aa", 2) == 0);
  5164. +
  5165. +        assert (iobuf.Read (data, 10) == 5);
  5166. +        assert (iobuf.ReadBufAvail() == 5);
  5167. +        assert (memcmp (data ,"bbbbb", 5) == 0);
  5168. +
  5169. +        assert (iobuf.Read (data, 5) == 5);
  5170. +        assert (iobuf.ReadBufAvail() == 0);
  5171. +        assert (memcmp (data ,"ccccc", 5) == 0);
  5172. +    }
  5173. +
  5174. +private:
  5175. +    QValueList<QByteArray> m_buffers;
  5176. +    uint m_pos;
  5177. +    Q_ULONG m_size;
  5178. +    QMutex m_socket_mutex;
  5179. +};
  5180. +
  5181. +
  5182. +/****************************************************************************/
  5183. +
  5184. +class ShoutCastRequest
  5185. +{
  5186. +public:
  5187. +    ShoutCastRequest() { }
  5188. +    ShoutCastRequest(const QUrl &url) { setUrl(url); }
  5189. +    ~ShoutCastRequest() { }
  5190. +       const char *data(void) { return m_data.data(); }
  5191. +       uint size(void) { return m_data.size(); }
  5192. +       
  5193. +private:
  5194. +    void setUrl(const QUrl &url) {
  5195. +        QString hdr;
  5196. +        hdr = QString("GET %1 HTTP/1.1\r\n"
  5197. +                      "Host: %2\r\n"
  5198. +                      "User-Agent: mythmusic/svn\r\n"
  5199. +                      "Keep-Alive:\r\n"
  5200. +                      "Connection: TE, Keep-Alive\r\n"
  5201. +                      "TE: trailers\r\n"
  5202. +                      "icy-metadata:1\r\n"
  5203. +                      "\r\n").arg(url.path()).arg(url.host());
  5204. +       
  5205. +        m_data.duplicate(hdr.ascii(), hdr.length());
  5206. +    }
  5207. +
  5208. +    QByteArray m_data;
  5209. +};
  5210. +
  5211. +
  5212. +/****************************************************************************/
  5213. +
  5214. +
  5215. +class ShoutCastResponse
  5216. +{
  5217. +  public:
  5218. +    ShoutCastResponse() { }
  5219. +    ~ShoutCastResponse() { }
  5220. +   
  5221. +    int getMetaint(void) { return getInt("icy-metaint"); }
  5222. +    int getBitrate(void) { return getInt("icy-br"); }
  5223. +    QString getGenre(void) { return getString("icy-genre"); }
  5224. +    QString getName(void) { return getString("icy-name"); }
  5225. +    int getStatus(void) { return getInt("status"); }
  5226. +    bool isICY(void) { return QString(m_data["protocol"]).left(3) == "ICY"; }
  5227. +    QString getContent(void) { return getString("content-type"); }
  5228. +    QString getLocation(void) { return getString("location"); }
  5229. +
  5230. +    QString getString(const QString &key) { return m_data[key]; }
  5231. +    int getInt(const QString &key) { return m_data[key].toInt(); }
  5232. +
  5233. +    int fillResponse(const char *data, int len);
  5234. +  private:
  5235. +    QMap<QString,QString> m_data;
  5236. +};
  5237. +
  5238. +/** \brief Consume bytes and parse shoutcast header
  5239. +    \returns number of bytes consumed
  5240. +*/
  5241. +int ShoutCastResponse::fillResponse(const char *s, int l)
  5242. +{
  5243. +    QCString d(s, l);
  5244. +    int result = 0;
  5245. +    // check each line
  5246. +    for (;;)
  5247. +    {
  5248. +        int pos = d.find("\r");
  5249. +
  5250. +        if (pos <= 0)
  5251. +            break;
  5252. +
  5253. +        // Extract the line
  5254. +        QCString snip(d.data(), pos + 1);
  5255. +        d.remove(0, pos + 2);
  5256. +        result += pos + 2;
  5257. +       
  5258. +        if (snip.left(4) == "ICY ")
  5259. +        {
  5260. +            int space = snip.find(' ');
  5261. +            m_data["protocol"] = "ICY";
  5262. +            QString tmp = snip.mid(space).simplifyWhiteSpace();
  5263. +            int second_space = tmp.find(' ');
  5264. +            if (second_space > 0) {
  5265. +                m_data["status"] = tmp.left(second_space);
  5266. +            } else {
  5267. +                m_data["status"] = tmp;
  5268. +            }
  5269. +        }
  5270. +        else if (snip.left(7) == "HTTP/1.")
  5271. +        {
  5272. +            int space = snip.find(' ');
  5273. +            m_data["protocol"] = snip.left(space);
  5274. +            QString tmp = snip.mid(space).simplifyWhiteSpace();
  5275. +            int second_space = tmp.find(' ');
  5276. +            if (second_space > 0) {
  5277. +                m_data["status"] = tmp.left(second_space);
  5278. +            } else {
  5279. +                m_data["status"] = tmp;
  5280. +            }
  5281. +        }
  5282. +        else if (snip.left(9).lower() == "location:")
  5283. +        {
  5284. +            m_data["location"] = snip.mid(9).stripWhiteSpace();
  5285. +        }
  5286. +        else if (snip.left(13).lower() == "content-type:")
  5287. +        {
  5288. +            m_data["content-type"] = snip.mid(13).stripWhiteSpace();
  5289. +        }
  5290. +        else if (snip.left(4) == "icy-")
  5291. +        {
  5292. +            int pos = snip.find(':');
  5293. +            QString key = snip.left(pos);
  5294. +            m_data[key.ascii()] = snip.mid(pos+1).stripWhiteSpace();
  5295. +        }
  5296. +    }
  5297. +
  5298. +    return result;
  5299. +}
  5300. +
  5301. +/****************************************************************************/
  5302. +
  5303. +class ShoutCastMetaParser
  5304. +{
  5305. +  public:
  5306. +       ShoutCastMetaParser() { }
  5307. +       ~ShoutCastMetaParser() { }
  5308. +
  5309. +       void setMetaFormat(const QString &metaformat);
  5310. +       ShoutCastMetaMap parseMeta(QString meta);
  5311. +
  5312. +  private:
  5313. +       QString m_meta_format;
  5314. +       int m_meta_artist_pos;
  5315. +       int m_meta_title_pos;
  5316. +       int m_meta_album_pos;
  5317. +};
  5318. +
  5319. +void ShoutCastMetaParser::setMetaFormat(const QString &metaformat)
  5320. +{
  5321. +/*
  5322. +  We support these metatags :
  5323. +  %a - artist
  5324. +  %t - track
  5325. +  %b - album
  5326. +  %r - random bytes
  5327. + */
  5328. +    m_meta_format = metaformat;
  5329. +
  5330. +    m_meta_artist_pos = 0;
  5331. +    m_meta_title_pos = 0;
  5332. +    m_meta_album_pos = 0;
  5333. +
  5334. +    int assign_index = 1;
  5335. +    int pos = 0;
  5336. +
  5337. +    pos = m_meta_format.find("%", pos);
  5338. +    while (pos >= 0) {
  5339. +        pos++;
  5340. +        QChar ch = m_meta_format.at(pos);
  5341. +
  5342. +        if (ch == '%') {
  5343. +            pos++;
  5344. +        }
  5345. +        else if (ch == 'r' || ch == 'a' || ch == 'b' || ch == 't')
  5346. +        {
  5347. +            if (ch == 'a')
  5348. +                m_meta_artist_pos = assign_index;
  5349. +           
  5350. +            if (ch == 'b')
  5351. +                m_meta_album_pos = assign_index;
  5352. +           
  5353. +            if (ch == 't')
  5354. +                m_meta_title_pos = assign_index;
  5355. +
  5356. +            assign_index++;
  5357. +        } else
  5358. +            fprintf(stderr, "CastDecoder: malformed metaformat '%s'\n", m_meta_format.ascii());
  5359. +
  5360. +        pos = m_meta_format.find("%", pos);
  5361. +    }
  5362. +   
  5363. +    m_meta_format.replace("%a", "(.*)");
  5364. +    m_meta_format.replace("%t", "(.*)");
  5365. +    m_meta_format.replace("%b", "(.*)");
  5366. +    m_meta_format.replace("%r", "(.*)");
  5367. +    m_meta_format.replace("%%", "%");
  5368. +}
  5369. +
  5370. +ShoutCastMetaMap ShoutCastMetaParser::parseMeta(QString meta)
  5371. +{
  5372. +    QCString metastring(meta);
  5373. +    ShoutCastMetaMap result;
  5374. +    int title_begin_pos = metastring.find("StreamTitle='");
  5375. +    int title_end_pos;
  5376. +
  5377. +    if (title_begin_pos >= 0)
  5378. +    {
  5379. +        title_begin_pos += 13;
  5380. +        title_end_pos = metastring.find("';", title_begin_pos);
  5381. +        QCString title = metastring.mid(title_begin_pos,
  5382. +                                        title_end_pos - title_begin_pos);
  5383. +        QRegExp rx;
  5384. +        rx.setPattern(m_meta_format);
  5385. +        if (rx.search(title) != -1)
  5386. +        {
  5387. +            VERBOSE(VB_PLAYBACK, QString("ShoutCast: Meta     : '%1'").
  5388. +                    arg(meta));
  5389. +            VERBOSE(VB_PLAYBACK, QString("ShoutCast: Parsed as: '%1' by '%2'").
  5390. +                    arg(rx.cap(m_meta_title_pos)).
  5391. +                    arg(rx.cap(m_meta_artist_pos)));
  5392. +           
  5393. +            if (m_meta_title_pos > 0)
  5394. +                result["title"] = rx.cap(m_meta_title_pos);
  5395. +           
  5396. +            if (m_meta_artist_pos > 0)
  5397. +                result["artist"] = rx.cap(m_meta_artist_pos);
  5398. +           
  5399. +            if (m_meta_album_pos > 0)
  5400. +                result["album"] = rx.cap(m_meta_album_pos);
  5401. +        }
  5402. +    }
  5403. +
  5404. +    return result;
  5405. +}
  5406. +
  5407. +/****************************************************************************/
  5408. +
  5409. +ShoutCastIODevice::ShoutCastIODevice ()
  5410. +    :  m_redirects (0),
  5411. +       m_scratchpad_pos (0),
  5412. +       m_state (NOT_CONNECTED)
  5413. +{
  5414. +    // Run some quick unittests. Put this here since there's no
  5415. +    // 'formal' unittest harness blabla...
  5416. +    ShoutCastBuffer::selfTest();
  5417. +    test_timeval_operators ();
  5418. +    // done testing...
  5419. +
  5420. +    m_buffer = new ShoutCastBuffer;
  5421. +    m_socket = new QSocket;
  5422. +    m_response = new ShoutCastResponse;
  5423. +
  5424. +    connect (m_socket, SIGNAL (hostFound ()), SLOT (socketHostFound ()));
  5425. +    connect (m_socket, SIGNAL (connected ()), SLOT (socketConnected ()));
  5426. +    connect (m_socket, SIGNAL (connectionClosed ()), SLOT (socketConnectionClosed ()));
  5427. +    connect (m_socket, SIGNAL (readyRead ()), SLOT (socketReadyRead ()));
  5428. +    connect (m_socket, SIGNAL (error (int)), SLOT (socketError (int)));
  5429. +
  5430. +    switchToState (NOT_CONNECTED);
  5431. +
  5432. +    setFlags (IO_Direct | IO_Async | IO_ReadOnly);
  5433. +
  5434. +    m_sample_tv.tv_sec = 0;
  5435. +    m_sample_tv.tv_usec = 0;
  5436. +}
  5437. +
  5438. +ShoutCastIODevice::~ShoutCastIODevice () {
  5439. +    delete m_buffer;
  5440. +    delete m_response;
  5441. +    m_socket->close ();
  5442. +    m_socket->disconnect (this);
  5443. +    m_socket->deleteLater ();
  5444. +}
  5445. +
  5446. +void ShoutCastIODevice::connectToUrl (const QUrl &url)  {
  5447. +    m_url = url;
  5448. +    switchToState (RESOLVING);
  5449. +    setMode(IO_ReadOnly);
  5450. +    setState(IO_Open);
  5451. +    return m_socket->connectToHost (m_url.host (), m_url.port ());
  5452. +}
  5453. +
  5454. +bool ShoutCastIODevice::open(int) {
  5455. +    VERBOSE(VB_NETWORK, "ShoutCastIODevice: open");
  5456. +    return true;
  5457. +}
  5458. +
  5459. +void ShoutCastIODevice::close() {
  5460. +    return m_socket->close();
  5461. +}
  5462. +
  5463. +void ShoutCastIODevice::flush() {
  5464. +    return m_socket->flush();
  5465. +}
  5466. +
  5467. +Q_ULONG ShoutCastIODevice::size() const {
  5468. +    return m_buffer->ReadBufAvail();
  5469. +}
  5470. +
  5471. +bool ShoutCastIODevice::waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed) {
  5472. +    struct timeval tv_begin;
  5473. +    gettimeofday (&tv_begin, 0);
  5474. +   
  5475. +    do {
  5476. +        if (m_state != STOPPED && m_buffer->ReadBufAvail() < bytes_needed) {
  5477. +            m_cond.wait (millisecs);           
  5478. +        }
  5479. +
  5480. +        if (m_buffer->ReadBufAvail() >= bytes_needed)
  5481. +            return true;
  5482. +
  5483. +        struct timeval tv_delta;
  5484. +        gettimeofday (&tv_delta, 0);
  5485. +        tv_delta -= tv_begin;       
  5486. +        Q_ULONG delta = (tv_delta.tv_sec * 1000) + (tv_delta.tv_usec / 1000);
  5487. +        if (delta > millisecs)
  5488. +            millisecs = 0;
  5489. +        else
  5490. +            millisecs -= delta;
  5491. +
  5492. +        VERBOSE(VB_PLAYBACK, QString ("slept for %1 ms, adjust to %2, avail is %3/%4").
  5493. +                arg((tv_delta.tv_sec * 1000) + (tv_delta.tv_usec / 1000)).
  5494. +                arg(millisecs).arg(m_buffer->ReadBufAvail()).arg(bytes_needed));
  5495. +    } while (millisecs > 0);
  5496. +
  5497. +    if (m_buffer->ReadBufAvail() < bytes_needed)
  5498. +        return false;
  5499. +
  5500. +    return true;
  5501. +}
  5502. +
  5503. +Q_LONG ShoutCastIODevice::readBlock(char *data, Q_ULONG maxlen) {
  5504. +    int result = 0;
  5505. +
  5506. +    if (waitForData (WAIT_FOR_MS, 1024) == false) {
  5507. +        VERBOSE(VB_PLAYBACK, "Waited for data in stream, got none");
  5508. +        switchToState(STOPPED);
  5509. +    }
  5510. +
  5511. +    if (m_state == STREAMING_META && parseMeta())
  5512. +        switchToState(STREAMING);
  5513. +   
  5514. +    if (m_state == STREAMING)
  5515. +    {
  5516. +        if (waitForData (WAIT_FOR_MS) == false) {
  5517. +            VERBOSE(VB_PLAYBACK, "Waited for in stream again, got none");
  5518. +            switchToState(STOPPED);
  5519. +        } else {
  5520. +            result = parseStream(data, maxlen);
  5521. +            if (m_bytes_till_next_meta == 0)
  5522. +                switchToState(STREAMING_META);
  5523. +        }
  5524. +    }
  5525. +   
  5526. +    if (m_state != STOPPED)
  5527. +        VERBOSE(VB_NETWORK, QString("ShoutCastIO2: %1 kb in buffer, btnm=%2/%3 state=%4, rb=%5/%6").
  5528. +                arg(m_buffer->ReadBufAvail () / 1024, 4).
  5529. +                arg(m_bytes_till_next_meta, 4).
  5530. +                arg(m_response->getMetaint()).
  5531. +                arg(stateString (m_state)).
  5532. +                arg(result).
  5533. +                arg(maxlen));
  5534. +    else
  5535. +        VERBOSE(VB_NETWORK, QString("ShoutCastIO2: stopped"));
  5536. +   
  5537. +    return result;
  5538. +}
  5539. +
  5540. +Q_LONG ShoutCastIODevice::writeBlock(const char *data, Q_ULONG sz) {
  5541. +    return m_socket->writeBlock (data, sz);
  5542. +}
  5543. +
  5544. +Q_ULONG ShoutCastIODevice::bytesAvailable () const {
  5545. +    return m_buffer->ReadBufAvail ();
  5546. +}
  5547. +
  5548. +int ShoutCastIODevice::getch() {
  5549. +    assert (0);
  5550. +    return -1;
  5551. +}
  5552. +int ShoutCastIODevice::putch(int) {
  5553. +    assert (0);
  5554. +    return -1;
  5555. +}
  5556. +int ShoutCastIODevice::ungetch(int) {
  5557. +    assert (0);
  5558. +    return -1;
  5559. +}
  5560. +
  5561. +void ShoutCastIODevice::socketHostFound () {
  5562. +    VERBOSE(VB_NETWORK, "ShoutCastIO2: Host Found");
  5563. +    switchToState (CONNECTING);
  5564. +}
  5565. +
  5566. +void ShoutCastIODevice::socketConnected () {
  5567. +    VERBOSE(VB_NETWORK, "ShoutCastIO2: Connected");
  5568. +    switchToState (CONNECTED);
  5569. +
  5570. +    ShoutCastRequest request (m_url);
  5571. +    Q_ULONG written = m_socket->writeBlock (request.data (), request.size ());
  5572. +    VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Sending Request, %1 of %1 bytes").arg(written).arg(request.size()));
  5573. +
  5574. +    if (written != request.size ()) {
  5575. +        m_scratchpad.duplicate (request.data () + written, request.size () - written);
  5576. +        m_scratchpad_pos = 0;
  5577. +        connect (m_socket, SIGNAL (bytesWritten (int)), SLOT (socketBytesWritten (int)));
  5578. +        switchToState (WRITING_HEADER);
  5579. +    } else {
  5580. +        switchToState (READING_HEADER);
  5581. +    }
  5582. +}
  5583. +
  5584. +void ShoutCastIODevice::socketConnectionClosed () {
  5585. +    VERBOSE(VB_NETWORK, "ShoutCastIO2: Connection Closed");
  5586. +    switchToState (STOPPED);
  5587. +}
  5588. +
  5589. +void ShoutCastIODevice::socketReadyRead () {
  5590. +    Q_ULONG sz = m_socket->bytesAvailable();
  5591. +
  5592. +    //VERBOSE(VB_IMPORTANT, QString ("ShoutCastIO2: %1 bytes readable").arg(sz));   
  5593. +
  5594. +    char *data = (char*)malloc (sz);
  5595. +    Q_ULONG actual_sz = m_socket->readBlock (data, sz);
  5596. +    if (actual_sz < sz)
  5597. +        data = (char*)realloc (data, actual_sz);
  5598. +
  5599. +    if (m_state == READING_HEADER) {
  5600. +        if (parseHeader (data, actual_sz) == 1) {
  5601. +            if (m_response->isICY () && m_response->getStatus () == 200) {
  5602. +                switchToState (PLAYING);
  5603. +               
  5604. +                m_response_gotten = true;
  5605. +               
  5606. +                // debug, collect kb/s info
  5607. +                gettimeofday (&m_sample_tv, NULL);
  5608. +                m_bytes_downloaded = 0;
  5609. +                m_bytes_till_next_meta = m_response->getMetaint();
  5610. +               
  5611. +                // whatever's left in the scratch pad, toss into m_buffer
  5612. +                free (data);
  5613. +                m_buffer->Write (m_scratchpad);
  5614. +                m_cond.wakeOne ();
  5615. +               
  5616. +                switchToState (STREAMING);
  5617. +            } else if (m_response->getStatus () == 302) {
  5618. +                if (++m_redirects > MAX_REDIRECTS) {
  5619. +                    VERBOSE (VB_NETWORK, QString ("Too many redirects"));
  5620. +                    switchToState (STOPPED);
  5621. +                } else {
  5622. +                    VERBOSE (VB_NETWORK, QString ("Redirect to %1").arg(m_response->getLocation()));
  5623. +                    connectToUrl(m_url);
  5624. +                }
  5625. +            } else {
  5626. +                VERBOSE (VB_NETWORK, QString ("Unknown response status %1").arg (m_response->getStatus ()));
  5627. +                switchToState (STOPPED);
  5628. +            }
  5629. +
  5630. +        }
  5631. +    } else {
  5632. +        m_buffer->Write (data, actual_sz);
  5633. +        m_cond.wakeOne ();
  5634. +        doKbPerSecond(actual_sz);
  5635. +    }
  5636. +}
  5637. +
  5638. +void ShoutCastIODevice::socketBytesWritten (int) {
  5639. +    Q_ULONG written = m_socket->writeBlock (m_scratchpad.data () + m_scratchpad_pos,
  5640. +                                            m_scratchpad.size () - m_scratchpad_pos);
  5641. +    VERBOSE(VB_NETWORK, QString ("ShoutCastIO: %1 bytes written").arg(written));
  5642. +
  5643. +    m_scratchpad_pos += written;
  5644. +    if (m_scratchpad_pos == m_scratchpad.size ()) {
  5645. +        m_scratchpad.truncate (0);
  5646. +        disconnect (m_socket, SIGNAL (bytesWritten (int)), this, 0);
  5647. +        switchToState (READING_HEADER);
  5648. +    }
  5649. +}
  5650. +
  5651. +void ShoutCastIODevice::socketError (int error) {
  5652. +    VERBOSE(VB_NETWORK, QString ("ShoutCastIO: Socket Error %1").arg(error));
  5653. +
  5654. +    switch (error) {
  5655. +    case QSocket::ErrConnectionRefused:
  5656. +        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Connection Refused");
  5657. +        switchToState(CANT_CONNECT);
  5658. +        break;
  5659. +    case QSocket::ErrHostNotFound:
  5660. +        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Host Not Found");
  5661. +        switchToState(CANT_RESOLVE);
  5662. +        break;
  5663. +    case QSocket::ErrSocketRead:
  5664. +        VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Socket Read");
  5665. +        switchToState (STOPPED);
  5666. +        break;
  5667. +    }
  5668. +}
  5669. +
  5670. +void ShoutCastIODevice::switchToState(const State &state)
  5671. +{
  5672. +    switch (state)
  5673. +    {
  5674. +    case PLAYING:
  5675. +        VERBOSE(VB_PLAYBACK, QString ("Playing %1 (%2) at %3 kbps").
  5676. +                arg(m_response->getName()).
  5677. +                arg(m_response->getGenre()).
  5678. +                arg(m_response->getBitrate()));
  5679. +        break;
  5680. +    case STREAMING:
  5681. +        if (m_state == STREAMING_META)
  5682. +            m_bytes_till_next_meta = m_response->getMetaint();
  5683. +        break;
  5684. +    case STOPPED:
  5685. +        m_socket->close ();
  5686. +        m_cond.wakeAll ();
  5687. +        break;
  5688. +    default:
  5689. +        break;
  5690. +    }
  5691. +
  5692. +    m_state = state;
  5693. +    emit changedState(m_state);
  5694. +}
  5695. +
  5696. +int ShoutCastIODevice::parseHeader(const char *data, Q_ULONG len) {
  5697. +    // Pad the read data to the end of m_response_buf
  5698. +    int old_buf_size = m_scratchpad.size();
  5699. +    m_scratchpad.resize(old_buf_size + len);
  5700. +    memcpy(m_scratchpad.data() + old_buf_size, data, len);
  5701. +   
  5702. +    int consumed = m_response->fillResponse(m_scratchpad.data(), m_scratchpad.size());       
  5703. +    VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header, %1 bytes").arg(consumed));
  5704. +    {
  5705. +        QString tmp;
  5706. +        tmp.setAscii (m_scratchpad.data (), consumed);       
  5707. +        VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header %1").arg(tmp));
  5708. +    }
  5709. +    moveFromByteArray (m_scratchpad, 0, consumed);
  5710. +   
  5711. +    if (m_scratchpad.size() >= 2 &&
  5712. +        m_scratchpad[0] == '\r' &&
  5713. +        m_scratchpad[1] == '\n')
  5714. +    {
  5715. +        moveFromByteArray (m_scratchpad, 0, 2);       
  5716. +        return 1;
  5717. +    }
  5718. +   
  5719. +    return 0;
  5720. +}
  5721. +
  5722. +bool ShoutCastIODevice::getResponse(ShoutCastResponse &response) {
  5723. +    if (! m_response_gotten)
  5724. +        return false;
  5725. +
  5726. +    response = *m_response;
  5727. +    return true;
  5728. +}
  5729. +
  5730. +int ShoutCastIODevice::parseStream(char *data, Q_ULONG maxlen) {
  5731. +    if (maxlen > m_bytes_till_next_meta)
  5732. +        maxlen = m_bytes_till_next_meta;
  5733. +
  5734. +    /*
  5735. +    // throttle... by leaving bytes, we can delay actually having to
  5736. +    // make readBlock block the caller waiting for more bytes, thereby
  5737. +    // making the decoder be more responsive to ie. stop requests.
  5738. +    if (maxlen > m_buffer->ReadBufAvail ()) {
  5739. +        VERBOSE(VB_NETWORK, QString ("throttling, %1 becomes %2").arg(maxlen).arg(m_buffer->ReadBufAvail()/2));
  5740. +        maxlen = m_buffer->ReadBufAvail () / 3;
  5741. +        if (maxlen < 128) {
  5742. +            maxlen = 128;
  5743. +            waitForData (2000, maxlen);
  5744. +        }
  5745. +    }
  5746. +    */
  5747. +
  5748. +    int result = m_buffer->Read (data, maxlen);
  5749. +    m_bytes_till_next_meta -= result;
  5750. +
  5751. +    doKbPerSecond(result);
  5752. +    return result;
  5753. +}
  5754. +
  5755. +bool ShoutCastIODevice::parseMeta() {
  5756. +    unsigned char ch;
  5757. +    m_buffer->Read (reinterpret_cast<char*>(&ch), 1);
  5758. +
  5759. +    Q_ULONG meta_size = 16 * ch;
  5760. +    if (meta_size == 0)
  5761. +        return true;
  5762. +
  5763. +    // FIXME: in case the stream is f*cked, we don't want to allocate too much
  5764. +    if (meta_size > MAX_ALLOWED_META_SIZE) {
  5765. +        VERBOSE(VB_PLAYBACK, QString ("Error in stream, got a meta size of %1").arg(meta_size));
  5766. +        switchToState (STOPPED);
  5767. +        return false;
  5768. +    }
  5769. +
  5770. +    VERBOSE(VB_NETWORK, QString("ShoutCast: Reading %1 bytes of meta").arg(meta_size));
  5771. +
  5772. +    if (waitForData (WAIT_FOR_MS, meta_size) == false) {
  5773. +        VERBOSE(VB_PLAYBACK, "Stream seems to have stopped");
  5774. +        switchToState(STOPPED);
  5775. +        return false;
  5776. +    }
  5777. +
  5778. +    // Read in a loop until we have all of meta_size (but no more)
  5779. +    QByteArray metadata (meta_size);
  5780. +    Q_ULONG bytes_read = 0;
  5781. +    do {
  5782. +        Q_ULONG len = m_buffer->Read (metadata.data () + bytes_read, meta_size - bytes_read);
  5783. +        if (len <= 0) {
  5784. +            VERBOSE(VB_PLAYBACK, QString ("Error in metadata, expected %1 bytes of meta, got %1").
  5785. +                    arg(meta_size).arg(len));
  5786. +            switchToState(STOPPED);
  5787. +            return false;
  5788. +        }
  5789. +        bytes_read += len;
  5790. +    } while (bytes_read < meta_size);
  5791. +    m_bytes_downloaded += bytes_read;
  5792. +
  5793. +    // Avoid sending signals if the data hasn't changed
  5794. +    QString metadata_string (metadata);
  5795. +    if (m_last_metadata == metadata_string)
  5796. +        return true;
  5797. +
  5798. +    m_last_metadata = metadata_string;
  5799. +    emit meta(metadata_string);
  5800. +
  5801. +    return true;
  5802. +}
  5803. +
  5804. +void ShoutCastIODevice::doKbPerSecond(int bytes_read)
  5805. +{
  5806. +    // debug, collect kb/s info
  5807. +    m_bytes_downloaded += bytes_read;
  5808. +    struct timeval tv = {0, 0};
  5809. +    gettimeofday(&tv, NULL);
  5810. +    int msecs =((tv.tv_sec * 1000000 + tv.tv_usec) -
  5811. +                (m_sample_tv.tv_sec * 1000000 + m_sample_tv.tv_usec)) / 1000;
  5812. +
  5813. +    if (msecs > 5000)
  5814. +    {
  5815. +        VERBOSE(VB_NETWORK, QString("ShoutCast: download speed, %1 kb in %2 s =  %3 kb/s").
  5816. +                arg(m_bytes_downloaded/1024.0, 1, 'f', 1).
  5817. +                arg(msecs/1000.0, 1, 'f', 1).
  5818. +                arg((m_bytes_downloaded/1024)/(msecs/1000.0), 3, 'f', 1));
  5819. +        m_sample_tv = tv;
  5820. +        m_bytes_downloaded = 0;
  5821. +    }
  5822. +}
  5823. +
  5824. +/****************************************************************************/
  5825. +
  5826. +DecoderIOFactoryShoutCast::DecoderIOFactoryShoutCast(DecoderHandler *parent)
  5827. +    : DecoderIOFactory(parent), m_timer(NULL), m_input(NULL)
  5828. +{
  5829. +    m_timer = new QTimer(this);
  5830. +}
  5831. +
  5832. +DecoderIOFactoryShoutCast::~DecoderIOFactoryShoutCast()
  5833. +{
  5834. +    closeIODevice();
  5835. +    if (!m_logo_grabber.isDone ()) {
  5836. +        m_logo_grabber.stop ();
  5837. +    }
  5838. +}
  5839. +
  5840. +QIODevice* DecoderIOFactoryShoutCast::takeInput ()
  5841. +{
  5842. +    QIODevice *result = m_input;
  5843. +    m_input = 0;
  5844. +    return result;
  5845. +}
  5846. +
  5847. +void DecoderIOFactoryShoutCast::makeIODevice()
  5848. +{
  5849. +    closeIODevice();
  5850. +
  5851. +    m_input = new ShoutCastIODevice();
  5852. +
  5853. +    connect(m_input, SIGNAL(meta(const QString&)),
  5854. +            this,    SLOT(shoutcastMeta(const QString&)));
  5855. +    connect(m_input, SIGNAL(changedState(ShoutCastIODevice::State)),
  5856. +            this,    SLOT(shoutcastChangedState(ShoutCastIODevice::State)));
  5857. +}
  5858. +
  5859. +void DecoderIOFactoryShoutCast::closeIODevice()
  5860. +{
  5861. +    if (m_input) {
  5862. +        m_input->disconnect();
  5863. +        if (m_input->isOpen()) {
  5864. +            m_input->close();
  5865. +        }
  5866. +        m_input->deleteLater();
  5867. +        m_input = NULL;
  5868. +        VERBOSE(VB_PLAYBACK, "DecoderIOFactoryShoutCast m_input is now 0");
  5869. +    }
  5870. +}
  5871. +
  5872. +
  5873. +void DecoderIOFactoryShoutCast::start()
  5874. +{
  5875. +    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactoryShoutCast %1").arg(m_url.toString()));
  5876. +    doOperationStart("Connecting");
  5877. +
  5878. +    makeIODevice();
  5879. +    m_input->connectToUrl(m_url);
  5880. +
  5881. +    QString urlstr;
  5882. +    m_meta->getField ("x-cast-logourl", &urlstr);
  5883. +    QUrl url (urlstr);
  5884. +    m_logo_grabber.request (url, 10000, false);
  5885. +    m_logo_grabber_timer = new QTimer (this);
  5886. +    connect (m_logo_grabber_timer, SIGNAL(timeout()), this, SLOT(checkLogoGrabber()));
  5887. +    m_logo_grabber_timer->start (1000);
  5888. +}
  5889. +
  5890. +void DecoderIOFactoryShoutCast::checkLogoGrabber () {
  5891. +    VERBOSE(VB_PLAYBACK, QString("Checking logograbber %1 %2 %3").
  5892. +            arg(m_logo_grabber.isDone ()?"done":"busy").
  5893. +            arg(m_logo_grabber.isTimedout ()?"timedout":"not-timedout").
  5894. +            arg(m_logo_grabber.getStatusCode()));
  5895. +    if (!m_logo_grabber.isDone ())
  5896. +        return;
  5897. +
  5898. +    m_logo_grabber_timer->stop ();
  5899. +    m_logo_grabber_timer->disconnect ();
  5900. +
  5901. +    if (m_logo_grabber.isTimedout() || m_logo_grabber.getStatusCode() != 200)
  5902. +        return;
  5903. +
  5904. +    m_meta->setAlbumArt(m_logo_grabber.getRawData());
  5905. +}
  5906. +
  5907. +void DecoderIOFactoryShoutCast::stop()
  5908. +{
  5909. +    if (m_timer)
  5910. +        m_timer->disconnect ();
  5911. +
  5912. +    doOperationStop();
  5913. +
  5914. +    Metadata mdata(*m_meta);   
  5915. +    mdata.setTitle("Stopped");
  5916. +    mdata.setArtist("");
  5917. +    mdata.setLength(-1);
  5918. +    DecoderHandlerEvent ev(mdata);
  5919. +    dispatch(ev);
  5920. +}
  5921. +
  5922. +void DecoderIOFactoryShoutCast::periodicallyCheckResponse(void)
  5923. +{
  5924. +    int res = checkResponseOK();       
  5925. +    if (res == 0)
  5926. +    {
  5927. +        ShoutCastResponse response;
  5928. +        m_input->getResponse (response);
  5929. +        m_prebuffer = PREBUFFER_SECS * response.getBitrate () * 125; // 125 = 1000/8 (kilo, bits...)
  5930. +        VERBOSE(VB_NETWORK, QString ("kbps is %1, prebuffering %2 secs = %3 kb").
  5931. +                arg(response.getBitrate()).arg(PREBUFFER_SECS).arg(m_prebuffer/1024));
  5932. +        m_timer->stop();
  5933. +        m_timer->disconnect();
  5934. +        connect(m_timer, SIGNAL(timeout()), this, SLOT(periodicallyCheckBuffered()));
  5935. +        m_timer->start(500);
  5936. +    }
  5937. +    else if (res < 0)
  5938. +    {
  5939. +        m_timer->stop();
  5940. +        doFailed("Cannot parse this stream");
  5941. +    }
  5942. +}
  5943. +
  5944. +void DecoderIOFactoryShoutCast::periodicallyCheckBuffered(void)
  5945. +{
  5946. +    VERBOSE(VB_NETWORK, QString("DecoderIOFactoryShoutCast: prebuffered %1/%2KB").
  5947. +            arg(m_input->bytesAvailable()/1024).arg(m_prebuffer/1024));
  5948. +   
  5949. +    if (m_input->bytesAvailable() < m_prebuffer)
  5950. +        return;
  5951. +   
  5952. +    ShoutCastResponse response;
  5953. +    m_input->getResponse (response);
  5954. +    VERBOSE(VB_PLAYBACK, QString ("contents '%1'").arg (response.getContent()));
  5955. +    if (response.getContent () == "audio/mpeg") {
  5956. +        doConnectDecoder("create-mp3-decoder.mp3");
  5957. +    } else if (response.getContent () == "audio/aacp") {
  5958. +        doConnectDecoder("create-aac-decoder.m4a");
  5959. +    } else {
  5960. +        doFailed (QObject::tr ("Unsupported content type for ShoutCast stream: %1").
  5961. +                  arg (response.getContent ()));
  5962. +    }
  5963. +
  5964. +    m_timer->disconnect();
  5965. +    m_timer->stop();
  5966. +}
  5967. +
  5968. +void DecoderIOFactoryShoutCast::shoutcastMeta(const QString &metadata)
  5969. +{
  5970. +    ShoutCastMetaParser parser;
  5971. +    parser.setMetaFormat(m_meta->CompilationArtist());
  5972. +
  5973. +    ShoutCastMetaMap meta_map = parser.parseMeta(metadata);
  5974. +
  5975. +    Metadata mdata(*m_meta);
  5976. +    mdata.setTitle(meta_map["title"]);
  5977. +    mdata.setArtist(meta_map["artist"]);
  5978. +    mdata.setAlbum(m_meta->Album()); // meta_map["album"]
  5979. +    mdata.setLength(-1);
  5980. +
  5981. +    DecoderHandlerEvent ev(mdata);
  5982. +    dispatch(ev);
  5983. +}
  5984. +
  5985. +void DecoderIOFactoryShoutCast::shoutcastChangedState(ShoutCastIODevice::State state)
  5986. +{   
  5987. +    //VERBOSE(VB_PLAYBACK, QString ("ShoutCast changed state to %1").arg(ShoutCastIODevice::stateString (state)));
  5988. +    if (state == ShoutCastIODevice::RESOLVING)
  5989. +        doOperationStart("Finding radio");
  5990. +    if (state == ShoutCastIODevice::CANT_RESOLVE)
  5991. +        doFailed (QObject::tr ("Cannot find radio.\nCheck the URL is correct."));
  5992. +
  5993. +    if (state == ShoutCastIODevice::CONNECTING)
  5994. +        doOperationStart("Connecting to radio");
  5995. +    if (state == ShoutCastIODevice::CANT_CONNECT)
  5996. +        doFailed (QObject::tr ("Cannot connect to radio.\nCheck the URL is correct."));
  5997. +    if (state == ShoutCastIODevice::CONNECTED) {
  5998. +        doOperationStart("Connected to radio");
  5999. +        m_timer->stop();
  6000. +        m_timer->disconnect();
  6001. +        connect(m_timer, SIGNAL(timeout()),
  6002. +                this, SLOT(periodicallyCheckResponse()));
  6003. +        m_timer->start(300);
  6004. +    }
  6005. +    if (state == ShoutCastIODevice::PLAYING) {
  6006. +        doOperationStart("Buffering");
  6007. +    }
  6008. +
  6009. +    if (state == ShoutCastIODevice::STOPPED)
  6010. +        stop();
  6011. +}
  6012. +
  6013. +int DecoderIOFactoryShoutCast::checkResponseOK()
  6014. +{
  6015. +    ShoutCastResponse response;
  6016. +
  6017. +    if (!m_input->getResponse(response))
  6018. +        return 0;
  6019. +
  6020. +    if (! response.isICY() &&
  6021. +        response.getStatus() == 302 &&
  6022. +        ! response.getLocation().isEmpty())
  6023. +    {
  6024. +        // restart with new location...
  6025. +        setUrl(response.getLocation());
  6026. +        start();
  6027. +        return 1;
  6028. +    }
  6029. +
  6030. +    if (! response.isICY() || response.getStatus() != 200)
  6031. +        return -1;
  6032. +
  6033. +    return 0;
  6034. +}
  6035. +   
  6036. Index: mythplugins/mythmusic/mythmusic/metaio.cpp
  6037. ===================================================================
  6038. --- mythplugins/mythmusic/mythmusic/metaio.cpp  (revision 13855)
  6039. +++ mythplugins/mythmusic/mythmusic/metaio.cpp  (working copy)
  6040. @@ -2,6 +2,8 @@
  6041.  
  6042.  #include "metaio.h"
  6043.  #include "metadata.h"
  6044. +#include <qdir.h>
  6045. +#include <qurl.h>
  6046.  #include <mythtv/mythcontext.h>
  6047.  
  6048.  //==========================================================================
  6049. @@ -105,3 +107,37 @@
  6050.  
  6051.      return retdata;
  6052.  }
  6053. +
  6054. +//==========================================================================
  6055. +/*!
  6056. + * \brief Retrieve the albumart.
  6057. + *
  6058. + * The default implementation picks a random image file from the
  6059. + * directory in which the file is.
  6060. + *
  6061. + * \param filename The filename to try and determin metadata for.
  6062. + * \returns A QByteArray that can be passed to QImage or such.
  6063. + */
  6064. +QByteArray MetaIO::getAlbumArt(const QString &filename)
  6065. +{
  6066. +    QString curdir = QUrl(filename).dirPath();
  6067. +    QString namefilter = gContext->GetSetting("AlbumArtFilter",
  6068. +                                              "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
  6069. +    // Get directory contents based on filter
  6070. +    QDir folder(curdir, namefilter, QDir::Name | QDir::IgnoreCase,
  6071. +                QDir::Files | QDir::Hidden);
  6072. +   
  6073. +    if (!folder.count())
  6074. +        return QByteArray();
  6075. +   
  6076. +    QString result = folder[rand() % folder.count()];
  6077. +    result.prepend("/");
  6078. +    result.prepend(curdir);
  6079. +   
  6080. +    QFile file(result);
  6081. +    file.open(IO_ReadOnly);
  6082. +    if (!file.isOpen())
  6083. +        return QByteArray();
  6084. +
  6085. +    return file.readAll();
  6086. +}
  6087. Index: mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp
  6088. ===================================================================
  6089. --- mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp   (revision 13855)
  6090. +++ mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp   (working copy)
  6091. @@ -78,19 +78,28 @@
  6092.          return true;
  6093.  
  6094.      int numSamps = 512;
  6095. +    int step = 1;
  6096. +
  6097.      if (node->length < 512)
  6098. -        numSamps = node->length;
  6099. +        step = 512 / node->length;
  6100.  
  6101.      signed short int data[2][512];
  6102.    
  6103.      int i = 0;
  6104. -    for (i = 0; i < numSamps; i++)
  6105. +    for (i = 0; i < numSamps; i += step)
  6106.      {
  6107.          data[0][i] = node->left[i];
  6108.          if (node->right)
  6109.              data[1][i] = node->right[i];
  6110.          else
  6111.              data[1][i] = data[0][i];
  6112. +
  6113. +        if (step > 1)
  6114. +            for (int j = 1; j < step; j++)
  6115. +            {
  6116. +                data[0][i+j] = data[0][i];
  6117. +                data[1][i+j] = data[1][i];
  6118. +            }
  6119.      }
  6120.  
  6121.      for (; i < 512; i++)
  6122. Index: mythplugins/mythmusic/mythmusic/visualize.h
  6123. ===================================================================
  6124. --- mythplugins/mythmusic/mythmusic/visualize.h (revision 13855)
  6125. +++ mythplugins/mythmusic/mythmusic/visualize.h (working copy)
  6126. @@ -123,15 +123,20 @@
  6127.      virtual ~Squares();
  6128.  
  6129.      void resize (const QSize &newsize);
  6130. +    bool process(VisualNode *node = 0);
  6131.      bool draw(QPainter *p, const QColor &back = Qt::black);
  6132.      void handleKeyPress(const QString &action) {(void) action;}
  6133.  
  6134.    private:
  6135.      void drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h);
  6136. +    void rotateHues ();
  6137. +
  6138.      QSize size;
  6139.      MainVisual *pParent;
  6140.      int fake_height;
  6141.      int number_of_squares;
  6142. +    int targetHue;
  6143. +    int startHue;
  6144.  };
  6145.  
  6146.  #ifdef OPENGL_SUPPORT
  6147. Index: mythplugins/mythmusic/mythmusic/maddecoder.cpp
  6148. ===================================================================
  6149. --- mythplugins/mythmusic/mythmusic/maddecoder.cpp      (revision 13855)
  6150. +++ mythplugins/mythmusic/mythmusic/maddecoder.cpp      (working copy)
  6151. @@ -278,6 +278,9 @@
  6152.  
  6153.  void MadDecoder::seek(double pos)
  6154.  {
  6155. +    if(!input()->isDirectAccess())
  6156. +        return;
  6157. +
  6158.      seekTime = pos;
  6159.  }
  6160.  

advertising

Update the Post

Either update this post and resubmit it with changes, or make a new post.

You may also comment on this post.

update paste below
details of the post (optional)

Note: Only the paste content is required, though the following information can be useful to others.

Save name / title?

(space separated, optional)



Please note that information posted here will not expire by default. If you do not want it to expire, please set the expiry time above. If it is set to expire, web search engines will not be allowed to index it prior to it expiring. Items that are not marked to expire will be indexable by search engines. Be careful with your passwords. All illegal activities will be reported and any information will be handed over to the authorities, so be good.

comments powered by Disqus
worth-right