add keyborad shortcuts, connect to lastfm, rearrange vendor files, sync page title for playing song, fix bug for click cancel button on favorite dialog show new song dialog
This commit is contained in:
parent
72e9cfdab2
commit
03f49562c4
13
README.md
13
README.md
@ -1,4 +1,4 @@
|
||||
Listen 1 (Chrome Extension) (最后更新于5月21日)
|
||||
Listen 1 (Chrome Extension) (最后更新于5月27日)
|
||||
==========
|
||||
|
||||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
|
||||
@ -17,8 +17,6 @@ Listen 1 (Chrome Extension) (最后更新于5月21日)
|
||||
|
||||
Chrome安装
|
||||
----
|
||||
不能直接用chrome打开安装,不能直接用chrome打开安装,不能直接用chrome打开安装。重要的话说三遍。
|
||||
|
||||
1. 下载项目的zip文件,在右上方有个 `Download ZIP`, 解压到本地
|
||||
|
||||
2. chrome右上角的设置按钮下找到更多工具,打开`扩展程序`
|
||||
@ -37,6 +35,15 @@ Firefox打包安装
|
||||
|
||||
更新日志
|
||||
-------
|
||||
`2016-05-27`
|
||||
|
||||
* 增加快捷键功能(输入?查看快捷键设置)
|
||||
* 支持同步播放记录到last.fm
|
||||
* 增加搜索loading时的图标(感谢@richdho的提交)
|
||||
* 页面标题增加显示当前播放信息
|
||||
* 修复了在收藏对话框点击取消出现新建歌单的bug
|
||||
* 重新组织代码文件夹结构
|
||||
|
||||
`2016-05-21`
|
||||
|
||||
* 增加歌单分页加载功能(感谢@wild-flame的提交)
|
||||
|
110
css/hotkeys.css
Normal file
110
css/hotkeys.css
Normal file
@ -0,0 +1,110 @@
|
||||
/*!
|
||||
* angular-hotkeys v1.7.0
|
||||
* https://chieffancypants.github.io/angular-hotkeys
|
||||
* Copyright (c) 2016 Wes Cruver
|
||||
* License: MIT
|
||||
*/
|
||||
.cfp-hotkeys-container {
|
||||
display: table !important;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: #333;
|
||||
font-size: 1em;
|
||||
background-color: rgba(255,255,255,0.9);
|
||||
}
|
||||
|
||||
.cfp-hotkeys-container.fade {
|
||||
z-index: -1024;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.15s linear;
|
||||
-moz-transition: opacity 0.15s linear;
|
||||
-o-transition: opacity 0.15s linear;
|
||||
transition: opacity 0.15s linear;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-container.fade.in {
|
||||
z-index: 10002;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-title {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.cfp-hotkeys {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cfp-hotkeys table {
|
||||
margin: auto;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cfp-content {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-keys {
|
||||
padding: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-key {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
border: 1px solid #333;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
box-shadow: inset 0 1px 0 #666, 0 1px 0 #bbb;
|
||||
padding: 5px 9px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-text {
|
||||
padding-left: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-close {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
min-height: 45px;
|
||||
min-width: 45px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cfp-hotkeys-close:hover {
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px) {
|
||||
.cfp-hotkeys {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 750px) {
|
||||
.cfp-hotkeys {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
@ -975,6 +975,15 @@ li {
|
||||
margin-left: 93px;
|
||||
}
|
||||
|
||||
.dialog-connect-lastfm .buttons {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.dialog-connect-lastfm .confirm-button{
|
||||
margin-left: 40px;
|
||||
margin-right: 48px;
|
||||
}
|
||||
|
||||
.source-list {
|
||||
position: absolute;
|
||||
right: -32px;
|
||||
|
174
js/app.js
174
js/app.js
@ -10,7 +10,7 @@
|
||||
return value && JSON.parse(value);
|
||||
}
|
||||
|
||||
var app = angular.module('listenone', ['angularSoundManager', 'ui-notification', 'loWebManager'])
|
||||
var app = angular.module('listenone', ['angularSoundManager', 'ui-notification', 'loWebManager', 'cfp.hotkeys', 'lastfmClient'])
|
||||
.config( [
|
||||
'$compileProvider',
|
||||
function( $compileProvider )
|
||||
@ -31,6 +31,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
app.config(function(hotkeysProvider) {
|
||||
hotkeysProvider.templateTitle = '快捷键列表';
|
||||
hotkeysProvider.cheatSheetDescription = '显示/隐藏快捷键列表';
|
||||
});
|
||||
|
||||
app.config(function(lastfmProvider) {
|
||||
lastfmProvider.setOptions({
|
||||
apiKey: '6790c00a181128dc7c4ce06cd99d17c8',
|
||||
apiSecret: 'd68f1dfc6ff43044c96a79ae7dfb5c27'
|
||||
});
|
||||
});
|
||||
|
||||
app.run(['angularPlayer', 'Notification', 'loWeb', function(angularPlayer, Notification, loWeb) {
|
||||
angularPlayer.setBootstrapTrack(
|
||||
loWeb.bootstrapTrack(
|
||||
@ -63,8 +75,12 @@
|
||||
app.controller('NavigationController', ['$scope', '$http',
|
||||
'$httpParamSerializerJQLike', '$timeout',
|
||||
'angularPlayer', 'Notification', '$rootScope', 'loWeb',
|
||||
'hotkeys', 'lastfm',
|
||||
function($scope, $http, $httpParamSerializerJQLike,
|
||||
$timeout, angularPlayer, Notification, $rootScope, loWeb){
|
||||
$timeout, angularPlayer, Notification, $rootScope,
|
||||
loWeb, hotkeys, lastfm) {
|
||||
|
||||
$rootScope.page_title = "Listen 1";
|
||||
$scope.window_url_stack = [];
|
||||
$scope.current_tag = 2;
|
||||
$scope.is_window_hidden = 1;
|
||||
@ -78,6 +94,8 @@
|
||||
$scope.dialog_title = '';
|
||||
|
||||
$scope.isDoubanLogin = false;
|
||||
|
||||
$scope.lastfm = lastfm;
|
||||
|
||||
$scope.$on('isdoubanlogin:update', function(event, data) {
|
||||
$scope.isDoubanLogin = data;
|
||||
@ -201,6 +219,10 @@
|
||||
$scope.dialog_cover_img_url = data.cover_img_url;
|
||||
$scope.dialog_playlist_title = data.playlist_title;
|
||||
}
|
||||
if (dialog_type == 4) {
|
||||
$scope.dialog_title = '连接到Last.fm';
|
||||
$scope.dialog_type = 4;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.chooseDialogOption = function(option_id) {
|
||||
@ -302,6 +324,10 @@
|
||||
$scope.closeDialog = function() {
|
||||
$scope.is_dialog_hidden = 1;
|
||||
$scope.dialog_type = 0;
|
||||
// update lastfm status if not authorized
|
||||
if (lastfm.isAuthRequested()) {
|
||||
lastfm.updateStatus();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setCurrentList = function(list_id) {
|
||||
@ -430,6 +456,22 @@
|
||||
};
|
||||
reader.readAsText(fileObject);
|
||||
}
|
||||
|
||||
$scope.showShortcuts = function() {
|
||||
hotkeys.toggleCheatSheet();
|
||||
}
|
||||
|
||||
hotkeys.add({
|
||||
combo: 'f',
|
||||
description: '快速搜索',
|
||||
callback: function() {
|
||||
$scope.showTag(3);
|
||||
$timeout(function(){$("#search-input").focus();}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}]);
|
||||
|
||||
app.directive('customOnChange', function() {
|
||||
@ -444,9 +486,12 @@
|
||||
|
||||
app.controller('PlayController', ['$scope', '$timeout','$log',
|
||||
'$anchorScroll', '$location', 'angularPlayer', '$http',
|
||||
'$httpParamSerializerJQLike','$rootScope', 'Notification','loWeb',
|
||||
function($scope, $timeout, $log, $anchorScroll, $location, angularPlayer,
|
||||
$http, $httpParamSerializerJQLike, $rootScope, Notification, loWeb){
|
||||
'$httpParamSerializerJQLike','$rootScope', 'Notification',
|
||||
'loWeb', 'hotkeys', 'lastfm',
|
||||
function($scope, $timeout, $log, $anchorScroll, $location,
|
||||
angularPlayer, $http, $httpParamSerializerJQLike,
|
||||
$rootScope, Notification, loWeb, hotkeys, lastfm){
|
||||
|
||||
$scope.menuHidden = true;
|
||||
$scope.volume = angularPlayer.getVolume();
|
||||
$scope.mute = angularPlayer.getMuteStatus();
|
||||
@ -455,6 +500,9 @@
|
||||
$scope.lyricLineNumber = -1;
|
||||
$scope.lastTrackId = null;
|
||||
|
||||
$scope.scrobbleTrackId = null;
|
||||
$scope.scrobbleTimer = new Timer();
|
||||
|
||||
$scope.loadLocalSettings = function() {
|
||||
var defaultSettings = {"playmode": 0, "nowplaying_track_id": -1, "volume": 90};
|
||||
var localSettings = localStorage.getObject('player-settings');
|
||||
@ -599,6 +647,45 @@
|
||||
localStorage.setObject('current-playing', data);
|
||||
});
|
||||
|
||||
|
||||
$scope.$on('currentTrack:duration', function(event, data) {
|
||||
if (!lastfm.isAuthorized()) {
|
||||
return;
|
||||
}
|
||||
if (data == 0) {
|
||||
return;
|
||||
}
|
||||
if ($scope.scrobbleTrackId == angularPlayer.getCurrentTrack()) {
|
||||
return;
|
||||
}
|
||||
// new song arrives
|
||||
$scope.scrobbleTrackId = angularPlayer.getCurrentTrack();
|
||||
var track = angularPlayer.getTrack($scope.scrobbleTrackId);
|
||||
var startTimestamp = Math.round((new Date()).valueOf() / 1000);
|
||||
$scope.scrobbleTimer.start(function(){
|
||||
lastfm.scrobble(startTimestamp, track.title, track.artist, track.album, function(){});
|
||||
});
|
||||
// according to scrobble rule
|
||||
// http://www.last.fm/api/scrobbling
|
||||
var secondsToScrobble = Math.min(data/1000/2, 60*4);
|
||||
$scope.scrobbleTimer.update(secondsToScrobble);
|
||||
});
|
||||
|
||||
$scope.$on('music:isPlaying', function(event, data) {
|
||||
if (!lastfm.isAuthorized()) {
|
||||
return;
|
||||
}
|
||||
if ($scope.scrobbleTrackId == null) {
|
||||
return;
|
||||
}
|
||||
if (data) {
|
||||
$scope.scrobbleTimer.resume();
|
||||
}
|
||||
else {
|
||||
$scope.scrobbleTimer.pause();
|
||||
};
|
||||
});
|
||||
|
||||
function parseLyric(lyric) {
|
||||
var lines = lyric.split('\n');
|
||||
var result = [];
|
||||
@ -679,6 +766,12 @@
|
||||
$(".lyric").animate({ scrollTop: "0px" }, 500);
|
||||
var url = '/lyric?track_id=' + data;
|
||||
var track = angularPlayer.getTrack(data);
|
||||
|
||||
$rootScope.page_title = '▶ ' + track.title + ' - ' + track.artist;
|
||||
if (lastfm.isAuthorized()) {
|
||||
lastfm.sendNowPlaying(track.title, track.artist, function(){});
|
||||
}
|
||||
|
||||
if (track.lyric_url != null) {
|
||||
url = url + '&lyric_url=' + track.lyric_url;
|
||||
}
|
||||
@ -713,6 +806,77 @@
|
||||
}
|
||||
});
|
||||
|
||||
// define keybind
|
||||
hotkeys.add({
|
||||
combo: 'p',
|
||||
description: '播放/暂停',
|
||||
callback: function() {
|
||||
if(angularPlayer.isPlayingStatus()) {
|
||||
//if playing then pause
|
||||
angularPlayer.pause();
|
||||
} else {
|
||||
//else play if not playing
|
||||
angularPlayer.play();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: '[',
|
||||
description: '上一首',
|
||||
callback: function() {
|
||||
angularPlayer.prevTrack();
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: ']',
|
||||
description: '下一首',
|
||||
callback: function() {
|
||||
angularPlayer.nextTrack();
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: 'm',
|
||||
description: '静音/取消静音',
|
||||
callback: function() {
|
||||
// mute indeed toggle mute status
|
||||
angularPlayer.mute();
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: 'l',
|
||||
description: '打开/关闭播放列表',
|
||||
callback: function() {
|
||||
$scope.togglePlaylist();
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: 's',
|
||||
description: '切换播放模式(顺序/随机)',
|
||||
callback: function() {
|
||||
$scope.changePlaymode();
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: 'u',
|
||||
description: '音量增加',
|
||||
callback: function() {
|
||||
$timeout(function(){angularPlayer.adjustVolume(true);});
|
||||
}
|
||||
});
|
||||
|
||||
hotkeys.add({
|
||||
combo: 'd',
|
||||
description: '音量减少',
|
||||
callback: function() {
|
||||
$timeout(function(){angularPlayer.adjustVolume(false);});
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller('InstantSearchController', ['$scope', '$http', '$timeout', 'angularPlayer', 'loWeb',
|
||||
|
258
js/lastfm.js
Normal file
258
js/lastfm.js
Normal file
@ -0,0 +1,258 @@
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
Storage.prototype.setObject = function(key, value) {
|
||||
this.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
Storage.prototype.getObject = function(key) {
|
||||
var value = this.getItem(key);
|
||||
return value && JSON.parse(value);
|
||||
}
|
||||
|
||||
angular.module('lastfmClient', []).provider('lastfm', function() {
|
||||
this.options = {
|
||||
apiKey: 'unknown',
|
||||
apiSecret: 'unknown'
|
||||
};
|
||||
|
||||
this.setOptions = function(options) {
|
||||
if (!angular.isObject(options)) throw new Error("Options should be an object!");
|
||||
this.options = angular.extend({}, this.options, options);
|
||||
};
|
||||
|
||||
this.apiUrl = 'https://ws.audioscrobbler.com/2.0/';
|
||||
|
||||
this.$get = ['$http', '$window', function($http, $window) {
|
||||
var options = this.options;
|
||||
var apiUrl = this.apiUrl;
|
||||
var status = 0;
|
||||
|
||||
/**
|
||||
* Computes string for signing request
|
||||
*
|
||||
* See http://www.last.fm/api/authspec#8
|
||||
*/
|
||||
function generateSign(params) {
|
||||
var keys = [];
|
||||
var o = '';
|
||||
|
||||
for (var x in params) {
|
||||
if (params.hasOwnProperty(x)) {
|
||||
keys.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
// params has to be ordered alphabetically
|
||||
keys.sort();
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (keys[i] == 'format' || keys[i] == 'callback') {
|
||||
continue;
|
||||
}
|
||||
|
||||
o = o + keys[i] + params[keys[i]];
|
||||
}
|
||||
|
||||
// append secret
|
||||
return MD5(o + options.apiSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates query string from object properties
|
||||
*/
|
||||
function createQueryString(params) {
|
||||
var parts = [];
|
||||
|
||||
for (var x in params) {
|
||||
if (params.hasOwnProperty(x)) {
|
||||
parts.push( x + '=' + encodeURIComponent(params[x]));
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
function getAuth(callback){
|
||||
var url = apiUrl + '?method=auth.gettoken&api_key=' + options.apiKey + '&format=json';
|
||||
$http.get(url).success(function(data) {
|
||||
var token = data.token;
|
||||
localStorage.setObject('lastfmtoken', token);
|
||||
var grant_url = 'http://www.last.fm/api/auth/?api_key=' + options.apiKey + '&token=' + token;
|
||||
$window.open(grant_url, '_blank');
|
||||
status = 1;
|
||||
if (callback != null) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function cancelAuth(){
|
||||
localStorage.removeItem('lastfmsession');
|
||||
localStorage.removeItem('lastfmtoken');
|
||||
updateStatus();
|
||||
};
|
||||
|
||||
function _isAuthRequested() {
|
||||
var token = localStorage.getObject('lastfmtoken');
|
||||
return (token != null);
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
// auth status
|
||||
// 0: never request for auth
|
||||
// 1: request but fail to success
|
||||
// 2: success auth
|
||||
if (!_isAuthRequested()) {
|
||||
status = 0;
|
||||
return;
|
||||
}
|
||||
getUserInfo(function(data){
|
||||
if (data == null) {
|
||||
status = 1;
|
||||
}
|
||||
else {
|
||||
status = 2;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getSession(callback) {
|
||||
// load session info from localStorage
|
||||
var mySession = localStorage.getObject('lastfmsession');
|
||||
if (mySession != null) {
|
||||
return callback(mySession);
|
||||
}
|
||||
// trade session with token
|
||||
var token = localStorage.getObject('lastfmtoken');
|
||||
if (token == null){
|
||||
return callback(null);
|
||||
}
|
||||
// token exists
|
||||
var params = {
|
||||
method: 'auth.getsession',
|
||||
api_key: options.apiKey,
|
||||
token: token
|
||||
};
|
||||
var apiSig = generateSign(params);
|
||||
var url = apiUrl + '?' + createQueryString(params) + '&api_sig=' + apiSig + '&format=json';
|
||||
$http.get(url).success(function(data){
|
||||
mySession = data.session;
|
||||
localStorage.setObject('lastfmsession', mySession);
|
||||
callback(mySession);
|
||||
}).error(function (errResponse, status) {
|
||||
if(status == 403){
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function sendNowPlaying(track, artist, callback) {
|
||||
getSession(function(session){
|
||||
var params = {
|
||||
method: 'track.updatenowplaying',
|
||||
track: track,
|
||||
artist: artist,
|
||||
api_key: options.apiKey,
|
||||
sk: session.key
|
||||
};
|
||||
|
||||
params.api_sig = generateSign(params);
|
||||
|
||||
var url = apiUrl + '?' + createQueryString(params) + '&format=json';
|
||||
$http.post(url).success(function(data){
|
||||
if (callback != null) {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function scrobble(timestamp, track, artist, album, callback) {
|
||||
getSession(function(session){
|
||||
var params = {
|
||||
method: 'track.scrobble',
|
||||
'timestamp[0]': timestamp,
|
||||
'track[0]': track,
|
||||
'artist[0]': artist,
|
||||
api_key: options.apiKey,
|
||||
sk: session.key
|
||||
};
|
||||
|
||||
if ((album !='') && (album != null)) {
|
||||
params['album[0]'] = album;
|
||||
}
|
||||
|
||||
params.api_sig = generateSign(params);
|
||||
|
||||
var url = apiUrl + '?' + createQueryString(params) + '&format=json';
|
||||
$http.post(url).success(function(data){
|
||||
if (callback != null) {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getUserInfo(callback) {
|
||||
getSession(function(session){
|
||||
if (session == null) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
var params = {
|
||||
method: 'user.getinfo',
|
||||
api_key: options.apiKey,
|
||||
sk: session.key
|
||||
};
|
||||
|
||||
params.api_sig = generateSign(params);
|
||||
|
||||
var url = apiUrl + '?' + createQueryString(params) + '&format=json';
|
||||
$http.post(url).success(function(data){
|
||||
if (callback != null) {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function isAuthorized() {
|
||||
return (status == 2);
|
||||
}
|
||||
|
||||
function isAuthRequested() {
|
||||
return !(status == 0);
|
||||
}
|
||||
|
||||
function getStatusText() {
|
||||
if(status == 0) {
|
||||
return '未连接';
|
||||
}
|
||||
if(status == 1) {
|
||||
return '连接中';
|
||||
}
|
||||
if(status == 2) {
|
||||
return '已连接';
|
||||
}
|
||||
}
|
||||
|
||||
var publicApi = {
|
||||
getAuth : getAuth,
|
||||
cancelAuth : cancelAuth,
|
||||
getSession : getSession,
|
||||
sendNowPlaying : sendNowPlaying,
|
||||
scrobble : scrobble,
|
||||
getUserInfo : getUserInfo,
|
||||
getStatusText : getStatusText,
|
||||
updateStatus : updateStatus,
|
||||
isAuthorized : isAuthorized,
|
||||
isAuthRequested : isAuthRequested
|
||||
};
|
||||
|
||||
return publicApi;
|
||||
}];
|
||||
});
|
||||
}) ();
|
||||
|
@ -47,6 +47,9 @@ var qq = (function() {
|
||||
}
|
||||
|
||||
function qq_get_image_url(qqimgid, img_type) {
|
||||
if (qqimgid == null) {
|
||||
return '';
|
||||
}
|
||||
var category = '';
|
||||
if(img_type == 'artist') {
|
||||
category = 'mid_singer_300'
|
||||
@ -54,6 +57,7 @@ var qq = (function() {
|
||||
if(img_type == 'album') {
|
||||
category = 'mid_album_300';
|
||||
}
|
||||
|
||||
var s = [category, qqimgid[qqimgid.length - 2], qqimgid[qqimgid.length - 1], qqimgid].join('/');
|
||||
var url = 'http://imgcache.qq.com/music/photo/' + s + '.jpg';
|
||||
return url;
|
0
js/aes.js → js/vendor/aes.js
vendored
0
js/aes.js → js/vendor/aes.js
vendored
0
js/bigint.js → js/vendor/bigint.js
vendored
0
js/bigint.js → js/vendor/bigint.js
vendored
1661
js/vendor/hotkeys.js
vendored
Normal file
1661
js/vendor/hotkeys.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
207
js/vendor/md5.js
vendored
Executable file
207
js/vendor/md5.js
vendored
Executable file
@ -0,0 +1,207 @@
|
||||
/**
|
||||
*
|
||||
* MD5 (Message-Digest Algorithm)
|
||||
* http://www.webtoolkit.info/
|
||||
* refer to: https://github.com/david-sabata/web-scrobbler/blob/master/vendor/md5.js
|
||||
**/
|
||||
|
||||
function MD5(string) {
|
||||
|
||||
function RotateLeft(lValue, iShiftBits) {
|
||||
return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
|
||||
}
|
||||
|
||||
function AddUnsigned(lX,lY) {
|
||||
var lX4,lY4,lX8,lY8,lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
}
|
||||
|
||||
function F(x,y,z) { return (x & y) | ((~x) & z); }
|
||||
function G(x,y,z) { return (x & z) | (y & (~z)); }
|
||||
function H(x,y,z) { return (x ^ y ^ z); }
|
||||
function I(x,y,z) { return (y ^ (x | (~z))); }
|
||||
|
||||
function FF(a,b,c,d,x,s,ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
|
||||
return AddUnsigned(RotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
function GG(a,b,c,d,x,s,ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
|
||||
return AddUnsigned(RotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
function HH(a,b,c,d,x,s,ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
|
||||
return AddUnsigned(RotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
function II(a,b,c,d,x,s,ac) {
|
||||
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
|
||||
return AddUnsigned(RotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
function ConvertToWordArray(string) {
|
||||
var lWordCount;
|
||||
var lMessageLength = string.length;
|
||||
var lNumberOfWords_temp1=lMessageLength + 8;
|
||||
var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
|
||||
var lWordArray=Array(lNumberOfWords-1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while ( lByteCount < lMessageLength ) {
|
||||
lWordCount = (lByteCount-(lByteCount % 4))/4;
|
||||
lBytePosition = (lByteCount % 4)*8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount-(lByteCount % 4))/4;
|
||||
lBytePosition = (lByteCount % 4)*8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
|
||||
lWordArray[lNumberOfWords-2] = lMessageLength<<3;
|
||||
lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
|
||||
return lWordArray;
|
||||
};
|
||||
|
||||
function WordToHex(lValue) {
|
||||
var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
|
||||
for (lCount = 0;lCount<=3;lCount++) {
|
||||
lByte = (lValue>>>(lCount*8)) & 255;
|
||||
WordToHexValue_temp = "0" + lByte.toString(16);
|
||||
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
|
||||
}
|
||||
return WordToHexValue;
|
||||
};
|
||||
|
||||
function Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g,"\n");
|
||||
var utftext = "";
|
||||
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
|
||||
var c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
}
|
||||
else if((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return utftext;
|
||||
};
|
||||
|
||||
var x=Array();
|
||||
var k,AA,BB,CC,DD,a,b,c,d;
|
||||
var S11=7, S12=12, S13=17, S14=22;
|
||||
var S21=5, S22=9 , S23=14, S24=20;
|
||||
var S31=4, S32=11, S33=16, S34=23;
|
||||
var S41=6, S42=10, S43=15, S44=21;
|
||||
|
||||
string = Utf8Encode(string);
|
||||
|
||||
x = ConvertToWordArray(string);
|
||||
|
||||
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
|
||||
|
||||
for (k=0;k<x.length;k+=16) {
|
||||
AA=a; BB=b; CC=c; DD=d;
|
||||
a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
|
||||
d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
|
||||
c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
|
||||
b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
|
||||
a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
|
||||
d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
|
||||
c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
|
||||
b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
|
||||
a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
|
||||
d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
|
||||
c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
|
||||
b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
|
||||
a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
|
||||
d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
|
||||
c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
|
||||
b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
|
||||
a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
|
||||
d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
|
||||
c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
|
||||
b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
|
||||
a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
|
||||
d=GG(d,a,b,c,x[k+10],S22,0x2441453);
|
||||
c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
|
||||
b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
|
||||
a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
|
||||
d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
|
||||
c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
|
||||
b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
|
||||
a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
|
||||
d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
|
||||
c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
|
||||
b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
|
||||
a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
|
||||
d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
|
||||
c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
|
||||
b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
|
||||
a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
|
||||
d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
|
||||
c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
|
||||
b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
|
||||
a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
|
||||
d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
|
||||
c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
|
||||
b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
|
||||
a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
|
||||
d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
|
||||
c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
|
||||
b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
|
||||
a=II(a,b,c,d,x[k+0], S41,0xF4292244);
|
||||
d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
|
||||
c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
|
||||
b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
|
||||
a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
|
||||
d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
|
||||
c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
|
||||
b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
|
||||
a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
|
||||
d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
|
||||
c=II(c,d,a,b,x[k+6], S43,0xA3014314);
|
||||
b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
|
||||
a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
|
||||
d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
|
||||
c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
|
||||
b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
|
||||
a=AddUnsigned(a,AA);
|
||||
b=AddUnsigned(b,BB);
|
||||
c=AddUnsigned(c,CC);
|
||||
d=AddUnsigned(d,DD);
|
||||
}
|
||||
|
||||
var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
|
||||
|
||||
return temp.toLowerCase();
|
||||
}
|
155
js/vendor/timer.js
vendored
Normal file
155
js/vendor/timer.js
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Timer
|
||||
* https://github.com/david-sabata/web-scrobbler/blob/master/core/background/timer.js
|
||||
*/
|
||||
function Timer() {
|
||||
|
||||
var callback = null,
|
||||
timeoutId = null,
|
||||
target = null, // target seconds
|
||||
pausedOn = null, // marks pause time in seconds
|
||||
startedOn = null, // marks start time in seconds
|
||||
spentPaused = 0, // sum of paused time in seconds
|
||||
hasTriggered = false; // already triggered callback?
|
||||
|
||||
/**
|
||||
* Returns current time in seconds
|
||||
*/
|
||||
function now() {
|
||||
return Math.round((new Date()).valueOf() / 1000);
|
||||
}
|
||||
|
||||
function setTrigger(seconds) {
|
||||
clearTrigger();
|
||||
timeoutId = setTimeout(function() {
|
||||
callback();
|
||||
hasTriggered = true;
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears internal timeout
|
||||
*/
|
||||
function clearTrigger() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set timer and define trigger callback.
|
||||
* Use update function to define time to trigger.
|
||||
*/
|
||||
this.start = function(cb) {
|
||||
this.reset();
|
||||
startedOn = now();
|
||||
callback = cb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pause timer
|
||||
*/
|
||||
this.pause = function() {
|
||||
// only if timer was started and was running
|
||||
if (startedOn !== null && pausedOn === null) {
|
||||
pausedOn = now();
|
||||
clearTrigger();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unpause timer
|
||||
*/
|
||||
this.resume = function() {
|
||||
// only if timer was started and was paused
|
||||
if (startedOn !== null && pausedOn !== null) {
|
||||
spentPaused += now() - pausedOn;
|
||||
pausedOn = null;
|
||||
|
||||
if (!hasTriggered && target !== null) {
|
||||
setTrigger(target - this.getElapsed());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update time for this timer before callback is triggered.
|
||||
* Already elapsed time is not modified and callback
|
||||
* will be triggered immediately if the new time is less than elapsed.
|
||||
*
|
||||
* Pass null to set destination time to 'never' - this prevents the timer from
|
||||
* triggering but still keeps it counting time.
|
||||
*
|
||||
* Intentionally does not check if the callback was already triggered.
|
||||
* This allows to update the timer after it went out once and still
|
||||
* be able to properly trigger the callback for the new timeout.
|
||||
*/
|
||||
this.update = function(seconds) {
|
||||
// only if timer was started
|
||||
if (startedOn !== null) {
|
||||
target = seconds;
|
||||
|
||||
if (seconds !== null) {
|
||||
if (pausedOn === null) {
|
||||
setTrigger(target - this.getElapsed());
|
||||
}
|
||||
} else {
|
||||
clearTrigger();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns seconds passed from the timer was started.
|
||||
* Time spent paused does not count
|
||||
*/
|
||||
this.getElapsed = function() {
|
||||
var val = now() - startedOn - spentPaused;
|
||||
|
||||
if (pausedOn !== null) {
|
||||
val -= (now() - pausedOn);
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if current timer has already triggered its callback
|
||||
*/
|
||||
this.hasTriggered = function() {
|
||||
return hasTriggered;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns remaining (unpaused) seconds or null if no destination time is set
|
||||
*/
|
||||
this.getRemainingSeconds = function() {
|
||||
if (target === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return target - this.getElapsed();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset timer
|
||||
*/
|
||||
this.reset = function() {
|
||||
target = null;
|
||||
startedOn = null;
|
||||
pausedOn = null;
|
||||
spentPaused = 0;
|
||||
callback = null;
|
||||
hasTriggered = false;
|
||||
|
||||
clearTrigger();
|
||||
};
|
||||
|
||||
};
|
||||
|
66
listen1.html
66
listen1.html
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" ng-app="listenone">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@ -8,33 +8,39 @@
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>Listen 1</title>
|
||||
<title ng-bind="page_title">Listen 1</title>
|
||||
|
||||
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="css/angular-ui-notification.css" rel="stylesheet">
|
||||
|
||||
<link href="css/cover.css" rel="stylesheet">
|
||||
<link href="css/player.css" rel="stylesheet">
|
||||
<link href="css/hotkeys.css" rel="stylesheet">
|
||||
|
||||
<script type="text/javascript" src="js/vendor/jquery-1.12.2.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/angular.min.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/angular-soundmanager2.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/angular-ui-notification.js"></script>
|
||||
<!-- require jQuery as dependency -->
|
||||
<script type="text/javascript" src="js/vendor/hotkeys.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/md5.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/aes.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/bigint.js"></script>
|
||||
<script type="text/javascript" src="js/vendor/timer.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/lastfm.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/aes.js"></script>
|
||||
<script type="text/javascript" src="js/bigint.js"></script>
|
||||
<script type="text/javascript" src="js/lowebutil.js"></script>
|
||||
<script type="text/javascript" src="js/xiami.js"></script>
|
||||
<script type="text/javascript" src="js/qq.js"></script>
|
||||
<script type="text/javascript" src="js/netease.js"></script>
|
||||
<script type="text/javascript" src="js/provider/xiami.js"></script>
|
||||
<script type="text/javascript" src="js/provider/qq.js"></script>
|
||||
<script type="text/javascript" src="js/provider/netease.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/myplaylist.js"></script>
|
||||
<script type="text/javascript" src="js/loweb.js"></script>
|
||||
<script type="text/javascript" src="js/app.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body ng-app="listenone" ng-controller="NavigationController">
|
||||
<body ng-controller="NavigationController">
|
||||
|
||||
<!-- dialog -->
|
||||
<div class="shadow" ng-hide="is_dialog_hidden==1"></div>
|
||||
@ -91,12 +97,21 @@
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary confirm-button" ng-click="editMyPlaylist(list_id)">修改歌单</button>
|
||||
<button class="btn btn-default" ng-click="cancelNewDialog()">取消</button>
|
||||
<button class="btn btn-default" ng-click="closeDialog()">取消</button>
|
||||
<div class='dialog-footer'>
|
||||
<button class="btn btn-danger remove-button" ng-click="removeMyPlaylist(list_id)">删除歌单</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="dialog_type==4" class="dialog-connect-lastfm">
|
||||
<p>正在打开Last.fm页面...</p>
|
||||
<p>请在打开的页面点击"Yes, all access", 允许Listen 1访问你的账户。</p>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-primary confirm-button" ng-click="lastfm.updateStatus();closeDialog();">已经完成授权</button>
|
||||
<button class="btn btn-warning warning-button" ng-click="lastfm.getAuth();">遇到问题,再次打开授权页</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -186,7 +201,7 @@
|
||||
<!-- Initialize a new AngularJS app and associate it with a module named "instantSearch"-->
|
||||
<div class="searchbox" ng-controller="InstantSearchController" >
|
||||
<!-- Create a binding between the searchString model and the text field -->
|
||||
<input type="text" class="form-control" ng-model="keywords" placeholder="输入歌曲名,歌手或专辑" ng-model-options="{debounce: 500}" />
|
||||
<input type="text" id="search-input" class="form-control" ng-model="keywords" placeholder="输入歌曲名,歌手或专辑" ng-model-options="{debounce: 500}" />
|
||||
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
@ -219,7 +234,7 @@
|
||||
|
||||
|
||||
<!-- content page: 设置 -->
|
||||
<div class="site-wrapper" ng-show="current_tag==4">
|
||||
<div class="site-wrapper" ng-show="current_tag==4" ng-init="lastfm.updateStatus()">
|
||||
<div class="site-wrapper-innerd" resize>
|
||||
<div class="cover-container">
|
||||
<!-- <div class="settings-title"><span>第三方登录<span></div>
|
||||
@ -241,6 +256,21 @@
|
||||
<input id="my-file-selector" type="file" style="display:none;" ng-model="myuploadfiles" custom-on-change="importMySettings">上传备份文件
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-title"><span>快捷键<span></div>
|
||||
<div class="settings-content">
|
||||
<div>
|
||||
<button class="btn btn-primary confirm-button" ng-click="showShortcuts()">查看快捷键列表</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-title"><span>连接到 Last.fm<span></div>
|
||||
<div class="settings-content">
|
||||
<div>
|
||||
<p> 状态:{{ lastfm.getStatusText() }} </p>
|
||||
<button class="btn btn-primary confirm-button" ng-show="!lastfm.isAuthRequested()" ng-click="lastfm.getAuth(); showDialog(4);">连接到 Last.fm</button>
|
||||
<button class="btn btn-warning confirm-button" ng-show="lastfm.isAuthRequested() && !lastfm.isAuthorized()" ng-click="lastfm.getAuth(); showDialog(4);">重新连接</button>
|
||||
<button class="btn btn-primary confirm-button" ng-show="lastfm.isAuthRequested()" ng-click="lastfm.cancelAuth();">取消连接</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-title"><span>关于<span></div>
|
||||
<div class="settings-content">
|
||||
<p> Listen 1 主页: <a href="http://listen1.github.io/listen1/" target="_blank"> http://listen1.github.io/listen1/ </a> </p>
|
||||
@ -302,9 +332,9 @@
|
||||
<div class="mastfoot" ng-controller="PlayController as playCtrl" ng-init="loadLocalSettings()">
|
||||
<div class="m-playbar" >
|
||||
<div class="btns">
|
||||
<a class="previous" title="上一首" prev-track >上一首</a>
|
||||
<a class="play" ng-class="{pas: isPlaying}" title="播放/暂停" play-pause-toggle>播放/暂停</a>
|
||||
<a class="next" title="下一首" next-track>下一首</a>
|
||||
<a class="previous" title="上一首([)" prev-track >上一首([)</a>
|
||||
<a class="play" ng-class="{pas: isPlaying}" title="播放/暂停(p)" play-pause-toggle>播放/暂停(p)</a>
|
||||
<a class="next" title="下一首(])" next-track>下一首(])</a>
|
||||
</div>
|
||||
|
||||
<div class="head">
|
||||
@ -341,12 +371,12 @@
|
||||
|
||||
<div class="ctrl">
|
||||
<a class="icn icn-add" ng-click="showDialog(0, currentPlaying)" title="添加到歌单">添加到歌单</a>
|
||||
<a class="icn" ng-class="{ 'icn-shuffle': settings.playmode == 1, 'icn-loop': settings.playmode == 0 }" title="{{ settings.playmode | playmode_title }}" ng-click="changePlaymode()"></a>
|
||||
<a class="icn icn-list" title="列表" ng-click="togglePlaylist()"></a>
|
||||
<a class="icn" ng-class="{ 'icn-shuffle': settings.playmode == 1, 'icn-loop': settings.playmode == 0 }" title="{{ settings.playmode | playmode_title }}(s)" ng-click="changePlaymode()"></a>
|
||||
<a class="icn icn-list" title="列表(l)" ng-click="togglePlaylist()"></a>
|
||||
</div>
|
||||
|
||||
<div class="volume-ctrl">
|
||||
<a class="icn" ng-class="{ 'icn-vol-mute': mute, 'icn-vol': mute == false }" title="音量" ng-click="toggleMuteStatus()"></a>
|
||||
<a class="icn" ng-class="{ 'icn-vol-mute': mute, 'icn-vol': mute == false }" title="静音(m) 增大(u) 减少(d)" ng-click="toggleMuteStatus()"></a>
|
||||
<div class="m-pbar volume" >
|
||||
<div class="barbg" id="volumebar" mode="volume" draggable>
|
||||
<div class="cur" ng-style="{width : volume + '%' }">
|
||||
|
Loading…
Reference in New Issue
Block a user