├── 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 | DoubanFM screenshot-1 26 | DoubanFM screenshot-1 27 | 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["
    ","
    ","","","","0% played","","","0% buffered","","
    ","","","","","","","","Time","00:00","","","","","","","","","","","","
    "].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 | 117 | menu 118 | 119 | 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 | 220 | twitter 221 | 222 | 223 | 224 | github2 225 | 226 | 227 | 228 | 229 | 230 | 231 |
    232 | 233 |
    234 | 235 |
    236 | 237 |
    238 |
    239 |
    240 | 241 |
    242 |
    243 |
    Loading...
    244 |
    245 |
    246 |
    247 | 248 |
    249 | 250 |
    251 | 254 |
    255 | 256 |
    257 |
    258 | 259 |
    260 |
    261 | 262 | 263 |
    264 |
    265 | 266 | 267 |
    268 | 271 |
    272 |
    273 | 274 | 296 |
    297 | 298 | 299 | 300 | 301 | 302 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Cyril on 15/3/9. 3 | */ 4 | 5 | var app = require("app"); 6 | var BrowserWindow = require('browser-window'); 7 | var Menu = require("menu"); 8 | 9 | require("crash-reporter").start(); 10 | 11 | var mainWindow = null; 12 | 13 | app.on("window-all-closed", function(){ 14 | 15 | app.quit(); 16 | 17 | }); 18 | 19 | 20 | var template = [ 21 | { 22 | label: 'DoubanFM', 23 | submenu: [ 24 | { 25 | label: 'About DoubanFM', 26 | selector: 'orderFrontStandardAboutPanel:' 27 | }, 28 | { 29 | type: 'separator' 30 | }, 31 | { 32 | label: 'Services', 33 | submenu: [] 34 | }, 35 | { 36 | type: 'separator' 37 | }, 38 | { 39 | label: 'Hide DoubanFM', 40 | accelerator: 'Command+H', 41 | selector: 'hide:' 42 | }, 43 | { 44 | label: 'Hide Others', 45 | accelerator: 'Command+Shift+H', 46 | selector: 'hideOtherApplications:' 47 | }, 48 | { 49 | label: 'Show All', 50 | selector: 'unhideAllApplications:' 51 | }, 52 | { 53 | type: 'separator' 54 | }, 55 | { 56 | label: 'Quit', 57 | accelerator: 'Command+Q', 58 | click: function() { app.quit(); } 59 | } 60 | ] 61 | }, 62 | { 63 | label: 'Edit', 64 | submenu: [ 65 | { 66 | label: 'Undo', 67 | accelerator: 'Command+Z', 68 | selector: 'undo:' 69 | }, 70 | { 71 | label: 'Redo', 72 | accelerator: 'Shift+Command+Z', 73 | selector: 'redo:' 74 | }, 75 | { 76 | type: 'separator' 77 | }, 78 | { 79 | label: 'Cut', 80 | accelerator: 'Command+X', 81 | selector: 'cut:' 82 | }, 83 | { 84 | label: 'Copy', 85 | accelerator: 'Command+C', 86 | selector: 'copy:' 87 | }, 88 | { 89 | label: 'Paste', 90 | accelerator: 'Command+V', 91 | selector: 'paste:' 92 | }, 93 | { 94 | label: 'Select All', 95 | accelerator: 'Command+A', 96 | selector: 'selectAll:' 97 | } 98 | ] 99 | }, 100 | { 101 | label: 'Window', 102 | submenu: [ 103 | { 104 | label: 'Minimize', 105 | accelerator: 'Command+M', 106 | selector: 'performMiniaturize:' 107 | }, 108 | { 109 | label: 'Close', 110 | accelerator: 'Command+W', 111 | selector: 'performClose:' 112 | }, 113 | { 114 | type: 'separator' 115 | }, 116 | { 117 | label: 'Bring All to Front', 118 | selector: 'arrangeInFront:' 119 | } 120 | ] 121 | } 122 | ]; 123 | 124 | 125 | 126 | 127 | app.on('ready', function() { 128 | 129 | menu = Menu.buildFromTemplate(template); 130 | 131 | Menu.setApplicationMenu(menu); 132 | 133 | 134 | mainWindow = new BrowserWindow({width: 450, height: 550, frame: false, transparent: true}); 135 | 136 | mainWindow.loadUrl('file://' + __dirname + '/index.html'); 137 | 138 | mainWindow.on('closed', function() { 139 | mainWindow = null; 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DoubanFM", 3 | "version": "0.0.0", 4 | "description": "Douban.FM Client powered by Javascript", 5 | "main": "index", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/cyrilis/Douban-FM-Express.git" 12 | }, 13 | "keywords": [ 14 | "node-webkit", 15 | "atom-shell", 16 | "Douban.FM", 17 | "douban-fm", 18 | "DoubanFM", 19 | "doubanfm", 20 | "atom", 21 | "nw" 22 | ], 23 | "author": "Cyril Hou (http://cyrilis.com/)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/cyrilis/Douban-FM-Express/issues" 27 | }, 28 | "homepage": "https://github.com/cyrilis/Douban-FM-Express" 29 | } 30 | -------------------------------------------------------------------------------- /utility/proxy.js: -------------------------------------------------------------------------------- 1 | var server = require('http'); 2 | var remoteServer = process.argv.indexOf('-d')>0 ? process.argv[process.argv.indexOf('-d')+1]:"www.douban.com"; 3 | var listenPort = process.argv.indexOf('-p')>0 ? +process.argv[process.argv.indexOf('-p')+1]: process.env.PORT || 4000; 4 | var http = require('http'); 5 | server.createServer(function(q,s){ 6 | // console.dir(q); 7 | var headers = q.headers; 8 | headers.host = remoteServer; 9 | q.method === "GET" ? delete headers['Content-length']: console.log('Content-length:',headers['Content-lenght']); 10 | var options = { 11 | hostname: remoteServer, 12 | host: remoteServer, 13 | path: q.url, 14 | port: 80, 15 | method: q.method, 16 | headers: headers 17 | }; 18 | 19 | // console.log(options); 20 | var request = http.request(options, function(res){ 21 | console.log(options.hostname+options.path); 22 | res.setEncoding('binary'); 23 | s.setHeader("Access-Control-Allow-Origin", "*"); 24 | s.setHeader("Access-Control-Allow-Headers", "X-Requested-With"); 25 | s.writeHead(res.statusCode,res.headers); 26 | 27 | res.on('data', function (chunk) { 28 | s.write(chunk,'binary'); 29 | }); 30 | 31 | res.on('end', function(){ 32 | s.end(); 33 | request.end(); 34 | }); 35 | res.on('error', function ( e ){ 36 | console.log( 'Error with client ', e ); 37 | request.end(); 38 | console.log('DATA-ERROR'); 39 | }); 40 | }); 41 | 42 | request.on('error', function(e) { 43 | console.log(e); 44 | s.end('

    ERROR WHILE REQUEST REMOTE URL... =_=

    '); 45 | console.log('REQUEST-ERROR'); 46 | }); 47 | 48 | if(q.method==='GET'){ 49 | request.write('\n'); 50 | } 51 | var request_data = ""; 52 | q.on('data', function ( chunk ){ 53 | console.log( 'Write to server ', chunk.length ); 54 | console.log( chunk.toString( 'utf8' ) ); 55 | request_data = request_data + chunk; 56 | request.write( chunk, 'binary' ); 57 | } ); 58 | 59 | q.on( 'end', function(){ 60 | console.log( 'End chunk write to server' ); 61 | } ); 62 | 63 | q.on( 'error', function ( e ){ 64 | console.log( 'Problem with request ', e ); 65 | } ); 66 | 67 | }).listen(listenPort); 68 | console.log( 'Proxy started on port ' + listenPort +", API domain is set to: "+ remoteServer ); --------------------------------------------------------------------------------