PageRenderTime 115ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/SoundManager2_AS3.as

https://gitlab.com/gregtyka/SoundManager2
ActionScript | 925 lines | 717 code | 87 blank | 121 comment | 193 complexity | 7ad80ee6f347b3caffbef15e36288a85 MD5 | raw file
  1. /**
  2. * SoundManager 2: Javascript Sound for the Web
  3. * ----------------------------------------------
  4. * http://schillmania.com/projects/soundmanager2/
  5. *
  6. * Copyright (c) 2007, Scott Schiller. All rights reserved.
  7. * Code licensed under the BSD License:
  8. * http://www.schillmania.com/projects/soundmanager2/license.txt
  9. *
  10. * Flash 9 / ActionScript 3 version
  11. */
  12. package {
  13. import flash.display.Sprite;
  14. import flash.events.Event;
  15. import flash.events.IOErrorEvent;
  16. import flash.events.MouseEvent;
  17. import flash.events.SecurityErrorEvent;
  18. import flash.events.AsyncErrorEvent;
  19. import flash.events.NetStatusEvent;
  20. import flash.events.TimerEvent;
  21. import flash.external.ExternalInterface; // woo
  22. import flash.media.Sound;
  23. import flash.media.SoundChannel;
  24. import flash.media.SoundMixer;
  25. import flash.net.URLLoader;
  26. import flash.net.URLRequest;
  27. import flash.system.Security;
  28. import flash.system.System;
  29. import flash.text.TextField;
  30. import flash.text.TextFormat;
  31. import flash.text.TextFieldAutoSize;
  32. import flash.ui.ContextMenu;
  33. import flash.ui.ContextMenuItem;
  34. import flash.utils.setInterval;
  35. import flash.utils.clearInterval;
  36. import flash.utils.Dictionary;
  37. import flash.utils.Timer;
  38. public class SoundManager2_AS3 extends Sprite {
  39. public var version:String = "V2.97a.20150601";
  40. public var version_as:String = "(AS3/Flash 9)";
  41. /**
  42. * Cross-domain security options
  43. * HTML on foo.com loading .swf hosted on bar.com? Define your "HTML domain" here to allow JS+Flash communication to work.
  44. * // allow_xdomain_scripting = true;
  45. * // xdomain = "foo.com";
  46. * For all domains (possible security risk?), use xdomain = "*"; which ends up as System.security.allowDomain("*");
  47. * When loading from HTTPS, use System.security.allowInsecureDomain();
  48. * See http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/Security.html#allowDomain()
  49. */
  50. public var allow_xdomain_scripting:Boolean = false;
  51. public var xdomain:String = "*";
  52. // externalInterface references (for Javascript callbacks)
  53. public var baseJSController:String = "soundManager";
  54. public var baseJSObject:String = baseJSController + ".sounds";
  55. // internal objects
  56. public var sounds:Array = []; // indexed string array
  57. public var soundObjects: Dictionary = new Dictionary(); // associative Sound() object Dictionary type
  58. public var timer: Timer = null;
  59. public var pollingEnabled: Boolean = false; // polling (timer) flag - disabled by default, enabled by JS->Flash call
  60. public var debugEnabled: Boolean = true; // Flash debug output enabled by default, disabled by JS call
  61. public var flashDebugEnabled: Boolean = false; // Flash internal debug output (write to visible SWF in browser)
  62. public var loaded: Boolean = false;
  63. public var currentObject: SoundManager2_SMSound_AS3 = null;
  64. public var paramList:Object = null;
  65. public var messages:Array = [];
  66. public var textField: TextField = null;
  67. public var textStyle: TextFormat = new TextFormat();
  68. public var didSandboxMessage: Boolean = false;
  69. public var caughtFatal: Boolean = false;
  70. public function SoundManager2_AS3() {
  71. if (allow_xdomain_scripting && xdomain) {
  72. Security.allowDomain(xdomain);
  73. version_as += ' - cross-domain enabled';
  74. }
  75. this.paramList = this.root.loaderInfo.parameters;
  76. // <d>
  77. if (this.paramList['debug'] == 1) {
  78. this.flashDebugEnabled = true;
  79. }
  80. if (this.flashDebugEnabled) {
  81. var canvas: Sprite = new Sprite();
  82. canvas.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
  83. addChild(canvas);
  84. }
  85. // </d>
  86. flashDebug('SM2 SWF ' + version + ' ' + version_as);
  87. // context menu item with version info
  88. var sm2Menu:ContextMenu = new ContextMenu();
  89. var sm2MenuItem:ContextMenuItem = new ContextMenuItem('SoundManager ' + version + ' ' + version_as);
  90. sm2MenuItem.enabled = false;
  91. sm2Menu.customItems.push(sm2MenuItem);
  92. contextMenu = sm2Menu;
  93. if (ExternalInterface.available) {
  94. flashDebug('ExternalInterface available');
  95. try {
  96. flashDebug('Adding ExternalInterface callbacks...');
  97. ExternalInterface.addCallback('_load', _load);
  98. ExternalInterface.addCallback('_unload', _unload);
  99. ExternalInterface.addCallback('_stop', _stop);
  100. ExternalInterface.addCallback('_start', _start);
  101. ExternalInterface.addCallback('_pause', _pause);
  102. ExternalInterface.addCallback('_setPosition', _setPosition);
  103. ExternalInterface.addCallback('_setPan', _setPan);
  104. ExternalInterface.addCallback('_setVolume', _setVolume);
  105. ExternalInterface.addCallback('_setPolling', _setPolling);
  106. ExternalInterface.addCallback('_externalInterfaceTest', _externalInterfaceTest);
  107. ExternalInterface.addCallback('_disableDebug', _disableDebug);
  108. ExternalInterface.addCallback('_getMemoryUse', _getMemoryUse);
  109. ExternalInterface.addCallback('_createSound', _createSound);
  110. ExternalInterface.addCallback('_destroySound', _destroySound);
  111. ExternalInterface.addCallback('_setAutoPlay', _setAutoPlay);
  112. } catch(e: Error) {
  113. flashDebug('Fatal: ExternalInterface error: ' + e.toString());
  114. }
  115. } else {
  116. flashDebug('Fatal: ExternalInterface (Flash &lt;-&gt; JS) not available');
  117. };
  118. // call after delay, to be safe (ensure callbacks are registered by the time JS is called below)
  119. var timer: Timer = new Timer(20, 0);
  120. timer.addEventListener(TimerEvent.TIMER, function() : void {
  121. timer.reset();
  122. _externalInterfaceTest(true);
  123. // timer.reset();
  124. // flashDebug('Init OK');
  125. });
  126. timer.start();
  127. // delayed, see above
  128. // _externalInterfaceTest(true);
  129. } // SoundManager2()
  130. public function flashDebug (txt:String) : void {
  131. // <d>
  132. messages.push(txt);
  133. if (this.flashDebugEnabled) {
  134. var didCreate: Boolean = false;
  135. textStyle.font = 'Arial';
  136. textStyle.size = 12;
  137. // 320x240 if no stage dimensions (happens in IE, apparently 0 before stage resize event fires.)
  138. var w:Number = this.stage.width?this.stage.width:320;
  139. var h:Number = this.stage.height?this.stage.height:240;
  140. if (textField == null) {
  141. didCreate = true;
  142. textField = new TextField();
  143. textField.autoSize = TextFieldAutoSize.LEFT;
  144. textField.x = 0;
  145. textField.y = 0;
  146. textField.multiline = true;
  147. textField.textColor = 0;
  148. textField.wordWrap = true;
  149. }
  150. textField.htmlText = messages.join('\n');
  151. textField.setTextFormat(textStyle);
  152. textField.width = w;
  153. textField.height = h;
  154. if (didCreate) {
  155. this.addChild(textField);
  156. }
  157. }
  158. // </d>
  159. }
  160. public function _setAutoPlay(sID:String, autoPlay:Boolean) : void {
  161. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  162. if (s) {
  163. s.setAutoPlay(autoPlay);
  164. }
  165. }
  166. // methods
  167. // -----------------------------------
  168. public function writeDebug (s:String, logLevel:Number = 0) : Boolean {
  169. if (!debugEnabled) {
  170. return false;
  171. }
  172. // <d>
  173. ExternalInterface.call(baseJSController + "['_writeDebug']", "(Flash): " + s, null, logLevel);
  174. // </d>
  175. return true;
  176. }
  177. public function _externalInterfaceTest(isFirstCall: Boolean) : Boolean {
  178. var sandboxType:String = flash.system.Security['sandboxType'];
  179. if (!didSandboxMessage && sandboxType != 'localTrusted' && sandboxType != 'remote') {
  180. didSandboxMessage = true;
  181. flashDebug('<br><b>Fatal: Security sandbox error: Got "' + sandboxType + '", expected "remote" or "localTrusted".<br>Additional security permissions need to be granted.<br>See <a href="http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html">flash security settings panel</a> for non-HTTP, eg., file:// use.</b><br>http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html<br><br>You may also be able to right-click this movie and choose from the menu: <br>"Global Settings" -> "Advanced" tab -> "Trusted Location Settings"<br>');
  182. }
  183. try {
  184. if (isFirstCall == true) {
  185. flashDebug('Testing Flash -&gt; JS...');
  186. ExternalInterface.call(baseJSController + "._externalInterfaceOK", version);
  187. flashDebug('Flash -&gt; JS OK');
  188. flashDebug('Waiting for JS -&gt; Flash...');
  189. } else {
  190. // writeDebug('SM2 SWF ' + version + ' ' + version_as, 1);
  191. ExternalInterface.call(baseJSController + "._setSandboxType", sandboxType);
  192. flashDebug('JS -&gt; Flash OK');
  193. }
  194. } catch(e: Error) {
  195. flashDebug('Fatal: Flash &lt;-&gt; JS error: ' + e.toString());
  196. writeDebug('_externalInterfaceTest: Error: ' + e.toString(), 2);
  197. if (!caughtFatal) {
  198. caughtFatal = true;
  199. }
  200. return false;
  201. }
  202. return true; // to verify that a call from JS to here, works. (eg. JS receives "true", thus OK.)
  203. }
  204. public function _disableDebug() : void {
  205. // prevent future debug calls from Flash going to client (maybe improve performance)
  206. writeDebug('_disableDebug()');
  207. debugEnabled = false;
  208. }
  209. public function checkLoadProgress(e: Event) : void {
  210. try {
  211. var oSound:Object = e.target;
  212. var bL: int = oSound.bytesLoaded;
  213. var bT: int = oSound.bytesTotal;
  214. var nD: int = oSound.length || oSound.duration || 0;
  215. var sMethod:String = baseJSObject + "['" + oSound.sID + "']._whileloading";
  216. ExternalInterface.call(sMethod, bL, bT, nD);
  217. if (bL && bT && bL != oSound.lastValues.bytes) {
  218. oSound.lastValues.bytes = bL;
  219. ExternalInterface.call(sMethod, bL, bT, nD);
  220. }
  221. } catch(e: Error) {
  222. writeDebug('checkLoadProgress(): ' + e.toString());
  223. }
  224. }
  225. public function checkSoundProgress(oSound:SoundManager2_SMSound_AS3, forceCheck:Boolean = false, forceEndCheck:Boolean = false) : void {
  226. var bL: int = 0;
  227. var bT: int = 0;
  228. var nD: int = 0;
  229. var nP: int = 0;
  230. var bufferLength: int = 0;
  231. var lP:Number = 0;
  232. var rP:Number = 0;
  233. var isBuffering:Object = null;
  234. var oSoundChannel: flash.media.SoundChannel = null;
  235. var sMethod:String = null;
  236. var newPeakData: Boolean = false;
  237. var newWaveformData: Boolean = false;
  238. var newEQData: Boolean = false;
  239. var areSoundsInaccessible: Boolean = SoundMixer.areSoundsInaccessible();
  240. var isPlaying: Boolean = true; // special case for NetStream when ending
  241. var hasNew:Boolean = false;
  242. var hasNewLoaded:Boolean = false;
  243. if (!oSound || !oSound.useEvents || oSound.failed || !oSound.connected) {
  244. // edge cases for ignoring: if sounds are destructed within event handlers while checkProgress() is running, may be null
  245. return;
  246. }
  247. sMethod = baseJSObject + "['" + oSound.sID + "']._whileloading";
  248. if (oSound.useNetstream) {
  249. // Don't do anything if there is no NetStream object yet
  250. if (!oSound.ns) {
  251. return;
  252. }
  253. // stream
  254. bufferLength = oSound.ns.bufferLength;
  255. bL = oSound.ns.bytesLoaded;
  256. bT = oSound.ns.bytesTotal;
  257. nD = int(oSound.duration || 0); // can sometimes be null with short MP3s? Wack.
  258. nP = oSound.ns.time * 1000;
  259. if (oSound.paused) {
  260. // special case: paused netStreams don't update if setPosition() is called while they are paused.
  261. // instead, return lastValues.position which should reflect setPosition() call.
  262. // writeDebug('paused case, setting nP of '+nP+' to -1');
  263. // writeDebug('lastValues: '+oSound.lastValues.position);
  264. nP = oSound.lastValues.position;
  265. }
  266. if (nP >= 0 && nP != oSound.lastValues.position) {
  267. oSound.lastValues.position = nP;
  268. hasNew = true;
  269. }
  270. if (nD > oSound.lastValues.duration) {
  271. oSound.lastValues.duration = nD;
  272. hasNew = true;
  273. }
  274. if (bL > oSound.lastValues.bytesLoaded) {
  275. oSound.lastValues.bytesLoaded = bL;
  276. hasNew = true;
  277. }
  278. if (bT > oSound.lastValues.bytes) {
  279. oSound.lastValues.bytes = bT;
  280. hasNew = true;
  281. }
  282. if (bufferLength != oSound.lastValues.bufferLength) {
  283. oSound.lastValues.bufferLength = bufferLength;
  284. hasNew = true;
  285. }
  286. // Don't set loaded for streams because bytesLoaded and bytesTotal are always 0
  287. // writeDebug('ns: time/duration, bytesloaded/total: '+nP+'/'+nD+', '+bL+'/'+bT);
  288. if (oSound.loaded != true && nD > 0 && bL == bT && bL != 0 && bT != 0) {
  289. // non-MP3 has loaded
  290. oSound.loaded = true;
  291. try {
  292. ExternalInterface.call(sMethod, bL, bT, nD, bufferLength);
  293. ExternalInterface.call(baseJSObject + "['" + oSound.sID + "']._onload", oSound.duration > 0 ? 1 : 0);
  294. } catch(e: Error) {
  295. writeDebug('_whileLoading/_onload error: ' + e.toString(), 2);
  296. }
  297. } else if (oSound.loaded != true && hasNew) {
  298. ExternalInterface.call(sMethod, bL, bT, nD, bufferLength);
  299. }
  300. } else {
  301. // MP3 sound
  302. oSoundChannel = oSound.soundChannel;
  303. bL = oSound.bytesLoaded;
  304. bT = oSound.bytesTotal;
  305. nD = int(oSound.length || 0); // can sometimes be null with short MP3s? Wack.
  306. isBuffering = oSound.isBuffering;
  307. if (oSoundChannel) {
  308. nP = (oSoundChannel.position || 0);
  309. if (oSound.lastValues.loops > 1 && nP > oSound.length) {
  310. // round down to nearest loop
  311. var playedLoops:Number = Math.floor(nP/oSound.length);
  312. nP = nP - (oSound.length*playedLoops);
  313. }
  314. if (oSound.usePeakData) {
  315. lP = int((oSoundChannel.leftPeak) * 1000) / 1000;
  316. rP = int((oSoundChannel.rightPeak) * 1000) / 1000;
  317. } else {
  318. lP = 0;
  319. rP = 0;
  320. }
  321. } else {
  322. // stopped, not loaded or feature not used
  323. nP = 0;
  324. }
  325. if (forceEndCheck) {
  326. // sound finish case: Ensure position is at end (sound duration), as flash 9 does not always correctly match the two.
  327. if (nP < nD) {
  328. writeDebug('correcting sound ' + oSound.sID + ' end position ('+nP+') to length: '+ nD, 2);
  329. nP = nD;
  330. }
  331. }
  332. if (nP != oSound.lastValues.position && nP !== 0 && !oSound.didFinish) { // once "completed", sound is locked via didFinish so no more position-type events fire.
  333. oSound.lastValues.position = nP;
  334. hasNew = true;
  335. }
  336. if (nD > oSound.lastValues.duration) { // original sound duration * number of sound loops
  337. oSound.lastValues.duration = nD;
  338. hasNew = true;
  339. }
  340. if (bL > oSound.lastValues.bytesLoaded) {
  341. oSound.lastValues.bytesLoaded = bL;
  342. hasNew = true;
  343. }
  344. if (bT > oSound.lastValues.bytes) {
  345. oSound.lastValues.bytes = bT;
  346. hasNew = true;
  347. hasNewLoaded = true;
  348. }
  349. // loading progress
  350. if (hasNewLoaded) {
  351. oSound.lastValues.bytes = bL;
  352. ExternalInterface.call(sMethod, bL, bT, nD);
  353. }
  354. }
  355. // peak data
  356. if (oSoundChannel && oSound.usePeakData) {
  357. if (lP != oSound.lastValues.leftPeak) {
  358. oSound.lastValues.leftPeak = lP;
  359. newPeakData = true;
  360. }
  361. if (rP != oSound.lastValues.rightPeak) {
  362. oSound.lastValues.rightPeak = rP;
  363. newPeakData = true;
  364. }
  365. }
  366. var newDataError:Boolean = false;
  367. var dataError:String;
  368. // special case: Netstream may try to fire whileplaying() after finishing. check that stop hasn't fired.
  369. isPlaying = (oSound.didLoad && !oSound.paused && (!oSound.useNetstream || (oSound.useNetstream && oSound.lastNetStatus != "NetStream.Play.Stop"))); // don't update if stream has ended
  370. // raw waveform + EQ spectrum data
  371. if (isPlaying && (oSoundChannel || oSound.useNetstream)) {
  372. if (oSound.useWaveformData) {
  373. if (!areSoundsInaccessible && !oSound.handledDataError && !oSound.ignoreDataError) {
  374. try {
  375. oSound.getWaveformData();
  376. } catch(e: Error) {
  377. if (!oSound.handledDataError) {
  378. writeDebug('getWaveformData() (waveform data) '+e.toString());
  379. }
  380. // oSound.useWaveformData = false;
  381. newDataError = true;
  382. dataError = e.toString();
  383. }
  384. }
  385. }
  386. if (oSound.useEQData) {
  387. if (!areSoundsInaccessible && !oSound.handledDataError && !oSound.ignoreDataError) {
  388. try {
  389. oSound.getEQData();
  390. } catch(e: Error) {
  391. if (!oSound.handledDataError) {
  392. writeDebug('computeSpectrum() (EQ data) '+e.toString());
  393. }
  394. // oSound.useEQData = false;
  395. newDataError = true;
  396. dataError = e.toString();
  397. }
  398. }
  399. }
  400. if (oSound.waveformDataArray != oSound.lastValues.waveformDataArray) {
  401. oSound.lastValues.waveformDataArray = oSound.waveformDataArray;
  402. newWaveformData = true;
  403. }
  404. if (oSound.eqDataArray != oSound.lastValues.eqDataArray) {
  405. oSound.lastValues.eqDataArray = oSound.eqDataArray;
  406. newEQData = true;
  407. }
  408. if (newDataError && !oSound.handledDataError) {
  409. sMethod = baseJSObject + "['" + oSound.sID + "']._ondataerror";
  410. ExternalInterface.call(sMethod, 'data unavailable: ' + dataError);
  411. oSound.handledDataError = true;
  412. }
  413. }
  414. if (typeof nP != 'undefined' && (hasNew && (oSound.soundChannel || oSound.useNetstream || forceCheck || forceEndCheck))) { // && isPlaying - removed to allow updates while paused, eg. from setPosition() calls. Also be more liberal if we're using netStream.
  415. // oSound.lastValues.position = nP;
  416. sMethod = baseJSObject + "['" + oSound.sID + "']._whileplaying";
  417. var waveDataLeft:String = (newWaveformData ? oSound.waveformDataArray.slice(0, 256).join(',') : null);
  418. var waveDataRight:String = (newWaveformData ? oSound.waveformDataArray.slice(256).join(',') : null);
  419. var eqDataLeft:String = (newEQData ? oSound.eqDataArray.slice(0, 256).join(',') : null);
  420. var eqDataRight:String = (newEQData ? oSound.eqDataArray.slice(256).join(',') : null);
  421. ExternalInterface.call(sMethod, nP, (newPeakData ? {
  422. leftPeak: lP,
  423. rightPeak: rP
  424. } : null), waveDataLeft, waveDataRight, (newEQData ? {
  425. leftEQ: eqDataLeft,
  426. rightEQ: eqDataRight
  427. } : null));
  428. }
  429. // check isBuffering
  430. if (!oSound.useNetstream && oSound.isBuffering != oSound.lastValues.isBuffering) {
  431. // property has changed
  432. oSound.lastValues.isBuffering = oSound.isBuffering;
  433. sMethod = baseJSObject + "['" + oSound.sID + "']._onbufferchange";
  434. ExternalInterface.call(sMethod, oSound.isBuffering ? 1 : 0);
  435. }
  436. }
  437. public function checkProgress() : void {
  438. for (var i: int = 0, j: int = sounds.length; i < j; i++) {
  439. checkSoundProgress(soundObjects[sounds[i]]);
  440. }
  441. }
  442. public function onLoadError(oSound:Object) : void {
  443. // something went wrong. 404, bad format etc.
  444. ExternalInterface.call(baseJSObject + "['" + oSound.sID + "']._onload", 0);
  445. }
  446. public function onLoad(e: Event) : void {
  447. var oSound:Object = e.target;
  448. checkSoundProgress(soundObjects[oSound.sID]); // ensure progress stats are up-to-date
  449. if (!oSound.useNetstream) { // FLV must also have metadata
  450. oSound.loaded = true;
  451. // force duration update (doesn't seem to be always accurate)
  452. ExternalInterface.call(baseJSObject + "['" + oSound.sID + "']._whileloading", oSound.bytesLoaded, oSound.bytesTotal, oSound.length || oSound.duration);
  453. // duration > 0 means a valid sound was loaded.
  454. ExternalInterface.call(baseJSObject + "['" + oSound.sID + "']._onload", (oSound.length || oSound.duration ? 1 : 0));
  455. }
  456. }
  457. public function onID3(e: Event) : void {
  458. // --- NOTE: BUGGY (Flash 8 only? Haven't really checked 9 + 10.) ---
  459. // TODO: Investigate holes in ID3 parsing - for some reason, Album will be populated with Date if empty and date is provided. (?)
  460. // ID3V1 seem to parse OK, but "holes" / blanks in ID3V2 data seem to get messed up (eg. missing album gets filled with date.)
  461. // iTunes issues: onID3 was not called with a test MP3 encoded with iTunes 7.01, and what appeared to be valid ID3V2 data.
  462. // May be related to thumbnails for album art included in MP3 file by iTunes. See http://mabblog.com/blog/?p=33
  463. try {
  464. var oSound:Object = e.target;
  465. var id3Data:Array = [];
  466. var id3Props:Array = [];
  467. for (var prop:String in oSound.id3) {
  468. id3Props.push(prop);
  469. id3Data.push(oSound.id3[prop]);
  470. // writeDebug('id3['+prop+']: '+oSound.id3[prop]);
  471. }
  472. ExternalInterface.call(baseJSObject + "['" + oSound.sID + "']._onid3", id3Props, id3Data);
  473. // unhook own event handler, prevent second call (can fire twice as data is received - ID3V2 at beginning, ID3V1 at end.)
  474. // Therefore if ID3V2 data is received, ID3V1 is ignored.
  475. // soundObjects[oSound.sID].onID3 = null;
  476. } catch(e: Error) {
  477. writeDebug('onID3(): Unable to get ID3 info for ' + oSound.sID + '.');
  478. }
  479. oSound.removeEventListener(Event.ID3, onID3);
  480. }
  481. public function registerOnComplete(sID:String) : void {
  482. var oSound: SoundManager2_SMSound_AS3 = soundObjects[sID];
  483. if (oSound && oSound.soundChannel) {
  484. oSound.didFinish = false; // reset this flag
  485. oSound.soundChannel.addEventListener(Event.SOUND_COMPLETE, function() : void {
  486. if (oSound) {
  487. // force progress check, catching special end-of-sound case where position may not match duration.
  488. checkSoundProgress(oSound, true, true);
  489. try {
  490. oSound.ignoreDataError = true; // workaround: avoid data error handling for this manual step..
  491. // oSound.soundChannel.stop();
  492. oSound.didFinish = true; // "lock" sound, prevent extra whileplaying() position-type updates
  493. // call onfinish first (with end position)...
  494. ExternalInterface.call(baseJSObject + "['" + sID + "']._onfinish");
  495. // then reset sound so it can be played again.
  496. // oSound.start(0, 1); // go back to 0
  497. } catch(e: Error) {
  498. writeDebug('Could not set position on ' + sID + ': ' + e.toString());
  499. }
  500. oSound.ignoreDataError = false; // ..and reset
  501. oSound.handledDataError = false; // reset this flag
  502. } else {
  503. // safety net
  504. ExternalInterface.call(baseJSObject + "['" + sID + "']._onfinish");
  505. }
  506. });
  507. }
  508. }
  509. public function doSecurityError(oSound: SoundManager2_SMSound_AS3, e: SecurityErrorEvent) : void {
  510. writeDebug('securityError: ' + e.text);
  511. // when this happens, you don't have security rights on the server containing the FLV file
  512. // a crossdomain.xml file would fix the problem easily
  513. }
  514. public function _setPosition(sID:String, nSecOffset:Number, isPaused: Boolean, allowMultiShot: Boolean) : void {
  515. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  516. if (!s) return void;
  517. // writeDebug('_setPosition()');
  518. // stop current channel, start new one.
  519. if (s.lastValues) {
  520. s.lastValues.position = nSecOffset; // s.soundChannel.position;
  521. }
  522. if (s.useNetstream) {
  523. // Minimize the buffer so playback starts ASAP
  524. s.ns.bufferTime = s.bufferTime;
  525. writeDebug('setPosition ('+ sID + '): setting buffer to '+s.ns.bufferTime+' secs');
  526. nSecOffset = nSecOffset > 0 ? nSecOffset / 1000 : 0;
  527. s.ns.seek(nSecOffset);
  528. checkSoundProgress(s); // force UI update
  529. } else {
  530. if (s.soundChannel) {
  531. s.soundChannel.stop();
  532. }
  533. writeDebug('setPosition ('+ sID + '): ' + nSecOffset); // +', '+(s.lastValues.loops?s.lastValues.loops:1));
  534. if (s.lastValues.loops > 1 && nSecOffset != 0) {
  535. writeDebug('Warning: Looping functionality being disabled due to Flash limitation.');
  536. s.lastValues.loops = 1;
  537. }
  538. try {
  539. s.start(nSecOffset, s.lastValues.loops || 1, allowMultiShot); // start playing at new position
  540. } catch(e: Error) {
  541. writeDebug('Warning: Could not set position on ' + sID + ': ' + e.toString());
  542. }
  543. checkSoundProgress(s); // force UI update
  544. try {
  545. registerOnComplete(sID);
  546. } catch(e: Error) {
  547. writeDebug('_setPosition(): Could not register onComplete');
  548. }
  549. if (isPaused && s.soundChannel) {
  550. // writeDebug('_setPosition: stopping (paused) sound');
  551. // writeDebug('last position: '+s.lastValues.position+' vs '+s.soundChannel.position);
  552. s.soundChannel.stop();
  553. }
  554. }
  555. }
  556. public function _load(sID:String, sURL:String, bStream: Boolean, bAutoPlay: Boolean, nLoops: Number, bAutoLoad: Boolean, bCheckPolicyFile: Boolean) : void {
  557. // writeDebug('_load()');
  558. if (typeof bAutoPlay == 'undefined') bAutoPlay = false;
  559. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  560. if (!s) return void;
  561. var didRecreate: Boolean = false;
  562. if (s.didLoad == true) {
  563. // need to recreate sound
  564. didRecreate = true;
  565. writeDebug('recreating sound ' + sID + ' in order to load ' + sURL);
  566. var ns:Object = new Object();
  567. ns.sID = s.sID;
  568. ns.loops = nLoops||1;
  569. ns.usePeakData = s.usePeakData;
  570. ns.useWaveformData = s.useWaveformData;
  571. ns.useEQData = s.useEQData;
  572. ns.useNetstream = s.useNetstream;
  573. ns.bufferTime = s.bufferTime;
  574. ns.serverUrl = s.serverUrl;
  575. ns.duration = s.duration;
  576. ns.checkPolicyFile = s.checkPolicyFile;
  577. ns.useEvents = true;
  578. _destroySound(s.sID);
  579. _createSound(ns.sID, sURL, ns.usePeakData, ns.useWaveformData, ns.useEQData, ns.useNetstream, ns.bufferTime, ns.loops, ns.serverUrl, ns.duration, bAutoPlay, ns.useEvents, bAutoLoad, ns.checkPolicyFile);
  580. s = soundObjects[sID];
  581. // writeDebug('Sound object replaced');
  582. }
  583. checkSoundProgress(s);
  584. if (!s.didLoad) {
  585. try {
  586. s.addEventListener(Event.ID3, onID3);
  587. s.addEventListener(Event.COMPLETE, onLoad);
  588. } catch(e: Error) {
  589. writeDebug('_load(): could not assign ID3/complete event handlers');
  590. }
  591. }
  592. // don't try to load if same request already made
  593. s.sURL = sURL;
  594. try {
  595. if (!s.useNetstream) {
  596. s.addEventListener(IOErrorEvent.IO_ERROR, function(e: IOErrorEvent) : void {
  597. s.doIOError(e);
  598. });
  599. }
  600. s.loadSound(sURL);
  601. } catch(e: Error) {
  602. // oh well
  603. writeDebug('_load: Error loading ' + sURL + '. Flash error detail: ' + e.toString());
  604. }
  605. }
  606. public function _unload(sID:String) : void {
  607. var s: SoundManager2_SMSound_AS3 = (soundObjects[sID] || null);
  608. if (!s) return void;
  609. var sURL:String = s.sURL; // save existing sound URL for object recreation
  610. try {
  611. removeEventListener(Event.ID3, onID3);
  612. removeEventListener(Event.COMPLETE, onLoad);
  613. } catch(e: Error) {
  614. writeDebug('_unload() warn: Could not remove ID3/complete events');
  615. }
  616. s.paused = false;
  617. if (s.soundChannel) {
  618. s.soundChannel.stop();
  619. }
  620. try {
  621. if (s.didLoad && !s.loaded && !s.useNetstream) {
  622. s.close(); // close stream only if still loading?
  623. }
  624. } catch(e: Error) {
  625. // stream may already have closed if sound loaded, etc.
  626. writeDebug(sID + '._unload(): Note: Unable to close stream: ' + e.toString());
  627. // oh well
  628. }
  629. // destroy and recreate Flash sound object, try to reclaim memory
  630. // writeDebug('sound._unload(): recreating sound '+sID+' to free memory');
  631. if (s.useNetstream) {
  632. // writeDebug('_unload(): closing netStream stuff');
  633. try {
  634. s.removeNetstreamEvents();
  635. s.ns.close();
  636. s.nc.close();
  637. // s.nc = null;
  638. // s.ns = null;
  639. } catch(e: Error) {
  640. // oh well
  641. writeDebug('_unload(): caught exception during netConnection/netStream close');
  642. }
  643. }
  644. var ns:Object = new Object();
  645. ns.sID = s.sID;
  646. ns.loops = s.loops;
  647. ns.usePeakData = s.usePeakData;
  648. ns.useWaveformData = s.useWaveformData;
  649. ns.useEQData = s.useEQData;
  650. ns.useNetstream = s.useNetstream;
  651. ns.bufferTime = s.bufferTime;
  652. ns.serverUrl = s.serverUrl;
  653. ns.duration = s.duration;
  654. ns.autoPlay = s.autoPlay;
  655. ns.autoLoad = s.autoLoad;
  656. ns.checkPolicyFile = s.checkPolicyFile;
  657. _destroySound(s.sID);
  658. _createSound(ns.sID, sURL, ns.usePeakData, ns.useWaveformData, ns.useEQData, ns.useNetstream, ns.bufferTime, ns.loops, ns.serverUrl, ns.duration, ns.autoPlay, false, ns.autoLoad, ns.checkPolicyFile); // false: don't allow events just yet
  659. soundObjects[sID].connected = true; // fake it?
  660. writeDebug(s.sID + '.unload(): ok');
  661. }
  662. public function _createSound(sID:String, sURL:String, usePeakData: Boolean, useWaveformData: Boolean, useEQData: Boolean, useNetstream: Boolean, bufferTime:Number, loops:Number, serverUrl:String, duration:Number, autoPlay:Boolean, useEvents:Boolean, autoLoad:Boolean, checkPolicyFile:Boolean) : void {
  663. var s: SoundManager2_SMSound_AS3 = new SoundManager2_SMSound_AS3(this, sID, sURL, usePeakData, useWaveformData, useEQData, useNetstream, bufferTime, serverUrl, duration, autoPlay, useEvents, autoLoad, checkPolicyFile);
  664. if (!soundObjects[sID]) {
  665. sounds.push(sID);
  666. }
  667. soundObjects[sID] = s;
  668. this.currentObject = s;
  669. s.sID = sID;
  670. s.sURL = sURL;
  671. s.paused = false;
  672. s.loaded = false;
  673. s.checkPolicyFile = checkPolicyFile;
  674. s.lastValues = {
  675. bytes: 0,
  676. duration: 0,
  677. position: 0,
  678. loops: loops||1,
  679. leftPeak: 0,
  680. rightPeak: 0,
  681. bufferLength: 0
  682. };
  683. }
  684. public function _destroySound(sID:String) : void {
  685. // for the power of garbage collection! .. er, Greyskull!
  686. var s: SoundManager2_SMSound_AS3 = (soundObjects[sID] || null);
  687. if (!s) return void;
  688. // try to unload the sound
  689. for (var i: int = 0, j: int = sounds.length; i < j; i++) {
  690. if (sounds[i] == sID) {
  691. sounds.splice(i, 1);
  692. break;
  693. }
  694. }
  695. if (s.soundChannel) {
  696. s.soundChannel.stop();
  697. }
  698. // if is a movie, remove that as well.
  699. if (s.useNetstream) {
  700. // s.nc.client = null;
  701. try {
  702. s.removeNetstreamEvents();
  703. // s.nc.removeEventListener(NetStatusEvent.NET_STATUS, s.doNetStatus);
  704. } catch(e: Error) {
  705. writeDebug('_destroySound(): Events already removed from netStream/netConnection?');
  706. }
  707. if (s.didLoad) {
  708. // TODO: figure out if stream is still open first, can't close an already-closed stream.
  709. try {
  710. s.ns.close();
  711. s.nc.close();
  712. } catch(e: Error) {
  713. // oh well
  714. writeDebug('_destroySound(): caught exception: '+e.toString());
  715. }
  716. }
  717. } else if (s.didLoad) {
  718. // non-netstream case
  719. try {
  720. s.close(); // close stream only if still loading?
  721. } catch(e: Error) {
  722. // oh well
  723. }
  724. }
  725. s = null;
  726. soundObjects[sID] = null;
  727. delete soundObjects[sID];
  728. }
  729. public function _stop(sID:String, bStopAll: Boolean) : void {
  730. // stop this particular instance (or "all", based on parameter)
  731. if (bStopAll) {
  732. SoundMixer.stopAll();
  733. } else {
  734. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  735. if (!s) return void;
  736. if (s.useNetstream && s.ns) {
  737. s.ns.pause();
  738. } else if (s.soundChannel) {
  739. s.soundChannel.stop();
  740. }
  741. s.paused = false;
  742. }
  743. }
  744. public function _start(sID:String, nLoops: int, nMsecOffset: int, allowMultiShot: Boolean) : Boolean {
  745. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  746. var result: Boolean;
  747. if (!s) return true;
  748. writeDebug('start (' + sID + '): ' + nMsecOffset + (nLoops > 1 ? ', loops: ' + nLoops : ''));
  749. s.lastValues.paused = false; // reset pause if applicable
  750. s.lastValues.loops = (nLoops || 1);
  751. if (!s.useNetstream) {
  752. s.lastValues.position = nMsecOffset;
  753. }
  754. s.handledDataError = false; // reset this flag
  755. try {
  756. result = s.start(nMsecOffset, nLoops, allowMultiShot);
  757. } catch(e: Error) {
  758. writeDebug('Could not start ' + sID + ': ' + e.toString());
  759. }
  760. try {
  761. registerOnComplete(sID);
  762. } catch(e: Error) {
  763. writeDebug('_start(): registerOnComplete failed');
  764. }
  765. return result;
  766. }
  767. public function _pause(sID:String, allowMultiShot: Boolean) : void {
  768. // writeDebug('_pause(): ' + sID);
  769. var s: SoundManager2_SMSound_AS3 = soundObjects[sID];
  770. if (!s) return void;
  771. // writeDebug('s.paused: '+s.paused);
  772. if (!s.paused) {
  773. // reference current position, stop sound
  774. s.paused = true;
  775. // writeDebug('_pause(): position: '+s.lastValues.position);
  776. if (s.useNetstream) {
  777. if (s.ns) {
  778. s.lastValues.position = s.ns.time*1000;
  779. s.ns.pause();
  780. } else if (s.autoPlay) {
  781. s.setAutoPlay(false);
  782. }
  783. } else {
  784. if (s.soundChannel) {
  785. s.lastValues.position = s.soundChannel.position;
  786. s.soundChannel.stop();
  787. }
  788. }
  789. } else {
  790. // resume playing from last position
  791. // writeDebug('resuming - playing at '+s.lastValues.position+', '+s.lastValues.loops+' times');
  792. s.paused = false;
  793. if (s.useNetstream) {
  794. s.ns.resume();
  795. } else {
  796. s.start(s.lastValues.position, s.lastValues.loops, allowMultiShot);
  797. }
  798. try {
  799. registerOnComplete(sID);
  800. } catch(e: Error) {
  801. writeDebug('_pause(): registerOnComplete() failed');
  802. }
  803. }
  804. }
  805. public function _setPan(sID:String, nPan:Number) : void {
  806. if (soundObjects[sID]) {
  807. soundObjects[sID].setPan(nPan);
  808. }
  809. }
  810. public function _setVolume(sID:String, nVol:Number) : void {
  811. // writeDebug('_setVolume: '+nVol);
  812. if (soundObjects[sID]) {
  813. soundObjects[sID].setVolume(nVol);
  814. }
  815. }
  816. public function _setPolling(bPolling: Boolean = false, nTimerInterval: uint = 50) : void {
  817. pollingEnabled = bPolling;
  818. if (timer == null && pollingEnabled) {
  819. flashDebug('Enabling polling, ' + nTimerInterval + ' ms interval');
  820. timer = new Timer(nTimerInterval, 0);
  821. timer.addEventListener(TimerEvent.TIMER, function() : void {
  822. checkProgress();
  823. }); // direct reference eg. checkProgress doesn't work? .. odd.
  824. timer.start();
  825. } else if (timer && !pollingEnabled) {
  826. flashDebug('Disabling polling');
  827. // flash.utils.clearInterval(timer);
  828. timer.reset();
  829. }
  830. }
  831. public function _getMemoryUse() : String {
  832. return System.totalMemory.toString();
  833. }
  834. // -----------------------------------
  835. // end methods
  836. }
  837. // package
  838. }