PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/SoundManager2_SMSound_AS3.as

https://github.com/darkseed/SoundManager2
ActionScript | 427 lines | 328 code | 45 blank | 54 comment | 47 complexity | f2079f2f7b759a4b0450ca0576d2340e MD5 | raw file
  1. /*
  2. SoundManager 2: Javascript Sound for the Web
  3. ----------------------------------------------
  4. http://schillmania.com/projects/soundmanager2/
  5. Copyright (c) 2007, Scott Schiller. All rights reserved.
  6. Code licensed under the BSD License:
  7. http://www.schillmania.com/projects/soundmanager2/license.txt
  8. Flash 9 / ActionScript 3 version
  9. */
  10. package {
  11. import flash.external.*;
  12. import flash.events.*;
  13. import flash.display.Sprite;
  14. import flash.display.StageDisplayState;
  15. import flash.display.StageScaleMode;
  16. import flash.display.StageAlign;
  17. import flash.geom.Rectangle;
  18. import flash.media.Sound;
  19. import flash.media.SoundChannel;
  20. import flash.media.SoundLoaderContext;
  21. import flash.media.SoundTransform;
  22. import flash.media.SoundMixer;
  23. import flash.media.Video;
  24. import flash.net.URLRequest;
  25. import flash.utils.ByteArray;
  26. import flash.net.NetConnection;
  27. import flash.net.NetStream;
  28. public class SoundManager2_SMSound_AS3 extends Sound {
  29. public var sm: SoundManager2_AS3 = null;
  30. // externalInterface references (for Javascript callbacks)
  31. public var baseJSController: String = "soundManager";
  32. public var baseJSObject: String = baseJSController + ".sounds";
  33. public var soundChannel: SoundChannel = new SoundChannel();
  34. public var urlRequest: URLRequest;
  35. public var soundLoaderContext: SoundLoaderContext;
  36. public var waveformData: ByteArray = new ByteArray();
  37. public var waveformDataArray: Array = [];
  38. public var eqData: ByteArray = new ByteArray();
  39. public var eqDataArray: Array = [];
  40. public var usePeakData: Boolean = false;
  41. public var useWaveformData: Boolean = false;
  42. public var useEQData: Boolean = false;
  43. public var sID: String;
  44. public var sURL: String;
  45. public var justBeforeFinishOffset: int;
  46. public var didJustBeforeFinish: Boolean;
  47. public var didFinish: Boolean;
  48. public var loaded: Boolean;
  49. public var connected: Boolean;
  50. public var failed: Boolean;
  51. public var paused: Boolean;
  52. public var finished: Boolean;
  53. public var duration: Number;
  54. public var totalBytes: Number;
  55. public var handledDataError: Boolean = false;
  56. public var ignoreDataError: Boolean = false;
  57. public var autoPlay: Boolean = false;
  58. public var pauseOnBufferFull: Boolean = true;
  59. public var loops: Number = 1;
  60. public var lastValues: Object = {
  61. bytes: 0,
  62. position: 0,
  63. volume: 100,
  64. pan: 0,
  65. loops: 1,
  66. leftPeak: 0,
  67. rightPeak: 0,
  68. waveformDataArray: null,
  69. eqDataArray: null,
  70. isBuffering: null,
  71. bufferLength: 0
  72. };
  73. public var didLoad: Boolean = false;
  74. public var useEvents: Boolean = false;
  75. public var sound: Sound = new Sound();
  76. public var cc: Object;
  77. public var nc: NetConnection;
  78. public var ns: NetStream = null;
  79. public var st: SoundTransform;
  80. public var useNetstream: Boolean;
  81. public var useVideo: Boolean = false;
  82. public var bufferTime: Number = 3;
  83. // public var bufferTime: Number = 0.1;
  84. public var lastNetStatus: String = null;
  85. public var serverUrl: String = null;
  86. public var oVideo: Video = null;
  87. public var videoWidth: Number = 0;
  88. public var videoHeight: Number = 0;
  89. public function SoundManager2_SMSound_AS3(oSoundManager: SoundManager2_AS3, sIDArg: String = null, sURLArg: String = null, usePeakData: Boolean = false, useWaveformData: Boolean = false, useEQData: Boolean = false, useNetstreamArg: Boolean = false, useVideoArg: Boolean = false, netStreamBufferTime: Number = 1, serverUrl: String = null, duration: Number = 0, totalBytes: Number = 0, autoPlay: Boolean = false, useEvents: Boolean = false) {
  90. this.sm = oSoundManager;
  91. this.sID = sIDArg;
  92. this.sURL = sURLArg;
  93. this.usePeakData = usePeakData;
  94. this.useWaveformData = useWaveformData;
  95. this.useEQData = useEQData;
  96. this.urlRequest = new URLRequest(sURLArg);
  97. this.justBeforeFinishOffset = 0;
  98. this.didJustBeforeFinish = false;
  99. this.didFinish = false; // non-MP3 formats only
  100. this.loaded = false;
  101. this.connected = false;
  102. this.failed = false;
  103. this.finished = false;
  104. this.soundChannel = null;
  105. this.lastNetStatus = null;
  106. this.useNetstream = useNetstreamArg;
  107. this.serverUrl = serverUrl;
  108. this.duration = duration;
  109. this.totalBytes = totalBytes;
  110. this.useEvents = useEvents;
  111. this.useVideo = useVideoArg;
  112. if (netStreamBufferTime) {
  113. this.bufferTime = netStreamBufferTime;
  114. }
  115. setAutoPlay(autoPlay);
  116. writeDebug('SoundManager2_SMSound_AS3: Got duration: '+duration+', totalBytes: '+totalBytes+', autoPlay: '+autoPlay);
  117. if (this.useNetstream) {
  118. this.cc = new Object();
  119. this.nc = new NetConnection();
  120. // Handle FMS bandwidth check callback.
  121. // @see onBWDone
  122. // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
  123. // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
  124. this.nc.client = this;
  125. // TODO: security/IO error handling
  126. // this.nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doSecurityError);
  127. nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
  128. if (this.serverUrl != null) {
  129. writeDebug('SoundManager2_SMSound_AS3: NetConnection: connecting to server ' + this.serverUrl + '...');
  130. }
  131. this.nc.connect(serverUrl);
  132. } else {
  133. this.connected = true;
  134. }
  135. }
  136. private function netStatusHandler(event:NetStatusEvent):void {
  137. if (this.useEvents) {
  138. writeDebug('netStatusHandler: '+event.info.code);
  139. }
  140. switch (event.info.code) {
  141. case "NetConnection.Connect.Success":
  142. writeDebug('NetConnection: connected');
  143. try {
  144. this.ns = new NetStream(this.nc);
  145. this.ns.checkPolicyFile = true;
  146. // bufferTime reference: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#bufferTime
  147. this.ns.bufferTime = this.bufferTime; // set to 0.1 or higher. 0 is reported to cause playback issues with static files.
  148. this.st = new SoundTransform();
  149. this.cc.onMetaData = this.metaDataHandler;
  150. this.ns.client = this.cc;
  151. this.ns.receiveAudio(true);
  152. if (this.useVideo) {
  153. this.oVideo = new Video();
  154. this.ns.receiveVideo(true);
  155. this.sm.stage.addEventListener(Event.RESIZE, this.resizeHandler);
  156. this.oVideo.smoothing = true; // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/Video.html#smoothing
  157. this.oVideo.visible = false; // hide until metadata received
  158. this.sm.addChild(this.oVideo);
  159. this.oVideo.attachNetStream(this.ns);
  160. writeDebug('setting video w/h to stage: ' + this.sm.stage.stageWidth + 'x' + this.sm.stage.stageHeight);
  161. this.oVideo.width = this.sm.stage.stageWidth;
  162. this.oVideo.height = this.sm.stage.stageHeight;
  163. }
  164. this.connected = true;
  165. if (this.useEvents) {
  166. writeDebug('firing _onconnect for '+this.sID);
  167. ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 1);
  168. }
  169. } catch(e: Error) {
  170. this.failed = true;
  171. writeDebug('netStream error: ' + e.toString());
  172. ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onfailure", 'Connection failed!');
  173. }
  174. break;
  175. case "NetStream.Play.StreamNotFound":
  176. this.failed = true;
  177. writeDebug("NetConnection: Stream not found!");
  178. ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onfailure", 'Stream not found!');
  179. break;
  180. case "NetConnection.Connect.Closed":
  181. this.failed = true;
  182. writeDebug("NetConnection: Connection closed!");
  183. ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onfailure", 'Connection closed!');
  184. break;
  185. default:
  186. this.failed = true;
  187. writeDebug("NetConnection: got unhandled code '" + event.info.code + "'!");
  188. ExternalInterface.call(this.sm.baseJSObject + "['" + this.sID + "']._onconnect", 0);
  189. break;
  190. }
  191. }
  192. public function resizeHandler(e: Event) : void {
  193. // scale video to stage dimensions
  194. // probably less performant than using native flash scaling, but that doesn't quite seem to work. I'm probably missing something simple.
  195. this.oVideo.width = this.sm.stage.stageWidth;
  196. this.oVideo.height = this.sm.stage.stageHeight;
  197. }
  198. public function writeDebug (s: String, bTimestamp: Boolean = false) : Boolean {
  199. return this.sm.writeDebug (s, bTimestamp); // defined in main SM object
  200. }
  201. public function doNetStatus(e: NetStatusEvent) : void {
  202. writeDebug('netStatusEvent: ' + e.info.code);
  203. }
  204. public function metaDataHandler(infoObject: Object) : void {
  205. /*
  206. var data:String = new String();
  207. for (var prop:* in infoObject) {
  208. data += prop+': '+infoObject[prop]+' ';
  209. }
  210. ExternalInterface.call('soundManager._writeDebug','Metadata: '+data);
  211. */
  212. if (this.oVideo) {
  213. // set dimensions accordingly
  214. if (!infoObject.width && !infoObject.height) {
  215. writeDebug('No width/height specified, using stage dimensions');
  216. infoObject.width = this.sm.stage.width;
  217. infoObject.height = this.sm.stage.height;
  218. }
  219. writeDebug('video dimensions: ' + infoObject.width + 'x' + infoObject.height + ' (w/h)');
  220. this.videoWidth = infoObject.width;
  221. this.videoHeight = infoObject.height;
  222. // implement a subset of metadata to pass over EI bridge
  223. // some formats have extra stuff, eg. "aacaot", "avcprofile"
  224. // http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00000267.html
  225. var oMeta: Object = new Object();
  226. var item: Object = null;
  227. for (item in infoObject) {
  228. // exclude seekpoints for now, presumed not useful and overly large.
  229. if (item != 'seekpoints') {
  230. oMeta[item] = infoObject[item];
  231. }
  232. }
  233. ExternalInterface.call(baseJSObject + "['" + this.sID + "']._onmetadata", oMeta);
  234. writeDebug('showing video for ' + this.sID);
  235. this.oVideo.visible = true; // show ze video!
  236. }
  237. if (!this.loaded) {
  238. // writeDebug('not loaded yet: '+this.ns.bytesLoaded+', '+this.ns.bytesTotal+', '+infoObject.duration*1000);
  239. // TODO: investigate loaded/total values
  240. // ExternalInterface.call(baseJSObject + "['" + this.sID + "']._whileloading", this.ns.bytesLoaded, this.ns.bytesTotal, infoObject.duration*1000);
  241. ExternalInterface.call(baseJSObject + "['" + this.sID + "']._whileloading", this.bytesLoaded, (this.bytesTotal || this.totalBytes), (infoObject.duration || this.duration))
  242. }
  243. this.duration = infoObject.duration * 1000;
  244. // null this out for the duration of this object's existence.
  245. // it may be called multiple times.
  246. this.cc.onMetaData = function (infoObject: Object) : void {}
  247. }
  248. public function getWaveformData() : void {
  249. // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
  250. SoundMixer.computeSpectrum(this.waveformData, false, 0); // sample wave data at 44.1 KHz
  251. this.waveformDataArray = [];
  252. for (var i: int = 0, j: int = this.waveformData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
  253. this.waveformDataArray.push(int(this.waveformData.readFloat() * 1000) / 1000);
  254. }
  255. }
  256. public function getEQData() : void {
  257. // http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundMixer.html#computeSpectrum()
  258. SoundMixer.computeSpectrum(this.eqData, true, 0); // sample EQ data at 44.1 KHz
  259. this.eqDataArray = [];
  260. for (var i: int = 0, j: int = this.eqData.length / 4; i < j; i++) { // get all 512 values (256 per channel)
  261. this.eqDataArray.push(int(this.eqData.readFloat() * 1000) / 1000);
  262. }
  263. }
  264. public function start(nMsecOffset: int, nLoops: int) : void {
  265. this.sm.currentObject = this; // reference for video, full-screen
  266. this.useEvents = true;
  267. if (this.useNetstream) {
  268. writeDebug("SMSound::start nMsecOffset "+ nMsecOffset+ ' nLoops '+nLoops + ' current bufferTime '+this.ns.bufferTime+' current bufferLength '+this.ns.bufferLength+ ' this.lastValues.position '+this.lastValues.position);
  269. this.cc.onMetaData = this.metaDataHandler;
  270. // Don't seek if we don't have to because it destroys the buffer
  271. if (this.lastValues.position != null && this.lastValues.position != nMsecOffset) {
  272. // Minimize the buffer so playback starts ASAP
  273. this.ns.bufferTime = this.bufferTime;
  274. writeDebug('setting buffer to '+this.bufferTime+' secs');
  275. this.ns.seek(nMsecOffset);
  276. this.lastValues.position = nMsecOffset; // https://gist.github.com/1de8a3113cf33d0cff67
  277. }
  278. if (this.paused) {
  279. writeDebug('start: resuming from paused state');
  280. this.ns.resume(); // get the sound going again
  281. if (!this.didLoad) {
  282. this.didLoad = true;
  283. }
  284. this.paused = false;
  285. } else if (!this.didLoad) {
  286. writeDebug('start: !didLoad - playing '+this.sURL);
  287. this.ns.play(this.sURL);
  288. this.didLoad = true;
  289. this.paused = false;
  290. } else {
  291. // previously loaded, perhaps stopped/finished. play again?
  292. writeDebug('playing again (not paused, didLoad = true)');
  293. this.ns.play(this.sURL);
  294. }
  295. // this.ns.addEventListener(Event.SOUND_COMPLETE, _onfinish);
  296. this.applyTransform();
  297. } else {
  298. // writeDebug('start: seeking to '+nMsecOffset+', '+nLoops+(nLoops==1?' loop':' loops'));
  299. this.soundChannel = this.play(nMsecOffset, nLoops);
  300. this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
  301. this.applyTransform();
  302. }
  303. }
  304. private function _onfinish() : void {
  305. this.removeEventListener(Event.SOUND_COMPLETE, _onfinish);
  306. }
  307. public function loadSound(sURL: String, bStream: Boolean) : void {
  308. if (this.useNetstream) {
  309. this.useEvents = true;
  310. if (this.didLoad != true) {
  311. ExternalInterface.call('loadSound(): loading ' + this.sURL);
  312. this.ns.play(this.sURL);
  313. this.didLoad = true;
  314. }
  315. // this.addEventListener(Event.SOUND_COMPLETE, _onfinish);
  316. this.applyTransform();
  317. } else {
  318. try {
  319. this.didLoad = true;
  320. this.urlRequest = new URLRequest(sURL);
  321. this.soundLoaderContext = new SoundLoaderContext(1000, true); // check for policy (crossdomain.xml) file on remote domains - http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/media/SoundLoaderContext.html
  322. this.load(this.urlRequest, this.soundLoaderContext);
  323. } catch(e: Error) {
  324. writeDebug('error during loadSound(): ' + e.toString());
  325. }
  326. }
  327. }
  328. public function setAutoPlay(autoPlay: Boolean) : void {
  329. if (!this.serverUrl) {
  330. // don't apply to non-RTMP, netstream stuff.
  331. this.autoPlay = true;
  332. this.pauseOnBufferFull = false;
  333. } else {
  334. this.autoPlay = autoPlay;
  335. if (this.autoPlay) {
  336. this.pauseOnBufferFull = false;
  337. writeDebug('ignoring pauseOnBufferFull because autoPlay is on');
  338. } else if (!this.autoPlay) {
  339. this.pauseOnBufferFull = true;
  340. writeDebug('pausing on buffer full because autoPlay is off');
  341. }
  342. }
  343. }
  344. public function setVolume(nVolume: Number) : void {
  345. this.lastValues.volume = nVolume / 100;
  346. this.applyTransform();
  347. }
  348. public function setPan(nPan: Number) : void {
  349. this.lastValues.pan = nPan / 100;
  350. this.applyTransform();
  351. }
  352. public function applyTransform() : void {
  353. var st: SoundTransform = new SoundTransform(this.lastValues.volume, this.lastValues.pan);
  354. if (this.useNetstream) {
  355. if (this.ns) {
  356. this.ns.soundTransform = st;
  357. } else {
  358. // writeDebug('applyTransform(): Note: No active netStream');
  359. }
  360. } else if (this.soundChannel) {
  361. this.soundChannel.soundTransform = st; // new SoundTransform(this.lastValues.volume, this.lastValues.pan);
  362. }
  363. }
  364. // Handle FMS bandwidth check callback.
  365. // @see http://www.adobe.com/devnet/flashmediaserver/articles/dynamic_stream_switching_04.html
  366. // @see http://www.johncblandii.com/index.php/2007/12/fms-a-quick-fix-for-missing-onbwdone-onfcsubscribe-etc.html
  367. public function onBWDone() : void {
  368. // writeDebug('onBWDone: called and ignored');
  369. }
  370. // NetStream client callback. Invoked when the song is complete.
  371. public function onPlayStatus(info:Object):void {
  372. writeDebug('onPlayStatus called with '+info);
  373. switch(info.code) {
  374. case "NetStream.Play.Complete":
  375. writeDebug('Song has finished!');
  376. break;
  377. }
  378. }
  379. }
  380. }