1 /* 2 * Copyright (C) 2012-2013 DFKI GmbH 3 * Deutsches Forschungszentrum fuer Kuenstliche Intelligenz 4 * German Research Center for Artificial Intelligence 5 * http://www.dfki.de 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 28 define(['module', 'constants', 'mediaManager', 'dictionary'], 29 /** 30 * 31 * @name NotificationManager 32 * @memberOf mmir 33 * @static 34 * @class 35 * 36 * @requires Dictionary 37 * @requires mmir.MediaManager 38 */ 39 function( 40 module, constants, mediaManager, Dictionary 41 ){ 42 //the next comment enables JSDoc2 to map all functions etc. to the correct class description 43 /** @scope mmir.NotificationManager.prototype *///for jsdoc2 44 45 46 //private members 47 48 49 /** 50 * TODO replace by "real" ENV mechanism ... instead of !forBrowser / ! constants.isBrowserEnv() 51 * 52 * @private 53 * @memberOf NotificationManager# 54 */ 55 var isCordovaEnv = ! constants.isBrowserEnv(); 56 57 /** 58 * @private 59 * @memberOf NotificationManager# 60 */ 61 var instance = null; 62 63 64 //private methods 65 66 /** 67 * Constructor-Method of Singleton mmir.NotificationManager.<br> 68 * 69 * @constructs NotificationManager 70 * @memberOf NotificationManager# 71 * @ignore 72 */ 73 function constructor(){ 74 75 /** 76 * @private 77 * @memberOf NotificationManager.prototype 78 */ 79 var INIT = 'init'; 80 81 /** 82 * VIBRATE initialization status 83 * @private 84 * @memberOf NotificationManager.prototype 85 */ 86 var isHapticEnabled = true; 87 88 /** 89 * Implementation for vibrate-function: 90 * platform-dependent (if platform/device does not support it: as stub-function) 91 * 92 * @private 93 * @type {Function} 94 * @memberOf NotificationManager.prototype 95 */ 96 var doVibrate = null; 97 98 /** 99 * Implementation for confirm-function: 100 * shows "native" platform-specific confirm-dialog. 101 * 102 * <code>function(message, confirmCallback, title, buttonLabels)</code> 103 * 104 * @private 105 * @type {Function} 106 * @memberOf NotificationManager.prototype 107 */ 108 var doConfirm = null; 109 110 /** 111 * Implementation for confirm-function: 112 * shows "native" platform-specific alert-dialog. 113 * 114 * <code>(message, alertCallback, title, buttonName)</code> 115 * 116 * @private 117 * @type {Function} 118 * @memberOf NotificationManager.prototype 119 */ 120 var doAlert = null; 121 122 /** 123 * Initialize the NotificationManager. 124 * 125 * At the moment this set the internal vibrate-function, 126 * if available in the current execution environment 127 * (or with a dummy function, if not). 128 * 129 * In addition, the alert-, and confirm-functions are set to their 130 * platform-specific implementation. 131 * 132 * @memberOf NotificationManager.prototype 133 * @private 134 * @function 135 */ 136 var _init = function(){ 137 if(isCordovaEnv){ 138 139 if(navigator.notification && navigator.notification.vibrate){ 140 // console.debug('Vibrate: navigator.notification'); 141 /** @ignore */ 142 doVibrate = function vibrate(n){ navigator.notification.vibrate(n); }; 143 } 144 else { 145 console.warn('mmir.NotificationManager.INIT: could not detect navigator.notification.vibrate, using NOOP dummy instead.'); 146 /** @ignore */ 147 doVibrate = function dummyVibrate(n){ console.error('mmir.NotificationManager.vibrate('+n+') triggered in CORDOVA environment, but no VIBRATE functionality available.'); };// DEBUG 148 } 149 150 } 151 else if (navigator.vibrate){ 152 // console.debug('Vibrate API'); 153 /** @ignore */ 154 doVibrate = function vibrate(n){ navigator.vibrate(n); }; 155 } 156 else if (navigator.webkitVibrate){ 157 // console.debug('Vibrate: webkit'); 158 /** @ignore */ 159 doVibrate = function vibrate(n){ navigator.webkitVibrate(n); }; 160 } 161 162 //set confirm-implementation 163 if(navigator.notification && navigator.notification.confirm){ 164 // console.debug('Confirm: navigator.notification'); 165 /** @ignore */ 166 doConfirm = function confirm(message, confirmCallback, title, buttonLabels){ 167 168 var cbWrapper = confirmCallback; 169 if(confirmCallback){ 170 var self = this; 171 cbWrapper = function(result){ 172 //need to convert NUMBER result to BOOLEAN: 173 // result = [1,2,..] 174 // -> default is: OK = 1, CANCEL = 2, close-the-dialog = 0 175 var res = result === 1 ? true : false; 176 confirmCallback.call(self, res); 177 }; 178 } 179 180 navigator.notification.confirm(message, cbWrapper, title, buttonLabels); 181 }; 182 } 183 else if(typeof window !== 'undefined' && window && window.confirm) { 184 /** @ignore */ 185 doConfirm = function confirmWindow(message, confirmCallback, title, buttonLabels){ 186 //TODO use setTimeout here to "simulate" async execution? 187 var result = window.confirm(message); 188 if(confirmCallback){ 189 confirmCallback.call(this, result); 190 } 191 }; 192 } 193 194 //set alert-implementation 195 if(navigator.notification && navigator.notification.alert){ 196 // console.debug('Alert: navigator.notification'); 197 /** @ignore */ 198 doAlert = function confirm(message, alertCallback, title, buttonLabels){ 199 navigator.notification.alert(message, alertCallback, title, buttonLabels); 200 }; 201 } 202 else if(typeof window !== 'undefined' && window && window.alert){ 203 /** @ignore */ 204 doAlert = function confirmWindow(message, alertCallback, title, buttonLabels){ 205 //TODO use setTimeout here to "simulate" async execution? 206 window.alert(message); 207 if(alertCallback){ 208 alertCallback.call(this); 209 } 210 }; 211 } 212 }; 213 214 215 //SOUND / BEEP initialization: 216 217 /** 218 * @private 219 * @type Number 220 * 221 * @memberOf NotificationManager.prototype 222 */ 223 var beepVolume = 1.0; 224 225 /** 226 * The Audio object for the <em>beep</em> sound. 227 * 228 * @private 229 * @type AudioObject 230 * 231 * @memberOf NotificationManager.prototype 232 */ 233 var beepAudio = null; 234 235 /** 236 * Dictionary that manages the currently loaded sounds 237 * 238 * @private 239 * @type Dictionary 240 * 241 * @memberOf NotificationManager.prototype 242 */ 243 //TODO add option for limiting size of soundMap (-> e.g. how many resources are max. cached/occupied for Android) 244 var soundMap = new Dictionary(); 245 246 /** 247 * Factory function for creating "sounds objects", 248 * i.e. extend the basic Audio objects with needed functions/properties 249 * 250 * @private 251 * @function 252 * 253 * @param {mmir.env.media.IAudio} audioObj 254 * @param {String} name 255 * 256 * @returns {mmir.env.media.INotificationSound} the extended audio object, i.e. a NotificationSound 257 * 258 * @memberOf NotificationManager.prototype 259 */ 260 function initNotificationSound(audioObj, name){ 261 audioObj.name = name; 262 audioObj.setVolume(beepVolume); 263 audioObj.isNotificationPlaying = false; 264 audioObj.repeatNotification = 0; 265 audioObj.playNotification = function(repeatNTimes){ 266 267 // console.debug('isPlaying: '+this.isNotificationPlaying+', repeat: '+this.repeatNotification+', args: '+repeatNTimes+''); 268 269 //"blocking mode": only re-start, if not already playing 270 if(!this.isNotificationPlaying){ 271 this.repeatNotification = repeatNTimes ? repeatNTimes : 0; 272 } 273 274 if( this.repeatNotification < 1){ 275 //end recurusion 276 this.isNotificationPlaying = false; 277 this.repeatNotification = 0; 278 } 279 else { 280 this.isNotificationPlaying = true; 281 --this.repeatNotification; 282 // this.stop(); 283 this.play(); 284 } 285 }; 286 287 audioObj.setCallbacks = function(onFinished, onError){ 288 this.onFinishedListener = onFinished; 289 this.onErrorListener = onError; 290 }; 291 audioObj.clearCallbacks = function(){ 292 this.onFinishedListener = null; 293 this.onErrorListener = null; 294 }; 295 audioObj.fireFinished = function(){ 296 297 var tempOnFinishedListener = this.onFinishedListener; 298 //clear callbacks 299 // NOTE: do this before triggering callback, in case the callback re-plays the notification with new callbacks! 300 // (if we would clear callbacks after invocation, we would delete the new callbacks!) 301 this.clearCallbacks(); 302 if(tempOnFinishedListener){ 303 tempOnFinishedListener(); 304 } 305 }; 306 audioObj.fireError = function(error){ 307 308 var tempOnErrorListener = this.onErrorListener; 309 //clear callbacks 310 // NOTE: do this before triggering callback, in case the callback re-plays the notification with new callbacks! 311 // (if we would clear callbacks after invocation, we would delete the new callbacks!) 312 this.clearCallbacks(); 313 314 //create error message with details 315 var id; 316 if(this.name){ 317 var entry = soundMap.get(this.name); 318 id = '"' + this.name + '" -> ' + (entry? '"'+entry.url+'"' : 'UNDEF'); 319 } 320 else { 321 id = '"BEEP" -> "'+constants.getBeepUrl()+'"'; 322 } 323 var msg = 'Notification: Error playing the sound for notification '+id; 324 325 //create Error object if necessary 326 if(!error){ 327 error = new Error(msg); 328 msg = null; 329 } 330 331 332 if(tempOnErrorListener){ 333 tempOnErrorListener(error, msg); 334 } 335 else { 336 //if no callback: print debug output in error stream: 337 console.error( (msg? msg + ': ' : '') + error); 338 } 339 }; 340 341 return audioObj; 342 }; 343 344 /** 345 * Helper for creating an Audio object 346 * 347 * @private 348 * @function 349 * 350 * @param {String} url 351 * @param {Function} success 352 * @param {Function} fail 353 * @param {Function} init 354 * 355 * @returns {AudioObject} audio object 356 * 357 * @memberOf NotificationManager.prototype 358 */ 359 function createAudio(url, success, fail, init){ 360 return mediaManager.getURLAsAudio(url, success, fail, init); 361 } 362 363 /** 364 * Helper for "registering" a NotificationSound. 365 * 366 * Stores the sound object in {@link #soundMap} 367 * with the ID <code>name</code>. 368 * 369 * The sound object will be initialized on first 370 * retrieval, ie. {@link #doGetSoundFromMap} 371 * 372 * @private 373 * @function 374 * 375 * @param {String} name 376 * @param {String} theUrl 377 * @param {Boolean} isKeepOnPause 378 * 379 * @memberOf NotificationManager.prototype 380 */ 381 function initAudioSoundEntry(name, theUrl, isKeepOnPause){ 382 var config = {url: theUrl, audio: null}; 383 if(isKeepOnPause){ 384 config.isKeepOnPause = true; 385 } 386 soundMap.put(name, config); 387 } 388 389 /** 390 * Helper for retrieving an existing sound from 391 * the {@link #soundMap}. 392 * 393 * Initializes the sound if necessary. 394 * 395 * @private 396 * @function 397 * 398 * @param {String} name 399 * @param {Function} onErrorCallback 400 * 401 * @memberOf NotificationManager.prototype 402 */ 403 function doGetSoundFromMap(name, onErrorCallback){ 404 var audioObj = null; 405 var audioUrl = null; 406 var keepOnPause = false; 407 408 //if no name: use default beep 409 if(!name){ 410 audioObj = beepAudio; 411 audioUrl = constants.getBeepUrl(); 412 } 413 else { 414 //retrieve information for sound 415 var soundEntry = soundMap.get(name); 416 if(soundEntry === INIT){ 417 //TODO set repeat-times? 418 419 //sound is still initializing -> return 420 return null; ////////////////////// EARLY EXIT ////////////////////// 421 } 422 423 if(!soundEntry){ 424 var errMsg = 'mmir.NotificationManager: no sound "'+name+'" initialized!'; 425 if(onErrorCallback){ 426 onErrorCallback(errMsg); 427 } 428 else { 429 console.error(errMsg); 430 } 431 // throw new Error(errMsg); 432 return null; ////////////////////// EARLY EXIT ////////////////////// 433 } 434 435 audioObj = soundEntry.audio;//<- may be null 436 audioUrl = soundEntry.url;//<- must NOT be null 437 keepOnPause = soundEntry.isKeepOnPause? true : false; 438 } 439 440 return { 441 sound: audioObj, 442 url: audioUrl, 443 isKeepOnPause: keepOnPause 444 }; 445 } 446 447 /** 448 * Helper for playing a registered notification sound. 449 * 450 * Initializes the sound if necessary. 451 * 452 * @private 453 * @function 454 * 455 * @param {String} name 456 * ID of the sound 457 * @param {Number} times 458 * @param {Function} onFinishedCallback 459 * @param {Function} onErrorCallback 460 * 461 * @memberOf NotificationManager.prototype 462 */ 463 function playAudioSound(name, times, onFinishedCallback, onErrorCallback){ 464 465 var soundEntry = doGetSoundFromMap(name, onErrorCallback); 466 467 if(soundEntry === null){ 468 //could not retrieve sound-object 469 // (error callback will already have been invoked, so just return) 470 return;/////////////////////// EARYL EXIT /////////// 471 } 472 473 var audioObj = soundEntry.sound; 474 var audioUrl = soundEntry.url; 475 var isKeepOnPause = soundEntry.isKeepOnPause; 476 477 //create audio-object, if not existing yet 478 if(audioObj === null){ 479 480 if(name){ 481 soundMap.put(name, INIT); 482 } 483 484 audioObj = createAudio( 485 audioUrl, 486 function onFinished(){ 487 this.playNotification(); 488 489 audioObj.fireFinished(); 490 }, 491 function onError(e){ 492 if(name) { 493 soundMap.remove(name); 494 }; 495 496 if(audioObj){ 497 audioObj.fireError(e); 498 } 499 else { 500 if(onErrorCallback){ 501 onErrorCallback(); 502 } 503 else { 504 console.error('Notification: Error playing the sound from "'+audioUrl+'": '+e); 505 } 506 } 507 }, 508 function onInit(){ 509 510 //FIX for Android/Cordova: return-value of createAudio will not set audioObj "fast enough" 511 // (i.e. may not use async-initialization, depending on where the audio-file is located) 512 // ... in order to be able to use keep variable audioObj useful -> do assignment now/here 513 audioObj = this; 514 515 initNotificationSound(audioObj, name); 516 audioObj.setCallbacks(onFinishedCallback, onErrorCallback); 517 518 //if no name: assume default beep 519 if(!name){ 520 beepAudio = audioObj; 521 } 522 else { 523 var theEntry = {url: audioUrl, audio: audioObj}; 524 if(isKeepOnPause){ 525 theEntry.isKeepOnPause = true; 526 } 527 soundMap.put(name, theEntry); 528 } 529 530 audioObj.playNotification(times); 531 532 } 533 ); 534 535 // //FIXME this is a QUICK-FIX: 536 // // Android needs invocation of a media-method, before it triggers the on-init callback. 537 // // We need to do this here, not within the 538 // if(isCordovaEnv){ 539 // audioObj.init(); 540 // } 541 } 542 else { 543 audioObj.setCallbacks(onFinishedCallback, onErrorCallback); 544 audioObj.playNotification(times); 545 } 546 547 } 548 549 /** 550 * Helper for stop playing a registered notification sound. 551 * 552 * Initializes the sound if necessary. 553 * 554 * @private 555 * @function 556 * 557 * @param {String} name 558 * ID of the sound 559 * @param {Function} onFinishedCallback 560 * @param {Function} onErrorCallback 561 * 562 * @memberOf NotificationManager.prototype 563 */ 564 function stopAudioSound(name, onFinishedCallback, onErrorCallback){ 565 566 var soundEntry = doGetSoundFromMap(name, onErrorCallback); 567 568 569 // console.error('Notification: invoked stop on notification-sound '+name+' -> '+JSON.stringify(soundEntry));//FIXM debug 570 571 if(soundEntry === null){ 572 //could not retrieve sound-object 573 // (error callback will already have been invoked, so just return) 574 return;/////////////////////// EARYL EXIT /////////// 575 } 576 577 var audioObj = soundEntry.sound; 578 //NOTE audioObj may be null, e.g. if sound is still initializing. 579 580 if(audioObj != null){ 581 if(audioObj.repeatNotification > 0) 582 audioObj.repeatNotification = 0; 583 if(audioObj.isNotificationPlaying === true) 584 audioObj.isNotificationPlaying = false; 585 if(audioObj.stop){ 586 // console.error('Notification: stopping notification-sound -> '+name);//FIXM debug 587 audioObj.stop(); 588 } 589 } 590 591 if(onFinishedCallback){ 592 onFinishedCallback.call(audioObj); 593 } 594 595 }; 596 597 //on Android: release resources on pause/exit, since they are limited 598 if(isCordovaEnv){ 599 600 document.addEventListener("resume", function(event){ 601 //initialize beep sound: 602 playAudioSound(null, 0); 603 }); 604 605 document.addEventListener( 606 "pause", 607 function(event){ 608 609 //use temporal variable for minimizing concurrency problems 610 var temp; 611 612 if(beepAudio !== null){ 613 614 temp = beepAudio; 615 beepAudio = null; 616 temp.release(); 617 618 console.info('Notification: released media resources for beep.'); 619 } 620 621 var keys = soundMap.getKeys(); 622 for(var i = keys.length - 1; i >= 0; --i){ 623 624 var entry = soundMap.get(keys[i]); 625 if(entry !== null && entry != INIT && ! entry.isKeepOnPause){ 626 627 temp = entry.audio; 628 629 //audio may not be initialized yet: 630 if(temp != null){ 631 entry.audio = null; 632 temp.release(); 633 } 634 635 console.info('Notification: released media resources for '+entry.url); 636 } 637 } 638 }, 639 false 640 ); 641 } 642 643 /** @lends mmir.NotificationManager.prototype */ 644 return { //public members and methods 645 /** @scope mmir.NotificationManager.prototype */ 646 647 /** 648 * Trigger a haptic vibration feedback. 649 * 650 * <p>Note: The device / execution environment may not support haptic vibration feedback 651 * 652 * @function 653 * @param {Number} milliseconds 654 * duration for vibration in milliseconds. Must be <code>> 0</code> 655 * @public 656 * 657 * @memberOf mmir.NotificationManager.prototype 658 */ 659 vibrate: function(milliseconds){ 660 if (isHapticEnabled && doVibrate){ 661 doVibrate(milliseconds); 662 } 663 }, 664 /** 665 * Check if {@link #vibrate} is functional and enabled. 666 * 667 * <p> 668 * If <code>false</code> is returned, calling the <code>vibrate()</code> 669 * function will have no effect. 670 * 671 * @function 672 * @returns {Boolean} <code>true</code> if {@link #vibrate} is functional 673 * @public 674 */ 675 isVibrateEnabled: function(){ 676 if (isHapticEnabled && doVibrate){ 677 return true; 678 } 679 else { 680 return false; 681 } 682 }, 683 /** 684 * Check if the execution environment supports {@link #vibrate}. 685 * 686 * <p> 687 * If <code>false</code> is returned, calling the <code>vibrate()</code> 688 * function will have no effect. 689 * 690 * @function 691 * @returns {Boolean} <code>true</code> if {@link #vibrate} is functional 692 * @public 693 */ 694 isVibrateAvailable: function(){ 695 if (doVibrate){ 696 return true; 697 } 698 else { 699 return false; 700 } 701 }, 702 /** 703 * Enable or disable {@link #vibrate}. 704 * <p> 705 * NOTE: If {@ #isVibrateAvailable} returns <code>false</code>, enabling will have no effect. 706 * 707 * @function 708 * @public 709 * 710 * @param {Boolean} enabled 711 * set vibrate function to <code>enable</code> 712 */ 713 setVibrateEnabled: function(enabled){ 714 isHapticEnabled = enabled; 715 }, 716 /** 717 * Opens a (native) alert-notification dialog. 718 * 719 * @param {String} message 720 * the alert message 721 * @param {Function} [alertCallback] 722 * callback that is triggered, after dialog was closed 723 * @param {String} [title] OPTIONAL 724 * the title for the alert dialog 725 * (may not be provided / settable in all execution environments) 726 * @param {String} [buttonName] OPTIONAL 727 * the label for the close button in the alert dialog 728 * (may not be provided / settable in all execution environments) 729 * @function 730 * @public 731 */ 732 alert: function(message, alertCallback, title, buttonName){ 733 if(doAlert){ 734 doAlert.call(this, message, alertCallback, title, buttonName); 735 } 736 else { 737 console.warn('NotificationManager.alert: No alert dialog implementation available ', message, alertCallback, title, buttonName); 738 } 739 }, 740 /** 741 * Opens a (native) confirm-notification dialog. 742 * 743 * @param {String} message 744 * the confirm message 745 * @param {Function} [alertCallback] 746 * callback that is triggered, after dialog was closed. 747 * The callback will be invoked with 1 argument:<br> 748 * <code>callback(wasConfirmed : Boolean)</code><br> 749 * if the OK/CONFIRM button was pressed, <code>wasConfirmed</code> 750 * will be <code>true</code>, otherwise <code>false</code>. 751 * @param {String} [title] OPTIONAL 752 * the title for the confirm dialog 753 * (may not be provided / settable in all execution environments) 754 * @param {Array<String>} [buttonLabels] OPTIONAL 755 * the labels for the buttons of the confirm dialog 756 * (may not be provided / settable in all execution environments) 757 * 758 * @function 759 * @public 760 */ 761 confirm: function(message, confirmCallback, title, buttonLabels){ 762 if(doConfirm){ 763 doConfirm.call(this, message, confirmCallback, title, buttonLabels); 764 } 765 else { 766 console.warn('NotificationManager.confirm: No confirm dialog implementation available ', message, confirmCallback, title, buttonLabels); 767 } 768 }, 769 /** 770 * Trigger a beep notification sound. 771 * 772 * @function 773 * @param {Number} times 774 * how many times should to beep repeated 775 * @public 776 */ 777 beep: function(times){ 778 if (times>0){ 779 playAudioSound(null, times); 780 } 781 }, 782 783 getVolume: function(){ 784 return beepVolume; 785 }, 786 /** 787 * Set the volume for sound notifications. 788 * 789 * @param {Number} vol 790 * the new volume: a number between [0, 1] 791 * 792 * @see mmir.env.media.IAudio#setVolume 793 */ 794 setVolume: function(vol){ 795 if(typeof vol !== 'number'){ 796 throw new TypeError('argument vol (value: '+vol+') must be a number, but is instead: '+(typeof vol)); 797 } 798 799 if(vol !== beepVolume){ 800 801 //set volume for beep notification 802 beepVolume = vol; 803 if(beepAudio){ 804 beepAudio.setVolume(beepVolume); 805 } 806 807 //set volume for notification sounds 808 var keys = soundMap.getKeys(); 809 var entry = null; 810 for(var i = 0, size = keys.length; i < size; ++i){ 811 entry = soundMap.get(keys[i]); 812 if(entry.audio !== null){ 813 entry.audio.setVolume(vol); 814 } 815 } 816 817 } 818 } 819 820 /** 821 * Trigger a sound notification by NAME (needs to be created first). 822 * 823 * @function 824 * @param {String} name 825 * the name / identifier for the sound (if <code>null</code>, beep notification is used) 826 * @param {Number} times 827 * how many times should to beep repeated 828 * @public 829 * 830 * @see #createSound 831 */ 832 ,playSound: function(name, times, onFinished, onError){ 833 if (times>0){ 834 playAudioSound(name, times, onFinished, onError); 835 } 836 }, 837 /** 838 * Create a sound notification. 839 * 840 * <p> 841 * After creation, the sound "theSoundId" can be played via 842 * <code>playSound("theSoundId", 1)</code> 843 * 844 * @function 845 * @param {String} name 846 * the name / identifier for the sound 847 * @param {String} url 848 * the URL for the audio of the sound 849 * @param {Boolean} [isKeepOnPause] OPTIONAL 850 * flag indicating, if the audio resources should be keept 851 * when the device goes into <em>pause mode</em> 852 * (may not apply to all execution environments; 853 * e.g. relevant for Android environment) 854 * <br> 855 * DEFAULT: <code>false</code> 856 * @public 857 */ 858 createSound: function(name, url, isKeepOnPause){ // TODO add callbacks? this would make the impl. more complex ..., successCallback, errorCallback){ 859 initAudioSoundEntry(name, url, isKeepOnPause); 860 861 //DISABLED this triggers an error if MediaManager / LanguageManager etc. are not initialized yet! 862 // console.error('created sound "'+name+'" for url "'+url+'", calling from: ' + new Error().stack); 863 // //immediately initialize the sound (but do not play it yet); 864 // playAudioSound(name, 0); 865 } 866 /** 867 * Stop a sound notification, if it is playing. 868 * 869 * Has no effect, if the notification is not playing. 870 * 871 * @function 872 * @param {String} name 873 * the name / identifier for the sound 874 */ 875 ,stopSound: function(name){ 876 stopAudioSound(name); 877 } 878 879 , initBeep: function(){//<- used by framework to initialize the default beep-sound 880 //initialize beep sound: 881 playAudioSound(null, 0); 882 } 883 /** 884 * Initialize a sound notification. 885 * 886 * <p> 887 * NOTE a sound does not need to be explicitly initialized, <code>playSound</code> will 888 * automatically initialize the sound if necessary. 889 * 890 * <p> 891 * Initializing a sound prepares all resources, so that the sound can be immediately played. 892 * 893 * For instance, a sound that needs to loaded from a remote server first, may take some time 894 * before it can be played. 895 * 896 * <p> 897 * NOTE the sound must be {@link #createSound|created} first, before initializing it. 898 * 899 * @function 900 * @param {String} name 901 * the name / identifier for the sound 902 * @public 903 * 904 * @see #createSound 905 */ 906 , initSound: function(name){ 907 //initialize sound (identified by its name): 908 playAudioSound(name, 0); 909 } 910 , init: function(){//<- used by framework to initialize the NotificationManager 911 _init(); 912 this.init = function(){ return this; }; 913 914 return this; 915 } 916 }; 917 } 918 919 instance = new constructor(); 920 921 /** 922 * @deprecated instead: use mmir.NotificationManager directly 923 * 924 * @function 925 * @name getInstance 926 * @memberOf mmir.NotificationManager# 927 */ 928 instance.getInstance = function(){ 929 return instance; 930 }; 931 932 return instance; 933 934 });//END: define(..., function(){...