Ext.BLANK_IMAGE_URL = '/html/images/spacer.gif'; // hack to fake IE8 into IE7 mode - let's consider them the same Ext.isIE8 = Ext.isIE && navigator.userAgent.toLowerCase().indexOf("msie 8") > -1; Ext.isIE7 = Ext.isIE7 || Ext.isIE8; var SqueezeJS = { Strings : new Array(), string : function(s){ return this.Strings[s]; }, contributorRoles : new Array('artist', 'composer', 'conductor', 'band', 'albumartist', 'trackartist'), coverFileSuffix : Ext.isIE && !Ext.isIE7 ? 'gif' : 'png', Controller : null }; _init(); // Initialize SqueezeJS.Controller // Look whether there's already a Controller running in a parent frame. // If one does exist, only create a proxy SqueezeJS.Controller hooking to it, // otherwise create new Controller. function _init() { var p = (window == window.parent ? null : window.parent); while (p) { if (p.SqueezeJS && p.SqueezeJS.Controller) { // proxy to parent Controller SqueezeJS.Controller = p.SqueezeJS.Controller; return; } p = p.parent; } // no parent Controller found - create our new, own instance SqueezeJS.Controller = new Ext.util.Observable(); Ext.apply(SqueezeJS.Controller, { observers : null, showBrieflyCache : '', init : function(o){ Ext.apply(this, o); this._initPlayerStatus(); this.player = -1; this.events = this.events || {}; this.addEvents({ 'playerselected' : true, 'playerlistupdate' : true, 'playlistchange' : true, 'buttonupdate' : true, 'playerstatechange': true, 'playtimeupdate' : true, 'showbriefly' : true, 'scannerupdate' : true }) // return immediately if a window doesn't need default observers if (this.noObserver) return; this.addObserver({ name : 'playerstatus', fn : function(self){ if (this.player && this.player != -1) { this.playerRequest({ params: [ "status", "-", 1, "tags:uB" ], // set timer callback: function(){ self.timer.delay(5000); }, success: function(response){ if (response && response.responseText) { response = Ext.util.JSON.decode(response.responseText); // only continue if we got a result and player if (response.result && response.result.player_connected) { this.fireEvent('buttonupdate', response.result); if (response.result.time) this.playerStatus.playtime = parseInt(response.result.time); if (response.result.duration) this.playerStatus.duration = parseInt(response.result.duration); // check whether we need to update our song info & playlist if (this._needUpdate(response.result)){ this.getStatus(); self.timer.delay(5000); } // display information if the player needs a firmware upgrade if (response.player_needs_upgrade && !response.player_is_upgrading) { this.fireEvent('playlistchange'); } } this.showBriefly(response.result); if (this.playerStatus.rescan != response.result.rescan) { this.playerStatus.rescan = response.result.rescan; this.fireEvent('scannerupdate', response.result); var updater = this.observers.get('serverstatus'); if (updater) updater.timer.delay(750); } } }, scope: this }) } else { self.timer.delay(5000); } } }); this.addObserver({ name : 'serverstatus', fn : function(self){ this.request({ params: [ '', [ "serverstatus", 0, 999 ] ], // set timer callback: function(){ self.timer.delay(this.player && !this.playerStatus.rescan ? 30000 : 10000); }, success: function(response){ this.selectPlayer(this._parseCurrentPlayerInfo(response)); response = Ext.util.JSON.decode(response.responseText); if (response && response.result) { this.fireEvent('playerlistupdate', response.result); // if (response.result.rescan || this.playerStatus.rescan) { this.playerStatus.rescan = response.result.rescan; this.fireEvent('scannerupdate', response.result); // } } }, scope: this }) } }); this.addObserver({ name : 'playtimeticker', fn : function(self){ if (this.playerStatus.duration > 0 && this.playerStatus.playtime >= this.playerStatus.duration-1 && this.playerStatus.playtime <= this.playerStatus.duration + 2) this.getStatus(); // force 0 for current time when stopped if (this.playerStatus.mode == 'stop') this.playerStatus.playtime = 0; this.fireEvent('playtimeupdate', { current: this.playerStatus.playtime, duration: this.playerStatus.duration, remaining: this.playerStatus.duration ? parseInt(this.playerStatus.playtime) - this.playerStatus.duration : 0 }) // only increment interim value if playing and not scanning (FWD/RWD) if (this.playerStatus.mode == 'play' && this.playerStatus.rate == 1) this.playerStatus.playtime++; self.timer.delay(950); } }); this.on({ playerselected: { fn: function(playerobj){ if (!(playerobj && playerobj.playerid)) playerobj = { playerid : '' } // remember the selected player if (playerobj.playerid) SqueezeJS.setCookie('SqueezeCenter-player', playerobj.playerid); this.player = playerobj.playerid; // legacy global variables for compatibility playerid = playerobj.playerid; player = encodeURIComponent(playerobj.playerid); this.getStatus(); } } }); }, _initPlayerStatus : function(){ this.playerStatus = { power: null, mode: null, rate: 0, current_title: null, title: null, track: null, index: null, duration: null, playtime: 0, timestamp: null, dontUpdate: false, player: null, rescan: 0, canSeek: false } }, addObserver : function(config){ if (!this.observers) this.observers = new Ext.util.MixedCollection(); config.timer = new Ext.util.DelayedTask(config.fn, this, [ config ]); config.timer.delay(0); this.observers.add(config.name, config); }, updateAll : function(){ if (this.observers){ this.playerStatus.power = null; this.observers.each(function(observer){ observer.timer.delay(0); }); } }, // different kind of requests to the server request : function(config){ // shortcut for .request('http://...') calls if (typeof config == 'string') config = { url: config, method: 'GET' }; if (config.showBriefly) this.showBriefly(config.showBriefly); Ext.Ajax.request({ url: config.url || '/jsonrpc.js', method: config.method ? config.method : 'POST', params: config.url ? null : Ext.util.JSON.encode({ id: 1, method: "slim.request", params: config.params }), timeout: config.timeout || 5000, callback: config.callback, success: config.success, failure: config.failure, scope: config.scope || this }); }, playerRequest : function(config){ config.params = [ this.player, config.params ]; this.request(config); }, // custom playerRequest which requires a controller update // ussually used in player controls playerControl : function(action, dontUpdate){ this.playerRequest({ success: function(response){ this.playerStatus.dontUpdate = dontUpdate; this.getStatus(); if (response && response.responseText) { response = Ext.util.JSON.decode(response.responseText); if (response && response.result && response.result.text) { this.showBriefly(response.result.text); } } }, params: action }); }, urlRequest : function(myUrl, updateStatus, showBriefly) { this.request({ url: myUrl, method: 'GET', callback: function(){ // try updating the player control in this or the parent document if (updateStatus) this.getStatus(); }, showBriefly: showBriefly }); }, playlistRequest : function(param, reload) { this.urlRequest('/status_header.html?' + param + 'ajaxRequest=1&force=1', true); if (reload) this.getStatus(); }, getStatus : function(){ if (this.player) { this.playerRequest({ params: [ "status", "-", 1, "tags:gABbehldiqtyrSuoKLN" ], failure: this._updateStatus, success: this._updateStatus, scope: this }); } }, _updateStatus : function(response) { if (!(response && response.responseText)) return; response = Ext.util.JSON.decode(response.responseText); // only continue if we got a result and player if (!(response.result && response.result.player_connected)) return; response = response.result; this.fireEvent('playerstatechange', response); if (this._needUpdate(response) && Ext.get('playList')) this.fireEvent('playlistchange', response); this.playerStatus = { // if power is undefined, set it to on for http clients power: (response.power == null) || response.power, mode: response.mode, rate: response.rate, current_title: response.current_title, title: response.playlist_tracks > 0 ? response.playlist_loop[0].title : '', track: response.playlist_tracks > 0 ? response.playlist_loop[0].url : '', index: response.playlist_cur_index, duration: parseInt(response.duration) || 0, canSeek: response.can_seek ? true : false, playtime: parseInt(response.time), timestamp: response.playlist_timestamp }; if ((response.power != null) && !response.power) { this.playerStatus.power = 0; } }, _needUpdate : function(result) { // the dontUpdate flag allows us to have the timestamp check ignored for one action // used to prevent updates during d'n'd if (this.playerStatus.dontUpdate) { this.playerStatus.timestamp = result.playlist_timestamp; this.playerStatus.dontUpdate = false; } var needUpdate = (result.power != null && (result.power != this.playerStatus.power)); needUpdate |= (result.mode != null && result.mode != this.playerStatus.mode); // play/paus mode needUpdate |= (result.playlist_timestamp != null && result.playlist_timestamp > this.playerStatus.timestamp); // playlist: time of last change needUpdate |= (result.playlist_cur_index != null && result.playlist_cur_index != this.playerStatus.index); // the currently playing song's position in the playlist needUpdate |= (result.current_title != null && result.current_title != this.playerStatus.current_title); // title (eg. radio stream) needUpdate |= (result.playlist_tracks > 0 && result.playlist_loop[0].title != this.playerStatus.title); // songtitle? needUpdate |= (result.playlist_tracks > 0 && result.playlist_loop[0].url != this.playerStatus.track); // track url needUpdate |= (result.playlist_tracks < 1 && this.playerStatus.track); // there's a player, but no song in the playlist needUpdate |= (result.playlist_tracks > 0 && !this.playerStatus.track); // track in playlist changed needUpdate |= (result.rate != null && result.rate != this.playerStatus.rate); // song is scanning (ffwd/frwd) return needUpdate; }, showBriefly : function(result){ if (typeof result == 'string') result = { showBriefly: [ result ] }; else if (typeof result == 'array') result = { showBriefly: result }; if (result && result.showBriefly) { var text = ''; for (var x = 0; x < result.showBriefly.length; x++) { if (result.showBriefly[x] && result.showBriefly[x].match(/^[\w\s\.;,:()\[\]%]/)) text += result.showBriefly[x] + ' '; } if (text && this.showBrieflyCache != text) { this.showBrieflyCache = text; this.fireEvent('showbriefly', text); } } }, setVolume : function(amount, d){ amount *= 10; if (d) amount = d + amount; this.playerControl(['mixer', 'volume', amount]); }, selectPlayer : function(playerobj){ if (typeof playerobj == 'object') { this._firePlayerSelected(playerobj); } else { this.request({ params: [ '', [ "serverstatus", 0, 999 ] ], success: function(response){ this._firePlayerSelected(this._parseCurrentPlayerInfo(response, playerobj)); }, scope: this }); } }, _firePlayerSelected : function(playerobj){ if (playerobj && playerobj.playerid) { if ((playerobj.playerid != this.player && encodeURIComponent(playerobj.playerid) != this.player) || this.player == -1) { this._initPlayerStatus(); this.fireEvent('playerselected', playerobj); } } else this.player = null; }, _parseCurrentPlayerInfo : function(response, activeplayer) { response = Ext.util.JSON.decode(response.responseText); if (response && response.result) response = response.result; activeplayer = activeplayer || SqueezeJS.getCookie('SqueezeCenter-player'); if (response && response.players_loop) { for (var x=0; x < response.players_loop.length; x++) { if (response.players_loop[x].playerid == activeplayer || encodeURIComponent(response.players_loop[x].playerid) == activeplayer) return response.players_loop[x]; } } }, getPlayer : function(){ return SqueezeJS.Controller.player; } }); } SqueezeJS.getPlayer = SqueezeJS.Controller.getPlayer; Ext.apply(SqueezeJS, { loadStrings : function(strings) { var newStrings = ''; for (var x = 0; x < strings.length; x++) { if (!this.Strings[strings[x].toLowerCase()] > '') { newStrings += strings[x] + ','; } } if (newStrings > '') { newStrings = newStrings.replace(/,$/, ''); this.Controller.request({ params: [ '', [ 'getstring', newStrings ] ], scope: this, success: function(response) { if (response && response.responseText) { response = Ext.util.JSON.decode(response.responseText); for (x in response.result) { this.Strings[x.toLowerCase()] = response.result[x]; } } } }) } }, loadString : function(string) { this.loadStrings([string]); } }); SqueezeJS.SonginfoParser = { tpl : { raw : { title : new Ext.Template('{title}'), album : new Ext.Template('{album}'), contributor : new Ext.Template('{contributor}'), year : new Ext.Template('{year}'), coverart : new Ext.Template('') }, linked : { title : new Ext.Template('{title}'), album : new Ext.Template('{album}'), contributor : new Ext.Template('{contributor}'), year : new Ext.Template('{year}'), coverart : new Ext.Template('') } }, title : function(result, noLink, noRemoteTitle, noTrackNo){ var title; var link; var id; if (result.playlist_tracks > 0) { if (result.current_title) title = result.current_title; else if (result.playlist_loop[0].remote_title && !noRemoteTitle) title = result.playlist_loop[0].remote_title; else if (noTrackNo) title = result.playlist_loop[0].title; else title = (result.playlist_loop[0].disc ? result.playlist_loop[0].disc + '-' : '') + (result.playlist_loop[0].tracknum ? result.playlist_loop[0].tracknum + ". " : '') + result.playlist_loop[0].title; link = result.playlist_loop[0].info_link || 'songinfo.html'; id = result.playlist_loop[0].id; } return this.tpl[(noLink ? 'raw' : 'linked')].title.apply({ link: link, id: id, title: title, player: SqueezeJS.getPlayer() }); }, album : function(result, noLink){ var album = ''; var id = null; if (result.playlist_tracks > 0) { if (result.playlist_loop[0].album) { if (result.playlist_loop[0].album_id) id = result.playlist_loop[0].album_id; album = result.playlist_loop[0].album; } else if (result.playlist_loop[0].remote_title && result.playlist_loop[0].title) album = result.playlist_loop[0].title; } return this.tpl[((noLink || id == null) ? 'raw' : 'linked')].album.apply({ id: id, album: album, player: SqueezeJS.getPlayer() }); }, contributors : function(result, noLink){ var currentContributors = new Array(); var contributorRoles = SqueezeJS.contributorRoles; var contributorList = ''; if (result.playlist_tracks > 0) { for (var x = 0; x < contributorRoles.length; x++) { if (result.playlist_loop[0][contributorRoles[x]]) { var contributors = result.playlist_loop[0][contributorRoles[x]].split(','); var ids = result.playlist_loop[0][contributorRoles[x] + '_ids'] ? result.playlist_loop[0][contributorRoles[x] + '_ids'].split(',') : new Array(); for (var i = 0; i < contributors.length; i++) { // only add to the list if it's not already in there if (!currentContributors[contributors[i]]) { currentContributors[contributors[i]] = 1; if (contributorList) contributorList += ', '; contributorList += this.tpl[((ids[i] && !noLink) ? 'linked' : 'raw')].contributor.apply({ id: (ids[i] || null), contributor: contributors[i], player: SqueezeJS.getPlayer() }); } } } } } return contributorList; }, year : function(result, noLink){ var year; if (result.playlist_tracks > 0 && parseInt(result.playlist_loop[0].year) > 0) year = parseInt(result.playlist_loop[0].year); return this.tpl[(noLink || !year ? 'raw' : 'linked')].year.apply({ year: year, player: SqueezeJS.getPlayer() }); }, bitrate : function(result){ var bitrate = ''; if (result.playlist_tracks > 0 && result.playlist_loop[0].bitrate && result.remote) { bitrate = result.playlist_loop[0].bitrate + (result.playlist_loop[0].type ? ', ' + result.playlist_loop[0].type : '' ); } return bitrate; }, coverart : function(result, noLink, width){ var coverart = '/music/0/cover' + (width ? '_' + width + 'x' + width + '_p.' : '.') + SqueezeJS.coverFileSuffix; var id = 0; var link; if (result.playlist_tracks > 0) { coverart = this.coverartUrl(result, width); if (result.playlist_loop[0].id) { id = result.playlist_loop[0].id; link = result.playlist_loop[0].info_link || 'songinfo.html'; } } return this.tpl[((noLink || id == null) ? 'raw' : 'linked')].coverart.apply({ id: id, src: coverart, width: width ? 'width="' + width + '"' : '', height: width ? 'height="' + width + '"' : '', link: link }); }, coverartUrl : function(result, width){ var coverart = '/music/0/cover' + (width ? '_' + width + 'x' + width + '_p.' : '.') + SqueezeJS.coverFileSuffix; var link; if (result.playlist_tracks > 0) { if (result.playlist_loop[0].artwork_url) { coverart = result.playlist_loop[0].artwork_url; } else { coverart = '/music/' + (result.playlist_loop[0].id || 0) + '/cover' + (width ? '_' + width + 'x' + width + '_p.' : '.') + SqueezeJS.coverFileSuffix; } } return coverart; } }; SqueezeJS.Utils = { replacePlayerIDinUrl : function(url, id){ if (!id) return url; if (typeof url == 'object' && url.search != null) { var args = Ext.urlDecode(url.search.replace(/^\?/, '')); args.player = id; if (args.playerid) args.playerid = id; return url.pathname + '?' + Ext.urlEncode(args) + url.hash; } var rExp = /(=(\w\w(:|%3A)){5}(\w\w))|(=(\d{1,3}\.){3}\d{1,3})/gi; if (url.search(/player=/) && ! rExp.exec(url)) url = url.replace(/player=/ig, ''); return (rExp.exec(url) ? url.replace(rExp, '=' + id) : url + '&player=' + id); }, formatTime : function(seconds){ var remaining; if (seconds < 0) { remaining = true; seconds = Math.abs(seconds); } var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds - hours*3600) / 60); seconds = Math.floor(seconds % 60); var formattedTime = (hours ? hours + ':' : ''); formattedTime += (minutes ? (minutes < 10 && hours ? '0' : '') + minutes : '0') + ':'; formattedTime += (seconds ? (seconds < 10 ? '0' : '') + seconds : '00'); return (remaining ? '-' : '') + formattedTime; }, toggleFavorite : function(el, url, title) { var el = Ext.get(el); if (el) { SqueezeJS.UI.setProgressCursor(250); el.getUpdateManager().showLoadIndicator = false; el.load({ url: 'plugins/Favorites/favcontrol.html?url=' + url + '&title=' + title + '&player=' + player, method: 'GET' }); } } }; // our own cookie manager doesn't prepend 'ys-' to any cookie SqueezeJS.CookieManager = new Ext.state.CookieProvider({ expires : new Date(new Date().getTime() + 1000*60*60*24*365), readCookies : function(){ var cookies = {}; var c = document.cookie + ";"; var re = /\s?(.*?)=(.*?);/g; var matches; while((matches = re.exec(c)) != null){ var name = matches[1]; var value = matches[2]; if(name){ cookies[name] = value; } } return cookies; }, setCookie : function(name, value){ document.cookie = name + "=" + value + ((this.expires == null) ? "" : ("; expires=" + this.expires.toGMTString())) + ((this.path == null) ? "" : ("; path=" + this.path)) + ((this.domain == null) ? "" : ("; domain=" + this.domain)) + ((this.secure == true) ? "; secure" : ""); }, clearCookie : function(name){ document.cookie = name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" + ((this.path == null) ? "" : ("; path=" + this.path)) + ((this.domain == null) ? "" : ("; domain=" + this.domain)) + ((this.secure == true) ? "; secure" : ""); } }); SqueezeJS.setCookie = function(name, value) { SqueezeJS.CookieManager.set(name, value); }; SqueezeJS.getCookie = function(name, failover) { return SqueezeJS.CookieManager.get(name, failover); }; SqueezeJS.clearCookie = function(name, failover) { return SqueezeJS.CookieManager.clear(name); }; // XXX some legacy stuff - should eventually go away // there to be compatible across different skins function ajaxUpdate(url, params, callback) { var el = Ext.get('mainbody'); if (el) { var um = el.getUpdateManager(); if (um) um.loadScripts = true; el.load(url, params + '&ajaxUpdate=1&player=' + player, callback || SqueezeJS.UI.ScrollPanel.init); } } function ajaxRequest(url, params, callback) { if (typeof params == 'object') params = Ext.util.JSON.encode(params); Ext.Ajax.request({ method: 'POST', url: url, params: params, timeout: 5000, disableCaching: true, callback: callback || function(){} }); } // update the status if the Player is available function refreshStatus() { try { SqueezeJS.Controller.getStatus(); } catch(e) { try { parent.SqueezeJS.Controller.getStatus(); } catch(e) {} } } function setCookie(name, value) { SqueezeJS.setCookie(name, value); } function resize(src, width) { if (!width) { // special case for IE (argh) if (document.all) //if IE 4+ width = document.body.clientWidth*0.5; else if (document.getElementById) //else if NS6+ width = window.innerWidth*0.5; width = Math.min(150, parseInt(width)); } if (src.width > width || !src.width) src.width = width; }