Commits

Mathias Panzenböck committed 1133b00

torwards being able to accept more drops

made Magnatune.Playlist.prase* function interfaces work with async parsing

Comments (0)

Files changed (1)

javascripts/magnatune.js

 			}
 		},
 		importString: function (data, mimeType, index) {
-			var imported;
-			
 			try {
 				switch ((mimeType || "text/plain").split(";")[0]) {
 					case "application/octet-stream":
 					case "text/plain":
 					case "Text":
-						imported = this.parseAny(data);
+						this.parseAny(data, done);
 						break;
 
 					case "application/json":
 					case "text/x-json":
 					case "text/json":
-						imported = this.parseJson(data);
+						this.parseJson(data, done);
 						break;
 
 					case "audio/mpegurl":
 					case "audio/x-mpegurl":
 					case "text/uri-list": // basically the same, even has # comments
-						imported = this.parseM3u(data);
+						this.parseM3u(data, done);
 						break;
 
 					case "text/xml":
-						imported = this.parseXml(data);
+						this.parseXml(data, done);
 						break;
 
 					case "application/xspf+xml":
-						imported = this.parseXspf(data);
+						this.parseXspf(data, done);
 						break;
 
 					case "text/html":
 					case "application/xhtml+xml":
-						imported = this.parseHtml(data);
+						this.parseHtml(data, done);
 						break;
 
 					case "audio/x-scpls":
-						imported = this.parsePls(data);
+						this.parsePls(data, done);
 						break;
 
 					default:
 						return;
 				}
 			}
-			catch (e) {
-				alert("Error reading playlist: "+e.toString());
-				return;
-			}
-
-			var count = 0;
-			if (imported.songs) {
-				this.enqueue(imported.songs, index, true);
-				count = imported.songs.length;
-			}
-
-			if (imported.playlists) {
-				var playlists = this._getSavedPlaylists();
-				var conflict = [];
-				for (var name in imported.playlists) {
-					if (name in playlists) {
-						conflict.push(name);
+			catch (err) {
+				return done(err);
+			}
+
+			function done (err, imported) {
+				if (err) return alert("Error reading playlist: "+err.toString());
+
+				var count = 0;
+				if (imported.songs) {
+					Magnatune.Playlist.enqueue(imported.songs, index, true);
+					count = imported.songs.length;
+				}
+
+				if (imported.playlists) {
+					var playlists = Magnatune.Playlist._getSavedPlaylists();
+					var conflict = [];
+					for (var name in imported.playlists) {
+						if (name in playlists) {
+							conflict.push(name);
+						}
+						count += imported.playlists[name].length;
 					}
-					count += imported.playlists[name].length;
-				}
-				conflict.sort();
-				if (conflict.length === 0 || confirm(
-						"The folowing saved playlists already exist. Do you want to overwrite them?\n\n \u2022 "+
-						conflict.join("\n \u2022 "))) {
-					$.extend(playlists, imported.playlists);
-
-					if ($('#playlists-menu').is(':visible')) {
-						this._loadPlaylistMenu(playlists);
-					}
-
-					if (typeof(localStorage) !== "undefined") {
-						localStorage.setItem('playlist.saved', JSON.stringify(playlists));
+					conflict.sort();
+					if (conflict.length === 0 || confirm(
+							"The folowing saved playlists already exist. Do you want to overwrite them?\n\n \u2022 "+
+							conflict.join("\n \u2022 "))) {
+						$.extend(playlists, imported.playlists);
+
+						if ($('#playlists-menu').is(':visible')) {
+							Magnatune.Playlist._loadPlaylistMenu(playlists);
+						}
+
+						if (typeof(localStorage) !== "undefined") {
+							localStorage.setItem('playlist.saved', JSON.stringify(playlists));
+						}
 					}
 				}
-			}
-			if (imported.unknown > 0) {
-				alert($.format("Could not detect {unknown} of {all} Magnatune playlist entries.", {
-					unknown: imported.unknown,
-					all: count + imported.unknown
-				}));
-			}
-		},
-		parseAny: function (data) {
-			if (/^#EXTM3U\b/.test(data)) {
-				return this.parseM3u(data);
+				if (imported.unknown > 0) {
+					alert($.format("Could not detect {unknown} of {all} Magnatune playlist entries.", {
+						unknown: imported.unknown,
+						all: count + imported.unknown
+					}));
+				}
+			}
+		},
+		parseAny: function (data, done) {
+			if (/^#EXTM3U\b/.test(data) || /^https?:\/\/\S+(\r?\n)*$/.test(data)) {
+				this.parseM3u(data, done);
 			}
 			else if (/^\s*\{/.test(data)) {
-				return this.parseJson(data);
+				this.parseJson(data, done);
 			}
 			else if (/^\s*(<\?.*?\?>\s*|<!--.*?-->|<!.*?>\s*)*<(\S+:)?playlist\b/.test(data)) {
-				return this.parseXspf(data);
+				this.parseXspf(data, done);
 			}
 			else if (/^\s*(<\?.*?\?>\s*|<!--.*?-->|<!.*?>\s*)*<html\b/i.test(data)) {
-				return this.parseHtml(data);
+				this.parseHtml(data, done);
 			}
 			else if (/^(\s*(;[^\n]*)?\n)*\[playlist\]\s*\n/i.test(data)) {
-				return this.parsePls(data);
+				this.parsePls(data, done);
 			}
 			else {
-				throw new Error("Failed to detect file type.");
-			}
-		},
-		parseXml: function (data) {
+				done(new Error("Failed to detect file type."));
+			}
+		},
+		parseXml: function (data, done) {
 			if (/^\s*(<\?.*?\?>\s*|<!--.*?-->|<!.*?>\s*)*<(\S+:)?playlist\b/.test(data)) {
-				return this.parseXspf(data);
+				this.parseXspf(data, done);
 			}
 			else if (/^\s*(<\?.*?\?>\s*|<!--.*?-->|<!.*?>\s*)*<html\b/i.test(data)) {
-				return this.parseHtml(data);
+				this.parseHtml(data, done);
 			}
 			else {
-				throw new Error("Failed to detect file type.");
-			}
-		},
-		parseXspf: function (data) {
-			var doc = $.parseXML(data);
-			var root = doc.documentElement;
-			var localName = root.localName;
-			if (!localName) {
-				// IE
-				if (root.prefix) {
-					localName = root.nodeName.slice(root.prefix.length + 1);
+				done(new Error("Failed to detect file type."));
+			}
+		},
+		parseXspf: function (data, done) {
+			try {
+				var doc = $.parseXML(data);
+				var root = doc.documentElement;
+				var localName = root.localName;
+				if (!localName) {
+					// IE
+					if (root.prefix) {
+						localName = root.nodeName.slice(root.prefix.length + 1);
+					}
+					else {
+						localName = root.nodeName;
+					}
+				}
+				if (root.namespaceURI !== "http://xspf.org/ns/0/" || localName !== "playlist") {
+					throw new Error("Unrecognized file format.");
+				}
+	
+				// The default base should be the URL of the XSPF file, but this is inaccessible here.
+				var base = absurl($(root).attr("xml:base")||"","http://download.magnatune.com/");
+				var unknown = 0;
+				var songs = [];
+	
+				var trackLists = $(root).find("> trackList");
+				for (var trackListIndex = 0; trackListIndex < trackLists.length; ++ trackListIndex) {
+					var trackList = $(trackLists[trackListIndex]);
+					var trackListBase = absurl(trackList.attr("xml:base")||"",base);
+					var tracks = trackList.find("> track");
+					
+					for (var i = 0; i < tracks.length; ++ i) {
+						var track = $(tracks[i]);
+						var trackBase = absurl(track.attr("xml:base")||"",trackListBase);
+						var song = {
+							albumname: track.find('> album').text(),
+							artist:    track.find('> creator').text(),
+							desc:      track.find('> title').text(),
+							duration:  parseFloat(track.find('> duration').text()) / 1000,
+							number:    parseInt(track.find('> trackNum').text(),10)
+						};
+	
+						var location = track.find('> location');
+						var locationBase = absurl(location.attr("xml:base")||"",trackBase);
+						location = absurl(location.text(),locationBase);
+						var mp3 = /^https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:all|music\/[^\/=?&#]+\/[^\/=?&#]+)\/((?:(\d+)-)?[^\/=?&#]*?)(?:_nospeech|-lofi|_spoken|_hq)?\.(?:mp3|ogg|m4a|flac|wav)$/.exec(location);
+	
+						if (mp3 && (!song.albumname || !song.artist)) {
+							// complete missing information from urls if possible
+							// there is no sense in doing this when the location could not be parsed
+							var guess_regex = /^https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/music\/([^\/=?&#]+)\/([^\/=?&#]+)\//;
+							var guess = guess_regex.exec(location);
+	
+							if (!guess) {
+								var image = track.find('> image');
+								var imageUrl = image.text();
+			
+								if (imageUrl) {
+									var imageBase = absurl(image.attr("xml:base")||"",trackBase);
+									imageUrl = absurl(imageUrl,imageBase);
+								}
+								guess = guess_regex.exec(imageUrl);
+							}
+	
+							if (guess) {
+								if (!song.artist) {
+									song.artist = guess[1];
+								}
+	
+								if (!song.albumname) {
+									song.albumname = guess[2];
+								}
+							}
+						}
+	
+						if (mp3 && (!song.desc || isNaN(song.duration))) {
+							// complete missing information from annotation
+							var annotation = /^(.*)\s+\((?:(?:(\d+):)?(\d+):)?(\d+)\)$/.exec(track.find("> annotation").text());
+							if (annotation) {
+								if (!song.desc) {
+									song.desc = annotation[1];
+								}
+	
+								if (isNaN(song.duration)) {
+									song.duration = (Number(annotation[2]||0)*60+Number(annotation[3]||0))*60+Number(annotation[4]);
+								}
+							}
+						}
+	
+						var album;
+						if (!mp3 || !song.albumname || !song.desc ||
+							(isNaN(song.number) && (!mp3[2] || isNaN(song.number = parseInt(mp3[2],10)))) ||
+							song.number <= 0 || !(album = Magnatune.Collection.Albums[song.albumname]) ||
+							album.artist.artist !== song.artist) {
+							++ unknown;
+						}
+						else {
+							song.mp3 = decodeURIComponent(mp3[1])+'.mp3';
+							songs.push(song);
+						}
+					}
+				}
+	
+				done(null,{songs: songs, unknown: unknown});
+			}
+			catch (err) {
+				done(err);
+			}
+		},
+		parseHtml: function (data, done) {
+			try {
+				var doc;
+				if ($.parseHTML) {
+					doc = $.parseHTML(data);
 				}
 				else {
-					localName = root.nodeName;
+					// IE, try xml
+					try {
+						doc = $.parseXML(data);
+					}
+					catch (e) {
+						throw new Error("Your browser does not support parsing HTML files in JavaScript.");
+					}
 				}
-			}
-			if (root.namespaceURI !== "http://xspf.org/ns/0/" || localName !== "playlist") {
-				throw new Error("Unrecognized file format.");
-			}
-
-			// The default base should be the URL of the XSPF file, but this is inaccessible here.
-			var base = absurl($(root).attr("xml:base")||"","http://download.magnatune.com/");
-			var unknown = 0;
-			var songs = [];
-
-			var trackLists = $(root).find("> trackList");
-			for (var trackListIndex = 0; trackListIndex < trackLists.length; ++ trackListIndex) {
-				var trackList = $(trackLists[trackListIndex]);
-				var trackListBase = absurl(trackList.attr("xml:base")||"",base);
-				var tracks = trackList.find("> track");
-				
+	
+				// first try to parse HTML with hAudio/hMedia microformats:
+				var tracks = $(doc.documentElement).find('.haudio, .hmedia');
+				var unknown = 0;
+				var songs = [];
+	
 				for (var i = 0; i < tracks.length; ++ i) {
 					var track = $(tracks[i]);
-					var trackBase = absurl(track.attr("xml:base")||"",trackListBase);
 					var song = {
-						albumname: track.find('> album').text(),
-						artist:    track.find('> creator').text(),
-						desc:      track.find('> title').text(),
-						duration:  parseFloat(track.find('> duration').text()) / 1000,
-						number:    parseInt(track.find('> trackNum').text(),10)
+						albumname: track.find('.album').text(),
+						artist:    track.find('.contributor').text(),
+						desc:      track.find('.fn').text(),
+						number:    parseInt(track.find('.number').text(), 10),
+						duration:  NaN
 					};
-
-					var location = track.find('> location');
-					var locationBase = absurl(location.attr("xml:base")||"",trackBase);
-					location = absurl(location.text(),locationBase);
-					var mp3 = /^https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:all|music\/[^\/=?&#]+\/[^\/=?&#]+)\/((?:(\d+)-)?[^\/=?&#]*?)(?:_nospeech|-lofi|_spoken|_hq)?\.(?:mp3|ogg|m4a|flac|wav)$/.exec(location);
-
-					if (mp3 && (!song.albumname || !song.artist)) {
-						// complete missing information from urls if possible
-						// there is no sense in doing this when the location could not be parsed
-						var guess_regex = /^https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/music\/([^\/=?&#]+)\/([^\/=?&#]+)\//;
-						var guess = guess_regex.exec(location);
-
-						if (!guess) {
-							var image = track.find('> image');
-							var imageUrl = image.text();
-		
-							if (imageUrl) {
-								var imageBase = absurl(image.attr("xml:base")||"",trackBase);
-								imageUrl = absurl(imageUrl,imageBase);
+	
+					var duration = track.find('.duration');
+					var hours = parseInt(duration.find('.h').text(),10);
+					var mins  = parseInt(duration.find('.min').text(),10);
+					var secs  = parseFloat(duration.find('.s').text());
+	
+					if (isNaN(hours) && isNaN(mins) && isNaN(secs)) {
+						if (duration.is('abbr')) {
+							// parse ISO 8601 duration (with some extensions)
+							duration = (duration.attr('title')||'').trim();
+							var match;
+	
+							if ((match = /^P(?:Y(\d+))?(?:M(\d+))?(?:D(\d+))?(?:T(?:H(\d+))?(?:M(\d+))?(?:S(\d+(?:\.\d+)?(?:[eE][-+]?\d+)?))?)?$/.exec(duration)) ||
+								(match = /^P?(\d+)-(\d+)-(\d+)(?:T|\s+)(\d+):(\d+):(\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)$/.exec(duration)) ||
+								(match = /^P(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d(?:\.\d+)?(?:[eE][-+]?\d+)?)$/.exec(duration))) {
+								song.duration = (((parseInt(match[1]||0,10)*365 + parseInt(match[2]||0,10)*(365/12) + parseInt(match[3]||0,10))*24 +
+									parseInt(match[4]||0,10))*60 + parseInt(match[5]||0,10))*60 + parseFloat(match[6]||0);
 							}
-							guess = guess_regex.exec(imageUrl);
-						}
-
-						if (guess) {
-							if (!song.artist) {
-								song.artist = guess[1];
-							}
-
-							if (!song.albumname) {
-								song.albumname = guess[2];
+							else if ((match = /^PW(\d+)$/.exec(duration))) {
+								song.duration = parseInt(match[1],10) * 7 * 24 * 60 * 60;
 							}
 						}
 					}
-
-					if (mp3 && (!song.desc || isNaN(song.duration))) {
-						// complete missing information from annotation
-						var annotation = /^(.*)\s+\((?:(?:(\d+):)?(\d+):)?(\d+)\)$/.exec(track.find("> annotation").text());
-						if (annotation) {
-							if (!song.desc) {
-								song.desc = annotation[1];
-							}
-
-							if (isNaN(song.duration)) {
-								song.duration = (Number(annotation[2]||0)*60+Number(annotation[3]||0))*60+Number(annotation[4]);
-							}
+					else if (!isNaN(hours) || !isNaN(mins) || !isNaN(secs)) {
+						song.duration = ((hours||0)*60 + (mins||0))*60 + (secs||0);
+					}
+	
+					var mp3 = track.find('a[rel="enclosure"], a[rel="sample"]');
+					
+					if (mp3.length > 0) {
+						mp3 = mp3.attr('href');
+					}
+					else {
+						var audio = track.find('audio.player, audio.enclosure, audio.sample');
+						mp3 = audio.attr('src');
+						if (!mp3) {
+							mp3 = audio.find('source').attr('src');
 						}
 					}
-
+					
+					mp3 = /^http:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:all|music\/[^\/=?&#]+\/[^\/=?&#]+)\/((?:(\d+)-)?[^\/=?&#]*?)(?:_nospeech|-lofi|_spoken|_hq)?\.(?:mp3|ogg|m4a|flac|wav)$/.exec(mp3||'');
+	
 					var album;
 					if (!mp3 || !song.albumname || !song.desc ||
 						(isNaN(song.number) && (!mp3[2] || isNaN(song.number = parseInt(mp3[2],10)))) ||
 						songs.push(song);
 					}
 				}
-			}
-
-			return {songs: songs, unknown: unknown};
-		},
-		parseHtml: function (data) {
-			var doc;
-			if ($.parseHTML) {
-				doc = $.parseHTML(data);
-			}
-			else {
-				// IE, try xml
-				try {
-					doc = $.parseXML(data);
-				}
-				catch (e) {
-					throw new Error("Your browser does not support parsing HTML files in JavaScript.");
-				}
-			}
-
-			// first try to parse HTML with hAudio/hMedia microformats:
-			var tracks = $(doc.documentElement).find('.haudio, .hmedia');
-			var unknown = 0;
-			var songs = [];
-
-			for (var i = 0; i < tracks.length; ++ i) {
-				var track = $(tracks[i]);
-				var song = {
-					albumname: track.find('.album').text(),
-					artist:    track.find('.contributor').text(),
-					desc:      track.find('.fn').text(),
-					number:    parseInt(track.find('.number').text(), 10),
-					duration:  NaN
-				};
-
-				var duration = track.find('.duration');
-				var hours = parseInt(duration.find('.h').text(),10);
-				var mins  = parseInt(duration.find('.min').text(),10);
-				var secs  = parseFloat(duration.find('.s').text());
-
-				if (isNaN(hours) && isNaN(mins) && isNaN(secs)) {
-					if (duration.is('abbr')) {
-						// parse ISO 8601 duration (with some extensions)
-						duration = (duration.attr('title')||'').trim();
-						var match;
-
-						if ((match = /^P(?:Y(\d+))?(?:M(\d+))?(?:D(\d+))?(?:T(?:H(\d+))?(?:M(\d+))?(?:S(\d+(?:\.\d+)?(?:[eE][-+]?\d+)?))?)?$/.exec(duration)) ||
-							(match = /^P?(\d+)-(\d+)-(\d+)(?:T|\s+)(\d+):(\d+):(\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)$/.exec(duration)) ||
-							(match = /^P(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d(?:\.\d+)?(?:[eE][-+]?\d+)?)$/.exec(duration))) {
-							song.duration = (((parseInt(match[1]||0,10)*365 + parseInt(match[2]||0,10)*(365/12) + parseInt(match[3]||0,10))*24 +
-								parseInt(match[4]||0,10))*60 + parseInt(match[5]||0,10))*60 + parseFloat(match[6]||0);
+	
+				if (songs.length === 0 && unknown === 0) {
+					// if that fails just search for links to audio files
+					tracks = $(doc.documentElement).find('a[href]');
+					for (var i = 0; i < tracks.length; ++ i) {
+						var url = tracks[i].href;
+						var song = this._guessSongFromUrl(url);
+						if (song) {
+							songs.push(song);
 						}
-						else if ((match = /^PW(\d+)$/.exec(duration))) {
-							song.duration = parseInt(duration[1]) * 7 * 24 * 60 * 60;
+						else if (/https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:music|all)\/[^?&#]*\.(?:mp3|ogg|m4a|flac|wav)$/i.test(url)) {
+							// don't complain about links that do not point to Magnatune songs
+							++ unknown;
 						}
 					}
 				}
-				else if (!isNaN(hours) || !isNaN(mins) || !isNaN(secs)) {
-					song.duration = ((hours||0)*60 + (mins||0))*60 + (secs||0);
+	
+				done(null,{songs: songs, unknown: unknown});
+			}
+			catch (err) {
+				done(err);
+			}
+		},
+		_guessSongsFromUrl: function (url, done) {
+			var prefix = location.protocol+"//"+location.host+location.pathname;
+			if (url.slice(0,prefix.length) === prefix) {
+				var m = /^#\/([^\/]*)\/([^\/]*)/.exec(url.slice(prefix.length));
+				if (!m) return done(new Error("Faild to load songs from url: "+url));
+				var name = decodeURIComponent(m[2]);
+				switch (m[1]) {
+					case "playlist":
+						var playlist = Magnatune.Playlist._getSavedPlaylists()[name];
+						if (!playlist) return done(new Error("No such playlist: "+name));
+						return done(null,playlist);
+
+					case "album":
+						var album = Magnatune.Collection.Albums[name];
+						if (!album) return done(new Error("Album \u00bb"+name+"\u00ab was not found."));
+						
+						Magnatune.Collection.request({
+							args: {action: 'album', name: album.albumname},
+							success: function (data) {
+								if (!data.body) return done(new Error("Album \u00bb"+name+"\u00ab was not found."));
+								done(null,data.body.songs);
+							},
+							error: function (request, textStatus, errorThrown) {
+								done(errorThrown||new Error(textStatus));
+							}
+						});
+						break;
+
+					case "artist":
+						// TODO
+					case "genre":
+						// TODO
+					default:
+						return done(new Error("Faild to load songs from url: "+url));
 				}
-
-				var mp3 = track.find('a[rel="enclosure"], a[rel="sample"]');
-				
-				if (mp3.length > 0) {
-					mp3 = mp3.attr('href');
-				}
-				else {
-					var audio = track.find('audio.player, audio.enclosure, audio.sample');
-					mp3 = audio.attr('src');
-					if (!mp3) {
-						mp3 = audio.find('source').attr('src');
-					}
-				}
-				
-				mp3 = /^http:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:all|music\/[^\/=?&#]+\/[^\/=?&#]+)\/((?:(\d+)-)?[^\/=?&#]*?)(?:_nospeech|-lofi|_spoken|_hq)?\.(?:mp3|ogg|m4a|flac|wav)$/.exec(mp3||'');
-
-				var album;
-				if (!mp3 || !song.albumname || !song.desc ||
-					(isNaN(song.number) && (!mp3[2] || isNaN(song.number = parseInt(mp3[2],10)))) ||
-					song.number <= 0 || !(album = Magnatune.Collection.Albums[song.albumname]) ||
-					album.artist.artist !== song.artist) {
-					++ unknown;
-				}
-				else {
-					song.mp3 = decodeURIComponent(mp3[1])+'.mp3';
-					songs.push(song);
-				}
-			}
-
-			if (songs.length === 0 && unknown === 0) {
-				// if that fails just search for links to audio files
-				tracks = $(doc.documentElement).find('a[href]');
-				for (var i = 0; i < tracks.length; ++ i) {
-					var url = tracks[i].href;
-					var song = this._guessSongFromUrl(url);
-					if (song) {
-						songs.push(song);
-					}
-					else if (/https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/(?:music|all)\/[^?&#]*\.(?:mp3|ogg|m4a|flac|wav)$/i.test(url)) {
-						// don't complain about links that do not point to Magnatune songs
-						++ unknown;
-					}
-				}
-			}
-
-			return {songs: songs, unknown: unknown};
+			}
+			else {
+				// TODO: match more urls, like:
+				// http://*.magnatune.com/artists/albums/jasmineb-mews/*
+				// http://*.magnatune.com/artists/jasmine_brunch
+				// http://*.magnatune.com/genres/ambient/
+				// http://*.magnatune.com/artists/albums/sieber-hidden/06_nospeech.m3u
+				// etc.
+				var song = this._guessSongsFromUrl(url);
+				if (!song) return done(new Error("Faild to load songs from url: "+url));
+				done(null,[song]);
+			}
 		},
 		_guessSongFromUrl: function (url) {
 			var guess = /^https?:\/\/(?:[^\/=?&#]+@)?(?:download|stream|he3)\.magnatune\.com\/music\/([^\/=?&#]+)\/([^\/=?&#]+)\/((\d+)-[^\/=?&#]*?)(?:_nospeech|-lofi|_spoken|_hq)?\.(?:mp3|ogg|m4a|flac|wav)$/.exec(url);
 
 			return song;
 		},
-		parseM3u: function (data) {
-			var lines = data.split(/\r?\n/g);
-			var songs = [];
-			var extm3u = lines[0] === "#EXTM3U";
-			var unknown = 0;
-
-			var duration = NaN;
-			var desc = null;
-			for (var i = extm3u ? 1 : 0; i < lines.length; ++ i) {
-				var line = lines[i];
-
-				if (!line) continue;
-
-				if (extm3u && /^#EXTINF:/.test(line)) {
-					var info = /^#EXTINF:([^,]*)(?:,(.*))?$/.exec(line);
-					desc = info[2];
-					duration = info[1];
+		parseM3u: function (data, done) {
+			try {
+				var lines = data.split(/\r?\n/g);
+				var songs = [];
+				var extm3u = lines[0] === "#EXTM3U";
+				var unknown = 0;
+	
+				var duration = NaN;
+				var desc = null;
+				for (var i = extm3u ? 1 : 0; i < lines.length; ++ i) {
+					var line = lines[i];
+	
+					if (!line) continue;
+	
+					if (extm3u && /^#EXTINF:/.test(line)) {
+						var info = /^#EXTINF:([^,]*)(?:,(.*))?$/.exec(line);
+						desc = info[2];
+						duration = info[1];
+					}
+					else if (!/^#/.test(line)) {
+						var song = this._guessSong(line,desc,duration);
+						
+						if (song) {
+							songs.push(song);
+						}
+						else {
+							++ unknown;
+						}
+	
+						duration = NaN;
+						desc = null;
+					}
 				}
-				else if (!/^#/.test(line)) {
-					var song = this._guessSong(line,desc,duration);
+	
+				done(null,{songs: songs, unknown: unknown});
+			}
+			catch (err) {
+				done(err);
+			}
+		},
+		parsePls: function (data, done) {
+			try {
+				var lines = data.split(/\r?\n/g);
+				var songs = [];
+				var unknown = 0;
+	
+				var ini = {'': {}};
+				var section = ini[''];
+				
+				// hopefully not to simple ini parser:
+				for (var i = 0; i < lines.length; ++ i) {
+					var line = lines[i];
 					
+					if (/^\s*(;.*)?$/.test(line)) {
+						continue;
+					}
+	
+					var match;
+					if ((match = /^\[(.*?)\]\s*$/.exec(line))) {
+						var name = match[1].trim().toLowerCase();
+						if (name in ini) {
+							section = ini[name];
+						}
+						else {
+							section = ini[name] = {};
+						}
+					}
+					else {
+						match = /^(.*?)(?:=(.*))?$/.exec(line);
+						section[match[1].trim().toLowerCase()] = match[2]||'';
+					}
+				}
+	
+				var playlist = ini.playlist;
+				var length;
+	
+				if (!playlist || !playlist.version || parseInt(playlist.version,10) !== 2 ||
+					!playlist.numberofentries || isNaN(length = parseInt(playlist.numberofentries, 10)) ||
+					length < 0 || length === Infinity) {
+					throw new Error("Unrecognized file format.");
+				}
+	
+				for (var i = 1; i < length + 1; ++ i) {
+					var desc = playlist["title"+i]||'';
+					var url  = playlist["file"+i]||'';
+					var duration = playlist["length"+i]||'';
+	
+					var song = this._guessSong(url,desc,duration);
 					if (song) {
 						songs.push(song);
 					}
 					else {
 						++ unknown;
 					}
-
-					duration = NaN;
-					desc = null;
 				}
-			}
-
-			return {songs: songs, unknown: unknown};
-		},
-		parsePls: function (data) {
-			var lines = data.split(/\r?\n/g);
-			var songs = [];
-			var unknown = 0;
-
-			var ini = {'': {}};
-			var section = ini[''];
-			
-			// hopefully not to simple ini parser:
-			for (var i = 0; i < lines.length; ++ i) {
-				var line = lines[i];
-				
-				if (/^\s*(;.*)?$/.test(line)) {
-					continue;
+	
+				done(null,{songs: songs, unknown: unknown});
+			}
+			catch (err) {
+				done(err);
+			}
+		},
+		parseJson: function (data, done) {
+			try {
+				data = JSON.parse(data);
+	
+				if (!data || !data.head || data.head.type !== "magnatune-player" || !data.body) {
+					throw new Error("Unrecognized file format.");
 				}
-
-				var match;
-				if ((match = /^\[(.*?)\]\s*$/.exec(line))) {
-					var name = match[1].trim().toLowerCase();
-					if (name in ini) {
-						section = ini[name];
-					}
-					else {
-						section = ini[name] = {};
-					}
-				}
-				else {
-					match = /^(.*?)(?:=(.*))?$/.exec(line);
-					section[match[1].trim().toLowerCase()] = match[2]||'';
-				}
-			}
-
-			var playlist = ini.playlist;
-			var length;
-
-			if (!playlist || !playlist.version || parseInt(playlist.version,10) !== 2 ||
-				!playlist.numberofentries || isNaN(length = parseInt(playlist.numberofentries, 10)) ||
-				length < 0 || length === Infinity) {
-				throw new Error("Unrecognized file format.");
-			}
-
-			for (var i = 1; i < length + 1; ++ i) {
-				var desc = playlist["title"+i]||'';
-				var url  = playlist["file"+i]||'';
-				var duration = playlist["length"+i]||'';
-
-				var song = this._guessSong(url,desc,duration);
-				if (song) {
-					songs.push(song);
-				}
-				else {
-					++ unknown;
-				}
-			}
-
-			return {songs: songs, unknown: unknown};
-		},
-		parseJson: function (data) {
-			data = JSON.parse(data);
-
-			if (!data || !data.head || data.head.type !== "magnatune-player" || !data.body) {
-				throw new Error("Unrecognized file format.");
-			}
-
-			switch (data.head.subtype) {
-				case "playlist":
-					if (!$.isArray(data.body)) {
-						throw new Error("Unrecognized file format.");
-					}
-					// TODO: verify songs
-					return {songs: data.body, unknown: 0};
-
-				case "playlists":
-					if (typeof(data.body) !== "object") {
-						throw new Error("Unrecognized file format.");
-					}
-					for (var name in data.body) {
-						if (!$.isArray(data.body[name])) {
+	
+				switch (data.head.subtype) {
+					case "playlist":
+						if (!$.isArray(data.body)) {
 							throw new Error("Unrecognized file format.");
 						}
 						// TODO: verify songs
-					}
-					return {playlists: data.body, unknown: 0};
-
-				default:
-					throw new Error("Unrecognized file format.");
+						return done(null,{songs: data.body, unknown: 0});
+	
+					case "playlists":
+						if (typeof(data.body) !== "object") {
+							throw new Error("Unrecognized file format.");
+						}
+						for (var name in data.body) {
+							if (!$.isArray(data.body[name])) {
+								throw new Error("Unrecognized file format.");
+							}
+							// TODO: verify songs
+						}
+						return done(null,{playlists: data.body, unknown: 0});
+	
+					default:
+						throw new Error("Unrecognized file format.");
+				}
+			}
+			catch (err) {
+				done(err);
 			}
 		},
 		showPlaylistMenu: function () {