douban.fm 是我常用的網絡音樂電台,但因為是web應用,無法使用全局熱鍵控制,官方又沒有提供linux桌面版本,故打算自己開發一個linux doufan.fm播放器。
要實現的功能:
- 全局熱鍵控制
- 開始播放時桌面通知
- 登錄、選頻道
想要實現並不一定能實現,但我會花時間來慢慢攻破困難,目前為止已經簡單的實現了基本功能。
douban.fm 播放器的簡單實現
既然是簡單實現,那么在保證功能的前提下應該盡量保持實現的簡單,以便后續完善。在開發工具上我選擇python,以便快速交付和測試我的想法。
數據來源
如何獲取要播放的音樂及相關信息是首要解決的問題,這就必須要研究douban.fm的工作流程。其實我們可以思考一下,如果要我們實現一個網絡電台,該怎么實現?不可能把所有的播放列表都放在客戶端,是了,一個很好的方案是先由客戶端發送請求播放列表,服務端返回播放列表,客戶端再根據播放列表中的url請求文件以播放。
當然,具體情況還是要實際分析過才可得知,我們可以用網頁調試工具(firebug或chrome的開發者工具),甚至抓包軟件,分析douban.fm的數據通信。
刷新后,可以看到douban.fm發出的所有http請求,接下來,我們要有目的的尋找攜帶播放列表的http響應:
從url可以明顯的看到playlist字樣,展開該請求響應,可以看出響應數據的格式是json,繼續展開,終於找到了我們想要的數據。
接着,我們要研究該url的參數,以便定制我們的播放器。在新標簽打開該url:
經過多次嘗試,大概可以確定這幾個參數的行為:
- type:字面意義是類型,具體作用不明,由douban.fm發出的所有請求都使用了type=n,缺少該參數將無法正常獲取播放列表;
- sid:作用不明,可省,猜測與用戶相關;
- pt:作用不明,可省,猜測與音樂類型相關;
- chanel:頻道ID,經過測試發現,0:公共/私人(指定sid時)兆赫、1:華語、-3:紅心兆赫;
- from:客戶端來源,可省;
- r:從值來看,應該是random的縮寫,可省;
所以,一條最簡單的獲取播放列表的url是:http://douban.fm/j/mine/playlist?type=n&channel=0
播放音樂
有了音樂文件,怎么用python播放呢?我們沒有必要先把mp3文件下載到,又自己實現一個播放器。即使利用已有的模塊或庫都顯得過於麻煩。linux如此多基於命令行的流媒體播放器,為什么不直接拿來用?比如我經常使用的mplayer。
使用mplayer:mplayer http://mr3.douban.com/201212101049/fbc67748d2c7791b86653220b2ccbd08/view/song/small/p1472407.mp3,一條命令就可以播放在線的音樂。然后,用python的subprocess模塊調用即可。
至此已經可以寫出一個最簡單的douban.fm播放器:
httpConnection = httplib.HTTPConnection('douban.fm') httpConnection.request('GET', '/j/mine/playlist?type=n&channel=0') song = json.loads(httpConnection.getresponse().read())['song'] subprocess.call(['mplayer', song[0]['url']])
激動人心的是,這一功能的實現只用了4行代碼。
Ubuntu桌面通知
統一的桌面通知是很有意義的(想想windows下混亂的彈窗吧),從OS X v10.8 “Mountain Lion"開始,Mac開始統一Notification Center,與此同時各linux桌面也在完善各自的通知系統。
ubuntu unity擁有自己的桌面通知接口,打開~/.bashrc,可以發現有這樣一條命令別名:
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
在shell中輸入命令:alert "test",可以看到如下效果:
alert是別名,核心命令是notify-send,通過man notify-send,我們可以查看其用法。
ubuntu下播放器開啟notify插件后,每次切歌都會發出一個桌面提示:
這是一個很酷的功能,那么如何實現?
仔細想想的話並不難,圖片可以由song[0]['picture']下載,然后構造命令notify-send -i pictrue_path ['title'] ['artist'] ['albumtitle']即可。其中涉及到圖片文件的下載保存,可以用httplib+文件讀寫來完成,實際中我用的是wget。
用python來實現就是:
picture = 'images/' + song[0]['picture'].split('/')[4] # 下載專輯封面 if not os.path.exists(picture): subprocess.call([ 'wget', '-P', 'images', song[0]['picture']]) # 發送桌面通知 subprocess.call([ 'notify-send', '-i', os.getcwd() + '/' + picture, song[0]['title'], song[0]['artist'] + '\n' + song[0]['albumtitle']])
獲取圖片名時小小的hack了一下,song[0]['picture'].split('/')[4] 這句用來獲取url中的圖片名,當然,這並不是一個很好的方法。
小結
最后,結合前面的播放代碼,已經算是基本完成了,雖然沒有實現全局熱鍵控制、登錄,但已經可以使用了。
完整的代碼:
1 #!/usr/bin/python 2 # coding: utf-8 3 4 import httplib 5 import json 6 import os 7 import sys 8 import subprocess 9 import time 10 11 reload(sys) 12 sys.setdefaultencoding('utf-8') 13 14 while True: 15 # 獲取播放列表 16 httpConnection = httplib.HTTPConnection('douban.fm') 17 httpConnection.request('GET', '/j/mine/playlist?type=n&channel=0') 18 song = json.loads(httpConnection.getresponse().read())['song'] 19 20 picture = 'images/' + song[0]['picture'].split('/')[4] 21 22 # 下載專輯封面 23 if not os.path.exists(picture): 24 subprocess.call([ 25 'wget', 26 '-P', 27 'images', 28 song[0]['picture']]) 29 30 # 發送桌面通知 31 subprocess.call([ 32 'notify-send', 33 '-i', 34 os.getcwd() + '/' + picture, 35 song[0]['title'], 36 song[0]['artist'] + '\n' + song[0]['albumtitle']]) 37 38 # 播放 39 player = subprocess.Popen(['mplayer', song[0]['url']]) 40 time.sleep(song[0]['length']) 41 player.kill()