2 angular.module('ngAudio', [])
3 .directive('ngAudio', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
15 controller: ['$scope', '$attrs', '$element', '$timeout', function($scope, $attrs, $element, $timeout) {
17 /* Loads the sound from destination */
20 audio = ngAudio.load($attrs.ngAudio, $scope);
21 /* Add audio to local scope for modification with nested inputs */
22 $scope.$audio = audio;
24 /* Remove watching features for improved performance */
28 if (!$scope.disablePreload){
33 $element.on('click', function() {
34 if ($scope.clickPlay === false) {
38 if ($scope.disablePreload){
42 /* iOS workaround: Call the play method directly in listener function */
45 /* Set volume to $scope volume if it exists, or default to audio's current value */
46 audio.volume = $scope.volume || audio.volume;
47 audio.loop = $scope.loop;
48 audio.currentTime = $scope.start || 0;
50 /* Fixes a bug with Firefox (???) */
56 $element.on('$destroy', function() {
63 .directive('ngAudioHover', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
66 controller: ['$scope', '$attrs', '$element', '$timeout', function($scope, $attrs, $element, $timeout) {
68 var audio = ngAudio.load($attrs.ngAudioHover, $scope);
70 $element.on('mouseover rollover hover', function() {
72 /* iOS workaround: Call the play method directly in listener function */
75 audio.volume = $attrs.volumeHover || audio.volume;
76 audio.loop = $attrs.loop;
77 audio.currentTime = $attrs.startHover || 0;
81 $element.on('$destroy', function() {
88 .service('localAudioFindingService', ['$q', function($q) {
90 this.find = function(id) {
91 var deferred = $q.defer();
92 var $sound = document.getElementById(id);
94 deferred.resolve($sound);
99 return deferred.promise;
103 .service('remoteAudioFindingService', ['$q', function($q) {
105 this.find = function(url) {
106 var deferred = $q.defer();
107 var audio = new Audio();
109 audio.addEventListener('error', function() {
113 audio.addEventListener('loadstart', function() {
114 deferred.resolve(audio);
117 // bugfix for chrome...
118 setTimeout(function() {
122 return deferred.promise;
127 .service('cleverAudioFindingService', ['$q', 'localAudioFindingService', 'remoteAudioFindingService', function($q, localAudioFindingService, remoteAudioFindingService) {
128 this.find = function(id) {
129 var deferred = $q.defer();
131 id = id.replace('|', '/');
133 localAudioFindingService.find(id)
134 .then(deferred.resolve, function() {
135 return remoteAudioFindingService.find(id);
137 .then(deferred.resolve, deferred.reject);
139 return deferred.promise;
143 .value('ngAudioGlobals', {
150 .factory('NgAudioObject', ['cleverAudioFindingService', '$rootScope', '$interval', '$timeout', 'ngAudioGlobals', function(cleverAudioFindingService, $rootScope, $interval, $timeout, ngAudioGlobals) {
151 return function(id, scope) {
156 window.removeEventListener("click",twiddle);
163 $willRestart = false,
164 $willChangePlaybackRate = false,
165 $newPlaybackRate = false,
169 $observeProperties = true,
171 $scope = scope || $rootScope,
176 this.safeId = id.replace('/', '|');
179 this.unbind = function() {
180 $observeProperties = false;
183 this.play = function() {
188 var completeListeners = [];
189 this.complete = function(callback){
190 completeListeners.push(callback);
193 this.pause = function() {
197 this.restart = function() {
201 this.stop = function() {
205 this.setVolume = function(volume) {
206 $volumeToSet = volume;
209 this.setPlaybackRate = function(rate) {
210 $newPlaybackRate = rate;
211 $willChangePlaybackRate = true;
214 this.setMuting = function(muting) {
218 this.setProgress = function(progress) {
219 if (audio && audio.duration && isFinite(progress)) {
220 audio.currentTime = audio.duration * progress;
224 this.setCurrentTime = function(currentTime) {
225 if (audio && audio.duration) {
226 audio.currentTime = currentTime;
230 this.destroy = $destroy;
232 $scope.$on('$destroy', function() {
236 function $destroy() {
239 $interval.cancel(interval);
241 if ($intervalWatch) {
251 function $setWatch() {
255 $audioWatch = $scope.$watch(function() {
257 volume: audioObject.volume,
258 currentTime: audioObject.currentTime,
259 progress: audioObject.progress,
260 muting: audioObject.muting,
261 loop: audioObject.loop,
262 playbackRate: audioObject.playbackRate,
263 globalVolume: ngAudioGlobals.volume
265 }, function(newValue, oldValue) {
266 //console.log("ngaudio watch callback for: " + audioObject.id);
267 if (newValue.currentTime !== oldValue.currentTime) {
268 audioObject.setCurrentTime(newValue.currentTime);
271 if (newValue.progress !== oldValue.progress) {
272 audioObject.setProgress(newValue.progress);
274 if (newValue.volume !== oldValue.volume) {
275 audioObject.setVolume(newValue.volume);
278 if (newValue.playbackRate !== oldValue.playbackRate) {
279 audioObject.setPlaybackRate(newValue.playbackRate);
282 if (newValue.globalVolume !== oldValue.globalVolume) {
283 if (newValue.globalVolume === 0) {
284 audioObject.setMuting(true);
286 audioObject.setMuting(false);
287 audioObject.setVolume(newValue.globalVolume);
293 $looping = newValue.loop;
295 if (newValue.muting !== oldValue.muting) {
296 audioObject.setMuting(newValue.muting);
301 cleverAudioFindingService.find(id)
302 .then(function(nativeAudio) {
304 if (ngAudioGlobals.unlock) {
306 window.addEventListener("click", twiddle);
308 audio.addEventListener('playing', function() {
309 window.removeEventListener("click",twiddle);
314 audio.addEventListener('canplay', function() {
315 audioObject.canPlay = true;
319 audioObject.error = true;
324 var interval = $interval(checkWatchers, ngAudioGlobals.performance);
325 $intervalWatch = $scope.$watch(function(){
326 return ngAudioGlobals.performance;
328 $interval.cancel(interval);
329 interval = $interval(checkWatchers, ngAudioGlobals.performance);
332 function checkWatchers() {
338 if ($isMuting || ngAudioGlobals.muting) {
341 audio.volume = audioObject.volume !== undefined ? audioObject.volume : 1;
350 audio.src = 'about:blank';
351 $willRestart = false;
359 if ($willChangePlaybackRate) {
360 audio.playbackRate = $newPlaybackRate;
361 $willChangePlaybackRate = false;
365 audio.volume = $volumeToSet;
366 $volumeToSet = undefined;
369 if ($observeProperties) {
370 audioObject.currentTime = audio.currentTime;
371 audioObject.duration = audio.duration;
372 audioObject.remaining = audio.duration - audio.currentTime;
373 audioObject.progress = 0; //We set initial value to 0
374 audioObject.paused = audio.paused;
375 audioObject.src = audio.src;
377 //After we check if progress is bigger than 0, and we set
378 var tempProgress = (audio.currentTime / audio.duration);
379 if(tempProgress > 0 ){
380 audioObject.progress = tempProgress;
383 if (audioObject.currentTime >= audioObject.duration) {
384 completeListeners.forEach(function(listener){
385 listener(audioObject);
389 if ($looping && audioObject.currentTime >= audioObject.duration) {
390 if ($looping !== true) {
393 // if (!$looping) return;
395 audioObject.setCurrentTime(0);
401 if (!$isMuting && !ngAudioGlobals.muting) {
402 audioObject.volume = audio.volume;
405 audioObject.audio = audio;
412 .service('ngAudio', ['NgAudioObject', 'ngAudioGlobals', function(NgAudioObject, ngAudioGlobals) {
413 this.play = function(id, scope) {
415 var audio = new NgAudioObject(id, scope);
420 this.load = function(id, scope) {
421 return new NgAudioObject(id, scope);
424 this.mute = function() {
425 ngAudioGlobals.muting = true;
428 this.unmute = function() {
429 ngAudioGlobals.muting = false;
432 this.toggleMute = function() {
433 ngAudioGlobals.muting = !ngAudioGlobals.muting;
436 this.setUnlock = function(unlock) {
437 ngAudioGlobals.unlock = unlock;
440 this.setGlobalVolume = function(globalVolume) {
441 ngAudioGlobals.volume = globalVolume;
444 .filter("trackTime", function(){
445 /* Conveniently takes a number and returns the track time */
447 return function(input){
449 var totalSec = Math.floor(input | 0);
456 if (totalSec > 3599) {
458 hours = Math.floor(totalSec / 3600);
459 minutes = Math.floor((totalSec - (hours * 3600)) / 60);
460 seconds = (totalSec - ((minutes * 60) + (hours * 3600)));
462 if (hours.toString().length == 1) {
463 hours = "0" + (Math.floor(totalSec / 3600)).toString();
466 if (minutes.toString().length == 1) {
467 minutes = "0" + (Math.floor((totalSec - (hours * 3600)) / 60)).toString();
470 if (seconds.toString().length == 1) {
471 seconds = "0" + (totalSec - ((minutes * 60) + (hours * 3600))).toString();
474 output = hours + ":" + minutes + ":" + seconds;
476 } else if (totalSec > 59) {
478 minutes = Math.floor(totalSec / 60);
479 seconds = totalSec - (minutes * 60);
481 if (minutes.toString().length == 1) {
482 minutes = "0" + (Math.floor(totalSec / 60)).toString();
485 if (seconds.toString().length == 1) {
486 seconds = "0" + (totalSec - (minutes * 60)).toString();
489 output = minutes + ":" + seconds;
495 if (seconds.toString().length == 1) {
496 seconds = "0" + (totalSec).toString();
499 output = totalSec + "s";
503 if (typeof Number.isNaN === "function" && Number.isNaN(output)){