2 angular.module('ngAudio', [])
3 .directive('ngAudio', ['$compile', '$q', 'ngAudio', function($compile, $q, ngAudio) {
15 controller: 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: 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
264 }, function(newValue, oldValue) {
265 //console.log("ngaudio watch callback for: " + audioObject.id);
266 if (newValue.currentTime !== oldValue.currentTime) {
267 audioObject.setCurrentTime(newValue.currentTime);
270 if (newValue.progress !== oldValue.progress) {
271 audioObject.setProgress(newValue.progress);
273 if (newValue.volume !== oldValue.volume) {
274 audioObject.setVolume(newValue.volume);
277 if (newValue.playbackRate !== oldValue.playbackRate) {
278 audioObject.setPlaybackRate(newValue.playbackRate);
283 $looping = newValue.loop;
285 if (newValue.muting !== oldValue.muting) {
286 audioObject.setMuting(newValue.muting);
291 cleverAudioFindingService.find(id)
292 .then(function(nativeAudio) {
294 if (ngAudioGlobals.unlock) {
296 window.addEventListener("click", twiddle);
298 audio.addEventListener('playing', function() {
299 window.removeEventListener("click",twiddle);
304 audio.addEventListener('canplay', function() {
305 audioObject.canPlay = true;
309 audioObject.error = true;
314 var interval = $interval(checkWatchers, ngAudioGlobals.performance);
315 $intervalWatch = $scope.$watch(function(){
316 return ngAudioGlobals.performance;
318 $interval.cancel(interval);
319 interval = $interval(checkWatchers, ngAudioGlobals.performance);
322 function checkWatchers() {
328 if ($isMuting || ngAudioGlobals.isMuting) {
331 audio.volume = audioObject.volume !== undefined ? audioObject.volume : 1;
341 audio.currentTime = 0;
342 $willRestart = false;
350 if ($willChangePlaybackRate) {
351 audio.playbackRate = $newPlaybackRate;
352 $willChangePlaybackRate = false;
356 audio.volume = $volumeToSet;
357 $volumeToSet = undefined;
360 if ($observeProperties) {
361 audioObject.currentTime = audio.currentTime;
362 audioObject.duration = audio.duration;
363 audioObject.remaining = audio.duration - audio.currentTime;
364 audioObject.progress = audio.currentTime / audio.duration;
365 audioObject.paused = audio.paused;
366 audioObject.src = audio.src;
368 if (audioObject.currentTime >= audioObject.duration) {
369 completeListeners.forEach(function(listener){
370 listener(audioObject);
374 if ($looping && audioObject.currentTime >= audioObject.duration) {
375 if ($looping !== true) {
378 // if (!$looping) return;
380 audioObject.setCurrentTime(0);
386 if (!$isMuting && !ngAudioGlobals.isMuting) {
387 audioObject.volume = audio.volume;
390 audioObject.audio = audio;
397 .service('ngAudio', ['NgAudioObject', 'ngAudioGlobals', function(NgAudioObject, ngAudioGlobals) {
398 this.play = function(id, scope) {
400 var audio = new NgAudioObject(id, scope);
405 this.load = function(id, scope) {
406 return new NgAudioObject(id, scope);
409 this.mute = function() {
410 ngAudioGlobals.muting = true;
413 this.unmute = function() {
414 ngAudioGlobals.muting = false;
417 this.toggleMute = function() {
418 ngAudioGlobals.muting = !ngAudioGlobals.muting;
421 this.setUnlock = function(unlock) {
422 ngAudioGlobals.unlock = unlock;
425 .filter("trackTime", function(){
426 /* Conveniently takes a number and returns the track time */
428 return function(input){
430 var totalSec = Math.floor(input | 0);
437 if (totalSec > 3599) {
439 hours = Math.floor(totalSec / 3600);
440 minutes = Math.floor((totalSec - (hours * 3600)) / 60);
441 seconds = (totalSec - ((minutes * 60) + (hours * 3600)));
443 if (hours.toString().length == 1) {
444 hours = "0" + (Math.floor(totalSec / 3600)).toString();
447 if (minutes.toString().length == 1) {
448 minutes = "0" + (Math.floor((totalSec - (hours * 3600)) / 60)).toString();
451 if (seconds.toString().length == 1) {
452 seconds = "0" + (totalSec - ((minutes * 60) + (hours * 3600))).toString();
455 output = hours + ":" + minutes + ":" + seconds;
457 } else if (totalSec > 59) {
459 minutes = Math.floor(totalSec / 60);
460 seconds = totalSec - (minutes * 60);
462 if (minutes.toString().length == 1) {
463 minutes = "0" + (Math.floor(totalSec / 60)).toString();
466 if (seconds.toString().length == 1) {
467 seconds = "0" + (totalSec - (minutes * 60)).toString();
470 output = minutes + ":" + seconds;
476 if (seconds.toString().length == 1) {
477 seconds = "0" + (totalSec).toString();
480 output = totalSec + "s";
484 if (typeof Number.isNaN === "function" && Number.isNaN(output)){