From 208a398d3bc26cd1355d0eede4b09aaae6b9cf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2EFernando=20S=C3=A1nchez?= Date: Fri, 25 Jan 2013 11:36:38 +0100 Subject: [PATCH] User friendly downloads for files and Playlist The name is still missing in Firefox because it doesn't use the "download" attribute. However it won't open a new window for playlist or file downloads. --- css/main.css | 7 +- js/Blob.js | 177 ++++++++++++++++++++++++++++++++++++++++++ js/BlobBuilder.min.js | 2 + js/FileSaver.min.js | 2 + js/app.js | 12 ++- public/index.html | 7 +- 6 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 js/Blob.js create mode 100644 js/BlobBuilder.min.js create mode 100644 js/FileSaver.min.js diff --git a/css/main.css b/css/main.css index 21d0828..af87084 100644 --- a/css/main.css +++ b/css/main.css @@ -12,7 +12,7 @@ html,body { .sortable { position:relative; list-style-type: none; margin: 0; padding: 0; width: 100%; } .sortable li { margin: 0px 3px 3px 3px; padding: 0.4em; font-size: 1em; height: 18px; } .list-name { padding-left:3em; } - .list-controls span {float:left;display:inline;} + .list-controls span, .list-controls a {float:left;display:inline;} #wrapper { position:relative; height: auto !important; @@ -21,7 +21,7 @@ html,body { } #headerPage { position:relative; - min-height:20%; + height:20%; width: 100%; } .controls ul li { @@ -41,6 +41,7 @@ text-align: center; font-size:small; } #audio { + height:40px; width:100%; } @@ -76,6 +77,6 @@ text-align: center; display:none; } -.list-controls span { +.list-controls span a { cursor:pointer; } diff --git a/js/Blob.js b/js/Blob.js new file mode 100644 index 0000000..4560b0c --- /dev/null +++ b/js/Blob.js @@ -0,0 +1,177 @@ +/* Blob.js + * A Blob implementation. + * 2013-01-23 + * + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/eboyjr + * License: X11/MIT + * See LICENSE.md + */ + +/*global self, unescape */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ + +if (typeof Blob !== "function") +var Blob = (function (view) { + "use strict"; + + var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) { + var + get_class = function(object) { + return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; + } + , FakeBlobBuilder = function BlobBuilder() { + this.data = []; + } + , FakeBlob = function Blob(data, type, encoding) { + this.data = data; + this.size = data.length; + this.type = type; + this.encoding = encoding; + } + , FBB_proto = FakeBlobBuilder.prototype + , FB_proto = FakeBlob.prototype + , FileReaderSync = view.FileReaderSync + , FileException = function(type) { + this.code = this[this.name = type]; + } + , file_ex_codes = ( + "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" + ).split(" ") + , file_ex_code = file_ex_codes.length + , real_URL = view.URL || view.webkitURL || view + , real_create_object_URL = real_URL.createObjectURL + , real_revoke_object_URL = real_URL.revokeObjectURL + , URL = real_URL + , btoa = view.btoa + , atob = view.atob + , can_apply_typed_arrays = false + , can_apply_typed_arrays_test = function(pass) { + can_apply_typed_arrays = !pass; + } + + , ArrayBuffer = view.ArrayBuffer + , Uint8Array = view.Uint8Array + ; + FakeBlob.fake = FB_proto.fake = true; + while (file_ex_code--) { + FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; + } + try { + if (Uint8Array) { + can_apply_typed_arrays_test.apply(0, new Uint8Array(1)); + } + } catch (ex) {} + if (!real_URL.createObjectURL) { + URL = view.URL = {}; + } + URL.createObjectURL = function(blob) { + var + type = blob.type + , data_URI_header + ; + if (type === null) { + type = "application/octet-stream"; + } + if (blob instanceof FakeBlob) { + data_URI_header = "data:" + type; + if (blob.encoding === "base64") { + return data_URI_header + ";base64," + blob.data; + } else if (blob.encoding === "URI") { + return data_URI_header + "," + decodeURIComponent(blob.data); + } if (btoa) { + return data_URI_header + ";base64," + btoa(blob.data); + } else { + return data_URI_header + "," + encodeURIComponent(blob.data); + } + } else if (real_create_object_URL) { + return real_create_object_URL.call(real_URL, blob); + } + }; + URL.revokeObjectURL = function(object_URL) { + if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { + real_revoke_object_URL.call(real_URL, object_URL); + } + }; + FBB_proto.append = function(data/*, endings*/) { + var bb = this.data; + // decode data to a binary string + if (Uint8Array && data instanceof ArrayBuffer) { + if (can_apply_typed_arrays) { + bb.push(String.fromCharCode.apply(String, new Uint8Array(data))); + } else { + var + str = "" + , buf = new Uint8Array(data) + , i = 0 + , buf_len = buf.length + ; + for (; i < buf_len; i++) { + str += String.fromCharCode(buf[i]); + } + } + } else if (get_class(data) === "Blob" || get_class(data) === "File") { + if (FileReaderSync) { + var fr = new FileReaderSync; + bb.push(fr.readAsBinaryString(data)); + } else { + // async FileReader won't work as BlobBuilder is sync + throw new FileException("NOT_READABLE_ERR"); + } + } else if (data instanceof FakeBlob) { + if (data.encoding === "base64" && atob) { + bb.push(atob(data.data)); + } else if (data.encoding === "URI") { + bb.push(decodeURIComponent(data.data)); + } else if (data.encoding === "raw") { + bb.push(data.data); + } + } else { + if (typeof data !== "string") { + data += ""; // convert unsupported types to strings + } + // decode UTF-16 to binary string + bb.push(unescape(encodeURIComponent(data))); + } + }; + FBB_proto.getBlob = function(type) { + if (!arguments.length) { + type = null; + } + return new FakeBlob(this.data.join(""), type, "raw"); + }; + FBB_proto.toString = function() { + return "[object BlobBuilder]"; + }; + FB_proto.slice = function(start, end, type) { + var args = arguments.length; + if (args < 3) { + type = null; + } + return new FakeBlob( + this.data.slice(start, args > 1 ? end : this.data.length) + , type + , this.encoding + ); + }; + FB_proto.toString = function() { + return "[object Blob]"; + }; + return FakeBlobBuilder; + }(view)); + + return function Blob(blobParts, options) { + var type = options ? (options.type || "") : ""; + var builder = new BlobBuilder(); + if (blobParts) { + for (var i = 0, len = blobParts.length; i < len; i++) { + builder.append(blobParts[i]); + } + } + return builder.getBlob(type); + }; +}(self)); diff --git a/js/BlobBuilder.min.js b/js/BlobBuilder.min.js new file mode 100644 index 0000000..bcd2a9e --- /dev/null +++ b/js/BlobBuilder.min.js @@ -0,0 +1,2 @@ +/*! @source http://purl.eligrey.com/github/BlobBuilder.js/blob/master/BlobBuilder.js */ +var BlobBuilder=BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||(function(j){"use strict";var c=function(v){return Object.prototype.toString.call(v).match(/^\[object\s(.*)\]$/)[1]},u=function(){this.data=[]},t=function(x,v,w){this.data=x;this.size=x.length;this.type=v;this.encoding=w},k=u.prototype,s=t.prototype,n=j.FileReaderSync,a=function(v){this.code=this[this.name=v]},l=("NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR").split(" "),r=l.length,o=j.URL||j.webkitURL||j,p=o.createObjectURL,b=o.revokeObjectURL,e=o,i=j.btoa,f=j.atob,m=false,h=function(v){m=!v},d=j.ArrayBuffer,g=j.Uint8Array;u.fake=s.fake=true;while(r--){a.prototype[l[r]]=r+1}try{if(g){h.apply(0,new g(1))}}catch(q){}if(!o.createObjectURL){e=j.URL={}}e.createObjectURL=function(w){var x=w.type,v;if(x===null){x="application/octet-stream"}if(w instanceof t){v="data:"+x;if(w.encoding==="base64"){return v+";base64,"+w.data}else{if(w.encoding==="URI"){return v+","+decodeURIComponent(w.data)}}if(i){return v+";base64,"+i(w.data)}else{return v+","+encodeURIComponent(w.data)}}else{if(real_create_object_url){return real_create_object_url.call(o,w)}}};e.revokeObjectURL=function(v){if(v.substring(0,5)!=="data:"&&real_revoke_object_url){real_revoke_object_url.call(o,v)}};k.append=function(z){var B=this.data;if(g&&z instanceof d){if(m){B.push(String.fromCharCode.apply(String,new g(z)))}else{var A="",w=new g(z),x=0,y=w.length;for(;x1?v:this.data.length),x,this.encoding)};s.toString=function(){return"[object Blob]"};return u}(self)); \ No newline at end of file diff --git a/js/FileSaver.min.js b/js/FileSaver.min.js new file mode 100644 index 0000000..b9f98de --- /dev/null +++ b/js/FileSaver.min.js @@ -0,0 +1,2 @@ +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +var saveAs=saveAs||(function(h){"use strict";var r=h.document,l=function(){return h.URL||h.webkitURL||h},e=h.URL||h.webkitURL||h,n=r.createElementNS("http://www.w3.org/1999/xhtml","a"),g="download" in n,j=function(t){var s=r.createEvent("MouseEvents");s.initMouseEvent("click",true,false,h,0,0,0,0,0,false,false,false,false,0,null);return t.dispatchEvent(s)},o=h.webkitRequestFileSystem,p=h.requestFileSystem||o||h.mozRequestFileSystem,m=function(s){(h.setImmediate||h.setTimeout)(function(){throw s},0)},c="application/octet-stream",k=0,b=[],i=function(){var t=b.length;while(t--){var s=b[t];if(typeof s==="string"){e.revokeObjectURL(s)}else{s.remove()}}b.length=0},q=function(t,s,w){s=[].concat(s);var v=s.length;while(v--){var x=t["on"+s[v]];if(typeof x==="function"){try{x.call(t,w||t)}catch(u){m(u)}}}},f=function(t,u){var v=this,B=t.type,E=false,x,w,s=function(){var F=l().createObjectURL(t);b.push(F);return F},A=function(){q(v,"writestart progress write writeend".split(" "))},D=function(){if(E||!x){x=s(t)}w.location.href=x;v.readyState=v.DONE;A()},z=function(F){return function(){if(v.readyState!==v.DONE){return F.apply(this,arguments)}}},y={create:true,exclusive:false},C;v.readyState=v.INIT;if(!u){u="download"}if(g){x=s(t);n.href=x;n.download=u;if(j(n)){v.readyState=v.DONE;A();return}}if(h.chrome&&B&&B!==c){C=t.slice||t.webkitSlice;t=C.call(t,0,t.size,c);E=true}if(o&&u!=="download"){u+=".download"}if(B===c||o){w=h}else{w=h.open()}if(!p){D();return}k+=t.size;p(h.TEMPORARY,k,z(function(F){F.root.getDirectory("saved",y,z(function(G){var H=function(){G.getFile(u,y,z(function(I){I.createWriter(z(function(J){J.onwriteend=function(K){w.location.href=I.toURL();b.push(I);v.readyState=v.DONE;q(v,"writeend",K)};J.onerror=function(){var K=J.error;if(K.code!==K.ABORT_ERR){D()}};"writestart progress write abort".split(" ").forEach(function(K){J["on"+K]=v["on"+K]});J.write(t);v.abort=function(){J.abort();v.readyState=v.DONE};v.readyState=v.WRITING}),D)}),D)};G.getFile(u,{create:false},z(function(I){I.remove();H()}),z(function(I){if(I.code===I.NOT_FOUND_ERR){H()}else{D()}}))}),D)}),D)},d=f.prototype,a=function(s,t){return new f(s,t)};d.abort=function(){var s=this;s.readyState=s.DONE;q(s,"abort")};d.readyState=d.INIT=0;d.WRITING=1;d.DONE=2;d.error=d.onwritestart=d.onprogress=d.onwrite=d.onabort=d.onerror=d.onwriteend=null;h.addEventListener("unload",i,false);return a}(self)); \ No newline at end of file diff --git a/js/app.js b/js/app.js index 3db433c..6ddbce2 100644 --- a/js/app.js +++ b/js/app.js @@ -16,8 +16,10 @@ $(document).ready(function(){ function savePlaylist(){ saveState(); - uriContent = "data:application/json," + encodeURIComponent(localStorage.playlist); - window.open(uriContent,"PlayList.json"); +// uriContent = "data:application/json," + encodeURIComponent(localStorage.playlist); +// window.open(uriContent,"PlayList.json"); + var blob = new Blob([localStorage.playlist],{type:"text/json;charset=utf-8"}); + saveAs(blob, "playlist.json"); } @@ -225,6 +227,12 @@ $(document).ready(function(){ self.downloadSong = function(){ window.open("/play?id="+self.id(), self.formattedName()); } + self.downloadURL = ko.computed(function(){ + return "/play?id="+self.id(); + }); + self.downloadName = ko.computed(function(){ + return self.group()+" - "+self.title()+".mp3"; + }); } diff --git a/public/index.html b/public/index.html index cd859c2..65327a3 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,7 @@ + Go5Ears, listen in html5! @@ -48,7 +49,8 @@ Play Play Add - + + Name - Title @@ -69,7 +71,8 @@ Play Play Add - + +
Name - Title