PageSourceSearch

https://islam.cz/wp-content/plugins/quran-text-multilanguage/js/soundmanager.js?ver=1.0

js islam.cz collected 2026-10-02 21:34:05 UTC 146,194 bytes, 11,083 lines download raw bytes

1/** @license
2
3 *
4
5 * SoundManager 2: JavaScript Sound for the Web
6
7 * ----------------------------------------------
8
9 * http://schillmania.com/projects/soundmanager2/
10
11 *
12
13 * Copyright (c) 2007, Scott Schiller. All rights reserved.
14
15 * Code provided under the BSD License:
16
17 * http://schillmania.com/projects/soundmanager2/license.txt
18
19 *
20
21 * V2.97a.20120916
22
23 */
24
25
26
27/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio */
28
29/*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true */
30
31
32
33/**
34
35 * About this file
36
37 * ---------------
38
39 * This is the fully-commented source version of the SoundManager 2 API,
40
41 * recommended for use during development and testing.
42
43 *
44
45 * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)
46
47 * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion
48
49 * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)
50
51 *
52
53 * You may notice <d> and </d> comments in this source; these are delimiters for
54
55 * debug blocks which are removed in the -nodebug builds, further optimizing code size.
56
57 *
58
59 * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)
60
61 */
62
63
64 jQuery(document).ready(function($){
65
66    $.urlParam = function(name){
67      var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
68      if (results==null){
69         return null;
70      }
71      else{
72         return results[1] || 0;
73      }
74
75  }
76
77    $('span.ayaNum, .sm2_link').replaceWith(function(){
78
79    var suraName = $.urlParam('sourate');
80  
81    var sura  = suraName.match(/\d+/);
82
83    return "<a class='sm2_link' href='https://quran.s3.fr-par.scw.cloud/verset/Soudais/" +sura+ "/"+jQuery(this).html().match(/[0-9]+/)+".mp3'><span class='quranbadge quranbadge-info' id='kv"+jQuery(this).html().match(/[0-9]+/)+"'>  "+jQuery(this).html().match(/[0-9]+/)+" </span></a>";
84
85    });
86});     
87
88
89(function(window) {
90
91
92
93var soundManager = null;
94
95
96
97/**
98
99 * The SoundManager constructor.
100
101 *
102
103 * @constructor
104
105 * @param {string} smURL Optional: Path to SWF files
106
107 * @param {string} smID Optional: The ID to use for the SWF container element
108
109 * @this {SoundManager}
110
111 * @return {SoundManager} The new SoundManager instance
112
113 */
114
115
116
117function SoundManager(smURL, smID) {
118
119
120
121  /**
122
123   * soundManager configuration options list
124
125   * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
126
127   * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
128
129   */
130
131
132
133  this.setupOptions = {
134
135
136
137    'url': (smURL || null),             // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
138
139    'flashVersion': 8,                  // flash build to use (8 or 9.) Some API features require 9.
140
141    'debugMode': true,                  // enable debugging output (console.log() with HTML fallback)
142
143    'debugFlash': false,                // enable debugging output inside SWF, troubleshoot Flash/browser issues
144
145    'useConsole': true,                 // use console.log() if available (otherwise, writes to #soundmanager-debug element)
146
147    'consoleOnly': true,                // if console is being used, do not create/write to #soundmanager-debug
148
149    'waitForWindowLoad': false,         // force SM2 to wait for window.onload() before trying to call soundManager.onload()
150
151    'bgColor': '#ffffff',               // SWF background color. N/A when wmode = 'transparent'
152
153    'useHighPerformance': false,        // position:fixed flash movie can help increase js/flash speed, minimize lag
154
155    'flashPollingInterval': null,       // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
156
157    'html5PollingInterval': null,       // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
158
159    'flashLoadTimeout': 1000,           // msec to wait for flash movie to load before failing (0 = infinity)
160
161    'wmode': null,                      // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
162
163    'allowScriptAccess': 'always',      // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
164
165    'useFlashBlock': false,             // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
166
167    'useHTML5Audio': true,              // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
168
169    'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
170
171    'preferFlash': true,                // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky 
171in browsers.
172
173    'noSWFCache': false                 // if true, appends ?ts={date} to break aggressive SWF caching.
174
175
176
177  };
178
179
180
181  this.defaultOptions = {
182
183
184
185    /**
186
187     * the default configuration for sound objects made with createSound() and related methods
188
189     * eg., volume, auto-load behaviour and so forth
190
191     */
192
193
194
195    'autoLoad': false,        // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
196
197    'autoPlay': false,        // enable playing of file as soon as possible (much faster if "stream" is true)
198
199    'from': null,             // position to start playback within a sound (msec), default = beginning
200
201    'loops': 1,               // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)
202
203    'onid3': null,            // callback function for "ID3 data is added/available"
204
205    'onload': null,           // callback function for "load finished"
206
207    'whileloading': null,     // callback function for "download progress update" (X of Y bytes received)
208
209    'onplay': null,           // callback for "play" start
210
211    'onpause': null,          // callback for "pause"
212
213    'onresume': null,         // callback for "resume" (pause toggle)
214
215    'whileplaying': null,     // callback during play (position update)
216
217    'onposition': null,       // object containing times and function callbacks for positions of interest
218
219    'onstop': null,           // callback for "user stop"
220
221    'onfailure': null,        // callback function for when playing fails
222
223    'onfinish': null,         // callback function for "sound finished playing"
224
225    'multiShot': true,        // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
226
227    'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled
228
229    'position': null,         // offset (milliseconds) to seek to within loaded sound data.
230
231    'pan': 0,                 // "pan" settings, left-to-right, -100 to 100
232
233    'stream': true,           // allows playing before entire file has loaded (recommended)
234
235    'to': null,               // position to end playback within a sound (msec), default = end
236
237    'type': null,             // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3
238
239    'usePolicyFile': false,   // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)
240
241    'volume': 100             // self-explanatory. 0-100, the latter being the max.
242
243
244
245  };
246
247
248
249  this.flash9Options = {
250
251
252
253    /**
254
255     * flash 9-only options,
256
257     * merged into defaultOptions if flash 9 is being used
258
259     */
260
261
262
263    'isMovieStar': null,      // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL
264
265    'usePeakData': false,     // enable left/right channel peak (level) data
266
267    'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.
268
269    'useEQData': false,       // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.
270
271    'onbufferchange': null,   // callback for "isBuffering" property change
272
273    'ondataerror': null       // callback for waveform/eq data access error (flash playing audio in other tabs/domains)
274
275
276
277  };
278
279
280
281  this.movieStarOptions = {
282
283
284
285    /**
286
287     * flash 9.0r115+ MPEG4 audio options,
288
289     * merged into defaultOptions if flash 9+movieStar mode is enabled
290
291     */
292
293
294
295    'bufferTime': 3,          // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)
296
297    'serverURL': null,        // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants
298
299    'onconnect': null,        // rtmp: callback for connection to flash media server
300
301    'duration': null          // rtmp: song duration (msec)
302
303
304
305  };
306
307
308
309  this.audioFormats = {
310
311
312
313    /**
314
315     * determines HTML5 support + flash requirements.
316
317     * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
318
319     * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
320
321     */
322
323
324
325    'mp3': {
326
327      'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
328
329      'required': true
330
331    },
332
333
334
335    'mp4': {
336
337      'related': ['aac','m4a','m4b'], // additional formats under the MP4 container
338
339      'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
340
341      'required': false
342
343    },
344
345
346
347    'ogg': {
348
349      'type': ['audio/ogg; codecs=vorbis'],
350
351      'required': false
352
353    },
354
355
356
357    'wav': {
358
359      'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
360
361      'required': false
362
363    }
364
365
366
367  };
368
369
370
371  // HTML attributes (id + class names) for the SWF container
372
373
374
375  this.movieID = 'sm2-container';
376
377  this.id = (smID || 'sm2movie');
378
379
380
381  this.debugID = 'soundmanager-debug';
382
383  this.debugURLParam = /([#?&])debug=1/i;
384
385
386
387  // dynamic attributes
388
389
390
391  this.versionNumber = 'V2.97a.20120916';
392
393  this.version = null;
394
395  this.movieURL = null;
396
397  this.altURL = null;
398
399  this.swfLoaded = false;
400
401  this.enabled = false;
402
403  this.oMC = null;
404
405  this.sounds = {};
406
407  this.soundIDs = [];
408
409  this.muted = false;
410
411  this.didFlashBlock = false;
412
413  this.filePattern = null;
414
415
416
417  this.filePatterns = {
418
419
420
421    'flash8': /\.mp3(\?.*)?$/i,
422
423    'flash9': /\.mp3(\?.*)?$/i
424
425
426
427  };
428
429
430
431  // support indicators, set at init
432
433
434
435  this.features = {
436
437
438
439    'buffering': false,
440
441    'peakData': false,
442
443    'waveformData': false,
444
445    'eqData': false,
446
447    'movieStar': false
448
449
450
451  };
452
453
454
455  // flash sandbox info, used primarily in troubleshooting
456
457
458
459  this.sandbox = {
460
461
462
463    // <d>
464
465    'type': null,
466
467    'types': {
468
469      'remote': 'remote (domain-based) rules',
470
471      'localWithFile': 'local with file access (no internet access)',
472
473      'localWithNetwork': 'local with network (internet access only, no local access)',
474
475      'localTrusted': 'local, trusted (local+internet access)'
476
477    },
478
479    'description': null,
480
481    'noRemote': null,
482
483    'noLocal': null
484
485    // </d>
486
487
488
489  };
490
491
492
493  /**
494
495   * basic HTML5 Audio() support test
496
497   * try...catch because of IE 9 "not implemented" nonsense
498
499   * https://github.com/Modernizr/Modernizr/issues/224
500
501   */
502
503
504
505  this.hasHTML5 = (function() {
506
507    try {
508
509      // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.
510
511      return (typeof Audio !== 'undefined' && typeof (_isOpera && opera.version() < 10 ? new Audio(null) : new Audio()).canPlayType !== 'undefined');
512
513    } catch(e) {
514
515      return false;
516
517    }
518
519  }());
520
521
522
523  /**
524
525   * format support (html5/flash)
526
527   * stores canPlayType() results based on audioFormats.
528
529   * eg. { mp3: boolean, mp4: boolean }
530
531   * treat as read-only.
532
533   */
534
535
536
537  this.html5 = {
538
539    'usingFlash': null // set if/when flash fallback is needed
540
541  };
542
543
544
545  // file type support hash
546
547  this.flash = {};
548
549
550
551  // determined at init time
552
553  this.html5Only = false;
554
555
556
557  // used for special cases (eg. iPad/iPhone/palm OS?)
558
559  this.ignoreFlash = false;
560
561
562
563  /**
564
565   * a few private internals (OK, a lot. :D)
566
567   */
568
569
570
571  var SMSound,
572
573  _s = this, _flash = null, _sm = 'soundManager', _smc = _sm+'::', _h5 = 'HTML5::', _id, _ua = navigator.userAgent, _win = window, _wl = _win.location.href.toString(), _doc = document, _doNothing, _setProperties, _init, _fV, _on_queue = [], _debugOpen = true, _debugTS, _didAppend = false, _appendSuccess = false, _didInit = false, _disabled = false, _windowLoaded = false, _wDS, _wdCount = 0, _initComplete, _mixin, _assign, _extraOptions, _addOnEvent, _processOnEvents, _initUserOnload, _delayWaitForEI, _waitForEI, _setVersionInfo, _handleFocus, _strings, _initMovie, _domContentLoaded, _winOnLoad, _didDCLoaded, _getDocument, _createMovie, _catchError, _setPolling, _initDebug, _debugLevels = ['log', 'info', 'warn', 'error'], _defaultFlashVersion = 8, _disableObject, _failSafely, _normalizeMovieURL, _oRemoved = null, _oRemovedHTML = null, _str, _flashBlockHandler, _getSWFCSS, _swfCSS, _toggleDebug, _loopFix, _policyFix, _complain, _idCheck, _waitingForEI = false, _initPending = false, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL,
574
575  _needsFlash = null, _featureCheck, _html5OK, _html5CanPlay, _html5Ext, _html5Unload, _domContentLoadedIE, _testHTML5, _event, _slice = Array.prototype.slice, _useGlobalHTML5Audio = false, _hasFlash, _detectFlash, _badSafariFix, _html5_events, _showSupport,
576
577  _is_iDevice = _ua.match(/(ipad|iphone|ipod)/i), _isIE = _ua.match(/msie/i), _isWebkit = _ua.match(/webkit/i), _isSafari = (_ua.match(/safari/i) && !_ua.match(/chrome/i)), _isOpera = (_ua.match(/opera/i)), 
578
579  _mobileHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice),
580
581  _isBadSafari = (!_wl.match(/usehtml5audio/i) && !_wl.match(/sm2\-ignorebadua/i) && _isSafari && !_ua.match(/silk/i) && _ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159
582
583  _hasConsole = (typeof console !== 'undefined' && typeof console.log !== 'undefined'), _isFocused = (typeof _doc.hasFocus !== 'undefined'?_doc.hasFocus():null), _tryInitOnFocus = (_isSafari && (typeof _doc.hasFocus === 'undefined' || !_doc.hasFocus())), _okToDisable = !_tryInitOnFocus, _flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i,
584
585  _emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
586
587  _overHTTP = (_doc.location?_doc.location.protocol.match(/http/i):null),
588
589  _http = (!_overHTTP ? 'http:/'+'/' : ''),
590
591  // mp3, mp4, aac etc.
592
593  _netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,
594
595  // Flash v9.0r115+ "moviestar" formats
596
597  _netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],
598
599  _netStreamPattern = new RegExp('\\.(' + _netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
600
601
602
603  this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
604
605
606
607  // use altURL if not "online"
608
609  this.useAltURL = !_overHTTP;
610
611
612
613  this._global_a = null;
614
615
616
617  _swfCSS = {
618
619
620
621    'swfBox': 'sm2-object-box',
622
623    'swfDefault': 'movieContainer',
624
625    'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)
626
627    'swfTimedout': 'swf_timedout',
628
629    'swfLoaded': 'swf_loaded',
630
631    'swfUnblocked': 'swf_unblocked', // or loaded OK
632
633    'sm2Debug': 'sm2_debug',
634
635    'highPerf': 'high_performance',
636
637    'flashDebug': 'flash_debug'
638
639
640
641  };
642
643
644
645  if (_mobileHTML5) {
646
647
648
649    // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
650
651    _s.useHTML5Audio = true;
652
653    _s.preferFlash = false;
654
655
656
657    if (_is_iDevice) {
658
659      // by default, use global feature. iOS onfinish() -> next may fail otherwise.
660
661      _s.ignoreFlash = true;
662
663      _useGlobalHTML5Audio = true;
664
665    }
666
667
668
669  }
670
671
672
673  /**
674
675   * Public SoundManager API
676
677   * -----------------------
678
679   */
680
681
682
683  /**
684
685   * Configures top-level soundManager properties.
686
687   *
688
689   * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
690
691   * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
692
693   */
694
695
696
697  this.setup = function(options) {
698
699
700
701    var noURL = (!_s.url);
702
703
704
705    // warn if flash options have already been applied
706
707
708
709    if (typeof options !== 'undefined' && _didInit && _needsFlash && _s.ok() && (typeof options.flashVersion !== 'undefined' || typeof options.url !== 'undefined')) {
710
711      _complain(_str('setupLate'));
712
713    }
714
715
716
717    // TODO: defer: true?
718
719
720
721    _assign(options);
722
723
724
725    // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.
726
727
728
729    if (noURL && _didDCLoaded && typeof options.url !== 'undefined') {
730
731      _s.beginDelayedInit();
732
733    }
734
735
736
737    // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
738
739
740
741    if (!_didDCLoaded && typeof options.url !== 'undefined' && _doc.readyState === 'complete') {
742
743      setTimeout(_domContentLoaded, 1);
744
745    }
746
747
748
749    return _s;
750
751
752
753  };
754
755
756
757  this.ok = function() {
758
759
760
761    return (_needsFlash?(_didInit && !_disabled):(_s.useHTML5Audio && _s.hasHTML5));
762
763
764
765  };
766
767
768
769  this.supported = this.ok; // legacy
770
771
772
773  this.getMovie = function(smID) {
774
775
776
777    // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version
778
779    return _id(smID) || _doc[smID] || _win[smID];
780
781
782
783  };
784
785
786
787  /**
788
789   * Creates a SMSound sound object instance.
790
791   *
792
793   * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
794
795   * @return {object} SMSound The new SMSound object.
796
797   */
798
799
800
801  this.createSound = function(oOptions, _url) {
802
803
804
805    var _cs, _cs_string, thisOptions = null, oSound = null, _tO = null;
806
807
808
809    // <d>
810
811    _cs = _sm+'.createSound(): ';
812
813    _cs_string = _cs + _str(!_didInit?'notReady':'notOK');
814
815    // </d>
816
817
818
819    if (!_didInit || !_s.ok()) {
820
821      _complain(_cs_string);
822
823      return false;
824
825    }
826
827
828
829    if (typeof _url !== 'undefined') {
830
831      // function overloading in JS! :) ..assume simple createSound(id,url) use case
832
833      oOptions = {
834
835        'id': oOptions,
836
837        'url': _url
838
839      };
840
841    }
842
843
844
845    // inherit from defaultOptions
846
847    thisOptions = _mixin(oOptions);
848
849
850
851    thisOptions.url = _parseURL(thisOptions.url);
852
853
854
855    // local shortcut
856
857    _tO = thisOptions;
858
859
860
861    // <d>
862
863    if (_tO.id.toString().charAt(0).match(/^[0-9]$/)) {
864
865      _s._wD(_cs + _str('badID', _tO.id), 2);
866
867    }
868
869
870
871    _s._wD(_cs + _tO.id + ' (' + _tO.url + ')', 1);
872
873    // </d>
874
875
876
877    if (_idCheck(_tO.id, true)) {
878
879      _s._wD(_cs + _tO.id + ' exists', 1);
880
881      return _s.sounds[_tO.id];
882
883    }
884
885
886
887    function make() {
888
889
890
891      thisOptions = _loopFix(thisOptions);
892
893      _s.sounds[_tO.id] = new SMSound(_tO);
894
895      _s.soundIDs.push(_tO.id);
896
897      return _s.sounds[_tO.id];
898
899
900
901    }
902
903
904
905    if (_html5OK(_tO)) {
906
907
908
909      oSound = make();
910
911      _s._wD('Creating sound '+_tO.id+', using HTML5');
912
913      oSound._setup_html5(_tO);
914
915
916
917    } else {
918
919
920
921      if (_fV > 8) {
922
923        if (_tO.isMovieStar === null) {
924
925          // attempt to detect MPEG-4 formats
926
927          _tO.isMovieStar = !!(_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern));
928
929        }
930
931        // <d>
932
933        if (_tO.isMovieStar) {
934
935          _s._wD(_cs + 'using MovieStar handling');
936
937          if (_tO.loops > 1) {
938
939            _wDS('noNSLoop');
940
941          }
942
943        }
944
945        // </d>
946
947      }
948
949
950
951      _tO = _policyFix(_tO, _cs);
952
953      oSound = make();
954
955
956
957      if (_fV === 8) {
958
959        _flash._createSound(_tO.id, _tO.loops||1, _tO.usePolicyFile);
960
961      } else {
962
963        _flash._createSound(_tO.id, _tO.url, _tO.usePeakData, _tO.useWaveformData, _tO.useEQData, _tO.isMovieStar, (_tO.isMovieStar?_tO.bufferTime:false), _tO.loops||1, _tO.serverURL, _tO.duration||null, _tO.autoPlay, true, _tO.autoLoad, _tO.usePolicyFile);
964
965        if (!_tO.serverURL) {
966
967          // We are connected immediately
968
969          oSound.connected = true;
970
971          if (_tO.onconnect) {
972
973            _tO.onconnect.apply(oSound);
974
975          }
976
977        }
978
979      }
980
981
982
983      if (!_tO.serverURL && (_tO.autoLoad || _tO.autoPlay)) {
984
985        // call load for non-rtmp streams
986
987        oSound.load(_tO);
988
989      }
990
991
992
993    }
994
995
996
997    // rtmp will play in onconnect
998
999    if (!_tO.serverURL && _tO.autoPlay) {
1000
1001      oSound.play();
1002
1003    }
1004
1005
1006
1007    return oSound;
1008
1009
1010
1011  };
1012
1013
1014
1015  /**
1016
1017   * Destroys a SMSound sound object instance.
1018
1019   *
1020
1021   * @param {string} sID The ID of the sound to destroy
1022
1023   */
1024
1025
1026
1027  this.destroySound = function(sID, _bFromSound) {
1028
1029
1030
1031    // explicitly destroy a sound before normal page unload, etc.
1032
1033
1034
1035    if (!_idCheck(sID)) {
1036
1037      return false;
1038
1039    }
1040
1041
1042
1043    var oS = _s.sounds[sID], i;
1044
1045
1046
1047    // Disable all callbacks while the sound is being destroyed
1048
1049    oS._iO = {};
1050
1051
1052
1053    oS.stop();
1054
1055    oS.unload();
1056
1057
1058
1059    for (i = 0; i < _s.soundIDs.length; i++) {
1060
1061      if (_s.soundIDs[i] === sID) {
1062
1063        _s.soundIDs.splice(i, 1);
1064
1065        break;
1066
1067      }
1068
1069    }
1070
1071
1072
1073    if (!_bFromSound) {
1074
1075      // ignore if being called from SMSound instance
1076
1077      oS.destruct(true);
1078
1079    }
1080
1081
1082
1083    oS = null;
1084
1085    delete _s.sounds[sID];
1086
1087
1088
1089    return true;
1090
1091
1092
1093  };
1094
1095
1096
1097  /**
1098
1099   * Calls the load() method of a SMSound object by ID.
1100
1101   *
1102
1103   * @param {string} sID The ID of the sound
1104
1105   * @param {object} oOptions Optional: Sound options
1106
1107   */
1108
1109
1110
1111  this.load = function(sID, oOptions) {
1112
1113
1114
1115    if (!_idCheck(sID)) {
1116
1117      return false;
1118
1119    }
1120
1121    return _s.sounds[sID].load(oOptions);
1122
1123
1124
1125  };
1126
1127
1128
1129  /**
1130
1131   * Calls the unload() method of a SMSound object by ID.
1132
1133   *
1134
1135   * @param {string} sID The ID of the sound
1136
1137   */
1138
1139
1140
1141  this.unload = function(sID) {
1142
1143
1144
1145    if (!_idCheck(sID)) {
1146
1147      return false;
1148
1149    }
1150
1151    return _s.sounds[sID].unload();
1152
1153
1154
1155  };
1156
1157
1158
1159  /**
1160
1161   * Calls the onPosition() method of a SMSound object by ID.
1162
1163   *
1164
1165   * @param {string} sID The ID of the sound
1166
1167   * @param {number} nPosition The position to watch for
1168
1169   * @param {function} oMethod The relevant callback to fire
1170
1171   * @param {object} oScope Optional: The scope to apply the callback to
1172
1173   * @return {SMSound} The SMSound object
1174
1175   */
1176
1177
1178
1179  this.onPosition = function(sID, nPosition, oMethod, oScope) {
1180
1181
1182
1183    if (!_idCheck(sID)) {
1184
1185      return false;
1186
1187    }
1188
1189    return _s.sounds[sID].onposition(nPosition, oMethod, oScope);
1190
1191
1192
1193  };
1194
1195
1196
1197  // legacy/backwards-compability: lower-case method name
1198
1199  this.onposition = this.onPosition;
1200
1201
1202
1203  /**
1204
1205   * Calls the clearOnPosition() method of a SMSound object by ID.
1206
1207   *
1208
1209   * @param {string} sID The ID of the sound
1210
1211   * @param {number} nPosition The position to watch for
1212
1213   * @param {function} oMethod Optional: The relevant callback to fire
1214
1215   * @return {SMSound} The SMSound object
1216
1217   */
1218
1219
1220
1221  this.clearOnPosition = function(sID, nPosition, oMethod) {
1222
1223
1224
1225    if (!_idCheck(sID)) {
1226
1227      return false;
1228
1229    }
1230
1231    return _s.sounds[sID].clearOnPosition(nPosition, oMethod);
1232
1233
1234
1235  };
1236
1237
1238
1239  /**
1240
1241   * Calls the play() method of a SMSound object by ID.
1242
1243   *
1244
1245   * @param {string} sID The ID of the sound
1246
1247   * @param {object} oOptions Optional: Sound options
1248
1249   * @return {SMSound} The SMSound object
1250
1251   */
1252
1253
1254
1255  this.play = function(sID, oOptions) {
1256
1257
1258
1259    var result = false;
1260
1261
1262
1263    if (!_didInit || !_s.ok()) {
1264
1265      _complain(_sm+'.play(): ' + _str(!_didInit?'notReady':'notOK'));
1266
1267      return result;
1268
1269    }
1270
1271
1272
1273    if (!_idCheck(sID)) {
1274
1275      if (!(oOptions instanceof Object)) {
1276
1277        // overloading use case: play('mySound','/path/to/some.mp3');
1278
1279        oOptions = {
1280
1281          url: oOptions
1282
1283        };
1284
1285      }
1286
1287      if (oOptions && oOptions.url) {
1288
1289        // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
1290
1291        _s._wD(_sm+'.play(): attempting to create "' + sID + '"', 1);
1292
1293        oOptions.id = sID;
1294
1295        result = _s.createSound(oOptions).play();
1296
1297      }
1298
1299      return result;
1300
1301    }
1302
1303
1304
1305    return _s.sounds[sID].play(oOptions);
1306
1307
1308
1309  };
1310
1311
1312
1313  this.start = this.play; // just for convenience
1314
1315
1316
1317  /**
1318
1319   * Calls the setPosition() method of a SMSound object by ID.
1320
1321   *
1322
1323   * @param {string} sID The ID of the sound
1324
1325   * @param {number} nMsecOffset Position (milliseconds)
1326
1327   * @return {SMSound} The SMSound object
1328
1329   */
1330
1331
1332
1333  this.setPosition = function(sID, nMsecOffset) {
1334
1335
1336
1337    if (!_idCheck(sID)) {
1338
1339      return false;
1340
1341    }
1342
1343    return _s.sounds[sID].setPosition(nMsecOffset);
1344
1345
1346
1347  };
1348
1349
1350
1351  /**
1352
1353   * Calls the stop() method of a SMSound object by ID.
1354
1355   *
1356
1357   * @param {string} sID The ID of the sound
1358
1359   * @return {SMSound} The SMSound object
1360
1361   */
1362
1363
1364
1365  this.stop = function(sID) {
1366
1367
1368
1369    if (!_idCheck(sID)) {
1370
1371      return false;
1372
1373    }
1374
1375
1376
1377    _s._wD(_sm+'.stop(' + sID + ')', 1);
1378
1379    return _s.sounds[sID].stop();
1380
1381
1382
1383  };
1384
1385
1386
1387  /**
1388
1389   * Stops all currently-playing sounds.
1390
1391   */
1392
1393
1394
1395  this.stopAll = function() {
1396
1397
1398
1399    var oSound;
1400
1401    _s._wD(_sm+'.stopAll()', 1);
1402
1403
1404
1405    for (oSound in _s.sounds) {
1406
1407      if (_s.sounds.hasOwnProperty(oSound)) {
1408
1409        // apply only to sound objects
1410
1411        _s.sounds[oSound].stop();
1412
1413      }
1414
1415    }
1416
1417
1418
1419  };
1420
1421
1422
1423  /**
1424
1425   * Calls the pause() method of a SMSound object by ID.
1426
1427   *
1428
1429   * @param {string} sID The ID of the sound
1430
1431   * @return {SMSound} The SMSound object
1432
1433   */
1434
1435
1436
1437  this.pause = function(sID) {
1438
1439
1440
1441    if (!_idCheck(sID)) {
1442
1443      return false;
1444
1445    }
1446
1447    return _s.sounds[sID].pause();
1448
1449
1450
1451  };
1452
1453
1454
1455  /**
1456
1457   * Pauses all currently-playing sounds.
1458
1459   */
1460
1461
1462
1463  this.pauseAll = function() {
1464
1465
1466
1467    var i;
1468
1469    for (i = _s.soundIDs.length-1; i >= 0; i--) {
1470
1471      _s.sounds[_s.soundIDs[i]].pause();
1472
1473    }
1474
1475
1476
1477  };
1478
1479
1480
1481  /**
1482
1483   * Calls the resume() method of a SMSound object by ID.
1484
1485   *
1486
1487   * @param {string} sID The ID of the sound
1488
1489   * @return {SMSound} The SMSound object
1490
1491   */
1492
1493
1494
1495  this.resume = function(sID) {
1496
1497
1498
1499    if (!_idCheck(sID)) {
1500
1501      return false;
1502
1503    }
1504
1505    return _s.sounds[sID].resume();
1506
1507
1508
1509  };
1510
1511
1512
1513  /**
1514
1515   * Resumes all currently-paused sounds.
1516
1517   */
1518
1519
1520
1521  this.resumeAll = function() {
1522
1523
1524
1525    var i;
1526
1527    for (i = _s.soundIDs.length-1; i >= 0; i--) {
1528
1529      _s.sounds[_s.soundIDs[i]].resume();
1530
1531    }
1532
1533
1534
1535  };
1536
1537
1538
1539  /**
1540
1541   * Calls the togglePause() method of a SMSound object by ID.
1542
1543   *
1544
1545   * @param {string} sID The ID of the sound
1546
1547   * @return {SMSound} The SMSound object
1548
1549   */
1550
1551
1552
1553  this.togglePause = function(sID) {
1554
1555
1556
1557    if (!_idCheck(sID)) {
1558
1559      return false;
1560
1561    }
1562
1563    return _s.sounds[sID].togglePause();
1564
1565
1566
1567  };
1568
1569
1570
1571  /**
1572
1573   * Calls the setPan() method of a SMSound object by ID.
1574
1575   *
1576
1577   * @param {string} sID The ID of the sound
1578
1579   * @param {number} nPan The pan value (-100 to 100)
1580
1581   * @return {SMSound} The SMSound object
1582
1583   */
1584
1585
1586
1587  this.setPan = function(sID, nPan) {
1588
1589
1590
1591    if (!_idCheck(sID)) {
1592
1593      return false;
1594
1595    }
1596
1597    return _s.sounds[sID].setPan(nPan);
1598
1599
1600
1601  };
1602
1603
1604
1605  /**
1606
1607   * Calls the setVolume() method of a SMSound object by ID.
1608
1609   *
1610
1611   * @param {string} sID The ID of the sound
1612
1613   * @param {number} nVol The volume value (0 to 100)
1614
1615   * @return {SMSound} The SMSound object
1616
1617   */
1618
1619
1620
1621  this.setVolume = function(sID, nVol) {
1622
1623
1624
1625    if (!_idCheck(sID)) {
1626
1627      return false;
1628
1629    }
1630
1631    return _s.sounds[sID].setVolume(nVol);
1632
1633
1634
1635  };
1636
1637
1638
1639  /**
1640
1641   * Calls the mute() method of either a single SMSound object by ID, or all sound objects.
1642
1643   *
1644
1645   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
1646
1647   */
1648
1649
1650
1651  this.mute = function(sID) {
1652
1653
1654
1655    var i = 0;
1656
1657
1658
1659    if (typeof sID !== 'string') {
1660
1661      sID = null;
1662
1663    }
1664
1665
1666
1667    if (!sID) {
1668
1669      _s._wD(_sm+'.mute(): Muting all sounds');
1670
1671      for (i = _s.soundIDs.length-1; i >= 0; i--) {
1672
1673        _s.sounds[_s.soundIDs[i]].mute();
1674
1675      }
1676
1677      _s.muted = true;
1678
1679    } else {
1680
1681      if (!_idCheck(sID)) {
1682
1683        return false;
1684
1685      }
1686
1687      _s._wD(_sm+'.mute(): Muting "' + sID + '"');
1688
1689      return _s.sounds[sID].mute();
1690
1691    }
1692
1693
1694
1695    return true;
1696
1697
1698
1699  };
1700
1701
1702
1703  /**
1704
1705   * Mutes all sounds.
1706
1707   */
1708
1709
1710
1711  this.muteAll = function() {
1712
1713
1714
1715    _s.mute();
1716
1717
1718
1719  };
1720
1721
1722
1723  /**
1724
1725   * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.
1726
1727   *
1728
1729   * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
1730
1731   */
1732
1733
1734
1735  this.unmute = function(sID) {
1736
1737
1738
1739    var i;
1740
1741
1742
1743    if (typeof sID !== 'string') {
1744
1745      sID = null;
1746
1747    }
1748
1749
1750
1751    if (!sID) {
1752
1753
1754
1755      _s._wD(_sm+'.unmute(): Unmuting all sounds');
1756
1757      for (i = _s.soundIDs.length-1; i >= 0; i--) {
1758
1759        _s.sounds[_s.soundIDs[i]].unmute();
1760
1761      }
1762
1763      _s.muted = false;
1764
1765
1766
1767    } else {
1768
1769
1770
1771      if (!_idCheck(sID)) {
1772
1773        return false;
1774
1775      }
1776
1777      _s._wD(_sm+'.unmute(): Unmuting "' + sID + '"');
1778
1779      return _s.sounds[sID].unmute();
1780
1781
1782
1783    }
1784
1785
1786
1787    return true;
1788
1789
1790
1791  };
1792
1793
1794
1795  /**
1796
1797   * Unmutes all sounds.
1798
1799   */
1800
1801
1802
1803  this.unmuteAll = function() {
1804
1805
1806
1807    _s.unmute();
1808
1809
1810
1811  };
1812
1813
1814
1815  /**
1816
1817   * Calls the toggleMute() method of a SMSound object by ID.
1818
1819   *
1820
1821   * @param {string} sID The ID of the sound
1822
1823   * @return {SMSound} The SMSound object
1824
1825   */
1826
1827
1828
1829  this.toggleMute = function(sID) {
1830
1831
1832
1833    if (!_idCheck(sID)) {
1834
1835      return false;
1836
1837    }
1838
1839    return _s.sounds[sID].toggleMute();
1840
1841
1842
1843  };
1844
1845
1846
1847  /**
1848
1849   * Retrieves the memory used by the flash plugin.
1850
1851   *
1852
1853   * @return {number} The amount of memory in use
1854
1855   */
1856
1857
1858
1859  this.getMemoryUse = function() {
1860
1861
1862
1863    // flash-only
1864
1865    var ram = 0;
1866
1867
1868
1869    if (_flash && _fV !== 8) {
1870
1871      ram = parseInt(_flash._getMemoryUse(), 10);
1872
1873    }
1874
1875
1876
1877    return ram;
1878
1879
1880
1881  };
1882
1883
1884
1885  /**
1886
1887   * Undocumented: NOPs soundManager and all SMSound objects.
1888
1889   */
1890
1891
1892
1893  this.disable = function(bNoDisable) {
1894
1895
1896
1897    // destroy all functions
1898
1899    var i;
1900
1901
1902
1903    if (typeof bNoDisable === 'undefined') {
1904
1905      bNoDisable = false;
1906
1907    }
1908
1909
1910
1911    if (_disabled) {
1912
1913      return false;
1914
1915    }
1916
1917
1918
1919    _disabled = true;
1920
1921    _wDS('shutdown', 1);
1922
1923
1924
1925    for (i = _s.soundIDs.length-1; i >= 0; i--) {
1926
1927      _disableObject(_s.sounds[_s.soundIDs[i]]);
1928
1929    }
1930
1931
1932
1933    // fire "complete", despite fail
1934
1935    _initComplete(bNoDisable);
1936
1937    _event.remove(_win, 'load', _initUserOnload);
1938
1939
1940
1941    return true;
1942
1943
1944
1945  };
1946
1947
1948
1949  /**
1950
1951   * Determines playability of a MIME type, eg. 'audio/mp3'.
1952
1953   */
1954
1955
1956
1957  this.canPlayMIME = function(sMIME) {
1958
1959
1960
1961    var result;
1962
1963
1964
1965    if (_s.hasHTML5) {
1966
1967      result = _html5CanPlay({type:sMIME});
1968
1969    }
1970
1971
1972
1973    if (!result && _needsFlash) {
1974
1975      // if flash 9, test netStream (movieStar) types as well.
1976
1977      result = (sMIME && _s.ok() ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null);
1978
1979    }
1980
1981
1982
1983    return result;
1984
1985
1986
1987  };
1988
1989
1990
1991  /**
1992
1993   * Determines playability of a URL based on audio support.
1994
1995   *
1996
1997   * @param {string} sURL The URL to test
1998
1999   * @return {boolean} URL playability
2000
2001   */
2002
2003
2004
2005  this.canPlayURL = function(sURL) {
2006
2007
2008
2009    var result;
2010
2011
2012
2013    if (_s.hasHTML5) {
2014
2015      result = _html5CanPlay({url: sURL});
2016
2017    }
2018
2019
2020
2021    if (!result && _needsFlash) {
2022
2023      result = (sURL && _s.ok() ? !!(sURL.match(_s.filePattern)) : null);
2024
2025    }
2026
2027
2028
2029    return result;
2030
2031
2032
2033  };
2034
2035
2036
2037  /**
2038
2039   * Determines playability of an HTML DOM &lt;a&gt; object (or similar object literal) based on audio support.
2040
2041   *
2042
2043   * @param {object} oLink an HTML DOM &lt;a&gt; object or object literal including href and/or type attributes
2044
2045   * @return {boolean} URL playability
2046
2047   */
2048
2049
2050
2051  this.canPlayLink = function(oLink) {
2052
2053
2054
2055    if (typeof oLink.type !== 'undefined' && oLink.type) {
2056
2057      if (_s.canPlayMIME(oLink.type)) {
2058
2059        return true;
2060
2061      }
2062
2063    }
2064
2065
2066
2067    return _s.canPlayURL(oLink.href);
2068
2069
2070
2071  };
2072
2073
2074
2075  /**
2076
2077   * Retrieves a SMSound object by ID.
2078
2079   *
2080
2081   * @param {string} sID The ID of the sound
2082
2083   * @return {SMSound} The SMSound object
2084
2085   */
2086
2087
2088
2089  this.getSoundById = function(sID, _suppressDebug) {
2090
2091
2092
2093    if (!sID) {
2094
2095      throw new Error(_sm+'.getSoundById(): sID is null/undefined');
2096
2097    }
2098
2099
2100
2101    var result = _s.sounds[sID];
2102
2103
2104
2105    // <d>
2106
2107    if (!result && !_suppressDebug) {
2108
2109      _s._wD('"' + sID + '" is an invalid sound ID.', 2);
2110
2111    }
2112
2113    // </d>
2114
2115
2116
2117    return result;
2118
2119
2120
2121  };
2122
2123
2124
2125  /**
2126
2127   * Queues a callback for execution when SoundManager has successfully initialized.
2128
2129   *
2130
2131   * @param {function} oMethod The callback method to fire
2132
2133   * @param {object} oScope Optional: The scope to apply to the callback
2134
2135   */
2136
2137
2138
2139  this.onready = function(oMethod, oScope) {
2140
2141
2142
2143    var sType = 'onready',
2144
2145        result = false;
2146
2147
2148
2149    if (typeof oMethod === 'function') {
2150
2151
2152
2153      // <d>
2154
2155      if (_didInit) {
2156
2157        _s._wD(_str('queue', sType));
2158
2159      }
2160
2161      // </d>
2162
2163
2164
2165      if (!oScope) {
2166
2167        oScope = _win;
2168
2169      }
2170
2171
2172
2173      _addOnEvent(sType, oMethod, oScope);
2174
2175      _processOnEvents();
2176
2177
2178
2179      result = true;
2180
2181
2182
2183    } else {
2184
2185
2186
2187      throw _str('needFunction', sType);
2188
2189
2190
2191    }
2192
2193
2194
2195    return result;
2196
2197
2198
2199  };
2200
2201
2202
2203  /**
2204
2205   * Queues a callback for execution when SoundManager has failed to initialize.
2206
2207   *
2208
2209   * @param {function} oMethod The callback method to fire
2210
2211   * @param {object} oScope Optional: The scope to apply to the callback
2212
2213   */
2214
2215
2216
2217  this.ontimeout = function(oMethod, oScope) {
2218
2219
2220
2221    var sType = 'ontimeout',
2222
2223        result = false;
2224
2225
2226
2227    if (typeof oMethod === 'function') {
2228
2229
2230
2231      // <d>
2232
2233      if (_didInit) {
2234
2235        _s._wD(_str('queue', sType));
2236
2237      }
2238
2239      // </d>
2240
2241
2242
2243      if (!oScope) {
2244
2245        oScope = _win;
2246
2247      }
2248
2249
2250
2251      _addOnEvent(sType, oMethod, oScope);
2252
2253      _processOnEvents({type:sType});
2254
2255
2256
2257      result = true;
2258
2259
2260
2261    } else {
2262
2263
2264
2265      throw _str('needFunction', sType);
2266
2267
2268
2269    }
2270
2271
2272
2273    return result;
2274
2275
2276
2277  };
2278
2279
2280
2281  /**
2282
2283   * Writes console.log()-style debug output to a console or in-browser element.
2284
2285   * Applies when debugMode = true
2286
2287   *
2288
2289   * @param {string} sText The console message
2290
2291   * @param {string} sType Optional: Log type of 'info', 'warn' or 'error'
2292
2293   * @param {object} Optional: The scope to apply to the callback
2294
2295   */
2296
2297
2298
2299  this._writeDebug = function(sText, sType, _bTimestamp) {
2300
2301
2302
2303    // pseudo-private console.log()-style output
2304
2305    // <d>
2306
2307
2308
2309    var sDID = 'soundmanager-debug', o, oItem, sMethod;
2310
2311
2312
2313    if (!_s.debugMode) {
2314
2315      return false;
2316
2317    }
2318
2319
2320
2321    if (typeof _bTimestamp !== 'undefined' && _bTimestamp) {
2322
2323      sText = sText + ' | ' + new Date().getTime();
2324
2325    }
2326
2327
2328
2329    if (_hasConsole && _s.useConsole) {
2330
2331      sMethod = _debugLevels[sType];
2332
2333      if (typeof console[sMethod] !== 'undefined') {
2334
2335        console[sMethod](sText);
2336
2337      } else {
2338
2339        console.log(sText);
2340
2341      }
2342
2343      if (_s.consoleOnly) {
2344
2345        return true;
2346
2347      }
2348
2349    }
2350
2351
2352
2353    try {
2354
2355
2356
2357      o = _id(sDID);
2358
2359
2360
2361      if (!o) {
2362
2363        return false;
2364
2365      }
2366
2367
2368
2369      oItem = _doc.createElement('div');
2370
2371
2372
2373      if (++_wdCount % 2 === 0) {
2374
2375        oItem.className = 'sm2-alt';
2376
2377      }
2378
2379
2380
2381      if (typeof sType === 'undefined') {
2382
2383        sType = 0;
2384
2385      } else {
2386
2387        sType = parseInt(sType, 10);
2388
2389      }
2390
2391
2392
2393      oItem.appendChild(_doc.createTextNode(sText));
2394
2395
2396
2397      if (sType) {
2398
2399        if (sType >= 2) {
2400
2401          oItem.style.fontWeight = 'bold';
2402
2403        }
2404
2405        if (sType === 3) {
2406
2407          oItem.style.color = '#ff3333';
2408
2409        }
2410
2411      }
2412
2413
2414
2415      // top-to-bottom
2416
2417      // o.appendChild(oItem);
2418
2419
2420
2421      // bottom-to-top
2422
2423      o.insertBefore(oItem, o.firstChild);
2424
2425
2426
2427    } catch(e) {
2428
2429      // oh well
2430
2431    }
2432
2433
2434
2435    o = null;
2436
2437    // </d>
2438
2439
2440
2441    return true;
2442
2443
2444
2445  };
2446
2447
2448
2449  // alias
2450
2451  this._wD = this._writeDebug;
2452
2453
2454
2455  /**
2456
2457   * Provides debug / state information on all SMSound objects.
2458
2459   */
2460
2461
2462
2463  this._debug = function() {
2464
2465
2466
2467    // <d>
2468
2469    var i, j;
2470
2471    _wDS('currentObj', 1);
2472
2473
2474
2475    for (i = 0, j = _s.soundIDs.length; i < j; i++) {
2476
2477      _s.sounds[_s.soundIDs[i]]._debug();
2478
2479    }
2480
2481    // </d>
2482
2483
2484
2485  };
2486
2487
2488
2489  /**
2490
2491   * Restarts and re-initializes the SoundManager instance.
2492
2493   */
2494
2495
2496
2497  this.reboot = function() {
2498
2499
2500
2501    // attempt to reset and init SM2
2502
2503    _s._wD(_sm+'.reboot()');
2504
2505
2506
2507    // <d>
2508
2509    if (_s.soundIDs.length) {
2510
2511      _s._wD('Destroying ' + _s.soundIDs.length + ' SMSound objects...');
2512
2513    }
2514
2515    // </d>
2516
2517
2518
2519    var i, j;
2520
2521
2522
2523    for (i = _s.soundIDs.length-1; i >= 0; i--) {
2524
2525      _s.sounds[_s.soundIDs[i]].destruct();
2526
2527    }
2528
2529
2530
2531    // trash ze flash
2532
2533
2534
2535    if (_flash) {
2536
2537      try {
2538
2539        if (_isIE) {
2540
2541          _oRemovedHTML = _flash.innerHTML;
2542
2543        }
2544
2545        _oRemoved = _flash.parentNode.removeChild(_flash);
2546
2547        _s._wD('Flash movie removed.');
2548
2549      } catch(e) {
2550
2551        // uh-oh.
2552
2553        _wDS('badRemove', 2);
2554
2555      }
2556
2557    }
2558
2559
2560
2561    // actually, force recreate of movie.
2562
2563    _oRemovedHTML = _oRemoved = _needsFlash = null;
2564
2565
2566
2567    _s.enabled = _didDCLoaded = _didInit = _waitingForEI = _initPending = _didAppend = _appendSuccess = _disabled = _s.swfLoaded = false;
2568
2569    _s.soundIDs = [];
2570
2571    _s.sounds = {};
2572
2573    _flash = null;
2574
2575
2576
2577    for (i in _on_queue) {
2578
2579      if (_on_queue.hasOwnProperty(i)) {
2580
2581        for (j = _on_queue[i].length-1; j >= 0; j--) {
2582
2583          _on_queue[i][j].fired = false;
2584
2585        }
2586
2587      }
2588
2589    }
2590
2591
2592
2593    _s._wD(_sm + ': Rebooting...');
2594
2595    _win.setTimeout(_s.beginDelayedInit, 20);
2596
2597
2598
2599  };
2600
2601
2602
2603  /**
2604
2605   * Undocumented: Determines the SM2 flash movie's load progress.
2606
2607   *
2608
2609   * @return {number or null} Percent loaded, or if invalid/unsupported, null.
2610
2611   */
2612
2613
2614
2615  this.getMoviePercent = function() {
2616
2617
2618
2619    // interesting note: flash/ExternalInterface bridge methods are not typeof "function" nor instanceof Function, but are still valid.
2620
2621    return (_flash && typeof _flash.PercentLoaded !== 'undefined' ? _flash.PercentLoaded() : null);
2622
2623
2624
2625  };
2626
2627
2628
2629  /**
2630
2631   * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().
2632
2633   */
2634
2635
2636
2637  this.beginDelayedInit = function() {
2638
2639
2640
2641    _windowLoaded = true;
2642
2643    _domContentLoaded();
2644
2645
2646
2647    setTimeout(function() {
2648
2649
2650
2651      if (_initPending) {
2652
2653        return false;
2654
2655      }
2656
2657
2658
2659      _createMovie();
2660
2661      _initMovie();
2662
2663      _initPending = true;
2664
2665
2666
2667      return true;
2668
2669
2670
2671    }, 20);
2672
2673
2674
2675    _delayWaitForEI();
2676
2677
2678
2679  };
2680
2681
2682
2683  /**
2684
2685   * Destroys the SoundManager instance and all SMSound instances.
2686
2687   */
2688
2689
2690
2691  this.destruct = function() {
2692
2693
2694
2695    _s._wD(_sm+'.destruct()');
2696
2697    _s.disable(true);
2698
2699
2700
2701  };
2702
2703
2704
2705  /**
2706
2707   * SMSound() (sound object) constructor
2708
2709   * ------------------------------------
2710
2711   *
2712
2713   * @param {object} oOptions Sound options (id and url are required attributes)
2714
2715   * @return {SMSound} The new SMSound object
2716
2717   */
2718
2719
2720
2721  SMSound = function(oOptions) {
2722
2723
2724
2725    var _t = this, _resetProperties, _add_html5_events, _remove_html5_events, _stop_html5_timer, _start_html5_timer, _attachOnPosition, _onplay_called = false, _onPositionItems = [], _onPositionFired = 0, _detachOnPosition, _applyFromTo, _lastURL = null, _lastHTML5State;
2726
2727
2728
2729    _lastHTML5State = {
2730
2731      // tracks duration + position (time)
2732
2733      duration: null,
2734
2735      time: null
2736
2737    };
2738
2739
2740
2741    this.id = oOptions.id;
2742
2743
2744
2745    // legacy
2746
2747    this.sID = this.id;
2748
2749
2750
2751    this.url = oOptions.url;
2752
2753    this.options = _mixin(oOptions);
2754
2755
2756
2757    // per-play-instance-specific options
2758
2759    this.instanceOptions = this.options;
2760
2761
2762
2763    // short alias
2764
2765    this._iO = this.instanceOptions;
2766
2767
2768
2769    // assign property defaults
2770
2771    this.pan = this.options.pan;
2772
2773    this.volume = this.options.volume;
2774
2775
2776
2777    // whether or not this object is using HTML5
2778
2779    this.isHTML5 = false;
2780
2781
2782
2783    // internal HTML5 Audio() object reference
2784
2785    this._a = null;
2786
2787
2788
2789    /**
2790
2791     * SMSound() public methods
2792
2793     * ------------------------
2794
2795     */
2796
2797
2798
2799    this.id3 = {};
2800
2801
2802
2803    /**
2804
2805     * Writes SMSound object parameters to debug console
2806
2807     */
2808
2809
2810
2811    this._debug = function() {
2812
2813
2814
2815      // <d>
2816
2817      // pseudo-private console.log()-style output
2818
2819
2820
2821      if (_s.debugMode) {
2822
2823
2824
2825        var stuff = null, msg = [], sF, sfBracket, maxLength = 64;
2826
2827
2828
2829        for (stuff in _t.options) {
2830
2831          if (_t.options[stuff] !== null) {
2832
2833            if (typeof _t.options[stuff] === 'function') {
2834
2835              // handle functions specially
2836
2837              sF = _t.options[stuff].toString();
2838
2839              // normalize spaces
2840
2841              sF = sF.replace(/\s\s+/g, ' ');
2842
2843              sfBracket = sF.indexOf('{');
2844
2845              msg.push(' ' + stuff + ': {' + sF.substr(sfBracket + 1, (Math.min(Math.max(sF.indexOf('\n') - 1, maxLength), maxLength))).replace(/\n/g, '') + '... }');
2846
2847            } else {
2848
2849              msg.push(' ' + stuff + ': ' + _t.options[stuff]);
2850
2851            }
2852
2853          }
2854
2855        }
2856
2857
2858
2859        _s._wD('SMSound() merged options: {\n' + msg.join(', \n') + '\n}');
2860
2861
2862
2863      }
2864
2865      // </d>
2866
2867
2868
2869    };
2870
2871
2872
2873    // <d>
2874
2875    this._debug();
2876
2877    // </d>
2878
2879
2880
2881    /**
2882
2883     * Begins loading a sound per its *url*.
2884
2885     *
2886
2887     * @param {object} oOptions Optional: Sound options
2888
2889     * @return {SMSound} The SMSound object
2890
2891     */
2892
2893
2894
2895    this.load = function(oOptions) {
2896
2897
2898
2899      var oS = null, _iO;
2900
2901
2902
2903      if (typeof oOptions !== 'undefined') {
2904
2905        _t._iO = _mixin(oOptions, _t.options);
2906
2907        _t.instanceOptions = _t._iO;
2908
2909      } else {
2910
2911        oOptions = _t.options;
2912
2913        _t._iO = oOptions;
2914
2915        _t.instanceOptions = _t._iO;
2916
2917        if (_lastURL && _lastURL !== _t.url) {
2918
2919          _wDS('manURL');
2920
2921          _t._iO.url = _t.url;
2922
2923          _t.url = null;
2924
2925        }
2926
2927      }
2928
2929
2930
2931      if (!_t._iO.url) {
2932
2933        _t._iO.url = _t.url;
2934
2935      }
2936
2937
2938
2939      _t._iO.url = _parseURL(_t._iO.url);
2940
2941
2942
2943      _s._wD('SMSound.load(): ' + _t._iO.url, 1);
2944
2945
2946
2947      if (_t._iO.url === _t.url && _t.readyState !== 0 && _t.readyState !== 2) {
2948
2949        _wDS('onURL', 1);
2950
2951        // if loaded and an onload() exists, fire immediately.
2952
2953        if (_t.readyState === 3 && _t._iO.onload) {
2954
2955          // assume success based on truthy duration.
2956
2957          _t._iO.onload.apply(_t, [(!!_t.duration)]);
2958
2959        }
2960
2961        return _t;
2962
2963      }
2964
2965
2966
2967      // local shortcut
2968
2969      _iO = _t._iO;
2970
2971
2972
2973      // make a local copy of the old url before we re-assign it
2974
2975      _lastURL = (_t.url && _t.url.toString ? _t.url.toString() : null);
2976
2977
2978
2979      // reset a few state properties
2980
2981
2982
2983      _t.loaded = false;
2984
2985      _t.readyState = 1;
2986
2987      _t.playState = 0;
2988
2989      _t.id3 = {};
2990
2991
2992
2993      // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
2994
2995
2996
2997      if (_html5OK(_iO)) {
2998
2999
3000
3001        oS = _t._setup_html5(_iO);
3002
3003
3004
3005        if (!oS._called_load) {
3006
3007
3008
3009          _s._wD(_h5+'load: '+_t.id);
3010
3011
3012
3013          _t._html5_canplay = false;
3014
3015
3016
3017          // TODO: review called_load / html5_canplay logic
3018
3019
3020
3021          // if url provided directly to load(), assign it here.
3022
3023
3024
3025          if (_t._a.src !== _iO.url) {
3026
3027
3028
3029            _s._wD(_wDS('manURL') + ': ' + _iO.url);
3030
3031
3032
3033            _t._a.src = _iO.url;
3034
3035
3036
3037            // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
3038
3039
3040
3041            // reset position for new URL
3042
3043            _t.setPosition(0);
3044
3045
3046
3047          }
3048
3049
3050
3051          // given explicit load call, try to preload.
3052
3053
3054
3055          // early HTML5 implementation (non-standard)
3056
3057          _t._a.autobuffer = 'auto';
3058
3059
3060
3061          // standard
3062
3063          _t._a.preload = 'auto';
3064
3065
3066
3067          oS._called_load = true;
3068
3069
3070
3071          if (_iO.autoPlay) {
3072
3073            _t.play();
3074
3075          }
3076
3077
3078
3079        } else {
3080
3081
3082
3083          _s._wD(_h5+'ignoring request to load again: '+_t.id);
3084
3085
3086
3087        }
3088
3089
3090
3091      } else {
3092
3093
3094
3095        try {
3096
3097          _t.isHTML5 = false;
3098
3099          _t._iO = _policyFix(_loopFix(_iO));
3100
3101          // re-assign local shortcut
3102
3103          _iO = _t._iO;
3104
3105          if (_fV === 8) {
3106
3107            _flash._load(_t.id, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile);
3108
3109          } else {
3110
3111            _flash._load(_t.id, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile);
3112
3113          }
3114
3115        } catch(e) {
3116
3117          _wDS('smError', 2);
3118
3119          _debugTS('onload', false);
3120
3121          _catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});
3122
3123
3124
3125        }
3126
3127
3128
3129      }
3130
3131
3132
3133      // after all of this, ensure sound url is up to date.
3134
3135      _t.url = _iO.url;
3136
3137
3138
3139      return _t;
3140
3141
3142
3143    };
3144
3145
3146
3147    /**
3148
3149     * Unloads a sound, canceling any open HTTP requests.
3150
3151     *
3152
3153     * @return {SMSound} The SMSound object
3154
3155     */
3156
3157
3158
3159    this.unload = function() {
3160
3161
3162
3163      // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL
3164
3165      // Flash 9/AS3: Close stream, preventing further load
3166
3167      // HTML5: Most UAs will use empty URL
3168
3169
3170
3171      if (_t.readyState !== 0) {
3172
3173
3174
3175        _s._wD('SMSound.unload(): "' + _t.id + '"');
3176
3177
3178
3179        if (!_t.isHTML5) {
3180
3181
3182
3183          if (_fV === 8) {
3184
3185            _flash._unload(_t.id, _emptyURL);
3186
3187          } else {
3188
3189            _flash._unload(_t.id);
3190
3191          }
3192
3193
3194
3195        } else {
3196
3197
3198
3199          _stop_html5_timer();
3200
3201
3202
3203          if (_t._a) {
3204
3205
3206
3207            _t._a.pause();
3208
3209            _html5Unload(_t._a, _emptyURL);
3210
3211
3212
3213            // reset local URL for next load / play call, too
3214
3215            _t.url = _emptyURL;
3216
3217
3218
3219          }
3220
3221
3222
3223        }
3224
3225
3226
3227        // reset load/status flags
3228
3229        _resetProperties();
3230
3231
3232
3233      }
3234
3235
3236
3237      return _t;
3238
3239
3240
3241    };
3242
3243
3244
3245    /**
3246
3247     * Unloads and destroys a sound.
3248
3249     */
3250
3251
3252
3253    this.destruct = function(_bFromSM) {
3254
3255
3256
3257      _s._wD('SMSound.destruct(): "' + _t.id + '"');
3258
3259
3260
3261      if (!_t.isHTML5) {
3262
3263
3264
3265        // kill sound within Flash
3266
3267        // Disable the onfailure handler
3268
3269        _t._iO.onfailure = null;
3270
3271        _flash._destroySound(_t.id);
3272
3273
3274
3275      } else {
3276
3277
3278
3279        _stop_html5_timer();
3280
3281
3282
3283        if (_t._a) {
3284
3285          _t._a.pause();
3286
3287          _html5Unload(_t._a);
3288
3289          if (!_useGlobalHTML5Audio) {
3290
3291            _remove_html5_events();
3292
3293          }
3294
3295          // break obvious circular reference
3296
3297          _t._a._t = null;
3298
3299          _t._a = null;
3300
3301        }
3302
3303
3304
3305      }
3306
3307
3308
3309      if (!_bFromSM) {
3310
3311        // ensure deletion from controller
3312
3313        _s.destroySound(_t.id, true);
3314
3315
3316
3317      }
3318
3319
3320
3321    };
3322
3323
3324
3325    /**
3326
3327     * Begins playing a sound.
3328
3329     *
3330
3331     * @param {object} oOptions Optional: Sound options
3332
3333     * @return {SMSound} The SMSound object
3334
3335     */
3336
3337
3338
3339    this.play = function(oOptions, _updatePlayState) {
3340
3341
3342
3343      var fN, allowMulti, a, onready, startOK = true,
3344
3345          exit = null;
3346
3347
3348
3349      // <d>
3350
3351      fN = 'SMSound.play(): ';
3352
3353      // </d>
3354
3355
3356
3357      // default to true
3358
3359      _updatePlayState = (typeof _updatePlayState === 'undefined' ? true : _updatePlayState);
3360
3361
3362
3363      if (!oOptions) {
3364
3365        oOptions = {};
3366
3367      }
3368
3369
3370
3371      // first, use local URL (if specified)
3372
3373      if (_t.url) {
3374
3375        _t._iO.url = _t.url;
3376
3377      }
3378
3379
3380
3381      // mix in any options defined at createSound()
3382
3383      _t._iO = _mixin(_t._iO, _t.options);
3384
3385
3386
3387      // mix in any options specific to this method
3388
3389      _t._iO = _mixin(oOptions, _t._iO);
3390
3391
3392
3393      _t._iO.url = _parseURL(_t._iO.url);
3394
3395
3396
3397      _t.instanceOptions = _t._iO;
3398
3399
3400
3401      // RTMP-only
3402
3403      if (_t._iO.serverURL && !_t.connected) {
3404
3405        if (!_t.getAutoPlay()) {
3406
3407          _s._wD(fN+' Netstream not connected yet - setting autoPlay');
3408
3409          _t.setAutoPlay(true);
3410
3411        }
3412
3413        // play will be called in _onconnect()
3414
3415        return _t;
3416
3417      }
3418
3419
3420
3421      if (_html5OK(_t._iO)) {
3422
3423        _t._setup_html5(_t._iO);
3424
3425        _start_html5_timer();
3426
3427      }
3428
3429
3430
3431      if (_t.playState === 1 && !_t.paused) {
3432
3433        allowMulti = _t._iO.multiShot;
3434
3435        if (!allowMulti) {
3436
3437          _s._wD(fN + '"' + _t.id + '" already playing (one-shot)', 1);
3438
3439          exit = _t;
3440
3441        } else {
3442
3443          _s._wD(fN + '"' + _t.id + '" already playing (multi-shot)', 1);
3444
3445        }
3446
3447      }
3448
3449
3450
3451      if (exit !== null) {
3452
3453        return exit;
3454
3455      }
3456
3457
3458
3459      // edge case: play() with explicit URL parameter
3460
3461      if (oOptions.url && oOptions.url !== _t.url) {
3462
3463        // load using merged options
3464
3465        _t.load(_t._iO);
3466
3467      }
3468
3469
3470
3471      if (!_t.loaded) {
3472
3473
3474
3475        if (_t.readyState === 0) {
3476
3477
3478
3479          _s._wD(fN + 'Attempting to load "' + _t.id + '"', 1);
3480
3481
3482
3483          // try to get this sound playing ASAP
3484
3485          if (!_t.isHTML5) {
3486
3487            // assign directly because setAutoPlay() increments the instanceCount
3488
3489            _t._iO.autoPlay = true;
3490
3491            _t.load(_t._iO);
3492
3493          } else {
3494
3495            // iOS needs this when recycling sounds, loading a new URL on an existing object.
3496
3497            _t.load(_t._iO);
3498
3499          }
3500
3501
3502
3503        } else if (_t.readyState === 2) {
3504
3505
3506
3507          _s._wD(fN + 'Could not load "' + _t.id + '" - exiting', 2);
3508
3509          exit = _t;
3510
3511
3512
3513        } else {
3514
3515
3516
3517          _s._wD(fN + '"' + _t.id + '" is loading - attempting to play..', 1);
3518
3519
3520
3521        }
3522
3523
3524
3525      } else {
3526
3527
3528
3529        _s._wD(fN + '"' + _t.id + '"');
3530
3531
3532
3533      }
3534
3535
3536
3537      if (exit !== null) {
3538
3539        return exit;
3540
3541      }
3542
3543
3544
3545      if (!_t.isHTML5 && _fV === 9 && _t.position > 0 && _t.position === _t.duration) {
3546
3547        // flash 9 needs a position reset if play() is called while at the end of a sound.
3548
3549        _s._wD(fN + '"' + _t.id + '": Sound at end, resetting to position:0');
3550
3551        oOptions.position = 0;
3552
3553      }
3554
3555
3556
3557      /**
3558
3559       * Streams will pause when their buffer is full if they are being loaded.
3560
3561       * In this case paused is true, but the song hasn't started playing yet.
3562
3563       * If we just call resume() the onplay() callback will never be called.
3564
3565       * So only call resume() if the position is > 0.
3566
3567       * Another reason is because options like volume won't have been applied yet.
3568
3569       * For normal sounds, just resume.
3570
3571       */
3572
3573
3574
3575      if (_t.paused && _t.position >= 0 && (!_t._iO.serverURL || _t.position > 0)) {
3576
3577
3578
3579        // https://gist.github.com/37b17df75cc4d7a90bf6
3580
3581        _s._wD(fN + '"' + _t.id + '" is resuming from paused state',1);
3582
3583        _t.resume();
3584
3585
3586
3587      } else {
3588
3589
3590
3591        _t._iO = _mixin(oOptions, _t._iO);
3592
3593
3594
3595        // apply from/to parameters, if they exist (and not using RTMP)
3596
3597        if (_t._iO.from !== null && _t._iO.to !== null && _t.instanceCount === 0 && _t.playState === 0 && !_t._iO.serverURL) {
3598
3599
3600
3601          onready = function() {
3602
3603            // sound "canplay" or onload()
3604
3605            // re-apply from/to to instance options, and start playback
3606
3607            _t._iO = _mixin(oOptions, _t._iO);
3608
3609            _t.play(_t._iO);
3610
3611          };
3612
3613
3614
3615          // HTML5 needs to at least have "canplay" fired before seeking.
3616
3617          if (_t.isHTML5 && !_t._html5_canplay) {
3618
3619
3620
3621            // this hasn't been loaded yet. load it first, and then do this again.
3622
3623            _s._wD(fN+'Beginning load of "'+ _t.id+'" for from/to case');
3624
3625
3626
3627            _t.load({
3628
3629              _oncanplay: onready
3630
3631            });
3632
3633
3634
3635            exit = false;
3636
3637
3638
3639          } else if (!_t.isHTML5 && !_t.loaded && (!_t.readyState || _t.readyState !== 2)) {
3640
3641
3642
3643            // to be safe, preload the whole thing in Flash.
3644
3645
3646
3647            _s._wD(fN+'Preloading "'+ _t.id+'" for from/to case');
3648
3649
3650
3651            _t.load({
3652
3653              onload: onready
3654
3655            });
3656
3657
3658
3659            exit = false;
3660
3661
3662
3663          }
3664
3665
3666
3667          if (exit !== null) {
3668
3669            return exit;
3670
3671          }
3672
3673
3674
3675          // otherwise, we're ready to go. re-apply local options, and continue
3676
3677
3678
3679          _t._iO = _applyFromTo();
3680
3681
3682
3683        }
3684
3685
3686
3687        _s._wD(fN+'"'+ _t.id+'" is starting to play');
3688
3689
3690
3691        if (!_t.instanceCount || _t._iO.multiShotEvents || (!_t.isHTML5 && _fV > 8 && !_t.getAutoPlay())) {
3692
3693          _t.instanceCount++;
3694
3695        }
3696
3697
3698
3699        // if first play and onposition parameters exist, apply them now
3700
3701        if (_t._iO.onposition && _t.playState === 0) {
3702
3703          _attachOnPosition(_t);
3704
3705        }
3706
3707
3708
3709        _t.playState = 1;
3710
3711        _t.paused = false;
3712
3713
3714
3715        _t.position = (typeof _t._iO.position !== 'undefined' && !isNaN(_t._iO.position) ? _t._iO.position : 0);
3716
3717
3718
3719        if (!_t.isHTML5) {
3720
3721          _t._iO = _policyFix(_loopFix(_t._iO));
3722
3723        }
3724
3725
3726
3727        if (_t._iO.onplay && _updatePlayState) {
3728
3729          _t._iO.onplay.apply(_t);
3730
3731          _onplay_called = true;
3732
3733        }
3734
3735
3736
3737        _t.setVolume(_t._iO.volume, true);
3738
3739        _t.setPan(_t._iO.pan, true);
3740
3741
3742
3743        if (!_t.isHTML5) {
3744
3745
3746
3747          startOK = _flash._start(_t.id, _t._iO.loops || 1, (_fV === 9 ? _t._iO.position : _t._iO.position / 1000), _t._iO.multiShot);
3748
3749
3750
3751          if (_fV === 9 && !startOK) {
3752
3753            // edge case: no sound hardware, or 32-channel flash ceiling hit.
3754
3755            // applies only to Flash 9, non-NetStream/MovieStar sounds.
3756
3757            // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
3758
3759            _s._wD(fN+ _t.id+': No sound hardware, or 32-sound ceiling hit');
3760
3761            if (_t._iO.onplayerror) {
3762
3763              _t._iO.onplayerror.apply(_t);
3764
3765            }
3766
3767
3768
3769          }
3770
3771
3772
3773        } else {
3774
3775
3776
3777          _start_html5_timer();
3778
3779
3780
3781          a = _t._setup_html5();
3782
3783
3784
3785          _t.setPosition(_t._iO.position);
3786
3787
3788
3789          a.play();
3790
3791
3792
3793        }
3794
3795
3796
3797      }
3798
3799
3800
3801      return _t;
3802
3803
3804
3805    };
3806
3807
3808
3809    // just for convenience
3810
3811    this.start = this.play;
3812
3813
3814
3815    /**
3816
3817     * Stops playing a sound (and optionally, all sounds)
3818
3819     *
3820
3821     * @param {boolean} bAll Optional: Whether to stop all sounds
3822
3823     * @return {SMSound} The SMSound object
3824
3825     */
3826
3827
3828
3829    this.stop = function(bAll) {
3830
3831
3832
3833      var _iO = _t._iO, _oP;
3834
3835
3836
3837      if (_t.playState === 1) {
3838
3839
3840
3841        _t._onbufferchange(0);
3842
3843        _t._resetOnPosition(0);
3844
3845        _t.paused = false;
3846
3847
3848
3849        if (!_t.isHTML5) {
3850
3851          _t.playState = 0;
3852
3853        }
3854
3855
3856
3857        // remove onPosition listeners, if any
3858
3859        _detachOnPosition();
3860
3861
3862
3863        // and "to" position, if set
3864
3865        if (_iO.to) {
3866
3867          _t.clearOnPosition(_iO.to);
3868
3869        }
3870
3871
3872
3873        if (!_t.isHTML5) {
3874
3875
3876
3877          _flash._stop(_t.id, bAll);
3878
3879
3880
3881          // hack for netStream: just unload
3882
3883          if (_iO.serverURL) {
3884
3885            _t.unload();
3886
3887          }
3888
3889
3890
3891        } else {
3892
3893
3894
3895          if (_t._a) {
3896
3897
3898
3899            _oP = _t.position;
3900
3901
3902
3903            // act like Flash, though
3904
3905            _t.setPosition(0);
3906
3907
3908
3909            // hack: reflect old position for onstop() (also like Flash)
3910
3911            _t.position = _oP;
3912
3913
3914
3915            // html5 has no stop()
3916
3917            // NOTE: pausing means iOS requires interaction to resume.
3918
3919            _t._a.pause();
3920
3921
3922
3923            _t.playState = 0;
3924
3925
3926
3927            // and update UI
3928
3929            _t._onTimer();
3930
3931
3932
3933            _stop_html5_timer();
3934
3935
3936
3937          }
3938
3939
3940
3941        }
3942
3943
3944
3945        _t.instanceCount = 0;
3946
3947        _t._iO = {};
3948
3949
3950
3951        if (_iO.onstop) {
3952
3953          _iO.onstop.apply(_t);
3954
3955        }
3956
3957
3958
3959      }
3960
3961
3962
3963      return _t;
3964
3965
3966
3967    };
3968
3969
3970
3971    /**
3972
3973     * Undocumented/internal: Sets autoPlay for RTMP.
3974
3975     *
3976
3977     * @param {boolean} autoPlay state
3978
3979     */
3980
3981
3982
3983    this.setAutoPlay = function(autoPlay) {
3984
3985
3986
3987      _s._wD('sound '+_t.id+' turned autoplay ' + (autoPlay ? 'on' : 'off'));
3988
3989      _t._iO.autoPlay = autoPlay;
3990
3991
3992
3993      if (!_t.isHTML5) {
3994
3995        _flash._setAutoPlay(_t.id, autoPlay);
3996
3997        if (autoPlay) {
3998
3999          // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
4000
4001          if (!_t.instanceCount && _t.readyState === 1) {
4002
4003            _t.instanceCount++;
4004
4005            _s._wD('sound '+_t.id+' incremented instance count to '+_t.instanceCount);
4006
4007          }
4008
4009        }
4010
4011      }
4012
4013
4014
4015    };
4016
4017
4018
4019    /**
4020
4021     * Undocumented/internal: Returns the autoPlay boolean.
4022
4023     *
4024
4025     * @return {boolean} The current autoPlay value
4026
4027     */
4028
4029
4030
4031    this.getAutoPlay = function() {
4032
4033
4034
4035      return _t._iO.autoPlay;
4036
4037
4038
4039    };
4040
4041
4042
4043    /**
4044
4045     * Sets the position of a sound.
4046
4047     *
4048
4049     * @param {number} nMsecOffset Position (milliseconds)
4050
4051     * @return {SMSound} The SMSound object
4052
4053     */
4054
4055
4056
4057    this.setPosition = function(nMsecOffset) {
4058
4059
4060
4061      if (typeof nMsecOffset === 'undefined') {
4062
4063        nMsecOffset = 0;
4064
4065      }
4066
4067
4068
4069      var original_pos,
4070
4071          position, position1K,
4072
4073          // Use the duration from the instance options, if we don't have a track duration yet.
4074
4075          // position >= 0 and <= current available (loaded) duration
4076
4077          offset = (_t.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(_t.duration || _t._iO.duration, Math.max(nMsecOffset, 0)));
4078
4079
4080
4081      original_pos = _t.position;
4082
4083      _t.position = offset;
4084
4085      position1K = _t.position/1000;
4086
4087      _t._resetOnPosition(_t.position);
4088
4089      _t._iO.position = offset;
4090
4091
4092
4093      if (!_t.isHTML5) {
4094
4095
4096
4097        position = (_fV === 9 ? _t.position : position1K);
4098
4099        if (_t.readyState && _t.readyState !== 2) {
4100
4101          // if paused or not playing, will not resume (by playing)
4102
4103          _flash._setPosition(_t.id, position, (_t.paused || !_t.playState), _t._iO.multiShot);
4104
4105        }
4106
4107
4108
4109      } else if (_t._a) {
4110
4111
4112
4113        // Set the position in the canplay handler if the sound is not ready yet
4114
4115        if (_t._html5_canplay) {
4116
4117          if (_t._a.currentTime !== position1K) {
4118
4119            /**
4120
4121             * DOM/JS errors/exceptions to watch out for:
4122
4123             * if seek is beyond (loaded?) position, "DOM exception 11"
4124
4125             * "INDEX_SIZE_ERR": DOM exception 1
4126
4127             */
4128
4129            _s._wD('setPosition('+position1K+'): setting position');
4130
4131            try {
4132
4133              _t._a.currentTime = position1K;
4134
4135              if (_t.playState === 0 || _t.paused) {
4136
4137                // allow seek without auto-play/resume
4138
4139                _t._a.pause();
4140
4141              }
4142
4143            } catch(e) {
4144
4145              _s._wD('setPosition('+position1K+'): setting position failed: '+e.message, 2);
4146
4147            }
4148
4149          }
4150
4151        } else {
4152
4153          _s._wD('setPosition('+position1K+'): delaying, sound not ready');
4154
4155        }
4156
4157
4158
4159      }
4160
4161
4162
4163      if (_t.isHTML5) {
4164
4165        if (_t.paused) {
4166
4167          // if paused, refresh UI right away
4168
4169          // force update
4170
4171          _t._onTimer(true);
4172
4173        }
4174
4175      }
4176
4177
4178
4179      return _t;
4180
4181
4182
4183    };
4184
4185
4186
4187    /**
4188
4189     * Pauses sound playback.
4190
4191     *
4192
4193     * @return {SMSound} The SMSound object
4194
4195     */
4196
4197
4198
4199    this.pause = function(_bCallFlash) {
4200
4201
4202
4203      if (_t.paused || (_t.playState === 0 && _t.readyState !== 1)) {
4204
4205        return _t;
4206
4207      }
4208
4209
4210
4211      _s._wD('SMSound.pause()');
4212
4213      _t.paused = true;
4214
4215
4216
4217      if (!_t.isHTML5) {
4218
4219        if (_bCallFlash || typeof _bCallFlash === 'undefined') {
4220
4221          _flash._pause(_t.id, _t._iO.multiShot);
4222
4223        }
4224
4225      } else {
4226
4227        _t._setup_html5().pause();
4228
4229        _stop_html5_timer();
4230
4231      }
4232
4233
4234
4235      if (_t._iO.onpause) {
4236
4237        _t._iO.onpause.apply(_t);
4238
4239      }
4240
4241
4242
4243      return _t;
4244
4245
4246
4247    };
4248
4249
4250
4251    /**
4252
4253     * Resumes sound playback.
4254
4255     *
4256
4257     * @return {SMSound} The SMSound object
4258
4259     */
4260
4261
4262
4263    /**
4264
4265     * When auto-loaded streams pause on buffer full they have a playState of 0.
4266
4267     * We need to make sure that the playState is set to 1 when these streams "resume".
4268
4269     * When a paused stream is resumed, we need to trigger the onplay() callback if it
4270
4271     * hasn't been called already. In this case since the sound is being played for the
4272
4273     * first time, I think it's more appropriate to call onplay() rather than onresume().
4274
4275     */
4276
4277
4278
4279    this.resume = function() {
4280
4281
4282
4283      var _iO = _t._iO;
4284
4285
4286
4287      if (!_t.paused) {
4288
4289        return _t;
4290
4291      }
4292
4293
4294
4295      _s._wD('SMSound.resume()');
4296
4297      _t.paused = false;
4298
4299      _t.playState = 1;
4300
4301
4302
4303      if (!_t.isHTML5) {
4304
4305        if (_iO.isMovieStar && !_iO.serverURL) {
4306
4307          // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
4308
4309          _t.setPosition(_t.position);
4310
4311        }
4312
4313        // flash method is toggle-based (pause/resume)
4314
4315        _flash._pause(_t.id, _iO.multiShot);
4316
4317      } else {
4318
4319        _t._setup_html5().play();
4320
4321        _start_html5_timer();
4322
4323      }
4324
4325
4326
4327      if (!_onplay_called && _iO.onplay) {
4328
4329        _iO.onplay.apply(_t);
4330
4331        _onplay_called = true;
4332
4333      } else if (_iO.onresume) {
4334
4335        _iO.onresume.apply(_t);
4336
4337      }
4338
4339
4340
4341      return _t;
4342
4343
4344
4345    };
4346
4347
4348
4349    /**
4350
4351     * Toggles sound playback.
4352
4353     *
4354
4355     * @return {SMSound} The SMSound object
4356
4357     */
4358
4359
4360
4361    this.togglePause = function() {
4362
4363
4364
4365      _s._wD('SMSound.togglePause()');
4366
4367
4368
4369      if (_t.playState === 0) {
4370
4371        _t.play({
4372
4373          position: (_fV === 9 && !_t.isHTML5 ? _t.position : _t.position / 1000)
4374
4375        });
4376
4377        return _t;
4378
4379      }
4380
4381
4382
4383      if (_t.paused) {
4384
4385        _t.resume();
4386
4387      } else {
4388
4389        _t.pause();
4390
4391      }
4392
4393
4394
4395      return _t;
4396
4397
4398
4399    };
4400
4401
4402
4403    /**
4404
4405     * Sets the panning (L-R) effect.
4406
4407     *
4408
4409     * @param {number} nPan The pan value (-100 to 100)
4410
4411     * @return {SMSound} The SMSound object
4412
4413     */
4414
4415
4416
4417    this.setPan = function(nPan, bInstanceOnly) {
4418
4419
4420
4421      if (typeof nPan === 'undefined') {
4422
4423        nPan = 0;
4424
4425      }
4426
4427
4428
4429      if (typeof bInstanceOnly === 'undefined') {
4430
4431        bInstanceOnly = false;
4432
4433      }
4434
4435
4436
4437      if (!_t.isHTML5) {
4438
4439        _flash._setPan(_t.id, nPan);
4440
4441      } // else { no HTML5 pan? }
4442
4443
4444
4445      _t._iO.pan = nPan;
4446
4447
4448
4449      if (!bInstanceOnly) {
4450
4451        _t.pan = nPan;
4452
4453        _t.options.pan = nPan;
4454
4455      }
4456
4457
4458
4459      return _t;
4460
4461
4462
4463    };
4464
4465
4466
4467    /**
4468
4469     * Sets the volume.
4470
4471     *
4472
4473     * @param {number} nVol The volume value (0 to 100)
4474
4475     * @return {SMSound} The SMSound object
4476
4477     */
4478
4479
4480
4481    this.setVolume = function(nVol, _bInstanceOnly) {
4482
4483
4484
4485      /**
4486
4487       * Note: Setting volume has no effect on iOS "special snowflake" devices.
4488
4489       * Hardware volume control overrides software, and volume
4490
4491       * will always return 1 per Apple docs. (iOS 4 + 5.)
4492
4493       * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html
4494
4495       */
4496
4497
4498
4499      if (typeof nVol === 'undefined') {
4500
4501        nVol = 100;
4502
4503      }
4504
4505
4506
4507      if (typeof _bInstanceOnly === 'undefined') {
4508
4509        _bInstanceOnly = false;
4510
4511      }
4512
4513
4514
4515      if (!_t.isHTML5) {
4516
4517        _flash._setVolume(_t.id, (_s.muted && !_t.muted) || _t.muted?0:nVol);
4518
4519      } else if (_t._a) {
4520
4521        // valid range: 0-1
4522
4523        _t._a.volume = Math.max(0, Math.min(1, nVol/100));
4524
4525      }
4526
4527
4528
4529      _t._iO.volume = nVol;
4530
4531
4532
4533      if (!_bInstanceOnly) {
4534
4535        _t.volume = nVol;
4536
4537        _t.options.volume = nVol;
4538
4539      }
4540
4541
4542
4543      return _t;
4544
4545
4546
4547    };
4548
4549
4550
4551    /**
4552
4553     * Mutes the sound.
4554
4555     *
4556
4557     * @return {SMSound} The SMSound object
4558
4559     */
4560
4561
4562
4563    this.mute = function() {
4564
4565
4566
4567      _t.muted = true;
4568
4569
4570
4571      if (!_t.isHTML5) {
4572
4573        _flash._setVolume(_t.id, 0);
4574
4575      } else if (_t._a) {
4576
4577        _t._a.muted = true;
4578
4579      }
4580
4581
4582
4583      return _t;
4584
4585
4586
4587    };
4588
4589
4590
4591    /**
4592
4593     * Unmutes the sound.
4594
4595     *
4596
4597     * @return {SMSound} The SMSound object
4598
4599     */
4600
4601
4602
4603    this.unmute = function() {
4604
4605
4606
4607      _t.muted = false;
4608
4609      var hasIO = (typeof _t._iO.volume !== 'undefined');
4610
4611
4612
4613      if (!_t.isHTML5) {
4614
4615        _flash._setVolume(_t.id, hasIO?_t._iO.volume:_t.options.volume);
4616
4617      } else if (_t._a) {
4618
4619        _t._a.muted = false;
4620
4621      }
4622
4623
4624
4625      return _t;
4626
4627
4628
4629    };
4630
4631
4632
4633    /**
4634
4635     * Toggles the muted state of a sound.
4636
4637     *
4638
4639     * @return {SMSound} The SMSound object
4640
4641     */
4642
4643
4644
4645    this.toggleMute = function() {
4646
4647
4648
4649      return (_t.muted?_t.unmute():_t.mute());
4650
4651
4652
4653    };
4654
4655
4656
4657    /**
4658
4659     * Registers a callback to be fired when a sound reaches a given position during playback.
4660
4661     *
4662
4663     * @param {number} nPosition The position to watch for
4664
4665     * @param {function} oMethod The relevant callback to fire
4666
4667     * @param {object} oScope Optional: The scope to apply the callback to
4668
4669     * @return {SMSound} The SMSound object
4670
4671     */
4672
4673
4674
4675    this.onPosition = function(nPosition, oMethod, oScope) {
4676
4677
4678
4679      // TODO: basic dupe checking?
4680
4681
4682
4683      _onPositionItems.push({
4684
4685        position: parseInt(nPosition, 10),
4686
4687        method: oMethod,
4688
4689        scope: (typeof oScope !== 'undefined' ? oScope : _t),
4690
4691        fired: false
4692
4693      });
4694
4695
4696
4697      return _t;
4698
4699
4700
4701    };
4702
4703
4704
4705    // legacy/backwards-compability: lower-case method name
4706
4707    this.onposition = this.onPosition;
4708
4709
4710
4711    /**
4712
4713     * Removes registered callback(s) from a sound, by position and/or callback.
4714
4715     *
4716
4717     * @param {number} nPosition The position to clear callback(s) for
4718
4719     * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position
4720
4721     * @return {SMSound} The SMSound object
4722
4723     */
4724
4725
4726
4727    this.clearOnPosition = function(nPosition, oMethod) {
4728
4729
4730
4731      var i;
4732
4733
4734
4735      nPosition = parseInt(nPosition, 10);
4736
4737
4738
4739      if (isNaN(nPosition)) {
4740
4741        // safety check
4742
4743        return false;
4744
4745      }
4746
4747
4748
4749      for (i=0; i < _onPositionItems.length; i++) {
4750
4751
4752
4753        if (nPosition === _onPositionItems[i].position) {
4754
4755          // remove this item if no method was specified, or, if the method matches
4756
4757          if (!oMethod || (oMethod === _onPositionItems[i].method)) {
4758
4759            if (_onPositionItems[i].fired) {
4760
4761              // decrement "fired" counter, too
4762
4763              _onPositionFired--;
4764
4765            }
4766
4767            _onPositionItems.splice(i, 1);
4768
4769          }
4770
4771        }
4772
4773
4774
4775      }
4776
4777
4778
4779    };
4780
4781
4782
4783    this._processOnPosition = function() {
4784
4785
4786
4787      var i, item, j = _onPositionItems.length;
4788
4789
4790
4791      if (!j || !_t.playState || _onPositionFired >= j) {
4792
4793        return false;
4794
4795      }
4796
4797
4798
4799      for (i=j-1; i >= 0; i--) {
4800
4801        item = _onPositionItems[i];
4802
4803        if (!item.fired && _t.position >= item.position) {
4804
4805          item.fired = true;
4806
4807          _onPositionFired++;
4808
4809          item.method.apply(item.scope, [item.position]);
4810
4811        }
4812
4813      }
4814
4815
4816
4817      return true;
4818
4819
4820
4821    };
4822
4823
4824
4825    this._resetOnPosition = function(nPosition) {
4826
4827
4828
4829      // reset "fired" for items interested in this position
4830
4831      var i, item, j = _onPositionItems.length;
4832
4833
4834
4835      if (!j) {
4836
4837        return false;
4838
4839      }
4840
4841
4842
4843      for (i=j-1; i >= 0; i--) {
4844
4845        item = _onPositionItems[i];
4846
4847        if (item.fired && nPosition <= item.position) {
4848
4849          item.fired = false;
4850
4851          _onPositionFired--;
4852
4853        }
4854
4855      }
4856
4857
4858
4859      return true;
4860
4861
4862
4863    };
4864
4865
4866
4867    /**
4868
4869     * SMSound() private internals
4870
4871     * --------------------------------
4872
4873     */
4874
4875
4876
4877    _applyFromTo = function() {
4878
4879
4880
4881      var _iO = _t._iO,
4882
4883          f = _iO.from,
4884
4885          t = _iO.to,
4886
4887          start, end;
4888
4889
4890
4891      end = function() {
4892
4893
4894
4895        // end has been reached.
4896
4897        _s._wD(_t.id + ': "to" time of ' + t + ' reached.');
4898
4899
4900
4901        // detach listener
4902
4903        _t.clearOnPosition(t, end);
4904
4905
4906
4907        // stop should clear this, too
4908
4909        _t.stop();
4910
4911
4912
4913      };
4914
4915
4916
4917      start = function() {
4918
4919
4920
4921        _s._wD(_t.id + ': playing "from" ' + f);
4922
4923
4924
4925        // add listener for end
4926
4927        if (t !== null && !isNaN(t)) {
4928
4929          _t.onPosition(t, end);
4930
4931        }
4932
4933
4934
4935      };
4936
4937
4938
4939      if (f !== null && !isNaN(f)) {
4940
4941
4942
4943        // apply to instance options, guaranteeing correct start position.
4944
4945        _iO.position = f;
4946
4947
4948
4949        // multiShot timing can't be tracked, so prevent that.
4950
4951        _iO.multiShot = false;
4952
4953
4954
4955        start();
4956
4957
4958
4959      }
4960
4961
4962
4963      // return updated instanceOptions including starting position
4964
4965      return _iO;
4966
4967
4968
4969    };
4970
4971
4972
4973    _attachOnPosition = function() {
4974
4975
4976
4977      var item,
4978
4979          op = _t._iO.onposition;
4980
4981
4982
4983      // attach onposition things, if any, now.
4984
4985
4986
4987      if (op) {
4988
4989
4990
4991        for (item in op) {
4992
4993          if (op.hasOwnProperty(item)) {
4994
4995            _t.onPosition(parseInt(item, 10), op[item]); 
4996
4997          }
4998
4999        }
5000
5001
5002
5003      }
5004
5005
5006
5007    };
5008
5009
5010
5011    _detachOnPosition = function() {
5012
5013
5014
5015      var item,
5016
5017          op = _t._iO.onposition;
5018
5019
5020
5021      // detach any onposition()-style listeners.
5022
5023
5024
5025      if (op) {
5026
5027
5028
5029        for (item in op) {
5030
5031          if (op.hasOwnProperty(item)) {
5032
5033            _t.clearOnPosition(parseInt(item, 10));
5034
5035          }
5036
5037        }
5038
5039
5040
5041      }
5042
5043
5044
5045    };
5046
5047
5048
5049    _start_html5_timer = function() {
5050
5051
5052
5053      if (_t.isHTML5) {
5054
5055        _startTimer(_t);
5056
5057      }
5058
5059
5060
5061    };
5062
5063
5064
5065    _stop_html5_timer = function() {
5066
5067
5068
5069      if (_t.isHTML5) {
5070
5071        _stopTimer(_t);
5072
5073      }
5074
5075
5076
5077    };
5078
5079
5080
5081    _resetProperties = function(retainPosition) {
5082
5083
5084
5085      if (!retainPosition) {
5086
5087        _onPositionItems = [];
5088
5089        _onPositionFired = 0;
5090
5091      }
5092
5093
5094
5095      _onplay_called = false;
5096
5097
5098
5099      _t._hasTimer = null;
5100
5101      _t._a = null;
5102
5103      _t._html5_canplay = false;
5104
5105      _t.bytesLoaded = null;
5106
5107      _t.bytesTotal = null;
5108
5109      _t.duration = (_t._iO && _t._iO.duration ? _t._iO.duration : null);
5110
5111      _t.durationEstimate = null;
5112
5113      _t.buffered = [];
5114
5115
5116
5117      // legacy: 1D array
5118
5119      _t.eqData = [];
5120
5121
5122
5123      _t.eqData.left = [];
5124
5125      _t.eqData.right = [];
5126
5127
5128
5129      _t.failures = 0;
5130
5131      _t.isBuffering = false;
5132
5133      _t.instanceOptions = {};
5134
5135      _t.instanceCount = 0;
5136
5137      _t.loaded = false;
5138
5139      _t.metadata = {};
5140
5141
5142
5143      // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
5144
5145      _t.readyState = 0;
5146
5147
5148
5149      _t.muted = false;
5150
5151      _t.paused = false;
5152
5153
5154
5155      _t.peakData = {
5156
5157        left: 0,
5158
5159        right: 0
5160
5161      };
5162
5163
5164
5165      _t.waveformData = {
5166
5167        left: [],
5168
5169        right: []
5170
5171      };
5172
5173
5174
5175      _t.playState = 0;
5176
5177      _t.position = null;
5178
5179
5180
5181      _t.id3 = {};
5182
5183
5184
5185    };
5186
5187
5188
5189    _resetProperties();
5190
5191
5192
5193    /**
5194
5195     * Pseudo-private SMSound internals
5196
5197     * --------------------------------
5198
5199     */
5200
5201
5202
5203    this._onTimer = function(bForce) {
5204
5205
5206
5207      /**
5208
5209       * HTML5-only _whileplaying() etc.
5210
5211       * called from both HTML5 native events, and polling/interval-based timers
5212
5213       * mimics flash and fires only when time/duration change, so as to be polling-friendly
5214
5215       */
5216
5217
5218
5219      var duration, isNew = false, time, x = {};
5220
5221
5222
5223      if (_t._hasTimer || bForce) {
5224
5225
5226
5227        // TODO: May not need to track readyState (1 = loading)
5228
5229
5230
5231        if (_t._a && (bForce || ((_t.playState > 0 || _t.readyState === 1) && !_t.paused))) {
5232
5233
5234
5235          duration = _t._get_html5_duration();
5236
5237
5238
5239          if (duration !== _lastHTML5State.duration) {
5240
5241
5242
5243            _lastHTML5State.duration = duration;
5244
5245            _t.duration = duration;
5246
5247            isNew = true;
5248
5249
5250
5251          }
5252
5253
5254
5255          // TODO: investigate why this goes wack if not set/re-set each time.
5256
5257          _t.durationEstimate = _t.duration;
5258
5259
5260
5261          time = (_t._a.currentTime * 1000 || 0);
5262
5263
5264
5265          if (time !== _lastHTML5State.time) {
5266
5267
5268
5269            _lastHTML5State.time = time;
5270
5271            isNew = true;
5272
5273
5274
5275          }
5276
5277
5278
5279          if (isNew || bForce) {
5280
5281
5282
5283            _t._whileplaying(time,x,x,x,x);
5284
5285
5286
5287          }
5288
5289
5290
5291        }/* else {
5292
5293
5294
5295          // _s._wD('_onTimer: Warn for "'+_t.id+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK'));
5296
5297
5298
5299          return false;
5300
5301
5302
5303        }*/
5304
5305
5306
5307        return isNew;
5308
5309
5310
5311      }
5312
5313
5314
5315    };
5316
5317
5318
5319    this._get_html5_duration = function() {
5320
5321
5322
5323      var _iO = _t._iO,
5324
5325          // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
5326
5327          d = (_t._a && _t._a.duration ? _t._a.duration*1000 : (_iO && _iO.duration ? _iO.duration : null)),
5328
5329          result = (d && !isNaN(d) && d !== Infinity ? d : null);
5330
5331
5332
5333      return result;
5334
5335
5336
5337    };
5338
5339
5340
5341    this._apply_loop = function(a, nLoops) {
5342
5343
5344
5345      /**
5346
5347       * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
5348
5349       * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
5350
5351       */
5352
5353
5354
5355      // <d>
5356
5357      if (!a.loop && nLoops > 1) {
5358
5359        _s._wD('Note: Native HTML5 looping is infinite.');
5360
5361      }
5362
5363      // </d>
5364
5365
5366
5367      a.loop = (nLoops > 1 ? 'loop' : '');
5368
5369
5370
5371    };
5372
5373
5374
5375    this._setup_html5 = function(oOptions) {
5376
5377
5378
5379      var _iO = _mixin(_t._iO, oOptions), d = decodeURI,
5380
5381          _a = _useGlobalHTML5Audio ? _s._global_a : _t._a,
5382
5383          _dURL = d(_iO.url),
5384
5385          _oldIO = (_a && _a._t ? _a._t.instanceOptions : null),
5386
5387          result;
5388
5389
5390
5391      if (_a) {
5392
5393
5394
5395        if (_a._t) {
5396
5397
5398
5399          if (!_useGlobalHTML5Audio && _dURL === d(_lastURL)) {
5400
5401
5402
5403            // same url, ignore request
5404
5405            result = _a; 
5406
5407
5408
5409          } else if (_useGlobalHTML5Audio && _oldIO.url === _iO.url && (!_lastURL || (_lastURL === _oldIO.url))) {
5410
5411
5412
5413            // iOS-type reuse case
5414
5415            result = _a;
5416
5417
5418
5419          }
5420
5421
5422
5423          if (result) {
5424
5425
5426
5427            _t._apply_loop(_a, _iO.loops);
5428
5429            return result;
5430
5431
5432
5433          }
5434
5435
5436
5437        }
5438
5439
5440
5441        _s._wD('setting URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : ''));
5442
5443
5444
5445        /**
5446
5447         * "First things first, I, Poppa.." (reset the previous state of the old sound, if playing)
5448
5449         * Fixes case with devices that can only play one sound at a time
5450
5451         * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
5452
5453         */
5454
5455
5456
5457        if (_useGlobalHTML5Audio && _a._t && _a._t.playState && _iO.url !== _oldIO.url) {
5458
5459
5460
5461          _a._t.stop();
5462
5463
5464
5465        }
5466
5467
5468
5469        // reset load/playstate, onPosition etc. if the URL is new.
5470
5471        // somewhat-tricky object re-use vs. new SMSound object, old vs. new URL comparisons
5472
5473        _resetProperties((_oldIO && _oldIO.url ? _iO.url === _oldIO.url : (_lastURL ? _lastURL === _iO.url : false)));
5474
5475
5476
5477        _a.src = _iO.url;
5478
5479        _t.url = _iO.url;
5480
5481        _lastURL = _iO.url;
5482
5483        _a._called_load = false;
5484
5485
5486
5487      } else {
5488
5489
5490
5491        _wDS('h5a');
5492
5493
5494
5495        if (_iO.autoLoad || _iO.autoPlay) {
5496
5497
5498
5499          _t._a = new Audio(_iO.url);
5500
5501
5502
5503        } else {
5504
5505
5506
5507          // null for stupid Opera 9.64 case
5508
5509          _t._a = (_isOpera && opera.version() < 10 ? new Audio(null) : new Audio());
5510
5511
5512
5513        }
5514
5515
5516
5517        // assign local reference
5518
5519        _a = _t._a;
5520
5521
5522
5523        _a._called_load = false;
5524
5525
5526
5527        if (_useGlobalHTML5Audio) {
5528
5529
5530
5531          _s._global_a = _a;
5532
5533
5534
5535        }
5536
5537
5538
5539      }
5540
5541
5542
5543      _t.isHTML5 = true;
5544
5545
5546
5547      // store a ref on the track
5548
5549      _t._a = _a;
5550
5551
5552
5553      // store a ref on the audio
5554
5555      _a._t = _t;
5556
5557
5558
5559      _add_html5_events();
5560
5561
5562
5563      _t._apply_loop(_a, _iO.loops);
5564
5565
5566
5567      if (_iO.autoLoad || _iO.autoPlay) {
5568
5569
5570
5571        _t.load();
5572
5573
5574
5575      } else {
5576
5577
5578
5579        // early HTML5 implementation (non-standard)
5580
5581        _a.autobuffer = false;
5582
5583
5584
5585        // standard ('none' is also an option.)
5586
5587        _a.preload = 'auto';
5588
5589
5590
5591      }
5592
5593
5594
5595      return _a;
5596
5597
5598
5599    };
5600
5601
5602
5603    _add_html5_events = function() {
5604
5605
5606
5607      if (_t._a._added_events) {
5608
5609        return false;
5610
5611      }
5612
5613
5614
5615      var f;
5616
5617
5618
5619      function add(oEvt, oFn, bCapture) {
5620
5621        return _t._a ? _t._a.addEventListener(oEvt, oFn, bCapture||false) : null;
5622
5623      }
5624
5625
5626
5627      _t._a._added_events = true;
5628
5629
5630
5631      for (f in _html5_events) {
5632
5633        if (_html5_events.hasOwnProperty(f)) {
5634
5635          add(f, _html5_events[f]);
5636
5637        }
5638
5639      }
5640
5641
5642
5643      return true;
5644
5645
5646
5647    };
5648
5649
5650
5651    _remove_html5_events = function() {
5652
5653
5654
5655      // Remove event listeners
5656
5657
5658
5659      var f;
5660
5661
5662
5663      function remove(oEvt, oFn, bCapture) {
5664
5665        return (_t._a ? _t._a.removeEventListener(oEvt, oFn, bCapture||false) : null);
5666
5667      }
5668
5669
5670
5671      _s._wD(_h5+'removing event listeners: '+_t.id);
5672
5673      _t._a._added_events = false;
5674
5675
5676
5677      for (f in _html5_events) {
5678
5679        if (_html5_events.hasOwnProperty(f)) {
5680
5681          remove(f, _html5_events[f]);
5682
5683        }
5684
5685      }
5686
5687
5688
5689    };
5690
5691
5692
5693    /**
5694
5695     * Pseudo-private event internals
5696
5697     * ------------------------------
5698
5699     */
5700
5701
5702
5703    this._onload = function(nSuccess) {
5704
5705
5706
5707
5708
5709      var fN,
5710
5711          // check for duration to prevent false positives from flash 8 when loading from cache.
5712
5713          loadOK = (!!(nSuccess) || (!_t.isHTML5 && _fV === 8 && _t.duration));
5714
5715
5716
5717      // <d>
5718
5719      fN = 'SMSound._onload(): ';
5720
5721      _s._wD(fN + '"' + _t.id + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2));
5722
5723      if (!loadOK && !_t.isHTML5) {
5724
5725        if (_s.sandbox.noRemote === true) {
5726
5727          _s._wD(fN + _str('noNet'), 1);
5728
5729        }
5730
5731        if (_s.sandbox.noLocal === true) {
5732
5733          _s._wD(fN + _str('noLocal'), 1);
5734
5735        }
5736
5737      }
5738
5739      // </d>
5740
5741
5742
5743      _t.loaded = loadOK;
5744
5745      _t.readyState = loadOK?3:2;
5746
5747      _t._onbufferchange(0);
5748
5749
5750
5751      if (_t._iO.onload) {
5752
5753        _t._iO.onload.apply(_t, [loadOK]);
5754
5755      }
5756
5757
5758
5759      return true;
5760
5761
5762
5763    };
5764
5765
5766
5767    this._onbufferchange = function(nIsBuffering) {
5768
5769
5770
5771      if (_t.playState === 0) {
5772
5773        // ignore if not playing
5774
5775        return false;
5776
5777      }
5778
5779
5780
5781      if ((nIsBuffering && _t.isBuffering) || (!nIsBuffering && !_t.isBuffering)) {
5782
5783        return false;
5784
5785      }
5786
5787
5788
5789      _t.isBuffering = (nIsBuffering === 1);
5790
5791      if (_t._iO.onbufferchange) {
5792
5793        _s._wD('SMSound._onbufferchange(): ' + nIsBuffering);
5794
5795        _t._iO.onbufferchange.apply(_t);
5796
5797      }
5798
5799
5800
5801      return true;
5802
5803
5804
5805    };
5806
5807
5808
5809    /**
5810
5811     * Notify Mobile Safari that user action is required
5812
5813     * to continue playing / loading the audio file.
5814
5815     */
5816
5817
5818
5819    this._onsuspend = function() {
5820
5821
5822
5823      if (_t._iO.onsuspend) {
5824
5825        _s._wD('SMSound._onsuspend()');
5826
5827        _t._iO.onsuspend.apply(_t);
5828
5829      }
5830
5831
5832
5833      return true;
5834
5835
5836
5837    };
5838
5839
5840
5841    /**
5842
5843     * flash 9/movieStar + RTMP-only method, should fire only once at most
5844
5845     * at this point we just recreate failed sounds rather than trying to reconnect
5846
5847     */
5848
5849
5850
5851    this._onfailure = function(msg, level, code) {
5852
5853
5854
5855      _t.failures++;
5856
5857      _s._wD('SMSound._onfailure(): "'+_t.id+'" count '+_t.failures);
5858
5859
5860
5861      if (_t._iO.onfailure && _t.failures === 1) {
5862
5863        _t._iO.onfailure(_t, msg, level, code);
5864
5865      } else {
5866
5867        _s._wD('SMSound._onfailure(): ignoring');
5868
5869      }
5870
5871
5872
5873    };
5874
5875
5876
5877    this._onfinish = function() {
5878
5879
5880
5881      // store local copy before it gets trashed..
5882
5883      var _io_onfinish = _t._iO.onfinish;
5884
5885
5886
5887      _t._onbufferchange(0);
5888
5889      _t._resetOnPosition(0);
5890
5891
5892
5893      // reset some state items
5894
5895      if (_t.instanceCount) {
5896
5897
5898
5899        _t.instanceCount--;
5900
5901
5902
5903        if (!_t.instanceCount) {
5904
5905
5906
5907          // remove onPosition listeners, if any
5908
5909          _detachOnPosition();
5910
5911
5912
5913          // reset instance options
5914
5915          _t.playState = 0;
5916
5917          _t.paused = false;
5918
5919          _t.instanceCount = 0;
5920
5921          _t.instanceOptions = {};
5922
5923          _t._iO = {};
5924
5925          _stop_html5_timer();
5926
5927
5928
5929          // reset position, too
5930
5931          if (_t.isHTML5) {
5932
5933            _t.position = 0;
5934
5935          }
5936
5937
5938
5939        }
5940
5941
5942
5943        if (!_t.instanceCount || _t._iO.multiShotEvents) {
5944
5945          // fire onfinish for last, or every instance
5946
5947          if (_io_onfinish) {
5948
5949            _s._wD('SMSound._onfinish(): "' + _t.id + '"');
5950
5951            _io_onfinish.apply(_t);
5952
5953          }
5954
5955        }
5956
5957
5958
5959      }
5960
5961
5962
5963    };
5964
5965
5966
5967    this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) {
5968
5969
5970
5971      var _iO = _t._iO;
5972
5973
5974
5975      _t.bytesLoaded = nBytesLoaded;
5976
5977      _t.bytesTotal = nBytesTotal;
5978
5979      _t.duration = Math.floor(nDuration);
5980
5981      _t.bufferLength = nBufferLength;
5982
5983
5984
5985      if (!_t.isHTML5 && !_iO.isMovieStar) {
5986
5987
5988
5989        if (_iO.duration) {
5990
5991          // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.
5992
5993          _t.durationEstimate = (_t.duration > _iO.duration) ? _t.duration : _iO.duration;
5994
5995        } else {
5996
5997          _t.durationEstimate = parseInt((_t.bytesTotal / _t.bytesLoaded) * _t.duration, 10);
5998
5999        }
6000
6001
6002
6003      } else {
6004
6005
6006
6007        _t.durationEstimate = _t.duration;
6008
6009
6010
6011      }
6012
6013
6014
6015      // for flash, reflect sequential-load-style buffering
6016
6017      if (!_t.isHTML5) {
6018
6019        _t.buffered = [{
6020
6021          'start': 0,
6022
6023          'end': _t.duration
6024
6025        }];
6026
6027      }
6028
6029
6030
6031      // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
6032
6033      if ((_t.readyState !== 3 || _t.isHTML5) && _iO.whileloading) {
6034
6035        _iO.whileloading.apply(_t);
6036
6037      }
6038
6039
6040
6041    };
6042
6043
6044
6045    this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {
6046
6047
6048
6049      var _iO = _t._iO,
6050
6051          eqLeft;
6052
6053
6054
6055      if (isNaN(nPosition) || nPosition === null) {
6056
6057        // flash safety net
6058
6059        return false;
6060
6061      }
6062
6063
6064
6065      // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.
6066
6067      _t.position = Math.max(0, nPosition);
6068
6069
6070
6071      _t._processOnPosition();
6072
6073
6074
6075      if (!_t.isHTML5 && _fV > 8) {
6076
6077
6078
6079        if (_iO.usePeakData && typeof oPeakData !== 'undefined' && oPeakData) {
6080
6081          _t.peakData = {
6082
6083            left: oPeakData.leftPeak,
6084
6085            right: oPeakData.rightPeak
6086
6087          };
6088
6089        }
6090
6091
6092
6093        if (_iO.useWaveformData && typeof oWaveformDataLeft !== 'undefined' && oWaveformDataLeft) {
6094
6095          _t.waveformData = {
6096
6097            left: oWaveformDataLeft.split(','),
6098
6099            right: oWaveformDataRight.split(',')
6100
6101          };
6102
6103        }
6104
6105
6106
6107        if (_iO.useEQData) {
6108
6109          if (typeof oEQData !== 'undefined' && oEQData && oEQData.leftEQ) {
6110
6111            eqLeft = oEQData.leftEQ.split(',');
6112
6113            _t.eqData = eqLeft;
6114
6115            _t.eqData.left = eqLeft;
6116
6117            if (typeof oEQData.rightEQ !== 'undefined' && oEQData.rightEQ) {
6118
6119              _t.eqData.right = oEQData.rightEQ.split(',');
6120
6121            }
6122
6123          }
6124
6125        }
6126
6127
6128
6129      }
6130
6131
6132
6133      if (_t.playState === 1) {
6134
6135
6136
6137        // special case/hack: ensure buffering is false if loading from cache (and not yet started)
6138
6139        if (!_t.isHTML5 && _fV === 8 && !_t.position && _t.isBuffering) {
6140
6141          _t._onbufferchange(0);
6142
6143        }
6144
6145
6146
6147        if (_iO.whileplaying) {
6148
6149          // flash may call after actual finish
6150
6151          _iO.whileplaying.apply(_t);
6152
6153        }
6154
6155
6156
6157      }
6158
6159
6160
6161      return true;
6162
6163
6164
6165    };
6166
6167
6168
6169    this._oncaptiondata = function(oData) {
6170
6171
6172
6173      /**
6174
6175       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
6176
6177       * 
6178
6179       * @param {object} oData
6180
6181       */
6182
6183
6184
6185      _s._wD('SMSound._oncaptiondata(): "' + this.id + '" caption data received.');
6186
6187
6188
6189      _t.captiondata = oData;
6190
6191
6192
6193      if (_t._iO.oncaptiondata) {
6194
6195        _t._iO.oncaptiondata.apply(_t, [oData]);
6196
6197      }
6198
6199
6200
6201	};
6202
6203
6204
6205    this._onmetadata = function(oMDProps, oMDData) {
6206
6207
6208
6209      /**
6210
6211       * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
6212
6213       * RTMP may include song title, MovieStar content may include encoding info
6214
6215       * 
6216
6217       * @param {array} oMDProps (names)
6218
6219       * @param {array} oMDData (values)
6220
6221       */
6222
6223
6224
6225      _s._wD('SMSound._onmetadata(): "' + this.id + '" metadata received.');
6226
6227
6228
6229      var oData = {}, i, j;
6230
6231
6232
6233      for (i = 0, j = oMDProps.length; i < j; i++) {
6234
6235        oData[oMDProps[i]] = oMDData[i];
6236
6237      }
6238
6239      _t.metadata = oData;
6240
6241
6242
6243      if (_t._iO.onmetadata) {
6244
6245        _t._iO.onmetadata.apply(_t);
6246
6247      }
6248
6249
6250
6251	};
6252
6253
6254
6255    this._onid3 = function(oID3Props, oID3Data) {
6256
6257
6258
6259      /**
6260
6261       * internal: flash 8 + flash 9 ID3 feature
6262
6263       * may include artist, song title etc.
6264
6265       * 
6266
6267       * @param {array} oID3Props (names)
6268
6269       * @param {array} oID3Data (values)
6270
6271       */
6272
6273
6274
6275      _s._wD('SMSound._onid3(): "' + this.id + '" ID3 data received.');
6276
6277
6278
6279      var oData = [], i, j;
6280
6281
6282
6283      for (i = 0, j = oID3Props.length; i < j; i++) {
6284
6285        oData[oID3Props[i]] = oID3Data[i];
6286
6287      }
6288
6289      _t.id3 = _mixin(_t.id3, oData);
6290
6291
6292
6293      if (_t._iO.onid3) {
6294
6295        _t._iO.onid3.apply(_t);
6296
6297      }
6298
6299
6300
6301    };
6302
6303
6304
6305    // flash/RTMP-only
6306
6307
6308
6309    this._onconnect = function(bSuccess) {
6310
6311
6312
6313      bSuccess = (bSuccess === 1);
6314
6315      _s._wD('SMSound._onconnect(): "'+_t.id+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2));
6316
6317      _t.connected = bSuccess;
6318
6319
6320
6321      if (bSuccess) {
6322
6323
6324
6325        _t.failures = 0;
6326
6327
6328
6329        if (_idCheck(_t.id)) {
6330
6331          if (_t.getAutoPlay()) {
6332
6333            // only update the play state if auto playing
6334
6335            _t.play(undefined, _t.getAutoPlay());
6336
6337          } else if (_t._iO.autoLoad) {
6338
6339            _t.load();
6340
6341          }
6342
6343        }
6344
6345
6346
6347        if (_t._iO.onconnect) {
6348
6349          _t._iO.onconnect.apply(_t, [bSuccess]);
6350
6351        }
6352
6353
6354
6355      }
6356
6357
6358
6359    };
6360
6361
6362
6363    this._ondataerror = function(sError) {
6364
6365
6366
6367      // flash 9 wave/eq data handler
6368
6369      // hack: called at start, and end from flash at/after onfinish()
6370
6371      if (_t.playState > 0) {
6372
6373        _s._wD('SMSound._ondataerror(): ' + sError);
6374
6375        if (_t._iO.ondataerror) {
6376
6377          _t._iO.ondataerror.apply(_t);
6378
6379        }
6380
6381      }
6382
6383
6384
6385    };
6386
6387
6388
6389  }; // SMSound()
6390
6391
6392
6393  /**
6394
6395   * Private SoundManager internals
6396
6397   * ------------------------------
6398
6399   */
6400
6401
6402
6403  _getDocument = function() {
6404
6405
6406
6407    return (_doc.body || _doc._docElement || _doc.getElementsByTagName('div')[0]);
6408
6409
6410
6411  };
6412
6413
6414
6415  _id = function(sID) {
6416
6417
6418
6419    return _doc.getElementById(sID);
6420
6421
6422
6423  };
6424
6425
6426
6427  _mixin = function(oMain, oAdd) {
6428
6429
6430
6431    // non-destructive merge
6432
6433    var o1 = (oMain || {}), o2, o;
6434
6435
6436
6437    // if unspecified, o2 is the default options object
6438
6439    o2 = (typeof oAdd === 'undefined' ? _s.defaultOptions : oAdd);
6440
6441
6442
6443    for (o in o2) {
6444
6445
6446
6447      if (o2.hasOwnProperty(o) && typeof o1[o] === 'undefined') {
6448
6449
6450
6451        if (typeof o2[o] !== 'object' || o2[o] === null) {
6452
6453
6454
6455          // assign directly
6456
6457          o1[o] = o2[o];
6458
6459
6460
6461        } else {
6462
6463
6464
6465          // recurse through o2
6466
6467          o1[o] = _mixin(o1[o], o2[o]);
6468
6469
6470
6471        }
6472
6473
6474
6475      }
6476
6477
6478
6479    }
6480
6481
6482
6483    return o1;
6484
6485
6486
6487  };
6488
6489
6490
6491  // additional soundManager properties that soundManager.setup() will accept
6492
6493
6494
6495  _extraOptions = {
6496
6497    'onready': 1,
6498
6499    'ontimeout': 1,
6500
6501    'defaultOptions': 1,
6502
6503    'flash9Options': 1,
6504
6505    'movieStarOptions': 1
6506
6507  };
6508
6509
6510
6511  _assign = function(o, oParent) {
6512
6513
6514
6515    /**
6516
6517     * recursive assignment of properties, soundManager.setup() helper
6518
6519     * allows property assignment based on whitelist
6520
6521     */
6522
6523
6524
6525    var i,
6526
6527        result = true,
6528
6529        hasParent = (typeof oParent !== 'undefined'),
6530
6531        setupOptions = _s.setupOptions,
6532
6533        extraOptions = _extraOptions;
6534
6535
6536
6537    // <d>
6538
6539
6540
6541    // if soundManager.setup() called, show accepted parameters.
6542
6543
6544
6545    if (typeof o === 'undefined') {
6546
6547
6548
6549      result = [];
6550
6551
6552
6553      for (i in setupOptions) {
6554
6555
6556
6557        if (setupOptions.hasOwnProperty(i)) {
6558
6559          result.push(i);
6560
6561        }
6562
6563
6564
6565      }
6566
6567
6568
6569      for (i in extraOptions) {
6570
6571
6572
6573        if (extraOptions.hasOwnProperty(i)) {
6574
6575
6576
6577          if (typeof _s[i] === 'object') {
6578
6579
6580
6581            result.push(i+': {...}');
6582
6583
6584
6585          } else if (_s[i] instanceof Function) {
6586
6587
6588
6589            result.push(i+': function() {...}');
6590
6591
6592
6593          } else {
6594
6595
6596
6597            result.push(i);
6598
6599
6600
6601          }
6602
6603
6604
6605        }
6606
6607
6608
6609      }
6610
6611
6612
6613      _s._wD(_str('setup', result.join(', ')));
6614
6615
6616
6617      return false;
6618
6619
6620
6621    }
6622
6623
6624
6625    // </d>
6626
6627
6628
6629    for (i in o) {
6630
6631
6632
6633      if (o.hasOwnProperty(i)) {
6634
6635
6636
6637        // if not an {object} we want to recurse through...
6638
6639
6640
6641        if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array) {
6642
6643
6644
6645          // check "allowed" options
6646
6647
6648
6649          if (hasParent && typeof extraOptions[oParent] !== 'undefined') {
6650
6651
6652
6653            // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
6654
6655            _s[oParent][i] = o[i];
6656
6657
6658
6659          } else if (typeof setupOptions[i] !== 'undefined') {
6660
6661
6662
6663            // special case: assign to setupOptions object, which soundManager property references
6664
6665            _s.setupOptions[i] = o[i];
6666
6667
6668
6669            // assign directly to soundManager, too
6670
6671            _s[i] = o[i];
6672
6673
6674
6675          } else if (typeof extraOptions[i] === 'undefined') {
6676
6677
6678
6679            // invalid or disallowed parameter. complain.
6680
6681            _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
6682
6683
6684
6685            result = false;
6686
6687
6688
6689          } else {
6690
6691
6692
6693            /**
6694
6695             * valid extraOptions parameter.
6696
6697             * is it a method, like onready/ontimeout? call it.
6698
6699             * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
6700
6701             */
6702
6703
6704
6705            if (_s[i] instanceof Function) {
6706
6707
6708
6709              _s[i].apply(_s, (o[i] instanceof Array? o[i] : [o[i]]));
6710
6711
6712
6713            } else {
6714
6715
6716
6717              // good old-fashioned direct assignment
6718
6719              _s[i] = o[i];
6720
6721
6722
6723            }
6724
6725
6726
6727          }
6728
6729
6730
6731        } else {
6732
6733
6734
6735          // recursion case, eg., { defaultOptions: { ... } }
6736
6737
6738
6739          if (typeof extraOptions[i] === 'undefined') {
6740
6741
6742
6743            // invalid or disallowed parameter. complain.
6744
6745            _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
6746
6747
6748
6749            result = false;
6750
6751
6752
6753          } else {
6754
6755
6756
6757            // recurse through object
6758
6759            return _assign(o[i], i);
6760
6761
6762
6763          }
6764
6765
6766
6767        }
6768
6769
6770
6771      }
6772
6773
6774
6775    }
6776
6777
6778
6779    return result;
6780
6781
6782
6783  };
6784
6785
6786
6787  function _preferFlashCheck(kind) {
6788
6789
6790
6791    // whether flash should play a given type
6792
6793    return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind]));
6794
6795
6796
6797  }
6798
6799
6800
6801  /**
6802
6803   * Internal DOM2-level event helpers
6804
6805   * ---------------------------------
6806
6807   */
6808
6809
6810
6811  _event = (function() {
6812
6813
6814
6815    var old = (_win.attachEvent),
6816
6817    evt = {
6818
6819      add: (old?'attachEvent':'addEventListener'),
6820
6821      remove: (old?'detachEvent':'removeEventListener')
6822
6823    };
6824
6825
6826
6827    function getArgs(oArgs) {
6828
6829
6830
6831      var args = _slice.call(oArgs),
6832
6833          len = args.length;
6834
6835
6836
6837      if (old) {
6838
6839        // prefix
6840
6841        args[1] = 'on' + args[1];
6842
6843        if (len > 3) {
6844
6845          // no capture
6846
6847          args.pop();
6848
6849        }
6850
6851      } else if (len === 3) {
6852
6853        args.push(false);
6854
6855      }
6856
6857
6858
6859      return args;
6860
6861
6862
6863    }
6864
6865
6866
6867    function apply(args, sType) {
6868
6869
6870
6871      var element = args.shift(),
6872
6873          method = [evt[sType]];
6874
6875
6876
6877      if (old) {
6878
6879        element[method](args[0], args[1]);
6880
6881      } else {
6882
6883        element[method].apply(element, args);
6884
6885      }
6886
6887
6888
6889    }
6890
6891
6892
6893    function add() {
6894
6895
6896
6897      apply(getArgs(arguments), 'add');
6898
6899
6900
6901    }
6902
6903
6904
6905    function remove() {
6906
6907
6908
6909      apply(getArgs(arguments), 'remove');
6910
6911
6912
6913    }
6914
6915
6916
6917    return {
6918
6919      'add': add,
6920
6921      'remove': remove
6922
6923    };
6924
6925
6926
6927  }());
6928
6929
6930
6931  /**
6932
6933   * Internal HTML5 event handling
6934
6935   * -----------------------------
6936
6937   */
6938
6939
6940
6941  function _html5_event(oFn) {
6942
6943
6944
6945    // wrap html5 event handlers so we don't call them on destroyed sounds
6946
6947
6948
6949    return function(e) {
6950
6951
6952
6953      var t = this._t,
6954
6955          result;
6956
6957
6958
6959      if (!t || !t._a) {
6960
6961        // <d>
6962
6963        if (t && t.id) {
6964
6965          _s._wD(_h5+'ignoring '+e.type+': '+t.id);
6966
6967        } else {
6968
6969          _s._wD(_h5+'ignoring '+e.type);
6970
6971        }
6972
6973        // </d>
6974
6975        result = null;
6976
6977      } else {
6978
6979        result = oFn.call(this, e);
6980
6981      }
6982
6983
6984
6985      return result;
6986
6987
6988
6989    };
6990
6991
6992
6993  }
6994
6995
6996
6997  _html5_events = {
6998
6999
7000
7001    // HTML5 event-name-to-handler map
7002
7003
7004
7005    abort: _html5_event(function() {
7006
7007
7008
7009      _s._wD(_h5+'abort: '+this._t.id);
7010
7011
7012
7013    }),
7014
7015
7016
7017    // enough has loaded to play
7018
7019
7020
7021    canplay: _html5_event(function() {
7022
7023
7024
7025      var t = this._t,
7026
7027          position1K;
7028
7029
7030
7031      if (t._html5_canplay) {
7032
7033        // this event has already fired. ignore.
7034
7035        return true;
7036
7037      }
7038
7039
7040
7041      t._html5_canplay = true;
7042
7043      _s._wD(_h5+'canplay: '+t.id+', '+t.url);
7044
7045      t._onbufferchange(0);
7046
7047
7048
7049      // position according to instance options
7050
7051      position1K = (typeof t._iO.position !== 'undefined' && !isNaN(t._iO.position)?t._iO.position/1000:null);
7052
7053
7054
7055      // set the position if position was set before the sound loaded
7056
7057      if (t.position && this.currentTime !== position1K) {
7058
7059        _s._wD(_h5+'canplay: setting position to '+position1K);
7060
7061        try {
7062
7063          this.currentTime = position1K;
7064
7065        } catch(ee) {
7066
7067          _s._wD(_h5+'setting position of ' + position1K + ' failed: '+ee.message, 2);
7068
7069        }
7070
7071      }
7072
7073
7074
7075      // hack for HTML5 from/to case
7076
7077      if (t._iO._oncanplay) {
7078
7079        t._iO._oncanplay();
7080
7081      }
7082
7083
7084
7085    }),
7086
7087
7088
7089    canplaythrough: _html5_event(function() {
7090
7091
7092
7093      var t = this._t;
7094
7095
7096
7097      if (!t.loaded) {
7098
7099        t._onbufferchange(0);
7100
7101        t._whileloading(t.bytesLoaded, t.bytesTotal, t._get_html5_duration());
7102
7103        t._onload(true);
7104
7105      }
7106
7107
7108
7109    }),
7110
7111
7112
7113    // TODO: Reserved for potential use
7114
7115    /*
7116
7117    emptied: _html5_event(function() {
7118
7119
7120
7121      _s._wD(_h5+'emptied: '+this._t.id);
7122
7123
7124
7125    }),
7126
7127    */
7128
7129
7130
7131    ended: _html5_event(function() {
7132
7133
7134
7135      var t = this._t;
7136
7137
7138
7139      _s._wD(_h5+'ended: '+t.id);
7140
7141      t._onfinish();
7142
7143
7144
7145    }),
7146
7147
7148
7149    error: _html5_event(function() {
7150
7151
7152
7153      _s._wD(_h5+'error: '+this.error.code);
7154
7155      // call load with error state?
7156
7157      this._t._onload(false);
7158
7159
7160
7161    }),
7162
7163
7164
7165    loadeddata: _html5_event(function() {
7166
7167
7168
7169      var t = this._t;
7170
7171
7172
7173      _s._wD(_h5+'loadeddata: '+this._t.id);
7174
7175
7176
7177      // safari seems to nicely report progress events, eventually totalling 100%
7178
7179      if (!t._loaded && !_isSafari) {
7180
7181        t.duration = t._get_html5_duration();
7182
7183      }
7184
7185
7186
7187    }),
7188
7189
7190
7191    loadedmetadata: _html5_event(function() {
7192
7193
7194
7195      _s._wD(_h5+'loadedmetadata: '+this._t.id);
7196
7197
7198
7199    }),
7200
7201
7202
7203    loadstart: _html5_event(function() {
7204
7205
7206
7207      _s._wD(_h5+'loadstart: '+this._t.id);
7208
7209      // assume buffering at first
7210
7211      this._t._onbufferchange(1);
7212
7213
7214
7215    }),
7216
7217
7218
7219    play: _html5_event(function() {
7220
7221
7222
7223      _s._wD(_h5+'play: '+this._t.id+', '+this._t.url);
7224
7225      // once play starts, no buffering
7226
7227      this._t._onbufferchange(0);
7228
7229
7230
7231    }),
7232
7233
7234
7235    playing: _html5_event(function() {
7236
7237
7238
7239      _s._wD(_h5+'playing: '+this._t.id);
7240
7241
7242
7243      // once play starts, no buffering
7244
7245      this._t._onbufferchange(0);
7246
7247
7248
7249    }),
7250
7251
7252
7253    progress: _html5_event(function(e) {
7254
7255
7256
7257      // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
7258
7259
7260
7261      var t = this._t,
7262
7263          i, j, str, buffered = 0,
7264
7265          isProgress = (e.type === 'progress'),
7266
7267          ranges = e.target.buffered,
7268
7269          // firefox 3.6 implements e.loaded/total (bytes)
7270
7271          loaded = (e.loaded||0),
7272
7273          total = (e.total||1),
7274
7275          // HTML5 returns msec. SM2 API uses seconds for setPosition() etc., whether Flash or HTML5.
7276
7277          scale = 1000;
7278
7279
7280
7281      // reset the "buffered" (loaded byte ranges) array
7282
7283      t.buffered = [];
7284
7285
7286
7287      if (ranges && ranges.length) {
7288
7289
7290
7291        // if loaded is 0, try TimeRanges implementation as % of load
7292
7293        // https://developer.mozilla.org/en/DOM/TimeRanges
7294
7295
7296
7297        // re-build "buffered" array
7298
7299        for (i=0, j=ranges.length; i<j; i++) {
7300
7301          t.buffered.push({
7302
7303            'start': ranges.start(i) * scale,
7304
7305            'end': ranges.end(i) * scale
7306
7307          });
7308
7309        }
7310
7311
7312
7313        // use the last value locally
7314
7315        buffered = (ranges.end(0) - ranges.start(0)) * scale;
7316
7317
7318
7319        // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
7320
7321        loaded = buffered/(e.target.duration*scale);
7322
7323
7324
7325        // <d>
7326
7327        if (isProgress && ranges.length > 1) {
7328
7329          str = [];
7330
7331          j = ranges.length;
7332
7333          for (i=0; i<j; i++) {
7334
7335            str.push(e.target.buffered.start(i)*scale +'-'+ e.target.buffered.end(i)*scale);
7336
7337          }
7338
7339          _s._wD(_h5+'progress: timeRanges: '+str.join(', '));
7340
7341        }
7342
7343
7344
7345        if (isProgress && !isNaN(loaded)) {
7346
7347          _s._wD(_h5+'progress: '+t.id+': ' + Math.floor(loaded*100)+'% loaded');
7348
7349        }
7350
7351        // </d>
7352
7353
7354
7355      }
7356
7357
7358
7359      if (!isNaN(loaded)) {
7360
7361
7362
7363        // if progress, likely not buffering
7364
7365        t._onbufferchange(0);
7366
7367        // TODO: prevent calls with duplicate values.
7368
7369        t._whileloading(loaded, total, t._get_html5_duration());
7370
7371        if (loaded && total && loaded === total) {
7372
7373          // in case "onload" doesn't fire (eg. gecko 1.9.2)
7374
7375          _html5_events.canplaythrough.call(this, e);
7376
7377        }
7378
7379
7380
7381      }
7382
7383
7384
7385    }),
7386
7387
7388
7389    ratechange: _html5_event(function() {
7390
7391
7392
7393      _s._wD(_h5+'ratechange: '+this._t.id);
7394
7395
7396
7397    }),
7398
7399
7400
7401    suspend: _html5_event(function(e) {
7402
7403
7404
7405      // download paused/stopped, may have finished (eg. onload)
7406
7407      var t = this._t;
7408
7409
7410
7411      _s._wD(_h5+'suspend: '+t.id);
7412
7413      _html5_events.progress.call(this, e);
7414
7415      t._onsuspend();
7416
7417
7418
7419    }),
7420
7421
7422
7423    stalled: _html5_event(function() {
7424
7425
7426
7427      _s._wD(_h5+'stalled: '+this._t.id);
7428
7429
7430
7431    }),
7432
7433
7434
7435    timeupdate: _html5_event(function() {
7436
7437
7438
7439      this._t._onTimer();
7440
7441
7442
7443    }),
7444
7445
7446
7447    waiting: _html5_event(function() {
7448
7449
7450
7451      var t = this._t;
7452
7453
7454
7455      // see also: seeking
7456
7457      _s._wD(_h5+'waiting: '+t.id);
7458
7459
7460
7461      // playback faster than download rate, etc.
7462
7463      t._onbufferchange(1);
7464
7465
7466
7467    })
7468
7469
7470
7471  };
7472
7473
7474
7475  _html5OK = function(iO) {
7476
7477
7478
7479    // playability test based on URL or MIME type
7480
7481
7482
7483    var result;
7484
7485
7486
7487    if (iO.serverURL || (iO.type && _preferFlashCheck(iO.type))) {
7488
7489
7490
7491      // RTMP, or preferring flash
7492
7493      result = false;
7494
7495
7496
7497    } else {
7498
7499
7500
7501      // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
7502
7503      result = ((iO.type ? _html5CanPlay({type:iO.type}) : _html5CanPlay({url:iO.url}) || _s.html5Only));
7504
7505
7506
7507    }
7508
7509
7510
7511    return result;
7512
7513
7514
7515  };
7516
7517
7518
7519  _html5Unload = function(oAudio, url) {
7520
7521
7522
7523    /**
7524
7525     * Internal method: Unload media, and cancel any current/pending network requests.
7526
7527     * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
7528
7529     * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
7530
7531     * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
7532
7533     * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
7534
7535     */
7536
7537
7538
7539    if (oAudio) {
7540
7541      // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
7542
7543      oAudio.src = url;
7544
7545    }
7546
7547
7548
7549  };
7550
7551
7552
7553  _html5CanPlay = function(o) {
7554
7555
7556
7557    /**
7558
7559     * Try to find MIME, test and return truthiness
7560
7561     * o = {
7562
7563     *  url: '/path/to/an.mp3',
7564
7565     *  type: 'audio/mp3'
7566
7567     * }
7568
7569     */
7570
7571
7572
7573    if (!_s.useHTML5Audio || !_s.hasHTML5) {
7574
7575      return false;
7576
7577    }
7578
7579
7580
7581    var url = (o.url || null),
7582
7583        mime = (o.type || null),
7584
7585        aF = _s.audioFormats,
7586
7587        result,
7588
7589        offset,
7590
7591        fileExt,
7592
7593        item;
7594
7595
7596
7597    // account for known cases like audio/mp3
7598
7599
7600
7601    if (mime && typeof _s.html5[mime] !== 'undefined') {
7602
7603      return (_s.html5[mime] && !_preferFlashCheck(mime));
7604
7605    }
7606
7607
7608
7609    if (!_html5Ext) {
7610
7611      _html5Ext = [];
7612
7613      for (item in aF) {
7614
7615        if (aF.hasOwnProperty(item)) {
7616
7617          _html5Ext.push(item);
7618
7619          if (aF[item].related) {
7620
7621            _html5Ext = _html5Ext.concat(aF[item].related);
7622
7623          }
7624
7625        }
7626
7627      }
7628
7629      _html5Ext = new RegExp('\\.('+_html5Ext.join('|')+')(\\?.*)?$','i');
7630
7631    }
7632
7633
7634
7635    // TODO: Strip URL queries, etc.
7636
7637    fileExt = (url ? url.toLowerCase().match(_html5Ext) : null);
7638
7639
7640
7641    if (!fileExt || !fileExt.length) {
7642
7643      if (!mime) {
7644
7645        result = false;
7646
7647      } else {
7648
7649        // audio/mp3 -> mp3, result should be known
7650
7651        offset = mime.indexOf(';');
7652
7653        // strip "audio/X; codecs.."
7654
7655        fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);
7656
7657      }
7658
7659    } else {
7660
7661      // match the raw extension name - "mp3", for example
7662
7663      fileExt = fileExt[1];
7664
7665    }
7666
7667
7668
7669    if (fileExt && typeof _s.html5[fileExt] !== 'undefined') {
7670
7671      // result known
7672
7673      result = (_s.html5[fileExt] && !_preferFlashCheck(fileExt));
7674
7675    } else {
7676
7677      mime = 'audio/'+fileExt;
7678
7679      result = _s.html5.canPlayType({type:mime});
7680
7681      _s.html5[fileExt] = result;
7682
7683      // _s._wD('canPlayType, found result: '+result);
7684
7685      result = (result && _s.html5[mime] && !_preferFlashCheck(mime));
7686
7687    }
7688
7689
7690
7691    return result;
7692
7693
7694
7695  };
7696
7697
7698
7699  _testHTML5 = function() {
7700
7701
7702
7703    /**
7704
7705     * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on
7706
7707     * assigns results to html5[] and flash[].
7708
7709     */
7710
7711
7712
7713    if (!_s.useHTML5Audio || !_s.hasHTML5) {
7714
7715      return false;
7716
7717    }
7718
7719
7720
7721    // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/
7722
7723    var a = (typeof Audio !== 'undefined' ? (_isOpera && opera.version() < 10 ? new Audio(null) : new Audio()) : null),
7724
7725        item, lookup, support = {}, aF, i;
7726
7727
7728
7729    function _cp(m) {
7730
7731
7732
7733      var canPlay, i, j,
7734
7735          result = false,
7736
7737          isOK = false;
7738
7739
7740
7741      if (!a || typeof a.canPlayType !== 'function') {
7742
7743        return result;
7744
7745      }
7746
7747
7748
7749      if (m instanceof Array) {
7750
7751        // iterate through all mime types, return any successes
7752
7753        for (i=0, j=m.length; i<j; i++) {
7754
7755          if (_s.html5[m[i]] || a.canPlayType(m[i]).match(_s.html5Test)) {
7756
7757            isOK = true;
7758
7759            _s.html5[m[i]] = true;
7760
7761            // note flash support, too
7762
7763            _s.flash[m[i]] = !!(m[i].match(_flashMIME));
7764
7765          }
7766
7767        }
7768
7769        result = isOK;
7770
7771      } else {
7772
7773        canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);
7774
7775        result = !!(canPlay && (canPlay.match(_s.html5Test)));
7776
7777      }
7778
7779
7780
7781      return result;
7782
7783
7784
7785    }
7786
7787
7788
7789    // test all registered formats + codecs
7790
7791
7792
7793    aF = _s.audioFormats;
7794
7795
7796
7797    for (item in aF) {
7798
7799
7800
7801      if (aF.hasOwnProperty(item)) {
7802
7803
7804
7805        lookup = 'audio/' + item;
7806
7807
7808
7809        support[item] = _cp(aF[item].type);
7810
7811
7812
7813        // write back generic type too, eg. audio/mp3
7814
7815        support[lookup] = support[item];
7816
7817
7818
7819        // assign flash
7820
7821        if (item.match(_flashMIME)) {
7822
7823
7824
7825          _s.flash[item] = true;
7826
7827          _s.flash[lookup] = true;
7828
7829
7830
7831        } else {
7832
7833
7834
7835          _s.flash[item] = false;
7836
7837          _s.flash[lookup] = false;
7838
7839
7840
7841        }
7842
7843
7844
7845        // assign result to related formats, too
7846
7847
7848
7849        if (aF[item] && aF[item].related) {
7850
7851
7852
7853          for (i=aF[item].related.length-1; i >= 0; i--) {
7854
7855
7856
7857            // eg. audio/m4a
7858
7859            support['audio/'+aF[item].related[i]] = support[item];
7860
7861            _s.html5[aF[item].related[i]] = support[item];
7862
7863            _s.flash[aF[item].related[i]] = support[item];
7864
7865
7866
7867          }
7868
7869
7870
7871        }
7872
7873
7874
7875      }
7876
7877
7878
7879    }
7880
7881
7882
7883    support.canPlayType = (a?_cp:null);
7884
7885    _s.html5 = _mixin(_s.html5, support);
7886
7887
7888
7889    return true;
7890
7891
7892
7893  };
7894
7895
7896
7897  _strings = {
7898
7899
7900
7901    // <d>
7902
7903    notReady: 'Not loaded yet - wait for soundManager.onready()',
7904
7905    notOK: 'Audio support is not available.',
7906
7907    domError: _smc + 'createMovie(): appendChild/innerHTML call failed. DOM not ready or other error.',
7908
7909    spcWmode: _smc + 'createMovie(): Removing wmode, preventing known SWF loading issue(s)',
7910
7911    swf404: _sm + ': Verify that %s is a valid path.',
7912
7913    tryDebug: 'Try ' + _sm + '.debugFlash = true for more security details (output goes to SWF.)',
7914
7915    checkSWF: 'See SWF output for more debug info.',
7916
7917    localFail: _sm + ': Non-HTTP page (' + _doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nM
7917ay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',
7918
7919    waitFocus: _sm + ': Special case: Waiting for SWF to load with window focus...',
7920
7921    waitImpatient: _sm + ': Getting impatient, still waiting for Flash%s...',
7922
7923    waitForever: _sm + ': Waiting indefinitely for Flash (will recover if unblocked)...',
7924
7925    waitSWF: _sm + ': Retrying, waiting for 100% SWF load...',
7926
7927    needFunction: _sm + ': Function object expected for %s',
7928
7929    badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
7930
7931    currentObj: '--- ' + _sm + '._debug(): Current sound objects ---',
7932
7933    waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash...',
7934
7935    waitOnload: _sm + ': Waiting for window.onload()',
7936
7937    docLoaded: _sm + ': Document already loaded',
7938
7939    onload: _smc + 'initComplete(): calling soundManager.onload()',
7940
7941    onloadOK: _sm + '.onload() complete',
7942
7943    init: _smc + 'init()',
7944
7945    didInit: _smc + 'init(): Already called?',
7946
7947    flashJS: _sm + ': Attempting JS to Flash call...',
7948
7949    secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',
7950
7951    badRemove: 'Warning: Failed to remove flash movie.',
7952
7953    shutdown: _sm + '.disable(): Shutting down',
7954
7955    queue: _sm + ': Queueing %s handler',
7956
7957    smFail: _sm + ': Failed to initialise.',
7958
7959    smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
7960
7961    fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS...',
7962
7963    fbLoaded: 'Flash loaded',
7964
7965    fbHandler: _smc+'flashBlockHandler()',
7966
7967    manURL: 'SMSound.load(): Using manually-assigned URL',
7968
7969    onURL: _sm + '.load(): current URL already assigned.',
7970
7971    badFV: _sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
7972
7973    as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
7974
7975    noNSLoop: 'Note: Looping not implemented for MovieStar formats',
7976
7977    needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
7978
7979    mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
7980
7981    needFlash: _sm + ': Fatal error: Flash is needed to play some required formats, but is not available.',
7982
7983    gotFocus: _sm + ': Got window focus.',
7984
7985    mfOn: 'mobileFlash::enabling on-screen flash repositioning',
7986
7987    policy: 'Enabling usePolicyFile for data access',
7988
7989    setup: _sm + '.setup(): allowed parameters: %s',
7990
7991    setupError: _sm + '.setup(): "%s" cannot be assigned with this method.',
7992
7993    setupUndef: _sm + '.setup(): Could not find option "%s"',
7994
7995    setupLate: _sm + '.setup(): url + flashVersion changes will not take effect until reboot().',
7996
7997    h5a: 'creating HTML5 Audio() object',
7998
7999    noURL: _sm + ': Flash URL required. Call soundManager.setup({url:...}) to get started.'
8000
8001    // </d>
8002
8003
8004
8005  };
8006
8007
8008
8009  _str = function() {
8010
8011
8012
8013    // internal string replace helper.
8014
8015    // arguments: o [,items to replace]
8016
8017    // <d>
8018
8019
8020
8021    // real array, please
8022
8023    var args = _slice.call(arguments),
8024
8025
8026
8027    // first arg
8028
8029    o = args.shift(),
8030
8031
8032
8033    str = (_strings && _strings[o]?_strings[o]:''), i, j;
8034
8035    if (str && args && args.length) {
8036
8037      for (i = 0, j = args.length; i < j; i++) {
8038
8039        str = str.replace('%s', args[i]);
8040
8041      }
8042
8043    }
8044
8045
8046
8047    return str;
8048
8049    // </d>
8050
8051
8052
8053  };
8054
8055
8056
8057  _loopFix = function(sOpt) {
8058
8059
8060
8061    // flash 8 requires stream = false for looping to work
8062
8063    if (_fV === 8 && sOpt.loops > 1 && sOpt.stream) {
8064
8065      _wDS('as2loop');
8066
8067      sOpt.stream = false;
8068
8069    }
8070
8071
8072
8073    return sOpt;
8074
8075
8076
8077  };
8078
8079
8080
8081  _policyFix = function(sOpt, sPre) {
8082
8083
8084
8085    if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) {
8086
8087      _s._wD((sPre || '') + _str('policy'));
8088
8089      sOpt.usePolicyFile = true;
8090
8091    }
8092
8093
8094
8095    return sOpt;
8096
8097
8098
8099  };
8100
8101
8102
8103  _complain = function(sMsg) {
8104
8105
8106
8107    // <d>
8108
8109    if (typeof console !== 'undefined' && typeof console.warn !== 'undefined') {
8110
8111      console.warn(sMsg);
8112
8113    } else {
8114
8115      _s._wD(sMsg);
8116
8117    }
8118
8119    // </d>
8120
8121
8122
8123  };
8124
8125
8126
8127  _doNothing = function() {
8128
8129
8130
8131    return false;
8132
8133
8134
8135  };
8136
8137
8138
8139  _disableObject = function(o) {
8140
8141
8142
8143    var oProp;
8144
8145
8146
8147    for (oProp in o) {
8148
8149      if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') {
8150
8151        o[oProp] = _doNothing;
8152
8153      }
8154
8155    }
8156
8157
8158
8159    oProp = null;
8160
8161
8162
8163  };
8164
8165
8166
8167  _failSafely = function(bNoDisable) {
8168
8169
8170
8171    // general failure exception handler
8172
8173
8174
8175    if (typeof bNoDisable === 'undefined') {
8176
8177      bNoDisable = false;
8178
8179    }
8180
8181
8182
8183    if (_disabled || bNoDisable) {
8184
8185      _wDS('smFail', 2);
8186
8187      _s.disable(bNoDisable);
8188
8189    }
8190
8191
8192
8193  };
8194
8195
8196
8197  _normalizeMovieURL = function(smURL) {
8198
8199
8200
8201    var urlParams = null, url;
8202
8203
8204
8205    if (smURL) {
8206
8207      if (smURL.match(/\.swf(\?.*)?$/i)) {
8208
8209        urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4);
8210
8211        if (urlParams) {
8212
8213          // assume user knows what they're doing
8214
8215          return smURL;
8216
8217        }
8218
8219      } else if (smURL.lastIndexOf('/') !== smURL.length - 1) {
8220
8221        // append trailing slash, if needed
8222
8223        smURL += '/';
8224
8225      }
8226
8227    }
8228
8229
8230
8231    url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + _s.movieURL;
8232
8233
8234
8235    if (_s.noSWFCache) {
8236
8237      url += ('?ts=' + new Date().getTime());
8238
8239    }
8240
8241
8242
8243    return url;
8244
8245
8246
8247  };
8248
8249
8250
8251  _setVersionInfo = function() {
8252
8253
8254
8255    // short-hand for internal use
8256
8257
8258
8259    _fV = parseInt(_s.flashVersion, 10);
8260
8261
8262
8263    if (_fV !== 8 && _fV !== 9) {
8264
8265      _s._wD(_str('badFV', _fV, _defaultFlashVersion));
8266
8267      _s.flashVersion = _fV = _defaultFlashVersion;
8268
8269    }
8270
8271
8272
8273    // debug flash movie, if applicable
8274
8275
8276
8277    var isDebug = (_s.debugMode || _s.debugFlash?'_debug.swf':'.swf');
8278
8279
8280
8281    if (_s.useHTML5Audio && !_s.html5Only && _s.audioFormats.mp4.required && _fV < 9) {
8282
8283      _s._wD(_str('needfl9'));
8284
8285      _s.flashVersion = _fV = 9;
8286
8287    }
8288
8289
8290
8291    _s.version = _s.versionNumber + (_s.html5Only?' (HTML5-only mode)':(_fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));
8292
8293
8294
8295    // set up default options
8296
8297    if (_fV > 8) {
8298
8299      // +flash 9 base options
8300
8301      _s.defaultOptions = _mixin(_s.defaultOptions, _s.flash9Options);
8302
8303      _s.features.buffering = true;
8304
8305      // +moviestar support
8306
8307      _s.defaultOptions = _mixin(_s.defaultOptions, _s.movieStarOptions);
8308
8309      _s.filePatterns.flash9 = new RegExp('\\.(mp3|' + _netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
8310
8311      _s.features.movieStar = true;
8312
8313    } else {
8314
8315      _s.features.movieStar = false;
8316
8317    }
8318
8319
8320
8321    // regExp for flash canPlay(), etc.
8322
8323    _s.filePattern = _s.filePatterns[(_fV !== 8?'flash9':'flash8')];
8324
8325
8326
8327    // if applicable, use _debug versions of SWFs
8328
8329    _s.movieURL = (_fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug);
8330
8331
8332
8333    _s.features.peakData = _s.features.waveformData = _s.features.eqData = (_fV > 8);
8334
8335
8336
8337  };
8338
8339
8340
8341  _setPolling = function(bPolling, bHighPerformance) {
8342
8343
8344
8345    if (!_flash) {
8346
8347      return false;
8348
8349    }
8350
8351
8352
8353    _flash._setPolling(bPolling, bHighPerformance);
8354
8355
8356
8357  };
8358
8359
8360
8361  _initDebug = function() {
8362
8363
8364
8365    // starts debug mode, creating output <div> for UAs without console object
8366
8367
8368
8369    // allow force of debug mode via URL
8370
8371    if (_s.debugURLParam.test(_wl)) {
8372
8373      _s.debugMode = true;
8374
8375    }
8376
8377
8378
8379    // <d>
8380
8381    if (_id(_s.debugID)) {
8382
8383      return false;
8384
8385    }
8386
8387
8388
8389    var oD, oDebug, oTarget, oToggle, tmp;
8390
8391
8392
8393    if (_s.debugMode && !_id(_s.debugID) && (!_hasConsole || !_s.useConsole || !_s.consoleOnly)) {
8394
8395
8396
8397      oD = _doc.createElement('div');
8398
8399      oD.id = _s.debugID + '-toggle';
8400
8401
8402
8403      oToggle = {
8404
8405        'position': 'fixed',
8406
8407        'bottom': '0px',
8408
8409        'right': '0px',
8410
8411        'width': '1.2em',
8412
8413        'height': '1.2em',
8414
8415        'lineHeight': '1.2em',
8416
8417        'margin': '2px',
8418
8419        'textAlign': 'center',
8420
8421        'border': '1px solid #999',
8422
8423        'cursor': 'pointer',
8424
8425        'background': '#fff',
8426
8427        'color': '#333',
8428
8429        'zIndex': 10001
8430
8431      };
8432
8433
8434
8435      oD.appendChild(_doc.createTextNode('-'));
8436
8437      oD.onclick = _toggleDebug;
8438
8439      oD.title = 'Toggle SM2 debug console';
8440
8441
8442
8443      if (_ua.match(/msie 6/i)) {
8444
8445        oD.style.position = 'absolute';
8446
8447        oD.style.cursor = 'hand';
8448
8449      }
8450
8451
8452
8453      for (tmp in oToggle) {
8454
8455        if (oToggle.hasOwnProperty(tmp)) {
8456
8457          oD.style[tmp] = oToggle[tmp];
8458
8459        }
8460
8461      }
8462
8463
8464
8465      oDebug = _doc.createElement('div');
8466
8467      oDebug.id = _s.debugID;
8468
8469      oDebug.style.display = (_s.debugMode?'block':'none');
8470
8471
8472
8473      if (_s.debugMode && !_id(oD.id)) {
8474
8475        try {
8476
8477          oTarget = _getDocument();
8478
8479          oTarget.appendChild(oD);
8480
8481        } catch(e2) {
8482
8483          throw new Error(_str('domError')+' \n'+e2.toString());
8484
8485        }
8486
8487        oTarget.appendChild(oDebug);
8488
8489      }
8490
8491
8492
8493    }
8494
8495
8496
8497    oTarget = null;
8498
8499    // </d>
8500
8501
8502
8503  };
8504
8505
8506
8507  _idCheck = this.getSoundById;
8508
8509
8510
8511  // <d>
8512
8513  _wDS = function(o, errorLevel) {
8514
8515
8516
8517    return (!o ? '' : _s._wD(_str(o), errorLevel));
8518
8519
8520
8521  };
8522
8523
8524
8525  // last-resort debugging option
8526
8527
8528
8529  if (_wl.indexOf('sm2-debug=alert') + 1 && _s.debugMode) {
8530
8531    _s._wD = function(sText) {window.alert(sText);};
8532
8533  }
8534
8535
8536
8537  _toggleDebug = function() {
8538
8539
8540
8541    var o = _id(_s.debugID),
8542
8543    oT = _id(_s.debugID + '-toggle');
8544
8545
8546
8547    if (!o) {
8548
8549      return false;
8550
8551    }
8552
8553
8554
8555    if (_debugOpen) {
8556
8557      // minimize
8558
8559      oT.innerHTML = '+';
8560
8561      o.style.display = 'none';
8562
8563    } else {
8564
8565      oT.innerHTML = '-';
8566
8567      o.style.display = 'block';
8568
8569    }
8570
8571
8572
8573    _debugOpen = !_debugOpen;
8574
8575
8576
8577  };
8578
8579
8580
8581  _debugTS = function(sEventType, bSuccess, sMessage) {
8582
8583
8584
8585    // troubleshooter debug hooks
8586
8587
8588
8589    if (typeof sm2Debugger !== 'undefined') {
8590
8591      try {
8592
8593        sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);
8594
8595      } catch(e) {
8596
8597        // oh well
8598
8599      }
8600
8601    }
8602
8603
8604
8605    return true;
8606
8607
8608
8609  };
8610
8611  // </d>
8612
8613
8614
8615  _getSWFCSS = function() {
8616
8617
8618
8619    var css = [];
8620
8621
8622
8623    if (_s.debugMode) {
8624
8625      css.push(_swfCSS.sm2Debug);
8626
8627    }
8628
8629
8630
8631    if (_s.debugFlash) {
8632
8633      css.push(_swfCSS.flashDebug);
8634
8635    }
8636
8637
8638
8639    if (_s.useHighPerformance) {
8640
8641      css.push(_swfCSS.highPerf);
8642
8643    }
8644
8645
8646
8647    return css.join(' ');
8648
8649
8650
8651  };
8652
8653
8654
8655  _flashBlockHandler = function() {
8656
8657
8658
8659    // *possible* flash block situation.
8660
8661
8662
8663    var name = _str('fbHandler'),
8664
8665        p = _s.getMoviePercent(),
8666
8667        css = _swfCSS,
8668
8669        error = {type:'FLASHBLOCK'};
8670
8671
8672
8673    if (_s.html5Only) {
8674
8675      return false;
8676
8677    }
8678
8679
8680
8681    if (!_s.ok()) {
8682
8683
8684
8685      if (_needsFlash) {
8686
8687        // make the movie more visible, so user can fix
8688
8689        _s.oMC.className = _getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError);
8690
8691        _s._wD(name+': '+_str('fbTimeout')+(p?' ('+_str('fbLoaded')+')':''));
8692
8693      }
8694
8695
8696
8697      _s.didFlashBlock = true;
8698
8699
8700
8701      // fire onready(), complain lightly
8702
8703      _processOnEvents({type:'ontimeout', ignoreInit:true, error:error});
8704
8705      _catchError(error);
8706
8707
8708
8709    } else {
8710
8711
8712
8713      // SM2 loaded OK (or recovered)
8714
8715
8716
8717      // <d>
8718
8719      if (_s.didFlashBlock) {
8720
8721        _s._wD(name+': Unblocked');
8722
8723      }
8724
8725      // </d>
8726
8727
8728
8729      if (_s.oMC) {
8730
8731        _s.oMC.className = [_getSWFCSS(), css.swfDefault, css.swfLoaded + (_s.didFlashBlock?' '+css.swfUnblocked:'')].join(' ');
8732
8733      }
8734
8735
8736
8737    }
8738
8739
8740
8741  };
8742
8743
8744
8745  _addOnEvent = function(sType, oMethod, oScope) {
8746
8747
8748
8749    if (typeof _on_queue[sType] === 'undefined') {
8750
8751      _on_queue[sType] = [];
8752
8753    }
8754
8755
8756
8757    _on_queue[sType].push({
8758
8759      'method': oMethod,
8760
8761      'scope': (oScope || null),
8762
8763      'fired': false
8764
8765    });
8766
8767
8768
8769  };
8770
8771
8772
8773  _processOnEvents = function(oOptions) {
8774
8775
8776
8777    // if unspecified, assume OK/error
8778
8779
8780
8781    if (!oOptions) {
8782
8783      oOptions = {
8784
8785        type: (_s.ok() ? 'onready' : 'ontimeout')
8786
8787      };
8788
8789    }
8790
8791
8792
8793    if (!_didInit && oOptions && !oOptions.ignoreInit) {
8794
8795      // not ready yet.
8796
8797      return false;
8798
8799    }
8800
8801
8802
8803    if (oOptions.type === 'ontimeout' && (_s.ok() || (_disabled && !oOptions.ignoreInit))) {
8804
8805      // invalid case
8806
8807      return false;
8808
8809    }
8810
8811
8812
8813    var status = {
8814
8815          success: (oOptions && oOptions.ignoreInit?_s.ok():!_disabled)
8816
8817        },
8818
8819
8820
8821        // queue specified by type, or none
8822
8823        srcQueue = (oOptions && oOptions.type?_on_queue[oOptions.type]||[]:[]),
8824
8825
8826
8827        queue = [], i, j,
8828
8829        args = [status],
8830
8831        canRetry = (_needsFlash && _s.useFlashBlock && !_s.ok());
8832
8833
8834
8835    if (oOptions.error) {
8836
8837      args[0].error = oOptions.error;
8838
8839    }
8840
8841
8842
8843    for (i = 0, j = srcQueue.length; i < j; i++) {
8844
8845      if (srcQueue[i].fired !== true) {
8846
8847        queue.push(srcQueue[i]);
8848
8849      }
8850
8851    }
8852
8853
8854
8855    if (queue.length) {
8856
8857      _s._wD(_sm + ': Firing ' + queue.length + ' '+oOptions.type+'() item' + (queue.length === 1?'':'s'));
8858
8859      for (i = 0, j = queue.length; i < j; i++) {
8860
8861        if (queue[i].scope) {
8862
8863          queue[i].method.apply(queue[i].scope, args);
8864
8865        } else {
8866
8867          queue[i].method.apply(this, args);
8868
8869        }
8870
8871        if (!canRetry) {
8872
8873          // flashblock case doesn't count here
8874
8875          queue[i].fired = true;
8876
8877        }
8878
8879      }
8880
8881    }
8882
8883
8884
8885    return true;
8886
8887
8888
8889  };
8890
8891
8892
8893  _initUserOnload = function() {
8894
8895
8896
8897    _win.setTimeout(function() {
8898
8899
8900
8901      if (_s.useFlashBlock) {
8902
8903        _flashBlockHandler();
8904
8905      }
8906
8907
8908
8909      _processOnEvents();
8910
8911
8912
8913      // call user-defined "onload", scoped to window
8914
8915
8916
8917      if (typeof _s.onload === 'function') {
8918
8919        _wDS('onload', 1);
8920
8921        _s.onload.apply(_win);
8922
8923        _wDS('onloadOK', 1);
8924
8925      }
8926
8927
8928
8929      if (_s.waitForWindowLoad) {
8930
8931        _event.add(_win, 'load', _initUserOnload);
8932
8933      }
8934
8935
8936
8937    },1);
8938
8939
8940
8941  };
8942
8943
8944
8945  _detectFlash = function() {
8946
8947
8948
8949    // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt
8950
8951
8952
8953    if (typeof _hasFlash !== 'undefined') {
8954
8955      // this work has already been done.
8956
8957      return _hasFlash;
8958
8959    }
8960
8961
8962
8963    var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = _win.ActiveXObject;
8964
8965
8966
8967    if (nP && nP.length) {
8968
8969      type = 'application/x-shockwave-flash';
8970
8971      types = n.mimeTypes;
8972
8973      if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) {
8974
8975        hasPlugin = true;
8976
8977      }
8978
8979    } else if (typeof AX !== 'undefined') {
8980
8981      try {
8982
8983        obj = new AX('ShockwaveFlash.ShockwaveFlash');
8984
8985      } catch(e) {
8986
8987        // oh well
8988
8989      }
8990
8991      hasPlugin = (!!obj);
8992
8993    }
8994
8995
8996
8997    _hasFlash = hasPlugin;
8998
8999
9000
9001    return hasPlugin;
9002
9003
9004
9005  };
9006
9007
9008
9009  _featureCheck = function() {
9010
9011
9012
9013    var needsFlash,
9014
9015        item,
9016
9017        result = true,
9018
9019        formats = _s.audioFormats,
9020
9021        // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
9022
9023        isSpecial = (_is_iDevice && !!(_ua.match(/os (1|2|3_0|3_1)/i)));
9024
9025
9026
9027    if (isSpecial) {
9028
9029
9030
9031      // has Audio(), but is broken; let it load links directly.
9032
9033      _s.hasHTML5 = false;
9034
9035
9036
9037      // ignore flash case, however
9038
9039      _s.html5Only = true;
9040
9041
9042
9043      if (_s.oMC) {
9044
9045        _s.oMC.style.display = 'none';
9046
9047      }
9048
9049
9050
9051      result = false;
9052
9053
9054
9055    } else {
9056
9057
9058
9059      if (_s.useHTML5Audio) {
9060
9061
9062
9063        if (!_s.html5 || !_s.html5.canPlayType) {
9064
9065          _s._wD('SoundManager: No HTML5 Audio() support detected.');
9066
9067          _s.hasHTML5 = false;
9068
9069        }
9070
9071
9072
9073        // <d>
9074
9075        if (_isBadSafari) {
9076
9077          _s._wD(_smc+'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - '+(!_hasFlash?' would use flash fallback for MP3/MP4, but none detected.':'will use flash fallback for MP3/MP4, if available'),1);
9078
9079        }
9080
9081        // </d>
9082
9083
9084
9085      }
9086
9087
9088
9089    }
9090
9091
9092
9093    if (_s.useHTML5Audio && _s.hasHTML5) {
9094
9095
9096
9097      for (item in formats) {
9098
9099        if (formats.hasOwnProperty(item)) {
9100
9101          if ((formats[item].required && !_s.html5.canPlayType(formats[item].type)) || (_s.preferFlash && (_s.flash[item] || _s.flash[formats[item].type]))) {
9102
9103            // flash may be required, or preferred for this format
9104
9105            needsFlash = true;
9106
9107          }
9108
9109        }
9110
9111      }
9112
9113
9114
9115    }
9116
9117
9118
9119    // sanity check...
9120
9121    if (_s.ignoreFlash) {
9122
9123      needsFlash = false;
9124
9125    }
9126
9127
9128
9129    _s.html5Only = (_s.hasHTML5 && _s.useHTML5Audio && !needsFlash);
9130
9131
9132
9133    return (!_s.html5Only);
9134
9135
9136
9137  };
9138
9139
9140
9141  _parseURL = function(url) {
9142
9143
9144
9145    /**
9146
9147     * Internal: Finds and returns the first playable URL (or failing that, the first URL.)
9148
9149     * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
9150
9151     */
9152
9153
9154
9155    var i, j, urlResult = 0, result;
9156
9157
9158
9159    if (url instanceof Array) {
9160
9161
9162
9163      // find the first good one
9164
9165      for (i=0, j=url.length; i<j; i++) {
9166
9167
9168
9169        if (url[i] instanceof Object) {
9170
9171          // MIME check
9172
9173          if (_s.canPlayMIME(url[i].type)) {
9174
9175            urlResult = i;
9176
9177            break;
9178
9179          }
9180
9181
9182
9183        } else if (_s.canPlayURL(url[i])) {
9184
9185          // URL string check
9186
9187          urlResult = i;
9188
9189          break;
9190
9191        }
9192
9193
9194
9195      }
9196
9197
9198
9199      // normalize to string
9200
9201      if (url[urlResult].url) {
9202
9203        url[urlResult] = url[urlResult].url;
9204
9205      }
9206
9207
9208
9209      result = url[urlResult];
9210
9211
9212
9213    } else {
9214
9215
9216
9217      // single URL case
9218
9219      result = url;
9220
9221
9222
9223    }
9224
9225
9226
9227    return result;
9228
9229
9230
9231  };
9232
9233
9234
9235
9236
9237  _startTimer = function(oSound) {
9238
9239
9240
9241    /**
9242
9243     * attach a timer to this sound, and start an interval if needed
9244
9245     */
9246
9247
9248
9249    if (!oSound._hasTimer) {
9250
9251
9252
9253      oSound._hasTimer = true;
9254
9255
9256
9257      if (!_mobileHTML5 && _s.html5PollingInterval) {
9258
9259
9260
9261        if (_h5IntervalTimer === null && _h5TimerCount === 0) {
9262
9263
9264
9265          _h5IntervalTimer = _win.setInterval(_timerExecute, _s.html5PollingInterval);
9266
9267   
9268
9269        }
9270
9271
9272
9273        _h5TimerCount++;
9274
9275
9276
9277      }
9278
9279
9280
9281    }
9282
9283
9284
9285  };
9286
9287
9288
9289  _stopTimer = function(oSound) {
9290
9291
9292
9293    /**
9294
9295     * detach a timer
9296
9297     */
9298
9299
9300
9301    if (oSound._hasTimer) {
9302
9303
9304
9305      oSound._hasTimer = false;
9306
9307
9308
9309      if (!_mobileHTML5 && _s.html5PollingInterval) {
9310
9311
9312
9313        // interval will stop itself at next execution.
9314
9315
9316
9317        _h5TimerCount--;
9318
9319
9320
9321      }
9322
9323
9324
9325    }
9326
9327
9328
9329  };
9330
9331
9332
9333  _timerExecute = function() {
9334
9335
9336
9337    /**
9338
9339     * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)
9340
9341     */
9342
9343
9344
9345    var i;
9346
9347
9348
9349    if (_h5IntervalTimer !== null && !_h5TimerCount) {
9350
9351
9352
9353      // no active timers, stop polling interval.
9354
9355
9356
9357      _win.clearInterval(_h5IntervalTimer);
9358
9359
9360
9361      _h5IntervalTimer = null;
9362
9363
9364
9365      return false;
9366
9367
9368
9369    }
9370
9371
9372
9373    // check all HTML5 sounds with timers
9374
9375
9376
9377    for (i = _s.soundIDs.length-1; i >= 0; i--) {
9378
9379
9380
9381      if (_s.sounds[_s.soundIDs[i]].isHTML5 && _s.sounds[_s.soundIDs[i]]._hasTimer) {
9382
9383
9384
9385        _s.sounds[_s.soundIDs[i]]._onTimer();
9386
9387
9388
9389      }
9390
9391
9392
9393    }
9394
9395
9396
9397  };
9398
9399
9400
9401  _catchError = function(options) {
9402
9403
9404
9405    options = (typeof options !== 'undefined' ? options : {});
9406
9407
9408
9409    if (typeof _s.onerror === 'function') {
9410
9411      _s.onerror.apply(_win, [{type:(typeof options.type !== 'undefined' ? options.type : null)}]);
9412
9413    }
9414
9415
9416
9417    if (typeof options.fatal !== 'undefined' && options.fatal) {
9418
9419      _s.disable();
9420
9421    }
9422
9423
9424
9425  };
9426
9427
9428
9429  _badSafariFix = function() {
9430
9431
9432
9433    // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4
9434
9435    if (!_isBadSafari || !_detectFlash()) {
9436
9437      // doesn't apply
9438
9439      return false;
9440
9441    }
9442
9443
9444
9445    var aF = _s.audioFormats, i, item;
9446
9447
9448
9449    for (item in aF) {
9450
9451      if (aF.hasOwnProperty(item)) {
9452
9453        if (item === 'mp3' || item === 'mp4') {
9454
9455          _s._wD(_sm+': Using flash fallback for '+item+' format');
9456
9457          _s.html5[item] = false;
9458
9459          // assign result to related formats, too
9460
9461          if (aF[item] && aF[item].related) {
9462
9463            for (i = aF[item].related.length-1; i >= 0; i--) {
9464
9465              _s.html5[aF[item].related[i]] = false;
9466
9467            }
9468
9469          }
9470
9471        }
9472
9473      }
9474
9475    }
9476
9477
9478
9479  };
9480
9481
9482
9483  /**
9484
9485   * Pseudo-private flash/ExternalInterface methods
9486
9487   * ----------------------------------------------
9488
9489   */
9490
9491
9492
9493  this._setSandboxType = function(sandboxType) {
9494
9495
9496
9497    // <d>
9498
9499    var sb = _s.sandbox;
9500
9501
9502
9503    sb.type = sandboxType;
9504
9505    sb.description = sb.types[(typeof sb.types[sandboxType] !== 'undefined'?sandboxType:'unknown')];
9506
9507
9508
9509    _s._wD('Flash security sandbox type: ' + sb.type);
9510
9511
9512
9513    if (sb.type === 'localWithFile') {
9514
9515
9516
9517      sb.noRemote = true;
9518
9519      sb.noLocal = false;
9520
9521      _wDS('secNote', 2);
9522
9523
9524
9525    } else if (sb.type === 'localWithNetwork') {
9526
9527
9528
9529      sb.noRemote = false;
9530
9531      sb.noLocal = true;
9532
9533
9534
9535    } else if (sb.type === 'localTrusted') {
9536
9537
9538
9539      sb.noRemote = false;
9540
9541      sb.noLocal = false;
9542
9543
9544
9545    }
9546
9547    // </d>
9548
9549
9550
9551  };
9552
9553
9554
9555  this._externalInterfaceOK = function(flashDate, swfVersion) {
9556
9557
9558
9559    // flash callback confirming flash loaded, EI working etc.
9560
9561    // flashDate = approx. timing/delay info for JS/flash bridge
9562
9563    // swfVersion: SWF build string
9564
9565
9566
9567    if (_s.swfLoaded) {
9568
9569      return false;
9570
9571    }
9572
9573
9574
9575    var e, eiTime = new Date().getTime();
9576
9577
9578
9579    _s._wD(_smc+'externalInterfaceOK()' + (flashDate?' (~' + (eiTime - flashDate) + ' ms)':''));
9580
9581    _debugTS('swf', true);
9582
9583    _debugTS('flashtojs', true);
9584
9585    _s.swfLoaded = true;
9586
9587    _tryInitOnFocus = false;
9588
9589
9590
9591    if (_isBadSafari) {
9592
9593      _badSafariFix();
9594
9595    }
9596
9597
9598
9599    // complain if JS + SWF build/version strings don't match, excluding +DEV builds
9600
9601    // <d>
9602
9603    if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== _s.versionNumber.replace(/\+dev/i, '')) {
9604
9605
9606
9607      e = _sm + ': Fatal: JavaScript file build "' + _s.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + _s.url + '. Ensure both are up-to-date.';
9608
9609
9610
9611      // escape flash -> JS stack so this error fires in window.
9612
9613      setTimeout(function versionMismatch() {
9614
9615        throw new Error(e);
9616
9617      }, 0);
9618
9619
9620
9621      // exit, init will fail with timeout
9622
9623      return false;
9624
9625
9626
9627    }
9628
9629    // </d>
9630
9631
9632
9633    // slight delay before init
9634
9635    setTimeout(_init, _isIE ? 100 : 1);
9636
9637
9638
9639  };
9640
9641
9642
9643  /**
9644
9645   * Private initialization helpers
9646
9647   * ------------------------------
9648
9649   */
9650
9651
9652
9653  _createMovie = function(smID, smURL) {
9654
9655
9656
9657    if (_didAppend && _appendSuccess) {
9658
9659      // ignore if already succeeded
9660
9661      return false;
9662
9663    }
9664
9665
9666
9667    function _initMsg() {
9668
9669      _s._wD('-- SoundManager 2 ' + _s.version + (!_s.html5Only && _s.useHTML5Audio?(_s.hasHTML5?' + HTML5 audio':', no HTML5 audio support'):'') + (!_s.html5Only ? (_s.useHighPerformance?', high performance mode, ':', ') + (( _s.flashPollingInterval ? 'custom (' + _s.flashPollingInterval + 'ms)' : 'normal') + ' polling') + (_s.wmode?', wmode: ' + _s.wmode:'') + (_s.debugFlash?', flash debug mode':'') + (_s.useFlashBlock?', flashBlock mode':'') : '') + ' --', 1);
9670
9671    }
9672
9673
9674
9675    if (_s.html5Only) {
9676
9677
9678
9679      // 100% HTML5 mode
9680
9681      _setVersionInfo();
9682
9683
9684
9685      _initMsg();
9686
9687      _s.oMC = _id(_s.movieID);
9688
9689      _init();
9690
9691
9692
9693      // prevent multiple init attempts
9694
9695      _didAppend = true;
9696
9697
9698
9699      _appendSuccess = true;
9700
9701
9702
9703      return false;
9704
9705
9706
9707    }
9708
9709
9710
9711    // flash path
9712
9713    var remoteURL = (smURL || _s.url),
9714
9715    localURL = (_s.altURL || remoteURL),
9716
9717    swfTitle = 'JS/Flash audio component (SoundManager 2)',
9718
9719    oTarget = _getDocument(),
9720
9721    extraClass = _getSWFCSS(),
9722
9723    isRTL = null,
9724
9725    html = _doc.getElementsByTagName('html')[0],
9726
9727    oEmbed, oMovie, tmp, movieHTML, oEl, s, x, sClass;
9728
9729
9730
9731    isRTL = (html && html.dir && html.dir.match(/rtl/i));
9732
9733    smID = (typeof smID === 'undefined'?_s.id:smID);
9734
9735
9736
9737    function param(name, value) {
9738
9739      return '<param name="'+name+'" value="'+value+'" />';
9740
9741    }
9742
9743
9744
9745    // safety check for legacy (change to Flash 9 URL)
9746
9747    _setVersionInfo();
9748
9749    _s.url = _normalizeMovieURL(_overHTTP?remoteURL:localURL);
9750
9751    smURL = _s.url;
9752
9753
9754
9755    _s.wmode = (!_s.wmode && _s.useHighPerformance ? 'transparent' : _s.wmode);
9756
9757
9758
9759    if (_s.wmode !== null && (_ua.match(/msie 8/i) || (!_isIE && !_s.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) {
9760
9761      /**
9762
9763       * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here
9764
9765       * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout
9766
9767       * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)
9768
9769       */
9770
9771      _wDS('spcWmode');
9772
9773      _s.wmode = null;
9774
9775    }
9776
9777
9778
9779    oEmbed = {
9780
9781      'name': smID,
9782
9783      'id': smID,
9784
9785      'src': smURL,
9786
9787      'quality': 'high',
9788
9789      'allowScriptAccess': _s.allowScriptAccess,
9790
9791      'bgcolor': _s.bgColor,
9792
9793      'pluginspage': _http+'www.macromedia.com/go/getflashplayer',
9794
9795      'title': swfTitle,
9796
9797      'type': 'application/x-shockwave-flash',
9798
9799      'wmode': _s.wmode,
9800
9801      // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
9802
9803      'hasPriority': 'true'
9804
9805    };
9806
9807
9808
9809    if (_s.debugFlash) {
9810
9811      oEmbed.FlashVars = 'debug=1';
9812
9813    }
9814
9815
9816
9817    if (!_s.wmode) {
9818
9819      // don't write empty attribute
9820
9821      delete oEmbed.wmode;
9822
9823    }
9824
9825
9826
9827    if (_isIE) {
9828
9829
9830
9831      // IE is "special".
9832
9833      oMovie = _doc.createElement('div');
9834
9835      movieHTML = [
9836
9837        '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + _http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',
9838
9839        param('movie', smURL),
9840
9841        param('AllowScriptAccess', _s.allowScriptAccess),
9842
9843        param('quality', oEmbed.quality),
9844
9845        (_s.wmode? param('wmode', _s.wmode): ''),
9846
9847        param('bgcolor', _s.bgColor),
9848
9849        param('hasPriority', 'true'),
9850
9851        (_s.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''),
9852
9853        '</object>'
9854
9855      ].join('');
9856
9857
9858
9859    } else {
9860
9861
9862
9863      oMovie = _doc.createElement('embed');
9864
9865      for (tmp in oEmbed) {
9866
9867        if (oEmbed.hasOwnProperty(tmp)) {
9868
9869          oMovie.setAttribute(tmp, oEmbed[tmp]);
9870
9871        }
9872
9873      }
9874
9875
9876
9877    }
9878
9879
9880
9881    _initDebug();
9882
9883    extraClass = _getSWFCSS();
9884
9885    oTarget = _getDocument();
9886
9887
9888
9889    if (oTarget) {
9890
9891
9892
9893      _s.oMC = (_id(_s.movieID) || _doc.createElement('div'));
9894
9895
9896
9897      if (!_s.oMC.id) {
9898
9899
9900
9901        _s.oMC.id = _s.movieID;
9902
9903        _s.oMC.className = _swfCSS.swfDefault + ' ' + extraClass;
9904
9905        s = null;
9906
9907        oEl = null;
9908
9909
9910
9911        if (!_s.useFlashBlock) {
9912
9913          if (_s.useHighPerformance) {
9914
9915            // on-screen at all times
9916
9917            s = {
9918
9919              'position': 'fixed',
9920
9921              'width': '8px',
9922
9923              'height': '8px',
9924
9925              // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.
9926
9927              'bottom': '0px',
9928
9929              'left': '0px',
9930
9931              'overflow': 'hidden'
9932
9933            };
9934
9935          } else {
9936
9937            // hide off-screen, lower priority
9938
9939            s = {
9940
9941              'position': 'absolute',
9942
9943              'width': '6px',
9944
9945              'height': '6px',
9946
9947              'top': '-9999px',
9948
9949              'left': '-9999px'
9950
9951            };
9952
9953            if (isRTL) {
9954
9955              s.left = Math.abs(parseInt(s.left,10))+'px';
9956
9957            }
9958
9959          }
9960
9961        }
9962
9963
9964
9965        if (_isWebkit) {
9966
9967          // soundcloud-reported render/crash fix, safari 5
9968
9969          _s.oMC.style.zIndex = 10000;
9970
9971        }
9972
9973
9974
9975        if (!_s.debugFlash) {
9976
9977          for (x in s) {
9978
9979            if (s.hasOwnProperty(x)) {
9980
9981              _s.oMC.style[x] = s[x];
9982
9983            }
9984
9985          }
9986
9987        }
9988
9989
9990
9991        try {
9992
9993          if (!_isIE) {
9994
9995            _s.oMC.appendChild(oMovie);
9996
9997          }
9998
9999          oTarget.appendChild(_s.oMC);
10000
10001          if (_isIE) {
10002
10003            oEl = _s.oMC.appendChild(_doc.createElement('div'));
10004
10005            oEl.className = _swfCSS.swfBox;
10006
10007            oEl.innerHTML = movieHTML;
10008
10009          }
10010
10011          _appendSuccess = true;
10012
10013        } catch(e) {
10014
10015          throw new Error(_str('domError')+' \n'+e.toString());
10016
10017        }
10018
10019
10020
10021      } else {
10022
10023
10024
10025        // SM2 container is already in the document (eg. flashblock use case)
10026
10027        sClass = _s.oMC.className;
10028
10029        _s.oMC.className = (sClass?sClass+' ':_swfCSS.swfDefault) + (extraClass?' '+extraClass:'');
10030
10031        _s.oMC.appendChild(oMovie);
10032
10033        if (_isIE) {
10034
10035          oEl = _s.oMC.appendChild(_doc.createElement('div'));
10036
10037          oEl.className = _swfCSS.swfBox;
10038
10039          oEl.innerHTML = movieHTML;
10040
10041        }
10042
10043        _appendSuccess = true;
10044
10045
10046
10047      }
10048
10049
10050
10051    }
10052
10053
10054
10055    _didAppend = true;
10056
10057    _initMsg();
10058
10059    _s._wD(_smc+'createMovie(): Trying to load ' + smURL + (!_overHTTP && _s.altURL?' (alternate URL)':''), 1);
10060
10061
10062
10063    return true;
10064
10065
10066
10067  };
10068
10069
10070
10071  _initMovie = function() {
10072
10073
10074
10075    if (_s.html5Only) {
10076
10077      _createMovie();
10078
10079      return false;
10080
10081    }
10082
10083
10084
10085    // attempt to get, or create, movie (may already exist)
10086
10087    if (_flash) {
10088
10089      return false;
10090
10091    }
10092
10093
10094
10095    if (!_s.url) {
10096
10097
10098
10099      /**
10100
10101       * Something isn't right - we've reached init, but the soundManager url property has not been set.
10102
10103       * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.
10104
10105       * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.
10106
10107       */
10108
10109
10110
10111       _wDS('noURL');
10112
10113       return false;
10114
10115
10116
10117    }
10118
10119
10120
10121    // inline markup case
10122
10123    _flash = _s.getMovie(_s.id);
10124
10125
10126
10127    if (!_flash) {
10128
10129      if (!_oRemoved) {
10130
10131        // try to create
10132
10133        _createMovie(_s.id, _s.url);
10134
10135      } else {
10136
10137        // try to re-append removed movie after reboot()
10138
10139        if (!_isIE) {
10140
10141          _s.oMC.appendChild(_oRemoved);
10142
10143        } else {
10144
10145          _s.oMC.innerHTML = _oRemovedHTML;
10146
10147        }
10148
10149        _oRemoved = null;
10150
10151        _didAppend = true;
10152
10153      }
10154
10155      _flash = _s.getMovie(_s.id);
10156
10157    }
10158
10159
10160
10161    // <d>
10162
10163    if (_flash) {
10164
10165      _wDS('waitEI');
10166
10167    }
10168
10169    // </d>
10170
10171
10172
10173    if (typeof _s.oninitmovie === 'function') {
10174
10175      setTimeout(_s.oninitmovie, 1);
10176
10177    }
10178
10179
10180
10181    return true;
10182
10183
10184
10185  };
10186
10187
10188
10189  _delayWaitForEI = function() {
10190
10191
10192
10193    setTimeout(_waitForEI, 1000);
10194
10195
10196
10197  };
10198
10199
10200
10201  _waitForEI = function() {
10202
10203
10204
10205    var p,
10206
10207        loadIncomplete = false;
10208
10209
10210
10211    if (!_s.url) {
10212
10213      // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.
10214
10215      return false;
10216
10217    }
10218
10219
10220
10221    if (_waitingForEI) {
10222
10223      return false;
10224
10225    }
10226
10227
10228
10229    _waitingForEI = true;
10230
10231    _event.remove(_win, 'load', _delayWaitForEI);
10232
10233
10234
10235    if (_tryInitOnFocus && !_isFocused) {
10236
10237      // Safari won't load flash in background tabs, only when focused.
10238
10239      _wDS('waitFocus');
10240
10241      return false;
10242
10243    }
10244
10245
10246
10247    if (!_didInit) {
10248
10249      p = _s.getMoviePercent();
10250
10251      _s._wD(_str('waitImpatient', (p > 0 ? ' (SWF ' + p + '% loaded)' : '')));
10252
10253      if (p > 0 && p < 100) {
10254
10255        loadIncomplete = true;
10256
10257      }
10258
10259    }
10260
10261
10262
10263    setTimeout(function() {
10264
10265
10266
10267      p = _s.getMoviePercent();
10268
10269
10270
10271      if (loadIncomplete) {
10272
10273        // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
10274
10275        _waitingForEI = false;
10276
10277        _s._wD(_str('waitSWF'));
10278
10279        _win.setTimeout(_delayWaitForEI, 1);
10280
10281        return false;
10282
10283      }
10284
10285
10286
10287      // <d>
10288
10289      if (!_didInit) {
10290
10291        _s._wD(_sm + ': No Flash response within expected time.\nLikely causes: ' + (p === 0?'Loading ' + _s.movieURL + ' may have failed (and/or Flash ' + _fV + '+ not present?), ':'') + 'Flash blocked or JS-Flash security error.' + (_s.debugFlash?' ' + _str('checkSWF'):''), 2);
10292
10293        if (!_overHTTP && p) {
10294
10295          _wDS('localFail', 2);
10296
10297          if (!_s.debugFlash) {
10298
10299            _wDS('tryDebug', 2);
10300
10301          }
10302
10303        }
10304
10305        if (p === 0) {
10306
10307          // if 0 (not null), probably a 404.
10308
10309          _s._wD(_str('swf404', _s.url));
10310
10311        }
10312
10313        _debugTS('flashtojs', false, ': Timed out' + _overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');
10314
10315      }
10316
10317      // </d>
10318
10319
10320
10321      // give up / time-out, depending
10322
10323
10324
10325      if (!_didInit && _okToDisable) {
10326
10327        if (p === null) {
10328
10329          // SWF failed. Maybe blocked.
10330
10331          if (_s.useFlashBlock || _s.flashLoadTimeout === 0) {
10332
10333            if (_s.useFlashBlock) {
10334
10335              _flashBlockHandler();
10336
10337            }
10338
10339            _wDS('waitForever');
10340
10341          } else {
10342
10343            // old SM2 behaviour, simply fail
10344
10345            _failSafely(true);
10346
10347          }
10348
10349        } else {
10350
10351          // flash loaded? Shouldn't be a blocking issue, then.
10352
10353          if (_s.flashLoadTimeout === 0) {
10354
10355             _wDS('waitForever');
10356
10357          } else {
10358
10359            _failSafely(true);
10360
10361          }
10362
10363        }
10364
10365      }
10366
10367
10368
10369    }, _s.flashLoadTimeout);
10370
10371
10372
10373  };
10374
10375
10376
10377  _handleFocus = function() {
10378
10379
10380
10381    function cleanup() {
10382
10383      _event.remove(_win, 'focus', _handleFocus);
10384
10385    }
10386
10387
10388
10389    if (_isFocused || !_tryInitOnFocus) {
10390
10391      // already focused, or not special Safari background tab case
10392
10393      cleanup();
10394
10395      return true;
10396
10397    }
10398
10399
10400
10401    _okToDisable = true;
10402
10403    _isFocused = true;
10404
10405    _wDS('gotFocus');
10406
10407
10408
10409    // allow init to restart
10410
10411    _waitingForEI = false;
10412
10413
10414
10415    // kick off ExternalInterface timeout, now that the SWF has started
10416
10417    _delayWaitForEI();
10418
10419
10420
10421    cleanup();
10422
10423    return true;
10424
10425
10426
10427  };
10428
10429
10430
10431  _showSupport = function() {
10432
10433
10434
10435    // <d>
10436
10437
10438
10439    var item, tests = [];
10440
10441
10442
10443    if (_s.useHTML5Audio && _s.hasHTML5) {
10444
10445      for (item in _s.audioFormats) {
10446
10447        if (_s.audioFormats.hasOwnProperty(item)) {
10448
10449          tests.push(item + ': ' + _s.html5[item] + (!_s.html5[item] && _hasFlash && _s.flash[item] ? ' (using flash)' : (_s.preferFlash && _s.flash[item] && _hasFlash ? ' (preferring flash)': (!_s.html5[item] ? ' (' + (_s.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));
10450
10451        }
10452
10453      }
10454
10455      _s._wD('-- SoundManager 2: HTML5 support tests ('+_s.html5Test+'): '+tests.join(', ')+' --',1);
10456
10457    }
10458
10459
10460
10461    // </d>
10462
10463
10464
10465  };
10466
10467
10468
10469  _initComplete = function(bNoDisable) {
10470
10471
10472
10473    if (_didInit) {
10474
10475      return false;
10476
10477    }
10478
10479
10480
10481    if (_s.html5Only) {
10482
10483      // all good.
10484
10485      _s._wD('-- SoundManager 2: loaded --');
10486
10487      _didInit = true;
10488
10489      _initUserOnload();
10490
10491      _debugTS('onload', true);
10492
10493      return true;
10494
10495    }
10496
10497
10498
10499    var wasTimeout = (_s.useFlashBlock && _s.flashLoadTimeout && !_s.getMoviePercent()),
10500
10501        result = true,
10502
10503        error;
10504
10505
10506
10507    if (!wasTimeout) {
10508
10509      _didInit = true;
10510
10511      if (_disabled) {
10512
10513        error = {type: (!_hasFlash && _needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')};
10514
10515      }
10516
10517    }
10518
10519
10520
10521    _s._wD('-- SoundManager 2 ' + (_disabled?'failed to load':'loaded') + ' (' + (_disabled?'Flash security/load error':'OK') + ') --', 1);
10522
10523
10524
10525    if (_disabled || bNoDisable) {
10526
10527      if (_s.useFlashBlock && _s.oMC) {
10528
10529        _s.oMC.className = _getSWFCSS() + ' ' + (_s.getMoviePercent() === null?_swfCSS.swfTimedout:_swfCSS.swfError);
10530
10531      }
10532
10533      _processOnEvents({type:'ontimeout', error:error, ignoreInit: true});
10534
10535      _debugTS('onload', false);
10536
10537      _catchError(error);
10538
10539      result = false;
10540
10541    } else {
10542
10543      _debugTS('onload', true);
10544
10545    }
10546
10547
10548
10549    if (!_disabled) {
10550
10551      if (_s.waitForWindowLoad && !_windowLoaded) {
10552
10553        _wDS('waitOnload');
10554
10555        _event.add(_win, 'load', _initUserOnload);
10556
10557      } else {
10558
10559        // <d>
10560
10561        if (_s.waitForWindowLoad && _windowLoaded) {
10562
10563          _wDS('docLoaded');
10564
10565        }
10566
10567        // </d>
10568
10569        _initUserOnload();
10570
10571      }
10572
10573    }
10574
10575
10576
10577    return result;
10578
10579
10580
10581  };
10582
10583
10584
10585  /**
10586
10587   * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
10588
10589   * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
10590
10591   */
10592
10593
10594
10595  _setProperties = function() {
10596
10597
10598
10599    var i,
10600
10601        o = _s.setupOptions;
10602
10603
10604
10605    for (i in o) {
10606
10607
10608
10609      if (o.hasOwnProperty(i)) {
10610
10611
10612
10613        // assign local property if not already defined
10614
10615
10616
10617        if (typeof _s[i] === 'undefined') {
10618
10619
10620
10621          _s[i] = o[i];
10622
10623
10624
10625        } else if (_s[i] !== o[i]) {
10626
10627
10628
10629          // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
10630
10631          _s.setupOptions[i] = _s[i];
10632
10633
10634
10635        }
10636
10637
10638
10639      }
10640
10641
10642
10643    }
10644
10645
10646
10647  };
10648
10649
10650
10651
10652
10653  _init = function() {
10654
10655
10656
10657    _wDS('init');
10658
10659
10660
10661    // called after onload()
10662
10663
10664
10665    if (_didInit) {
10666
10667      _wDS('didInit');
10668
10669      return false;
10670
10671    }
10672
10673
10674
10675    function _cleanup() {
10676
10677      _event.remove(_win, 'load', _s.beginDelayedInit);
10678
10679    }
10680
10681
10682
10683    if (_s.html5Only) {
10684
10685      if (!_didInit) {
10686
10687        // we don't need no steenking flash!
10688
10689        _cleanup();
10690
10691        _s.enabled = true;
10692
10693        _initComplete();
10694
10695      }
10696
10697      return true;
10698
10699    }
10700
10701
10702
10703    // flash path
10704
10705    _initMovie();
10706
10707
10708
10709    try {
10710
10711
10712
10713      _wDS('flashJS');
10714
10715
10716
10717      // attempt to talk to Flash
10718
10719      _flash._externalInterfaceTest(false);
10720
10721
10722
10723      // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling
10724
10725      // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)
10726
10727      _setPolling(true, (_s.flashPollingInterval || (_s.useHighPerformance ? 10 : 50)));
10728
10729
10730
10731      if (!_s.debugMode) {
10732
10733        // stop the SWF from making debug output calls to JS
10734
10735        _flash._disableDebug();
10736
10737      }
10738
10739
10740
10741      _s.enabled = true;
10742
10743      _debugTS('jstoflash', true);
10744
10745
10746
10747      if (!_s.html5Only) {
10748
10749        // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead
10750
10751        // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
10752
10753        _event.add(_win, 'unload', _doNothing);
10754
10755      }
10756
10757
10758
10759    } catch(e) {
10760
10761
10762
10763      _s._wD('js/flash exception: ' + e.toString());
10764
10765      _debugTS('jstoflash', false);
10766
10767      _catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});
10768
10769      // don't disable, for reboot()
10770
10771      _failSafely(true);
10772
10773      _initComplete();
10774
10775
10776
10777      return false;
10778
10779
10780
10781    }
10782
10783
10784
10785    _initComplete();
10786
10787
10788
10789    // disconnect events
10790
10791    _cleanup();
10792
10793
10794
10795    return true;
10796
10797
10798
10799  };
10800
10801
10802
10803  _domContentLoaded = function() {
10804
10805
10806
10807    if (_didDCLoaded) {
10808
10809      return false;
10810
10811    }
10812
10813
10814
10815    _didDCLoaded = true;
10816
10817
10818
10819    // assign top-level soundManager properties eg. soundManager.url
10820
10821    _setProperties();
10822
10823
10824
10825    _initDebug();
10826
10827
10828
10829    /**
10830
10831     * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
10832
10833     * Ditto for sm2-preferFlash, too.
10834
10835     */
10836
10837    // <d>
10838
10839    (function(){
10840
10841
10842
10843      var a = 'sm2-usehtml5audio=',
10844
10845          a2 = 'sm2-preferflash=',
10846
10847          b = null, 
10848
10849          b2 = null,
10850
10851          hasCon = (typeof console !== 'undefined' && typeof console.log === 'function'),
10852
10853          l = _wl.toLowerCase();
10854
10855
10856
10857      if (l.indexOf(a) !== -1) {
10858
10859        b = (l.charAt(l.indexOf(a)+a.length) === '1');
10860
10861        if (hasCon) {
10862
10863          console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
10864
10865        }
10866
10867        _s.setup({
10868
10869          'useHTML5Audio': b
10870
10871        });
10872
10873      }
10874
10875
10876
10877      if (l.indexOf(a2) !== -1) {
10878
10879        b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');
10880
10881        if (hasCon) {
10882
10883          console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
10884
10885        }
10886
10887        _s.setup({
10888
10889          'preferFlash': b2
10890
10891        });
10892
10893      }
10894
10895
10896
10897    }());
10898
10899    // </d>
10900
10901
10902
10903    if (!_hasFlash && _s.hasHTML5) {
10904
10905      _s._wD('SoundManager: No Flash detected'+(!_s.useHTML5Audio?', enabling HTML5.':'. Trying HTML5-only mode.'));
10906
10907      _s.setup({
10908
10909        'useHTML5Audio': true,
10910
10911        // make sure we aren't preferring flash, either
10912
10913        // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
10914
10915        'preferFlash': false
10916
10917      });
10918
10919    }
10920
10921
10922
10923    _testHTML5();
10924
10925    _s.html5.usingFlash = _featureCheck();
10926
10927    _needsFlash = _s.html5.usingFlash;
10928
10929    _showSupport();
10930
10931
10932
10933    if (!_hasFlash && _needsFlash) {
10934
10935      _wDS('needFlash');
10936
10937      // TODO: Fatal here vs. timeout approach, etc.
10938
10939      // hack: fail sooner.
10940
10941      _s.setup({
10942
10943        'flashLoadTimeout': 1
10944
10945      });
10946
10947    }
10948
10949
10950
10951    if (_doc.removeEventListener) {
10952
10953      _doc.removeEventListener('DOMContentLoaded', _domContentLoaded, false);
10954
10955    }
10956
10957
10958
10959    _initMovie();
10960
10961    return true;
10962
10963
10964
10965  };
10966
10967
10968
10969  _domContentLoadedIE = function() {
10970
10971
10972
10973    if (_doc.readyState === 'complete') {
10974
10975      _domContentLoaded();
10976
10977      _doc.detachEvent('onreadystatechange', _domContentLoadedIE);
10978
10979    }
10980
10981
10982
10983    return true;
10984
10985
10986
10987  };
10988
10989
10990
10991  _winOnLoad = function() {
10992
10993    // catch edge case of _initComplete() firing after window.load()
10994
10995    _windowLoaded = true;
10996
10997    _event.remove(_win, 'load', _winOnLoad);
10998
10999  };
11000
11001
11002
11003  // sniff up-front
11004
11005  _detectFlash();
11006
11007
11008
11009  // focus and window load, init (primarily flash-driven)
11010
11011  _event.add(_win, 'focus', _handleFocus);
11012
11013  _event.add(_win, 'load', _delayWaitForEI);
11014
11015  _event.add(_win, 'load', _winOnLoad);
11016
11017
11018
11019  if (_doc.addEventListener) {
11020
11021
11022
11023    _doc.addEventListener('DOMContentLoaded', _domContentLoaded, false);
11024
11025
11026
11027  } else if (_doc.attachEvent) {
11028
11029
11030
11031    _doc.attachEvent('onreadystatechange', _domContentLoadedIE);
11032
11033
11034
11035  } else {
11036
11037
11038
11039    // no add/attachevent support - safe to assume no JS -> Flash either
11040
11041    _debugTS('onload', false);
11042
11043    _catchError({type:'NO_DOM2_EVENTS', fatal:true});
11044
11045
11046
11047  }
11048
11049
11050
11051} // SoundManager()
11052
11053
11054
11055// SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading
11056
11057
11058
11059if (typeof SM2_DEFER === 'undefined' || !SM2_DEFER) {
11060
11061  soundManager = new SoundManager();
11062
11063}
11064
11065
11066
11067/**
11068
11069 * SoundManager public interfaces
11070
11071 * ------------------------------
11072
11073 */
11074
11075
11076
11077window.SoundManager = SoundManager; // constructor
11078
11079window.soundManager = soundManager; // public API, flash callbacks etc.
11080
11081
11082
11083}(window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.