├── README.md
├── assets
├── application.coffee
├── application.js
├── jquery-1.11.2.min.js
├── plyr.js
├── q.js
├── style.css
├── style.styl
└── test_cover.jpg
├── index.html
├── index.js
├── package.json
└── utility
└── proxy.js
/README.md:
--------------------------------------------------------------------------------
1 | Douban-FM-Express
2 | ==================
3 |
4 | ###Declaration: Deprecated due to the api change of http://douban.fm
5 |
6 | -----
7 |
8 | DoubanFM Desktop Client Powered by Atom-shell.
9 |
10 | More info: http://cyrilis.com/posts/another-doubanfm-implementation
11 |
12 | [Updated: 2015-03-22]
13 | > Changed from Node-webkit to Atom-shell.
14 |
15 | Features:
16 | -----------
17 | * User login
18 | * Star / Unstar / Trash songs.
19 | * Draggable progress bar.
20 | * Change channels.
21 | * Global short cuts. ( Play/pause, next, Star[via press previous media key])
22 |
23 | Screenshot:
24 | -----------
25 |
26 |
27 |
28 |
29 |
30 |
31 | Dependencies:
32 | -----------
33 | `atom-shell` is needed to run this project in dev mode. or you can download release for Mac at release page.
34 |
35 | ```shell
36 | sudo npm install atom-shell -g
37 | ```
38 |
39 |
40 | Usage(Develop):
41 | -----------
42 | ```shell
43 |
44 | git clone git@github.com:cyrilis/Douban-FM-Express.git
45 |
46 | cd Douban-FM-Express
47 |
48 | atom-shell .
49 |
50 | # Here we go!
51 | ```
52 | Usage(Production):
53 | -----------
54 |
55 | Download Mac binary file from [release page](https://github.com/cyrilis/Douban-FM-Express/releases). run DoubanFM.app.
56 |
57 | Todo:
58 | -----------
59 | * Show lyrics and album and artist info in side panel.
60 | * Desktop Lyrics.
61 |
62 | -----------
63 | Thanks:
64 | -----------
65 | https://github.com/atom/atom-shell
66 |
67 | http://douban.fm
68 |
--------------------------------------------------------------------------------
/assets/application.coffee:
--------------------------------------------------------------------------------
1 | #$ = jQuery = require('./assert/jquery-1.11.2.min.js');
2 | plyr.setup();
3 |
4 | __ = (val)-> "\n--------------------------#{val}----------------------------"
5 |
6 | window.player = document.querySelectorAll(".player")[0].plyr;
7 |
8 | $(".player-volume").on "input", (e)->
9 | min = e.target.min
10 | max = e.target.max
11 | val = e.target.value
12 | $(e.target).css({
13 | "backgroundSize": (val - min) * 100 / (max - min) + "% 100%"
14 | })
15 | .trigger('input')
16 |
17 | API_HOST = "http://www.douban.com"
18 |
19 | #API_HOST = "http://127.0.0.1:8080"
20 |
21 | CHANNELS_URL = API_HOST + "/j/app/radio/channels"
22 | AUTH_URL = API_HOST + "/j/app/login"
23 | PLAYLIST_URL = API_HOST + "/j/app/radio/people"
24 |
25 | app_name = "radio_desktop_win"
26 | version = 100
27 |
28 | Application = class Application
29 |
30 | constructor: ()->
31 | @channel = 0
32 | @user_id = localStorage.getItem("user_id")
33 | @token = localStorage.getItem("token")
34 | @expire = localStorage.getItem("expire")
35 | @email = localStorage.getItem("email")
36 | @user_name = localStorage.getItem("user_name")
37 | @sid = null
38 | @playlist = []
39 | @song = null
40 | player.media.addEventListener 'ended', ()=>
41 | @ended()
42 |
43 | player.media.addEventListener 'canplay', ()=>
44 | console.log "Can Play"
45 | @hideLoading()
46 |
47 | $(".album img").load ()->
48 | $(this).addClass('show')
49 |
50 | $("img").trigger('load')
51 | @initSidebar()
52 |
53 | $("button.button.login").click ()=>
54 | @login()
55 |
56 | $("button.button.logout").click ()=>
57 | @logout()
58 |
59 | $(".icon").bind "webkitAnimationEnd mozAnimationEnd animationEnd", ()->
60 | $(this).removeClass("animated")
61 |
62 | $(".icon").click ()->
63 | $(this).addClass("animated")
64 |
65 | @registerShortCut()
66 |
67 | initSidebar: ()->
68 | self = @
69 | console.log "Fetching channels"
70 | $.ajax(CHANNELS_URL).done (result)->
71 | console.log result
72 | channels = result.channels
73 | if self.user_id and self.token and self.expire
74 | self.getUserInfo().done ()->
75 | $(".channels").removeClass("hide")
76 | $(".sidebar .loading").addClass("hide")
77 | else
78 | $(".channels").removeClass("hide")
79 | $(".sidebar .loading").addClass("hide")
80 | $channels = $(".channels ul")
81 | channels.forEach (channel)->
82 | $("
#{channel.name} ").appendTo($channels).click (e)->
83 | id = $(e.currentTarget).data("id")
84 | self.switchChannel(id)
85 | $(".channels").find("li[data-id='#{self.channel}']").addClass("active")
86 |
87 |
88 | $channels.find("[data-id='#{@channel}']").addClass("active")
89 |
90 | getUserInfo:()->
91 | self = @
92 | $(".sidebar .login-form").addClass("hide")
93 | $(".sidebar .loading").removeClass("hide")
94 | @user_id = localStorage.getItem("user_id")
95 | console.log "Getting User Data"
96 | $.get("https://api.douban.com/v2/user/#{@user_id}?apikey=0776da5f62da51f816648f1e288ef5e8").done (result)->
97 | console.log "Got user info."
98 | $(".user-name").text(result.name)
99 | $(".user-desc").text(result.signature || result.desc)
100 | $(".avatar").css("background-image", "url(#{result.large_avatar})")
101 | $(".sidebar .loading").addClass("hide")
102 | $(".sidebar .user").removeClass("hide")
103 | .fail (err)->
104 | console.log JSON.stringify err
105 | # self.logout()
106 |
107 | login: ()->
108 | email = $("#user-email").val()
109 | password = $("#user-password").val()
110 | $(".sidebar .loading").removeClass("hide")
111 | $(".login-form").addClass("hide")
112 | self = @
113 | defer = new Q.defer()
114 | if not email or not password
115 | @logout()
116 | defer.reject({err: "Both email and password are needed!"})
117 | else
118 | console.log "Logging in..."
119 | $.post( AUTH_URL, { app_name, version, email, password}).done (result)->
120 | console.log result
121 | if result.r
122 | defer.reject(result.err)
123 | self.logout()
124 | else
125 | self.user_id = result.user_id
126 | self.token = result.token
127 | self.expire = result.expire
128 | self.email = result.email
129 | self.user_name = result.user_name
130 |
131 | localStorage.setItem("user_id", self.user_id)
132 | localStorage.setItem("token", self.token)
133 | localStorage.setItem("expire", self.expire)
134 | localStorage.setItem("email", self.email)
135 | localStorage.setItem("user_name", self.user_name)
136 |
137 | console.log "Fetching user...."
138 |
139 | self.getUserInfo()
140 |
141 | defer.resolve(result)
142 | defer.promise
143 |
144 | logout: ()->
145 | @user_id = null
146 | @token = null
147 | @expire = null
148 | @email = null
149 | @user_name = null
150 |
151 | localStorage.removeItem("user_id")
152 | localStorage.removeItem("token")
153 | localStorage.removeItem("expire")
154 | localStorage.removeItem("email")
155 | localStorage.removeItem("user_name")
156 |
157 | $(".login-form").removeClass("hide")
158 | $(".user").addClass('hide')
159 | $(".sidebar .loading").addClass("hide")
160 |
161 | fetchSong: (type = "n", shouldPlay, sid)->
162 | console.log "Fetching"
163 | self = @
164 | defer = new Q.defer()
165 | if type not in ["b","e","n","p","s","r","s","u"]
166 | defer.reject({err: "Type Error!"})
167 | else
168 | channel = @channel
169 | data = {app_name, version, type, channel}
170 | if @user_id and @token and @expire
171 | data.user_id = @user_id
172 | data.token = @token
173 | data.expire = @expire
174 |
175 | unless type is "n" # Don't need sid.
176 | data.sid = @sid
177 |
178 | if type is 'e'
179 | data.sid = sid
180 |
181 | $.get(PLAYLIST_URL, data).done (result)->
182 | console.log "Fetched...."
183 |
184 | if type is 'e'
185 | return false
186 |
187 | if result.r
188 | defer.reject(result.err)
189 | else
190 | if result.song
191 | self.playlist = result.song
192 | else
193 | console.log "------------------"
194 | console.log JSON.stringify result
195 | if shouldPlay
196 | self.play(result.song[0])
197 | defer.resolve(result.song)
198 |
199 | defer.promise
200 |
201 | play: (song)->
202 | console.log "play"
203 | if not song
204 | player.play()
205 | else
206 | @applyHeart(song)
207 | player.source(song.url)
208 | @sid = song.sid
209 | @song = song
210 | player.play()
211 | @setAlbum(song)
212 |
213 | setAlbum: (song)->
214 | pic = song.picture.replace("mpic", 'lpic')
215 | $(".album img").attr('src', pic)
216 | $(".information .title").text(song.title)
217 | $(".information .artist").text(song.artist)
218 | $(".information .album-title").text(song.albumtitle)
219 |
220 | applyHeart: (song)->
221 | star = !!song.like
222 | $(".player").toggleClass("like", star)
223 |
224 | next: (type = "p")->
225 | @showLoading()
226 | self = @
227 | $(".player-progress-seek").val(0)
228 | playedHalf = player.media.duration and player.media.currentTime / player.media.duration > 0.5
229 | console.log player.media.duration
230 | if playedHalf
231 | @sendRecord(@sid)
232 | if @playlist.length
233 | @play @playlist.pop()
234 | else
235 | @fetchSong(type).then ()->
236 | self.next()
237 | , (err)->
238 | console.log err
239 |
240 | heart: ()->
241 | @fetchSong("r")
242 |
243 | unheart: ()->
244 | @fetchSong("u")
245 |
246 | toggleHeart: ()->
247 | self = @
248 | hasLike = $("#player").hasClass("like")
249 | promise = if hasLike then @unheart() else @heart()
250 | sid = @sid
251 | promise.then ()->
252 | if sid is self.sid
253 | $("#player").toggleClass("like",!hasLike)
254 |
255 | sendRecord: (sid)->
256 | console.log sid
257 | @fetchSong('e', null, sid)
258 |
259 | block: ()->
260 | player.pause()
261 | @fetchSong("b", true)
262 |
263 | skip: ()->
264 | @next()
265 |
266 | ended: ()->
267 | @next()
268 |
269 | openLink: ()->
270 | if @song
271 | require('shell').openExternal( "http://music.douban.com#{@song.album}" )
272 | return false
273 |
274 | switchChannel: (id)->
275 | @channel = id
276 | @playlist = []
277 | $(".channels").find("li.active").removeClass("active")
278 | $(".channels").find("li[data-id='#{@channel}']").addClass("active")
279 | player.pause()
280 | @next()
281 |
282 | showLoading: ()->
283 | $(".album .loading").addClass("show")
284 | $(".album .img").removeClass("show")
285 |
286 | hideLoading: ()->
287 | $(".album .loading").removeClass("show")
288 |
289 | playOrPause: ()->
290 | isPlaying = $(".player").hasClass("playing")
291 | if isPlaying
292 | player.pause()
293 | else
294 | player.play()
295 |
296 | registerShortCut: ()->
297 | self = @
298 | globalShortcut = require('remote').require 'global-shortcut'
299 | ret1 = globalShortcut.register("MediaPlayPause", ()-> self.playOrPause())
300 | ret2 = globalShortcut.register("MediaNextTrack", ()-> self.next())
301 | ret3 = globalShortcut.register("MediaPreviousTrack", ()-> self.heart())
302 |
303 | if ret1 and ret2 and ret3
304 | console.log __ "Register Success! "
305 | else
306 | console.log __ "Register Failed....."
307 | console.log ret1, ret1, ret3
308 |
309 |
310 | fm = new Application()
311 | fm.next('n')
312 |
313 | $(".album .info").click ()-> fm.openLink()
314 | $(".album .close").click ()-> window.close()
315 | $(".album .menu").click ()->
316 | $(".wrapper").toggleClass("open");
317 | remote = require('remote');
318 | expand = $(".wrapper").hasClass("open")
319 | width = if expand then 650 else 450
320 | BrowserWindow = remote.require('browser-window');
321 | mainWindow = BrowserWindow.getFocusedWindow();
322 | if expand
323 | if window._delay
324 | window.clearTimeout( window._delay )
325 | mainWindow.setSize(width, 550);
326 | else
327 | window._delay = window.setTimeout ()->
328 | mainWindow.setSize(width, 550)
329 | , 300
330 | $(".controls .icon.play").click ()-> fm.playOrPause()
331 | $(".controls .icon.next").click ()-> fm.next()
332 | $(".controls .icon.heart").click ()-> fm.toggleHeart()
333 | $(".controls .icon.trash").click ()-> fm.block()
334 |
335 |
--------------------------------------------------------------------------------
/assets/application.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.7.1
2 | (function() {
3 | var API_HOST, AUTH_URL, Application, CHANNELS_URL, PLAYLIST_URL, app_name, fm, version, __;
4 |
5 | plyr.setup();
6 |
7 | __ = function(val) {
8 | return "\n--------------------------" + val + "----------------------------";
9 | };
10 |
11 | window.player = document.querySelectorAll(".player")[0].plyr;
12 |
13 | $(".player-volume").on("input", function(e) {
14 | var max, min, val;
15 | min = e.target.min;
16 | max = e.target.max;
17 | val = e.target.value;
18 | return $(e.target).css({
19 | "backgroundSize": (val - min) * 100 / (max - min) + "% 100%"
20 | });
21 | }).trigger('input');
22 |
23 | API_HOST = "http://www.douban.com";
24 |
25 | CHANNELS_URL = API_HOST + "/j/app/radio/channels";
26 |
27 | AUTH_URL = API_HOST + "/j/app/login";
28 |
29 | PLAYLIST_URL = API_HOST + "/j/app/radio/people";
30 |
31 | app_name = "radio_desktop_win";
32 |
33 | version = 100;
34 |
35 | Application = Application = (function() {
36 | function Application() {
37 | this.channel = 0;
38 | this.user_id = localStorage.getItem("user_id");
39 | this.token = localStorage.getItem("token");
40 | this.expire = localStorage.getItem("expire");
41 | this.email = localStorage.getItem("email");
42 | this.user_name = localStorage.getItem("user_name");
43 | this.sid = null;
44 | this.playlist = [];
45 | this.song = null;
46 | player.media.addEventListener('ended', (function(_this) {
47 | return function() {
48 | return _this.ended();
49 | };
50 | })(this));
51 | player.media.addEventListener('canplay', (function(_this) {
52 | return function() {
53 | console.log("Can Play");
54 | return _this.hideLoading();
55 | };
56 | })(this));
57 | $(".album img").load(function() {
58 | return $(this).addClass('show');
59 | });
60 | $("img").trigger('load');
61 | this.initSidebar();
62 | $("button.button.login").click((function(_this) {
63 | return function() {
64 | return _this.login();
65 | };
66 | })(this));
67 | $("button.button.logout").click((function(_this) {
68 | return function() {
69 | return _this.logout();
70 | };
71 | })(this));
72 | $(".icon").bind("webkitAnimationEnd mozAnimationEnd animationEnd", function() {
73 | return $(this).removeClass("animated");
74 | });
75 | $(".icon").click(function() {
76 | return $(this).addClass("animated");
77 | });
78 | this.registerShortCut();
79 | }
80 |
81 | Application.prototype.initSidebar = function() {
82 | var self;
83 | self = this;
84 | console.log("Fetching channels");
85 | return $.ajax(CHANNELS_URL).done(function(result) {
86 | var $channels, channels;
87 | console.log(result);
88 | channels = result.channels;
89 | if (self.user_id && self.token && self.expire) {
90 | self.getUserInfo().done(function() {
91 | $(".channels").removeClass("hide");
92 | return $(".sidebar .loading").addClass("hide");
93 | });
94 | } else {
95 | $(".channels").removeClass("hide");
96 | $(".sidebar .loading").addClass("hide");
97 | }
98 | $channels = $(".channels ul");
99 | channels.forEach(function(channel) {
100 | return $("" + channel.name + " ").appendTo($channels).click(function(e) {
101 | var id;
102 | id = $(e.currentTarget).data("id");
103 | return self.switchChannel(id);
104 | });
105 | });
106 | $(".channels").find("li[data-id='" + self.channel + "']").addClass("active");
107 | return $channels.find("[data-id='" + this.channel + "']").addClass("active");
108 | });
109 | };
110 |
111 | Application.prototype.getUserInfo = function() {
112 | var self;
113 | self = this;
114 | $(".sidebar .login-form").addClass("hide");
115 | $(".sidebar .loading").removeClass("hide");
116 | this.user_id = localStorage.getItem("user_id");
117 | console.log("Getting User Data");
118 | return $.get("https://api.douban.com/v2/user/" + this.user_id + "?apikey=0776da5f62da51f816648f1e288ef5e8").done(function(result) {
119 | console.log("Got user info.");
120 | $(".user-name").text(result.name);
121 | $(".user-desc").text(result.signature || result.desc);
122 | $(".avatar").css("background-image", "url(" + result.large_avatar + ")");
123 | $(".sidebar .loading").addClass("hide");
124 | return $(".sidebar .user").removeClass("hide");
125 | }).fail(function(err) {
126 | return console.log(JSON.stringify(err));
127 | });
128 | };
129 |
130 | Application.prototype.login = function() {
131 | var defer, email, password, self;
132 | email = $("#user-email").val();
133 | password = $("#user-password").val();
134 | $(".sidebar .loading").removeClass("hide");
135 | $(".login-form").addClass("hide");
136 | self = this;
137 | defer = new Q.defer();
138 | if (!email || !password) {
139 | this.logout();
140 | defer.reject({
141 | err: "Both email and password are needed!"
142 | });
143 | } else {
144 | console.log("Logging in...");
145 | $.post(AUTH_URL, {
146 | app_name: app_name,
147 | version: version,
148 | email: email,
149 | password: password
150 | }).done(function(result) {
151 | console.log(result);
152 | if (result.r) {
153 | defer.reject(result.err);
154 | return self.logout();
155 | } else {
156 | self.user_id = result.user_id;
157 | self.token = result.token;
158 | self.expire = result.expire;
159 | self.email = result.email;
160 | self.user_name = result.user_name;
161 | localStorage.setItem("user_id", self.user_id);
162 | localStorage.setItem("token", self.token);
163 | localStorage.setItem("expire", self.expire);
164 | localStorage.setItem("email", self.email);
165 | localStorage.setItem("user_name", self.user_name);
166 | console.log("Fetching user....");
167 | self.getUserInfo();
168 | return defer.resolve(result);
169 | }
170 | });
171 | }
172 | return defer.promise;
173 | };
174 |
175 | Application.prototype.logout = function() {
176 | this.user_id = null;
177 | this.token = null;
178 | this.expire = null;
179 | this.email = null;
180 | this.user_name = null;
181 | localStorage.removeItem("user_id");
182 | localStorage.removeItem("token");
183 | localStorage.removeItem("expire");
184 | localStorage.removeItem("email");
185 | localStorage.removeItem("user_name");
186 | $(".login-form").removeClass("hide");
187 | $(".user").addClass('hide');
188 | return $(".sidebar .loading").addClass("hide");
189 | };
190 |
191 | Application.prototype.fetchSong = function(type, shouldPlay, sid) {
192 | var channel, data, defer, self;
193 | if (type == null) {
194 | type = "n";
195 | }
196 | console.log("Fetching");
197 | self = this;
198 | defer = new Q.defer();
199 | if (type !== "b" && type !== "e" && type !== "n" && type !== "p" && type !== "s" && type !== "r" && type !== "s" && type !== "u") {
200 | defer.reject({
201 | err: "Type Error!"
202 | });
203 | } else {
204 | channel = this.channel;
205 | data = {
206 | app_name: app_name,
207 | version: version,
208 | type: type,
209 | channel: channel
210 | };
211 | if (this.user_id && this.token && this.expire) {
212 | data.user_id = this.user_id;
213 | data.token = this.token;
214 | data.expire = this.expire;
215 | }
216 | if (type !== "n") {
217 | data.sid = this.sid;
218 | }
219 | if (type === 'e') {
220 | data.sid = sid;
221 | }
222 | $.get(PLAYLIST_URL, data).done(function(result) {
223 | console.log("Fetched....");
224 | if (type === 'e') {
225 | return false;
226 | }
227 | if (result.r) {
228 | return defer.reject(result.err);
229 | } else {
230 | if (result.song) {
231 | self.playlist = result.song;
232 | } else {
233 | console.log("------------------");
234 | console.log(JSON.stringify(result));
235 | }
236 | if (shouldPlay) {
237 | self.play(result.song[0]);
238 | }
239 | return defer.resolve(result.song);
240 | }
241 | });
242 | }
243 | return defer.promise;
244 | };
245 |
246 | Application.prototype.play = function(song) {
247 | console.log("play");
248 | if (!song) {
249 | return player.play();
250 | } else {
251 | this.applyHeart(song);
252 | player.source(song.url);
253 | this.sid = song.sid;
254 | this.song = song;
255 | player.play();
256 | return this.setAlbum(song);
257 | }
258 | };
259 |
260 | Application.prototype.setAlbum = function(song) {
261 | var pic;
262 | pic = song.picture.replace("mpic", 'lpic');
263 | $(".album img").attr('src', pic);
264 | $(".information .title").text(song.title);
265 | $(".information .artist").text(song.artist);
266 | return $(".information .album-title").text(song.albumtitle);
267 | };
268 |
269 | Application.prototype.applyHeart = function(song) {
270 | var star;
271 | star = !!song.like;
272 | return $(".player").toggleClass("like", star);
273 | };
274 |
275 | Application.prototype.next = function(type) {
276 | var playedHalf, self;
277 | if (type == null) {
278 | type = "p";
279 | }
280 | this.showLoading();
281 | self = this;
282 | $(".player-progress-seek").val(0);
283 | playedHalf = player.media.duration && player.media.currentTime / player.media.duration > 0.5;
284 | console.log(player.media.duration);
285 | if (playedHalf) {
286 | this.sendRecord(this.sid);
287 | }
288 | if (this.playlist.length) {
289 | return this.play(this.playlist.pop());
290 | } else {
291 | return this.fetchSong(type).then(function() {
292 | return self.next();
293 | }, function(err) {
294 | return console.log(err);
295 | });
296 | }
297 | };
298 |
299 | Application.prototype.heart = function() {
300 | return this.fetchSong("r");
301 | };
302 |
303 | Application.prototype.unheart = function() {
304 | return this.fetchSong("u");
305 | };
306 |
307 | Application.prototype.toggleHeart = function() {
308 | var hasLike, promise, self, sid;
309 | self = this;
310 | hasLike = $("#player").hasClass("like");
311 | promise = hasLike ? this.unheart() : this.heart();
312 | sid = this.sid;
313 | return promise.then(function() {
314 | if (sid === self.sid) {
315 | return $("#player").toggleClass("like", !hasLike);
316 | }
317 | });
318 | };
319 |
320 | Application.prototype.sendRecord = function(sid) {
321 | console.log(sid);
322 | return this.fetchSong('e', null, sid);
323 | };
324 |
325 | Application.prototype.block = function() {
326 | player.pause();
327 | return this.fetchSong("b", true);
328 | };
329 |
330 | Application.prototype.skip = function() {
331 | return this.next();
332 | };
333 |
334 | Application.prototype.ended = function() {
335 | return this.next();
336 | };
337 |
338 | Application.prototype.openLink = function() {
339 | if (this.song) {
340 | require('shell').openExternal("http://music.douban.com" + this.song.album);
341 | }
342 | return false;
343 | };
344 |
345 | Application.prototype.switchChannel = function(id) {
346 | this.channel = id;
347 | this.playlist = [];
348 | $(".channels").find("li.active").removeClass("active");
349 | $(".channels").find("li[data-id='" + this.channel + "']").addClass("active");
350 | player.pause();
351 | return this.next();
352 | };
353 |
354 | Application.prototype.showLoading = function() {
355 | $(".album .loading").addClass("show");
356 | return $(".album .img").removeClass("show");
357 | };
358 |
359 | Application.prototype.hideLoading = function() {
360 | return $(".album .loading").removeClass("show");
361 | };
362 |
363 | Application.prototype.playOrPause = function() {
364 | var isPlaying;
365 | isPlaying = $(".player").hasClass("playing");
366 | if (isPlaying) {
367 | return player.pause();
368 | } else {
369 | return player.play();
370 | }
371 | };
372 |
373 | Application.prototype.registerShortCut = function() {
374 | var globalShortcut, ret1, ret2, ret3, self;
375 | self = this;
376 | globalShortcut = require('remote').require('global-shortcut');
377 | ret1 = globalShortcut.register("MediaPlayPause", function() {
378 | return self.playOrPause();
379 | });
380 | ret2 = globalShortcut.register("MediaNextTrack", function() {
381 | return self.next();
382 | });
383 | ret3 = globalShortcut.register("MediaPreviousTrack", function() {
384 | return self.heart();
385 | });
386 | if (ret1 && ret2 && ret3) {
387 | return console.log(__("Register Success! "));
388 | } else {
389 | console.log(__("Register Failed....."));
390 | return console.log(ret1, ret1, ret3);
391 | }
392 | };
393 |
394 | return Application;
395 |
396 | })();
397 |
398 | fm = new Application();
399 |
400 | fm.next('n');
401 |
402 | $(".album .info").click(function() {
403 | return fm.openLink();
404 | });
405 |
406 | $(".album .close").click(function() {
407 | return window.close();
408 | });
409 |
410 | $(".album .menu").click(function() {
411 | var BrowserWindow, expand, mainWindow, remote, width;
412 | $(".wrapper").toggleClass("open");
413 | remote = require('remote');
414 | expand = $(".wrapper").hasClass("open");
415 | width = expand ? 650 : 450;
416 | BrowserWindow = remote.require('browser-window');
417 | mainWindow = BrowserWindow.getFocusedWindow();
418 | if (expand) {
419 | if (window._delay) {
420 | window.clearTimeout(window._delay);
421 | }
422 | return mainWindow.setSize(width, 550);
423 | } else {
424 | return window._delay = window.setTimeout(function() {
425 | return mainWindow.setSize(width, 550);
426 | }, 300);
427 | }
428 | });
429 |
430 | $(".controls .icon.play").click(function() {
431 | return fm.playOrPause();
432 | });
433 |
434 | $(".controls .icon.next").click(function() {
435 | return fm.next();
436 | });
437 |
438 | $(".controls .icon.heart").click(function() {
439 | return fm.toggleHeart();
440 | });
441 |
442 | $(".controls .icon.trash").click(function() {
443 | return fm.block();
444 | });
445 |
446 | }).call(this);
447 |
--------------------------------------------------------------------------------
/assets/plyr.js:
--------------------------------------------------------------------------------
1 | !function(e){"use strict";function t(e,t){h.debug&&window.console&&console[t?"error":"log"](e)}function n(){var e,t,n,r=navigator.userAgent,s=navigator.appName,a=""+parseFloat(navigator.appVersion),o=parseInt(navigator.appVersion,10);return-1!==navigator.appVersion.indexOf("Windows NT")&&-1!==navigator.appVersion.indexOf("rv:11")?(s="IE",a="11;"):-1!==(t=r.indexOf("MSIE"))?(s="IE",a=r.substring(t+5)):-1!==(t=r.indexOf("Chrome"))?(s="Chrome",a=r.substring(t+7)):-1!==(t=r.indexOf("Safari"))?(s="Safari",a=r.substring(t+7),-1!==(t=r.indexOf("Version"))&&(a=r.substring(t+8))):-1!==(t=r.indexOf("Firefox"))?(s="Firefox",a=r.substring(t+8)):(e=r.lastIndexOf(" ")+1)<(t=r.lastIndexOf("/"))&&(s=r.substring(e,t),a=r.substring(t+1),s.toLowerCase()==s.toUpperCase()&&(s=navigator.appName)),-1!==(n=a.indexOf(";"))&&(a=a.substring(0,n)),-1!==(n=a.indexOf(" "))&&(a=a.substring(0,n)),o=parseInt(""+a,10),isNaN(o)&&(a=""+parseFloat(navigator.appVersion),o=parseInt(navigator.appVersion,10)),[s,o]}function r(e,t){var n=e.media;if("video"==e.type)switch(t){case"video/webm":return!(!n.canPlayType||!n.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/,""));case"video/mp4":return!(!n.canPlayType||!n.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/,""));case"video/ogg":return!(!n.canPlayType||!n.canPlayType('video/ogg; codecs="theora"').replace(/no/,""))}else if("audio"==e.type)switch(t){case"audio/mpeg":return!(!n.canPlayType||!n.canPlayType("audio/mpeg;").replace(/no/,""));case"audio/ogg":return!(!n.canPlayType||!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/,""));case"audio/wav":return!(!n.canPlayType||!n.canPlayType('audio/wav; codecs="1"').replace(/no/,""))}return!1}function s(e,t,n){return e.replace(new RegExp(t.replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1"),"g"),n)}function a(e,t){e.length||(e=[e]);for(var n=e.length-1;n>=0;n--){var r=n>0?t.cloneNode(!0):t,s=e[n],a=s.parentNode,o=s.nextSibling;r.appendChild(s),o?a.insertBefore(r,o):a.appendChild(r)}}function o(e){e.parentNode.removeChild(e)}function i(e,t){e.insertBefore(t,e.firstChild)}function l(e,t){for(var n in t)e.setAttribute(n,t[n])}function c(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var r=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=r+(n?" "+t:"")}}function u(e,t,n,r){t=t.split(" ");for(var s=0;sn;n++){if(e.prefix=t[n],"undefined"!=typeof document[e.prefix+"CancelFullScreen"]){e.supportsFullScreen=!0;break}if("undefined"!=typeof document.msExitFullscreen&&document.msFullscreenEnabled){e.prefix="ms",e.supportsFullScreen=!0;break}}return"webkit"===e.prefix&&navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)&&(e.supportsFullScreen=!1),e.supportsFullScreen&&(e.fullScreenEventName="ms"==e.prefix?"MSFullscreenChange":e.prefix+"fullscreenchange",e.isFullScreen=function(){switch(this.prefix){case"":return document.fullScreen;case"webkit":return document.webkitIsFullScreen;case"ms":return null!==document.msFullscreenElement;default:return document[this.prefix+"FullScreen"]}},e.requestFullScreen=function(e){return""===this.prefix?e.requestFullScreen():e[this.prefix+("ms"==this.prefix?"RequestFullscreen":"RequestFullScreen")]("webkit"===this.prefix?e.ALLOW_KEYBOARD_INPUT:null)},e.cancelFullScreen=function(){return""===this.prefix?document.cancelFullScreen():document[this.prefix+("ms"==this.prefix?"ExitFullscreen":"CancelFullScreen")]()},e.element=function(){return""===this.prefix?document.fullscreenElement:document[this.prefix+"FullscreenElement"]}),e}function b(){var e={supported:function(){try{return"localStorage"in window&&null!==window.localStorage}catch(e){return!1}}()};return e}function v(e){function u(e){if(!Z.usingTextTracks&&"video"===Z.type){for(Z.subcount=0,e="number"==typeof e?e:Z.media.currentTime;k(Z.captions[Z.subcount][0])Z.captions.length-1){Z.subcount=Z.captions.length-1;break}Z.media.currentTime.toFixed(1)>=v(Z.captions[Z.subcount][0])&&Z.media.currentTime.toFixed(1)<=k(Z.captions[Z.subcount][0])?(Z.currentCaption=Z.captions[Z.subcount][1],Z.captionsContainer.innerHTML=Z.currentCaption):Z.captionsContainer.innerHTML=""}}function m(){c(Z.container,h.classes.captions.enabled,!0),h.captions.defaultActive&&(c(Z.container,h.classes.captions.active,!0),Z.buttons.captions.setAttribute("checked","checked"))}function v(e){var t=[];return t=e.split(" --> "),x(t[0])}function k(e){var t=[];return t=e.split(" --> "),x(t[1])}function x(e){if(null===e||void 0===e)return 0;var t,n=[],r=[];return n=e.split(","),r=n[0].split(":"),t=Math.floor(60*r[0]*60)+Math.floor(60*r[1])+Math.floor(r[2])}function w(e){return Z.container.querySelectorAll(e)}function T(e){return w(e)[0]}function S(){try{return window.self!==window.top}catch(e){return!0}}function F(){t("Injecting custom controls.");var e=h.html;if(e=s(e,"{seektime}",h.seekTime),e=s(e,"{id}",Z.random),Z.container.insertAdjacentHTML("beforeend",e),h.tooltips)for(var n=w(h.selectors.labels),r=n.length-1;r>=0;r--){var a=n[r];c(a,h.classes.hidden,!1),c(a,h.classes.tooltip,!0)}}function N(){try{return Z.controls=T(h.selectors.controls),Z.buttons={},Z.buttons.seek=T(h.selectors.buttons.seek),Z.buttons.play=T(h.selectors.buttons.play),Z.buttons.pause=T(h.selectors.buttons.pause),Z.buttons.restart=T(h.selectors.buttons.restart),Z.buttons.rewind=T(h.selectors.buttons.rewind),Z.buttons.forward=T(h.selectors.buttons.forward),Z.buttons.mute=T(h.selectors.buttons.mute),Z.buttons.captions=T(h.selectors.buttons.captions),Z.buttons.fullscreen=T(h.selectors.buttons.fullscreen),Z.progress={},Z.progress.container=T(h.selectors.progress.container),Z.progress.buffer={},Z.progress.buffer.bar=T(h.selectors.progress.buffer),Z.progress.buffer.text=Z.progress.buffer.bar.getElementsByTagName("span")[0],Z.progress.played={},Z.progress.played.bar=T(h.selectors.progress.played),Z.progress.played.text=Z.progress.played.bar.getElementsByTagName("span")[0],Z.volume=T(h.selectors.buttons.volume),Z.duration=T(h.selectors.duration),Z.seekTime=w(h.selectors.seekTime),!0}catch(e){return t("It looks like there's a problem with your controls html. Bailing.",!0),!1}}function E(){var e=Z.buttons.play.innerText||"Play";"undefined"!=typeof h.title&&h.title.length&&(e+=", "+h.title),Z.buttons.play.setAttribute("aria-label",e)}function A(){if(Z.media=Z.container.querySelectorAll("audio, video")[0],!Z.media)return t("No audio or video element found!",!0),!1;if(Z.media.removeAttribute("controls"),Z.type="VIDEO"==Z.media.tagName?"video":"audio",c(Z.container,h.classes[Z.type],!0),c(Z.container,h.classes.stopped,null===Z.media.getAttribute("autoplay")),"video"===Z.type){var e=document.createElement("div");e.setAttribute("class",h.classes.videoWrapper),a(Z.media,e),Z.videoContainer=e}null!==Z.media.getAttribute("autoplay")&&I()}function C(){if("video"===Z.type){Z.videoContainer.insertAdjacentHTML("afterbegin","
"),Z.captionsContainer=T(h.selectors.captions),Z.usingTextTracks=!1,Z.media.textTracks&&(Z.usingTextTracks=!0);for(var e,n="",r=Z.media.childNodes,s=0;s=31||"Safari"===Z.browserName&&Z.browserMajorVersion>=7)&&(t("Detected IE 10/11 or Firefox 31+ or Safari 7+."),Z.usingTextTracks=!1),Z.usingTextTracks){t("TextTracks supported.");for(var i=0;i=7){t("Safari 7+ detected; removing track from DOM."),a=Z.media.getElementsByTagName("track");for(var d=0;dn?n=0:n>Z.media.duration&&(n=Z.media.duration),Z.media.currentTime=n.toFixed(1),t("Seeking to "+Z.media.currentTime+" seconds"),u(n)}function j(){c(Z.container,h.classes.playing,!Z.media.paused),c(Z.container,h.classes.stopped,Z.media.paused)}function q(e){var t=g.supportsFullScreen;e&&e.type===g.fullScreenEventName?h.fullscreen.active=g.isFullScreen():t?(g.isFullScreen()?g.cancelFullScreen():g.requestFullScreen(Z.container),h.fullscreen.active=g.isFullScreen()):(h.fullscreen.active=!h.fullscreen.active,h.fullscreen.active?(p(document,"keyup",H),document.body.style.overflow="hidden"):(d(document,"keyup",H),document.body.style.overflow="")),c(Z.container,h.classes.fullscreen.active,h.fullscreen.active)}function H(e){27===(e.which||e.charCode||e.keyCode)&&h.fullscreen.active&&q()}function B(e){"undefined"==typeof e&&(e=h.storage.enabled&&b().supported?window.localStorage[h.storage.key]||h.volume:h.volume),e>10&&(e=10),Z.volume.value=e,Z.media.volume=parseFloat(e/10),W(),h.storage.enabled&&b().supported&&(window.localStorage.plyr_volume=e)}function R(e){"undefined"==typeof active&&(e=!Z.media.muted,Z.buttons.mute.checked=e),Z.media.muted=e,W()}function D(e){"undefined"==typeof e&&(e=-1===Z.container.className.indexOf(h.classes.captions.active),Z.buttons.captions.checked=e),e?c(Z.container,h.classes.captions.active,!0):c(Z.container,h.classes.captions.active)}function W(){c(Z.container,h.classes.muted,0===Z.media.volume||Z.media.muted)}function _(e){var t="waiting"===e.type;clearTimeout(Z.loadingTimer),Z.loadingTimer=setTimeout(function(){c(Z.container,h.classes.loading,t)},t?250:0)}function U(e){var t=Z.progress.played.bar,n=Z.progress.played.text,r=0;if(e)switch(e.type){case"timeupdate":case"seeking":r=f(Z.media.currentTime,Z.media.duration),"timeupdate"==e.type&&(Z.buttons.seek.value=r);break;case"change":case"input":r=e.target.value;break;case"playing":case"progress":t=Z.progress.buffer.bar,n=Z.progress.buffer.text,r=function(){var e=Z.media.buffered;return e.length?f(e.end(0),Z.media.duration):0}()}t.value=r,n.innerHTML=r}function X(){Z.secs=parseInt(Z.media.currentTime%60),Z.mins=parseInt(Z.media.currentTime/60%60),Z.secs=("0"+Z.secs).slice(-2),Z.mins=("0"+Z.mins).slice(-2),Z.duration.innerHTML=Z.mins+":"+Z.secs}function J(e){X(),U(e)}function $(){for(var e=Z.media.querySelectorAll("source"),t=e.length-1;t>=0;t--)o(e[t]);Z.media.removeAttribute("src")}function z(e){if(e.src){var t=document.createElement("source");l(t,e),i(Z.media,t)}}function K(e){if(V(),L(),j(),$(),"string"==typeof e)Z.media.setAttribute("src",e);else if(e.constructor===Array)for(var t in e)z(e[t]);J(),Z.media.load(),null!==Z.media.getAttribute("autoplay")&&I()}function Y(e){"video"===Z.type&&Z.media.setAttribute("poster",e)}function G(){p(Z.buttons.play,"click",function(){I(),setTimeout(function(){Z.buttons.pause.focus()},100)}),p(Z.buttons.pause,"click",function(){V(),setTimeout(function(){Z.buttons.play.focus()},100)}),p(Z.buttons.restart,"click",L),p(Z.buttons.rewind,"click",O),p(Z.buttons.forward,"click",P),p(Z.volume,"change input",function(){B(this.value)}),p(Z.buttons.mute,"change",function(){R(this.checked)}),p(Z.buttons.fullscreen,"click",q),p(document,g.fullScreenEventName,q),"video"===Z.type&&h.click&&p(Z.videoContainer,"click",function(){Z.media.paused?I():Z.media.ended?(L(),I()):V()}),p(Z.media,"timeupdate seeking",J),p(Z.media,"timeupdate",u),p(Z.buttons.seek,"change input",L),p(Z.buttons.captions,"click",function(){D(this.checked)}),p(Z.media,"ended",function(){"video"===Z.type&&(Z.captionsContainer.innerHTML=""),j()}),p(Z.media,"progress",U),p(Z.media,"playing",U),p(Z.media,"volumechange",W),p(Z.media,"play pause",j),p(Z.media,"waiting canplay seeked",_)}function Q(){return g=y(),Z.browserInfo=n(),Z.browserName=Z.browserInfo[0],Z.browserMajorVersion=Z.browserInfo[1],t(Z.browserName+" "+Z.browserMajorVersion),"IE"!==Z.browserName||8!==Z.browserMajorVersion&&9!==Z.browserMajorVersion?(A(),Z.random=Math.floor(1e4*Math.random()),F(),N()?(E(),C(),B(),M(),G(),void 0):!1):(t("Browser not suppported.",!0),!1)}var Z=this;return Z.container=e,Q(),{media:Z.media,play:I,pause:V,restart:L,rewind:O,forward:P,seek:L,setVolume:B,toggleMute:R,toggleCaptions:D,source:K,poster:Y,support:function(e){return r(Z,e)}}}var g,h,k={enabled:!0,debug:!1,seekTime:10,volume:5,click:!0,tooltips:!1,selectors:{container:".player",controls:".player-controls",labels:"[data-player] .sr-only, label .sr-only",buttons:{seek:"[data-player='seek']",play:"[data-player='play']",pause:"[data-player='pause']",restart:"[data-player='restart']",rewind:"[data-player='rewind']",forward:"[data-player='fast-forward']",mute:"[data-player='mute']",volume:"[data-player='volume']",captions:"[data-player='captions']",fullscreen:"[data-player='fullscreen']"},progress:{container:".player-progress",buffer:".player-progress-buffer",played:".player-progress-played"},captions:".player-captions",duration:".player-duration"},classes:{video:"player-video",videoWrapper:"player-video-wrapper",audio:"player-audio",stopped:"stopped",playing:"playing",muted:"muted",loading:"loading",tooltip:"player-tooltip",hidden:"sr-only",captions:{enabled:"captions-enabled",active:"captions-active"},fullscreen:{enabled:"fullscreen-enabled",active:"fullscreen-active"}},captions:{defaultActive:!1},fullscreen:{enabled:!0,fallback:!0},storage:{enabled:!0,key:"plyr_volume"},html:function(){return["","
","
Seek ","
","
","0 % played"," ","
","0 % buffered"," ","
","
",""," ","Restart "," ",""," ","Rewind {seektime} secs "," ",""," ","Play "," ",""," ","Pause "," ",""," ","Forward {seektime} secs "," ","","Time ","00:00 "," "," ","
"," ",""," "," ","Toggle Mute "," ","Volume "," "," ",""," "," ","Toggle Captions "," ",""," "," ","Toggle Fullscreen "," "," ","
"].join("\n")}()};e.setup=function(e){if(h=m(k,e),!h.enabled)return!1;for(var t=document.querySelectorAll(h.selectors.container),n=[],r=t.length-1;r>=0;r--){var s=t[r];"VIDEO"===s.querySelectorAll("audio, video")[0].tagName&&/iPhone/i.test(navigator.userAgent)||("undefined"==typeof s.plyr&&(s.plyr=new v(s)),n.push(s.plyr))}return n}}(this.plyr=this.plyr||{});
--------------------------------------------------------------------------------
/assets/q.js:
--------------------------------------------------------------------------------
1 | // vim:ts=4:sts=4:sw=4:
2 | /*!
3 | *
4 | * Copyright 2009-2012 Kris Kowal under the terms of the MIT
5 | * license found at http://github.com/kriskowal/q/raw/master/LICENSE
6 | *
7 | * With parts by Tyler Close
8 | * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
9 | * at http://www.opensource.org/licenses/mit-license.html
10 | * Forked at ref_send.js version: 2009-05-11
11 | *
12 | * With parts by Mark Miller
13 | * Copyright (C) 2011 Google Inc.
14 | *
15 | * Licensed under the Apache License, Version 2.0 (the "License");
16 | * you may not use this file except in compliance with the License.
17 | * You may obtain a copy of the License at
18 | *
19 | * http://www.apache.org/licenses/LICENSE-2.0
20 | *
21 | * Unless required by applicable law or agreed to in writing, software
22 | * distributed under the License is distributed on an "AS IS" BASIS,
23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | * See the License for the specific language governing permissions and
25 | * limitations under the License.
26 | *
27 | */
28 |
29 | (function (definition) {
30 | "use strict";
31 |
32 | // This file will function properly as a
7 |
8 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | list
24 |
25 |
26 |
27 | grid2
28 |
29 |
30 |
31 | home
32 |
33 |
34 |
35 | image
36 |
37 |
38 |
39 |
40 |
41 | images
42 |
43 |
44 |
45 |
46 |
47 | headphones
48 |
49 |
50 |
51 |
52 |
53 | music
54 |
55 |
56 |
57 | file-text
58 |
59 |
60 |
61 | pushpin
62 |
63 |
64 |
65 | user
66 |
67 |
68 |
69 | spinner2
70 |
71 |
72 |
73 | spinner3
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | spinner10
89 |
90 |
91 |
92 | equalizer
93 |
94 |
95 |
96 | equalizer2
97 |
98 |
99 |
100 | bug
101 |
102 |
103 |
104 | rocket
105 |
106 |
107 |
108 | bin
109 |
110 |
111 |
112 |
113 | bin2
114 |
115 |
116 |
120 |
121 | link
122 |
123 |
124 |
125 |
126 | star-full
127 |
128 |
129 |
130 | heart
131 |
132 |
133 |
134 | heart-broken
135 |
136 |
137 |
138 | info
139 |
140 |
141 |
142 |
143 |
144 | cross
145 |
146 |
147 |
148 | checkmark
149 |
150 |
151 |
152 | play3
153 |
154 |
155 |
156 | pause2
157 |
158 |
159 |
160 | stop2
161 |
162 |
163 |
164 | backward2
165 |
166 |
167 |
168 | forward3
169 |
170 |
171 |
172 | first
173 |
174 |
175 |
176 | last
177 |
178 |
179 |
180 | volume-high
181 |
182 |
183 |
184 |
185 | volume-medium
186 |
187 |
188 |
189 |
190 | volume-low
191 |
192 |
193 |
194 |
195 | volume-mute
196 |
197 |
198 |
199 | volume-mute2
200 |
201 |
202 |
203 |
204 | checkbox-checked
205 |
206 |
207 |
208 | checkbox-unchecked
209 |
210 |
211 |
212 | radio-checked
213 |
214 |
215 |
216 | radio-checked2
217 |
218 |
219 |
223 |
224 | github2
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
247 |
248 |
249 |
250 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |