PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/src/web-app/flash/fogg/FOggPlayer.hx

https://github.com/FeanorsCurse/storm
Haxe | 686 lines | 384 code | 173 blank | 129 comment | 50 complexity | b490ed07f4d5948f4491622a7f04ffa0 MD5 | raw file
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // FOggPlayer - A simple Flash Ogg Vorbis player written in Haxe.
  4. //
  5. // Copyright (C) 2009 Bill Farmer
  6. //
  7. // This program is free software; you can redistribute it and/or modify
  8. // it under the terms of the GNU General Public License as published by
  9. // the Free Software Foundation; either version 2 of the License, or
  10. // (at your option) any later version.
  11. //
  12. // This program is distributed in the hope that it will be useful,
  13. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. // GNU General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU General Public License along
  18. // with this program; if not, write to the Free Software Foundation, Inc.,
  19. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. //
  21. // Bill Farmer william j farmer [at] tiscali [dot] co [dot] uk.
  22. //
  23. // Uses the development FOgg Ogg Vorbis HaXe library from xiph.org.
  24. //
  25. ////////////////////////////////////////////////////////////////////////////////
  26. import flash.Lib;
  27. import flash.Error;
  28. import flash.Vector;
  29. import flash.text.Font;
  30. import flash.geom.Matrix;
  31. import flash.utils.Timer;
  32. import flash.media.Sound;
  33. import flash.events.Event;
  34. import flash.display.Shape;
  35. import flash.net.URLLoader;
  36. import flash.net.URLStream;
  37. import flash.display.Sprite;
  38. import flash.net.URLRequest;
  39. import flash.text.TextField;
  40. import flash.errors.IOError;
  41. import flash.text.TextFormat;
  42. import flash.display.Graphics;
  43. import flash.display.MovieClip;
  44. import flash.events.MouseEvent;
  45. import flash.events.TimerEvent;
  46. import flash.media.SoundChannel;
  47. import flash.system.Capabilities;
  48. import flash.events.IOErrorEvent;
  49. import flash.display.GradientType;
  50. import flash.display.SpreadMethod;
  51. import flash.media.SoundTransform;
  52. import flash.events.ProgressEvent;
  53. import flash.display.SimpleButton;
  54. import flash.events.SampleDataEvent;
  55. import flash.media.SoundLoaderContext;
  56. import flash.events.SecurityErrorEvent;
  57. // import flash.filters.DropShadowFilter;
  58. import flash.display.InterpolationMethod;
  59. // import flash.filters.BitMapFilterQuality;
  60. // Declare the font as a class
  61. class Silkscreen extends flash.text.Font {}
  62. // FOggPlayer class
  63. class FOggPlayer
  64. {
  65. // Static colour strings and values
  66. static var COLOURS : Array<String> =
  67. ["red", "green", "blue",
  68. "cyan", "magenta", "yellow"];
  69. static var VALUES : Array<Array<Int>> =
  70. [[0xff4040, 0xc04040], [0x40ff40, 0xc0ffc0],
  71. [0x4040ff, 0x4040c0], [0x40ffff, 0x40c0c0],
  72. [0xff40ff, 0xc040c0], [0xffff40, 0xc0c040]];
  73. // Class variables
  74. var position : Float;
  75. var playTimer : Timer;
  76. var titleTimer: Timer;
  77. var volume : Float;
  78. var colour : String;
  79. var url : String;
  80. var title : String;
  81. var sound : OVSound;
  82. var titleText : TitleText;
  83. var colours : Array<Int>;
  84. var request : URLRequest;
  85. var channel : SoundChannel;
  86. // FIXME: find a better way to initialize those static bits?
  87. static function init_statics() : Void {
  88. org.xiph.fogg.Buffer._s_init();
  89. org.xiph.fvorbis.FuncFloor._s_init();
  90. org.xiph.fvorbis.FuncMapping._s_init();
  91. org.xiph.fvorbis.FuncTime._s_init();
  92. org.xiph.fvorbis.FuncResidue._s_init();
  93. }
  94. // Kick things off
  95. public static function main()
  96. {
  97. init_statics();
  98. var foggplayer = new FOggPlayer();
  99. }
  100. // Create a new FOggPlayer
  101. function new()
  102. {
  103. var clip : MovieClip = Lib.current;
  104. var g : Graphics = clip.graphics;
  105. // Create the buttons and volume control
  106. var playButton : PlayButton = new PlayButton();
  107. var pauseButton : PauseButton = new PauseButton();
  108. var stopButton : StopButton = new StopButton();
  109. var volumeControl : VolumeControl = new VolumeControl();
  110. // And the text field
  111. titleText = new TitleText();
  112. volume = 1;
  113. // Position the buttons etc
  114. pauseButton.x = playButton.width;
  115. stopButton.x = pauseButton.x + pauseButton.width;
  116. titleText.x = stopButton.x + stopButton.width + 4;
  117. titleText.y = 3;
  118. titleText.width = clip.width - ((playButton.width * 3) +
  119. volumeControl.width + 10);
  120. volumeControl.x = titleText.x + titleText.width + 3;
  121. volumeControl.y = 3;
  122. // Draw the frame background
  123. draw(g);
  124. // Add the buttons etc
  125. clip.addChild(playButton);
  126. clip.addChild(pauseButton);
  127. clip.addChild(stopButton);
  128. clip.addChild(titleText);
  129. clip.addChild(volumeControl);
  130. // If there's a url, enable the buttons, and add the event
  131. // listeners
  132. if (getParameters())
  133. {
  134. playButton.enabled = true;
  135. pauseButton.enabled = true;
  136. stopButton.enabled = true;
  137. // Change the titleText event listener to pause as replay
  138. // currently doesn't work with the FOgg decoder
  139. playButton.addEventListener(MouseEvent.CLICK, playSound);
  140. stopButton.addEventListener(MouseEvent.CLICK, stopSound);
  141. pauseButton.addEventListener(MouseEvent.CLICK, pauseSound);
  142. titleText.addEventListener(MouseEvent.CLICK, pauseSound);
  143. volumeControl.addEventListener(ValueChangeEvent.VALUE_CHANGE,
  144. changeVolume);
  145. }
  146. }
  147. // Draw the frame background
  148. function draw(g : Graphics)
  149. {
  150. // Set up a nice gradient for the right hand end of the frame
  151. // to look concave
  152. var colors : Array<Int> = [0xA0A0A0, 0xF0F0F0];
  153. var alphas : Array<Int> = [1, 1];
  154. var ratios : Array<Int> = [0, 255];
  155. var matrix : Matrix = new Matrix();
  156. matrix.createGradientBox(285, 19, Math.PI/2, 0, 0);
  157. g.beginGradientFill(GradientType.LINEAR,
  158. colors,
  159. alphas,
  160. ratios,
  161. matrix,
  162. SpreadMethod.PAD,
  163. InterpolationMethod.LINEAR_RGB,
  164. 0);
  165. // Draw the frame background
  166. g.drawRoundRect(titleText.x - 4, 0, 287, 21, 5, 5);
  167. g.endFill();
  168. // Change the colours for the text field background, and set
  169. // up another gradient
  170. colors = [0xffffff, 0xd0d0d0];
  171. matrix.createGradientBox(titleText.width - 2,
  172. titleText.height - 2,
  173. Math.PI/2, 0, 0);
  174. g.beginGradientFill(GradientType.LINEAR,
  175. colors,
  176. alphas,
  177. ratios,
  178. matrix,
  179. SpreadMethod.PAD,
  180. InterpolationMethod.LINEAR_RGB,
  181. 0);
  182. // Draw the text field background
  183. g.drawRoundRect(titleText.x, titleText.y,
  184. titleText.width, titleText.height, 5, 5);
  185. }
  186. // Get the url and title
  187. function getParameters() : Bool
  188. {
  189. var clip : MovieClip = Lib.current;
  190. var pars : Dynamic<String> = clip.loaderInfo.parameters;
  191. // Check the url
  192. if (pars.url == null)
  193. {
  194. title = "No URL";
  195. titleText.text = title;
  196. return false;
  197. }
  198. // If there's a url, see if there's a title
  199. url = pars.url;
  200. if (pars.title == null)
  201. {
  202. title = "No Title: " + url;
  203. titleText.text = title;
  204. }
  205. else
  206. {
  207. title = pars.title;
  208. titleText.text = title;
  209. }
  210. // See if there's a colour. Have to allow for those folks
  211. // across the pond who can't spell
  212. if (pars.color != null)
  213. {
  214. colour = pars.color;
  215. setColours();
  216. }
  217. if (pars.colour != null)
  218. {
  219. colour = pars.colour;
  220. setColours();
  221. }
  222. // Default colours
  223. if (colours == null)
  224. colours = [0x40ffff, 0x40c0c0];
  225. return true;
  226. }
  227. // Set colours
  228. function setColours()
  229. {
  230. var fg : Int;
  231. var bg : Int;
  232. // Does it start with '#'?
  233. if (colour.indexOf("#") == 0)
  234. {
  235. // Are there two values?
  236. var ac = colour.split(",");
  237. if (ac.length != 2)
  238. return;
  239. // Does the second one start with '#'?
  240. if (ac[1].indexOf("#") != 0)
  241. return;
  242. // Parse the two values
  243. fg = Std.parseInt("0x" + ac[0].substr(1));
  244. bg = Std.parseInt("0x" + ac[1].substr(1));
  245. colours = [fg, bg];
  246. }
  247. // Match a colour
  248. else
  249. {
  250. for (i in 0...COLOURS.length)
  251. {
  252. if (colour == COLOURS[i])
  253. {
  254. fg = VALUES[i][0];
  255. bg = VALUES[i][1];
  256. colours = [fg, bg];
  257. break;
  258. }
  259. }
  260. }
  261. }
  262. // Play button clicked, play the music
  263. function playSound(e)
  264. {
  265. // If there's a channel, stop it
  266. if (channel != null)
  267. channel.stop();
  268. // If position is zero set the sound object to null to force a
  269. // replay
  270. if (position == 0)
  271. sound = null;
  272. // If there's a url
  273. if (url != null)
  274. {
  275. // If no sound object, create one
  276. if (sound == null)
  277. {
  278. request = new URLRequest(url);
  279. sound = new OVSound();
  280. // Add the event listeners for errors
  281. sound.addEventListener(IOErrorEvent.IO_ERROR, ioError);
  282. sound.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
  283. ioError);
  284. // Load the request
  285. sound.load(request);
  286. // Set the position to the start because this is the
  287. // first time
  288. position = 0;
  289. }
  290. // Play the music
  291. if (sound != null)
  292. {
  293. // Add the event listeners for load progress, load
  294. // complete and sound open
  295. sound.addEventListener(ProgressEvent.PROGRESS, loadProgress);
  296. sound.addEventListener(Event.COMPLETE, loadComplete);
  297. sound.addEventListener(Event.OPEN, soundOpen);
  298. // Play the music, this will return null the first
  299. // time around because not enough data available
  300. channel = sound.play(position);
  301. // Set the volume, if the channel isn't null
  302. if (channel != null)
  303. {
  304. var transform = channel.soundTransform;
  305. transform.volume = volume;
  306. channel.soundTransform = transform;
  307. // Add the event listener for sound complete
  308. channel.addEventListener(Event.SOUND_COMPLETE,
  309. soundComplete);
  310. }
  311. // Start a timer to show play progress, there's no play
  312. // progress event
  313. startPlayTimer();
  314. // Set the text to show what's happening
  315. titleText.text = "Play";
  316. restoreTitle();
  317. }
  318. }
  319. }
  320. // Sound open event
  321. function soundOpen(e : OpenEvent)
  322. {
  323. channel = e.channel;
  324. // Set the volume, if the channel isn't null
  325. if (channel != null)
  326. {
  327. var transform = channel.soundTransform;
  328. transform.volume = volume;
  329. channel.soundTransform = transform;
  330. // Add the event listener for sound complete
  331. channel.addEventListener(Event.SOUND_COMPLETE,
  332. soundComplete);
  333. }
  334. }
  335. // IO error
  336. public function ioError(e : Event)
  337. {
  338. sound = null;
  339. channel = null;
  340. // Show the error
  341. titleText.text = e.type;
  342. restoreTitle();
  343. }
  344. // Pause button clicked
  345. function pauseSound(e)
  346. {
  347. // If there's a channel, save the position and stop the
  348. // channel and the timer
  349. if (channel != null)
  350. {
  351. position = channel.position;
  352. channel.stop();
  353. if (playTimer != null)
  354. playTimer.stop();
  355. // Show what's happening
  356. titleText.text = "Pause";
  357. restoreTitle();
  358. }
  359. }
  360. // Stop button clicked
  361. function stopSound(e)
  362. {
  363. // If there's a channel, stop it and the timer, and set the
  364. // position back to the start
  365. if (channel != null)
  366. {
  367. position = 0;
  368. channel.stop();
  369. if (playTimer != null)
  370. playTimer.stop();
  371. // Draw progress bar
  372. drawProgressBar(1, 0);
  373. // Show what's happening
  374. titleText.text = "Stop";
  375. restoreTitle();
  376. }
  377. }
  378. // Text field clicked, reposition the music. This currently
  379. // doesn't work with the FOgg decoder, so do nothing
  380. function replaySound(e)
  381. {
  382. }
  383. // Sound complete event
  384. public function soundComplete(e)
  385. {
  386. // Set the position back to the start
  387. position = 0;
  388. // Stop the timer
  389. if (playTimer != null)
  390. playTimer.stop();
  391. // Draw the progress bar
  392. drawProgressBar(1, 0);
  393. // Show what's happening
  394. var mins = Math.floor(sound.length / 60000);
  395. var secs = Math.round((sound.length / 10) / 100) - (mins * 60);
  396. titleText.text = "Complete: " + mins + " mins " + secs + " secs";
  397. restoreTitle();
  398. }
  399. // Load progress event
  400. public function loadProgress(e)
  401. {
  402. // Calculate the progress bar length
  403. var l = e.bytesLoaded / e.bytesTotal;
  404. // Draw the progress bar
  405. drawProgressBar(l, 0);
  406. }
  407. // Load complete event
  408. public function loadComplete(e)
  409. {
  410. // Draw the progress bar
  411. drawProgressBar(1, 0);
  412. // Show what's happening
  413. titleText.text = "Load complete: " + sound.bytesTotal + " bytes";
  414. restoreTitle();
  415. }
  416. // Start a play timer to show play progress. This currently
  417. // doesn't work with the FOgg decoder, so do nothing
  418. function startPlayTimer()
  419. {
  420. }
  421. // Play timer event, show play progress. This currently doesn't
  422. // work with the FOgg decoder, so do nothing
  423. function playProgress(e)
  424. {
  425. }
  426. // Draw progress bar
  427. function drawProgressBar(l : Float, p : Float)
  428. {
  429. var clip : MovieClip = Lib.current;
  430. var g : Graphics = clip.graphics;
  431. var lx = l * titleText.width;
  432. var px = p * titleText.width;
  433. // Draw the load progress bar, but not right away because of
  434. // the rounded corners
  435. if (lx > 3)
  436. {
  437. // Create a nice gradient
  438. var colors : Array<Int> = colours;
  439. var alphas : Array<Int> = [1, 1];
  440. var ratios : Array<Int> = [0, 255];
  441. var matrix : Matrix = new Matrix();
  442. matrix.createGradientBox(lx - 2,
  443. titleText.height - 2,
  444. Math.PI/2, 0, 0);
  445. g.beginGradientFill(GradientType.LINEAR,
  446. colors,
  447. alphas,
  448. ratios,
  449. matrix,
  450. SpreadMethod.PAD,
  451. InterpolationMethod.LINEAR_RGB,
  452. 0);
  453. // Draw the text field background
  454. g.drawRoundRect(titleText.x, titleText.y, lx,
  455. titleText.height, 5, 5);
  456. }
  457. // Draw the play progress bar, but not right away because of
  458. // the rounded corners
  459. if (px > 3)
  460. {
  461. var colors : Array<Int> = [0xffffff, 0xd0d0d0];
  462. var alphas : Array<Int> = [1, 1];
  463. var ratios : Array<Int> = [0, 255];
  464. var matrix : Matrix = new Matrix();
  465. matrix.createGradientBox(px - 2,
  466. titleText.height - 2,
  467. Math.PI/2, 0, 0);
  468. g.beginGradientFill(GradientType.LINEAR,
  469. colors,
  470. alphas,
  471. ratios,
  472. matrix,
  473. SpreadMethod.PAD,
  474. InterpolationMethod.LINEAR_RGB,
  475. 0);
  476. // Draw a nice shaded progress bar
  477. g.drawRoundRect(titleText.x, titleText.y, px,
  478. titleText.height, 5, 5);
  479. }
  480. }
  481. // Volume change event
  482. function changeVolume(e : ValueChangeEvent)
  483. {
  484. // Set the new volume
  485. volume = e.value;
  486. // If there's a channel, set the new volume on the channel
  487. if (channel != null)
  488. {
  489. var transform = channel.soundTransform;
  490. transform.volume = volume;
  491. channel.soundTransform = transform;
  492. }
  493. // Show what's happening
  494. titleText.text = "Volume: " + Math.round(volume * 100) + "%";
  495. restoreTitle();
  496. }
  497. // Restore the title after a delay
  498. function restoreTitle()
  499. {
  500. // If there's a timer, stop it
  501. if (titleTimer != null)
  502. titleTimer.stop();
  503. // Create a new timer, 2 secs
  504. titleTimer = new Timer(2000, 1);
  505. titleTimer.addEventListener(TimerEvent.TIMER, setTitle);
  506. titleTimer.start();
  507. }
  508. // Set the title after a delay
  509. function setTitle(e)
  510. {
  511. titleText.text = title;
  512. }
  513. }