diff options
Diffstat (limited to 'firefox')
-rw-r--r-- | firefox/bootstrap.js | 73 | ||||
-rw-r--r-- | firefox/chrome.manifest | 2 | ||||
-rw-r--r-- | firefox/content/icon.png | bin | 0 -> 7780 bytes | |||
-rw-r--r-- | firefox/content/module.jsm | 569 | ||||
-rw-r--r-- | firefox/content/overlay.js | 8 | ||||
-rw-r--r-- | firefox/content/overlay.xul | 6 | ||||
-rw-r--r-- | firefox/content/vkpc.js | 769 | ||||
-rw-r--r-- | firefox/install.rdf | 22 |
8 files changed, 1449 insertions, 0 deletions
diff --git a/firefox/bootstrap.js b/firefox/bootstrap.js new file mode 100644 index 0000000..4d65d68 --- /dev/null +++ b/firefox/bootstrap.js @@ -0,0 +1,73 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +try { + var console = (Components.utils.import("resource://gre/modules/devtools/Console.jsm", {})).console; +} catch (e) {} + +function log() { + var msgs = [], i, tmp; + for (i = 0; i < arguments.length; i++) { + if (arguments[i] instanceof Error) tmp = [arguments[i], arguments[i].stack]; + else tmp = arguments[i]; + msgs.push(tmp); + } + + msgs.unshift('[VKPC bootstrap.js]'); + try { + console.log.apply(console, msgs); + } catch(e) {} +} + +function startup(data, reason) { + Components.utils.import("chrome://vkpc/content/module.jsm"); + VKPC.startup(); + + var windows = Services.wm.getEnumerator("navigator:browser"); + while (windows.hasMoreElements()) { + var win = windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow); + VKPC.addWindow(win, win.document && win.document.readyState == 'complete'); + } + + Services.wm.addListener(WindowListener); +} +function shutdown(data, reason) { + if (reason == APP_SHUTDOWN) + return; + + Services.wm.removeListener(WindowListener); + + VKPC.shutdown(); + + if (reason == ADDON_DISABLE) { + Services.obs.notifyObservers(null, "startupcache-invalidate", null); + Services.obs.notifyObservers(null, "chrome-flush-caches", null); + } +} +function install(data, reason) {} +function uninstall(data, reason) {} + +function forEachOpenWindow() { + var windows = Services.wm.getEnumerator("navigator:browser"); + while (windows.hasMoreElements()) { + VKPC.addWindow(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow)); + } +} + +var WindowListener = { + onOpenWindow: function(xulWindow) { + var window = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow); + window.addEventListener("load", function onWindowLoad() { + window.removeEventListener("load", onWindowLoad); + + if (window.document.documentElement.getAttribute("windowtype") == "navigator:browser") { + VKPC.addWindow(window, true); + } + }); + }, + onCloseWindow: function(xulWindow) { + VKPC.removeWindow(xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow)); + }, + onWindowTitleChange: function(xulWindow, newTitle) { } +}; diff --git a/firefox/chrome.manifest b/firefox/chrome.manifest new file mode 100644 index 0000000..883ec3d --- /dev/null +++ b/firefox/chrome.manifest @@ -0,0 +1,2 @@ +content vkpc content/ +overlay chrome://browser/content/browser.xul chrome://vkpc/content/overlay.xul diff --git a/firefox/content/icon.png b/firefox/content/icon.png Binary files differnew file mode 100644 index 0000000..6667635 --- /dev/null +++ b/firefox/content/icon.png diff --git a/firefox/content/module.jsm b/firefox/content/module.jsm new file mode 100644 index 0000000..dbadee1 --- /dev/null +++ b/firefox/content/module.jsm @@ -0,0 +1,569 @@ +var EXPORTED_SYMBOLS = ['VKPC']; + +try { + var console = (Components.utils.import("resource://gre/modules/devtools/Console.jsm", {})).console; +} catch (e) {} + +var browser = { + id: 1, + chrome: false, + safari: false, + yandex: false, + firefox: true, + opera: false +}; + +var started = false; + +function createTimeout(callback, interval) { + return new function() { + var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + timer.initWithCallback({ + notify: function() { + callback(); + } + }, interval, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + + this.cancel = function() { + timer.cancel(); + timer = null; + }; + } +} +function createInterval(callback, interval) { + return new function() { + var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + timer.initWithCallback({ + notify: function() { + callback(); + } + }, interval, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + + this.cancel = function() { + timer.cancel(); + timer = null; + }; + } +} +function log() { + return; // comment for debugging + + var msgs = [], i, tmp; + for (i = 0; i < arguments.length; i++) { + if (arguments[i] instanceof Error) tmp = [arguments[i], arguments[i].stack]; + else tmp = arguments[i]; + msgs.push(tmp); + } + + msgs.unshift('[VKPC module.jsm]'); + try { + console.log.apply(console, msgs); + } catch(e) {} +} +function extend(dest, source) { + for (var i in source) { + dest[i] = source[i]; + } + return dest; +} +function createCData(data) { + var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] + .createInstance(Components.interfaces.nsIDOMParser); + var doc = parser.parseFromString('<xml></xml>', "application/xml"); + var cdata = doc.createCDATASection(data); + doc.getElementsByTagName('xml')[0].appendChild(cdata); + return cdata; +} +function remove(element) { + element.parentNode.removeChild(element); +} + +function InjectionResponses(id, count, callback) { + this.id = id; + this.results = []; + this.lsSource = null; + this.maxCount = count; + this.callback = callback || function() {}; + + Injections.register(this.id, this); +} +extend(InjectionResponses.prototype, { + addResponse: function(id, response) { + this.results.push({tab: id, data: response}); + if (!this.lsSource && response && response.lastInstanceId) this.lsSource = response.lastInstanceId; + if (this.results.length == this.maxCount) { + this.callback(); + } + }, + unregister: function() { + Injections.unregister(this.id); + } +}); + +var Injections = { + id: 0, + objs: {}, + getNextId: function() { + if (this.id == Number.MAX_VALUE) { + this.id = -1; + } + return ++this.id; + }, + get: function(id) { + return this.objs[id] || false; + }, + register: function(id, obj) { + this.objs[id] = obj; + }, + unregister: function(id) { + if (this.objs[id] !== undefined) delete this.objs[id]; + } +}; + +var WSC_STATE_NONE = 'NONE', + WSC_STATE_OK = 'OK', + WSC_STATE_CLOSED = 'CLOSED', + WSC_STATE_ERR = 'ERR'; +function WSClient(address, protocol, opts) { + this.state = WSC_STATE_NONE; + this._ws = null; + + this.address = address; + this.protocol = protocol; + + this._onmessage = opts.onmessage; + this._onclose = opts.onclose; + this._onerror = opts.onerror; + this._onopen = opts.onopen; + + this._pingTimer = null; + this._reconnectTimer = null; +} +extend(WSClient.prototype, { + connect: function(callback) { + this.state = WSC_STATE_NONE; + var self = this; + + this._waitForWebSocketAvailable(function(_websocket) { + log('_waitForWebSocketAvailable DONE'); + self._ws = new _websocket(self.address, self.protocol); + + if (!self._ws) { + log('websockets are not supported'); + return; + } + + self._ws.onopen = function() { + self.state = WSC_STATE_OK; + self._setTimers(); + self._onopen && self._onopen.apply(self); + }; + self._ws.onerror = function() { + self._unsetTimers(); + if (self.state != WSC_STATE_ERR) { + self.state = WSC_STATE_ERR; + } + self._onerror && self._onerror.apply(self); + }; + self._ws.onclose = function() { + self._unsetTimers(); + if (self.state != WSC_STATE_ERR) { + self.state = WSC_STATE_ERR; + } + self._onclose && self._onclose.apply(self); + }; + self._ws.onmessage = function(e) { + self._onmessage && self._onmessage.apply(self, [e.data]); + }; + + callback && callback(); + }, 200); + }, + close: function() { + this._unsetTimers(); + if (this._ws) { + this.state = WSC_STATE_CLOSED; + this._ws.close(); + } + }, + reconnect: function() { + var self = this; + if (this.state == WSC_STATE_OK) { + try { + log('[WSClient reconnect] state = '+this.state+', why reconnect?'); + } catch (e) {} + return; + } + if (this._reconnectTimer) { + this._reconnectTimer.cancel(); + } + this._reconnectTimer = createTimeout(function() { + self.connect(); + }, 3000); + }, + send: function(obj) { + obj._browser = browser.id; + var self = this; + this._waitForConnection(function() { + self._ws.send(JSON.stringify(obj)); + }, 200); + }, + _setTimers: function() { + var self = this; + this._unsetTimers(); + this._pingTimer = createInterval(function() { + try { + self._ws.send("PING"); + } catch (e) { + log('[WSClient _pingTimer]', e); + } + }, 30000); + }, + _unsetTimers: function() { + if (this._pingTimer) + this._pingTimer.cancel(); + }, + _waitForConnection: function(callback, interval) { + if (this._ws.readyState === 1) { + callback(); + } else { + var self = this; + var timer = createTimeout(function() { + timer.cancel(); + self._waitForConnection(callback, interval); + }, interval); + } + }, + _waitForWebSocketAvailable: function(callback, interval) { + var win, self = this; + try { + win = Components.classes["@mozilla.org/appshell/appShellService;1"]. + getService(Components.interfaces.nsIAppShellService). + hiddenDOMWindow; + } catch (e) { + var timer = createTimeout(function() { + timer.cancel(); + self._waitForWebSocketAvailable(callback, interval); + }, interval); + } finally { + if (win) { + callback(win.WebSocket || win.MozWebSocket); + } + } + } +}); + +var Documents = { + list: [], + add: function(doc) { + this.cleanup(); + this.list.push(doc); + }, + cleanup: function() { + this.list = this.list.filter(function(t) { + return Object.prototype.toString.call(t) != '[object DeadObject]'; + }); + }, + send: function(json) { + var self = this; + this.cleanup(); + + this.list.forEach(function(doc) { + self.sendToDoc(doc, json); + }); + }, + sendToDoc: function(doc, json) { + var cdata = createCData(JSON.stringify(json)); + doc.getElementById('utils').appendChild(cdata); + + var evt = doc.createEvent("Events"); + evt.initEvent("VKPCBgMessage", true, false); + cdata.dispatchEvent(evt); + }, + getCount: function() { + this.cleanup(); + return this.list.length; + } +}; + +function sendClear() { + wsc.send({command: 'clearPlaylist', data: null}); +} + +function prepareWindow(win) { + function onPageLoaded(e) { + var doc = e.originalTarget, loc = doc.location; + if (!loc.href.match(/^https?:\/\/vk.com\/.*$/)) return; + + doc.addEventListener("VKPCInjectedMessage", function(e) { + var target = e.target, json = JSON.parse(target.data || "{}"), doc = target.ownerDocument; + receiveMessage(json, doc, target); + }, false); + + var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://vkpc/content/vkpc.js", doc); + } + + var appcontent = win.document.getElementById("appcontent"); + if (appcontent) { + appcontent.addEventListener("DOMContentLoaded", onPageLoaded, true); + } +} + +// receive message from tab +function receiveMessage(json, doc, target) { + switch (json.cmd) { + case "register": + Documents.add(doc); + break; + + case "afterInjection": + var id = json.id; + var obj = Injections.get(id); + if (obj) { + obj.addResponse(doc, json.data); + } + break; + + case "to_app": + wsc.send(json.data); + break; + } + + try { + remove(target); + } catch (e) {} +} + +// send message to tabs +function sendMessage(data, tab) { + if (tab) { + Documents.sendToDoc(tab, data); + } else { + Documents.send(data); + } +} + +function inject(command/*, callback*/) { + //log('inject', command); + var injId = Injections.getNextId(); + var data = { + sid: Controller.sid, + command: command + }; + + var okTab_nowPlaying, okTab_playlistFound, okTab_lsSource, okTab_recentlyPlayed, okTab_havePlaylist, + activeTab, lastTab, outdatedTabs = [], tabsWithPlayingMusic = []; + var lsSourceId, appPlaylistFound = false; + + var injResponses, injResults; + + function onDone(step) { + var results = injResponses.results; + // var execCommand = getCode("VKPC.executeCommand('"+command+"', "+Controller.playlistId+")"); + var vkpcCommand = {cmd: 'vkpc', command: command, playlistId: Controller.playlistId}; + + if (command == 'afterInjection') { + // log('[afterInjection onDone] results.length='+results.length); + + for (var i = 0; i < results.length; i++) { + var data = results[i].data, tab = results[i].tab; + + if (data.playlistId != 0 && data.playlistId == Controller.playlistId) { + appPlaylistFound = true; + } + if (data.havePlaylist && data.playlistId != 0 && data.playlistId != Controller.playlistId) { + outdatedTabs.push(tab); + } + if (data.havePlaylist) { + okTab_havePlaylist = tab; + } + if (data.isPlaying) { + okTab_nowPlaying = tab; + } + } + + if (!appPlaylistFound) { + var okTab = okTab_nowPlaying || okTab_havePlaylist; + if (okTab !== undefined) { + sendMessage(vkpcCommand, okTab); + } else { + sendClear(); + } + } + + for (var i = 0; i < outdatedTabs.length; i++) { + sendMessage({cmd: 'vkpc', command: 'clearPlaylist'}, outdatedTabs[i]); + } + } else { + for (var i = 0; i < results.length; i++) { + var data = results[i].data; + if (!lsSourceId && data.lsSourceId) { + lsSourceId = data.lsSourceId; + break; + } + } + + for (var i = 0; i < results.length; i++) { + var data = results[i].data, tab = results[i].tab; + + if (data.playlistId == Controller.playlistId) { + okTab_playlistFound = tab; + } + if (data.havePlayer && (data.isPlaying || typeof data.trackId == 'string')) { + okTab_recentlyPlayed = tab; + } + if (data.isPlaying) { + okTab_nowPlaying = tab; + } + if (lsSourceId == data.tabId) { + okTab_lsSource = tab; + } + + lastTab = tab; + } + + var check = [okTab_nowPlaying, okTab_lsSource, okTab_recentlyPlayed, okTab_recentlyPlayed, okTab_havePlaylist, activeTab, lastTab]; + for (var i = 0; i < check.length; i++) { + if (check[i] !== undefined) { + sendMessage(vkpcCommand, check[i]); + // chrome.tabs.executeScript(check[i], {code: execCommand}); + break; + } + } + } + + injResponses.unregister(); + } + + var count = Documents.getCount(); + //log('vk tabs count: ' + count); + + if (!count) { + log('vk tabs not found'); + sendClear(); + return; + } + + injResponses = new InjectionResponses(injId, Documents.getCount(), onDone); + sendMessage({ + cmd: "afterInjection", + id: injId, + data: data + }); +}; + +var Controller = { + sid: 0, + playlistId: 0, + clear: function() { + this.sid = 0; + this.playlistId = 0; + } +}; + +var VKPC = new function() { + var timer; + log('VKPC()'); + + var windows = []; + this.addWindow = function(win, notWait) { + if (windows.indexOf(win) == -1) { + log('window added', win); + windows.push(win); + + if (!notWait) { + win.addEventListener('load', function load(e) { + win.removeEventListener('load', load, false); + prepareWindow(win); + }, false); + } else { + prepareWindow(win); + } + } + }; + this.removeWindow = function(win) { + var index; + if ((index = windows.indexOf(win)) != -1) { + log('window removed', win); + windows.splice(index, 1); + } + }; + + var self = this; + this.startup = function() { + if (started) { + log('already started, ignoring'); + return; + } + + wsc = new WSClient("wss://vkpc-local.ch1p.com:56130", "signaling-protocol", { + onopen: function() { + Controller.clear(); + this.send({command: 'setBrowser'}); + + if (timer) { + timer.cancel(); + } + timer = createInterval(function() { + inject('afterInjection'); + }, 2000); + }, + onmessage: function(cmd) { + log('[wsc onmessage] cmd:', cmd); + + var json = JSON.parse(cmd); + switch (json.command) { + case 'set_sid': + Controller.sid = json.data; + break; + + case 'set_playlist_id': + Controller.playlistId = json.data; + break; + + case 'vkpc': + inject(json.data); + break; + } + }, + onerror: function() { + if (timer) { + timer.cancel(); + } + this.reconnect(); + }, + onclose: function() { + if (timer) { + timer.cancel(); + } + if (started) { + this.reconnect(); + } + } + }); + wsc.connect(); + + self.wsc = wsc; + started = true; + }; + this.shutdown = function() { + if (!started) { + return; + } + + started = false; + + if (wsc) { + wsc.close(); + wsc = undefined; + } + if (timer) { + timer.cancel(); + timer = undefined; + } + }; + + log('init finish'); +}; diff --git a/firefox/content/overlay.js b/firefox/content/overlay.js new file mode 100644 index 0000000..49bb695 --- /dev/null +++ b/firefox/content/overlay.js @@ -0,0 +1,8 @@ +Components.utils.import("chrome://vkpc/content/module.jsm"); + +VKPC.startup(); + +VKPC.addWindow(window); +window.addEventListener('close', function() { + VKPC.removeWindow(window); +}); diff --git a/firefox/content/overlay.xul b/firefox/content/overlay.xul new file mode 100644 index 0000000..d2ca855 --- /dev/null +++ b/firefox/content/overlay.xul @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE overlay > +<overlay id="vkpc-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://vkpc/content/overlay.js" /> +</overlay> diff --git a/firefox/content/vkpc.js b/firefox/content/vkpc.js new file mode 100644 index 0000000..2ce3129 --- /dev/null +++ b/firefox/content/vkpc.js @@ -0,0 +1,769 @@ +// VKPC for Firefox + +(function() { + +if (!document.addEventListener) { + window.console && console.log("[VKPC] an outdated browser detected, very strange, plz update"); + return; +} + +// variables +var _debug = window.__vkpc_debug || true; +var _isFocused = false; + +(function(window, document) { + var queue = [], done = false, _top = true, root = document.documentElement, eventsAdded = false; + + function init(e) { + if (e.type == 'readystatechange' && document.readyState != 'complete') return; + (e.type == 'load' ? window : document).removeEventListener(e.type, init); + if (!done) { + done = true; + while (queue.length) { + queue.shift().call(window); + } + } + } + function poll() { + try { + root.doScroll('left'); + } catch (e) { + setTimeout(poll, 50); + return; + } + init('poll'); + } + + window.DOMContentLoaded = function(fn) { + if (document.readyState == 'complete' || done) { + fn.call(window); + } else { + queue.push(fn); + + if (!eventsAdded) { + if (document.createEventObject && root.doScroll) { + try { + _top = !window.frameElement; + } catch (e) {} + if (_top) poll(); + } + + document.addEventListener('DOMContentLoaded', init); + document.addEventListener('readystatechange', init); + window.addEventListener('load', init); + eventsAdded = true; + } + } + } +})(window, document); + +function log() { + if (!_debug) + return; + var args = Array.prototype.slice.call(arguments); + args.unshift(window.VKPC ? '[VKPC '+window.VKPC.getSID()+']' : '[VKPC]'); + try { + window.console && console.log.apply(console, args); + } catch (e) {} +} +function trim(string) { + return string.replace(/(^\s+)|(\s+$)/g, ""); +} +function startsWith(str, needle) { + return str.indexOf(needle) == 0; +} +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} +function random(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} +function shuffle(o) { + for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); + return o; +} +function getStackTrace(split) { + split = split === undefined ? true : split; + try { + o.lo.lo += 0; + } catch(e) { + if (e.stack) { + return split ? e.stack.split('\n') : e.stack; + } + } + return null; +} +function buildQueryString(obj) { + var list = [], i; + for (i in obj) { + list.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i])); + } + return list.join('&'); +} +function stripTags(html) { + var div = document.createElement("div"); + div.innerHTML = html; + return div.textContent || div.innerText || ""; +} +function decodeEntities(value) { + var textarea = document.createElement('textarea'); + textarea.innerHTML = value; + return textarea.value; +} +function createCData(data) { + var docu = new DOMParser().parseFromString('<xml></xml>', "application/xml"); + var cdata = docu.createCDATASection(data); + docu.getElementsByTagName('xml')[0].appendChild(cdata); + return cdata; +} +function remove() { + remove.parentNode.removeChild(remove); +} + +window.VKPC = new function() { + var _sid = null; + var _currentTrackId = null; + var _lastPlaylistSummary = null; + var _lastPlaylistId = 0; + var _operateQueue = []; + var _setTrackIdTimeout = null; + var _watchGraphicsChange = false; + var _checkPlaylistTimer = null; + + function wrapAudioMethods() { + // var self = this; + if (window.audioPlayer) { + if (!audioPlayer.__operate) { + audioPlayer.__operate = audioPlayer.operate; + audioPlayer.operate = function(id, nextPlaylist, opts) { + var currentId = audioPlayer.id, _status = id != currentId ? 'play' : null; + audioPlayer.__operate.apply(audioPlayer, arguments); + //self.firstOperateAfterPlaylistUpdating = false; + log('operate(), arguments:', arguments); + + if (existsInCurrentPlaylist(id)) { + log('operate(), found in current pl, setTrackId() now'); + setTrackId(id, _status); + } else { + log('operate(), not found, setToOperateQueue() now'); + setToOperateQueue(id, _status); + } + }; + } + + // disable it + if (false && !audioPlayer.__setGraphics) { + audioPlayer.__setGraphics = audioPlayer.setGraphics; + audioPlayer.setGraphics = function(act) { + audioPlayer.__setGraphics.apply(audioPlayer, arguments); + return; + /*if (self.watchGraphicsChange) { + if (browser.safari) self.sendOperateTrack(audioPlayer.id, (act == 'play' || act == 'load') ? 'play' : 'pause'); + self.watchGraphicsChange = false; + }*/ + }; + } + } + + log('[wrapAudioMethods] wrapped DONE'); + } + + function clear() { + log('clear()'); + _currentTrackId = null; + _lastPlaylistSummary = null; + _lastPlaylistId = null; + _sid = null; + _watchGraphicsChange = false; + } + + function getBrowser() { + return browser.safari ? 'safari' : 'chrome'; + } + + function executeCommand(command, plid) { + if (command == 'afterInjection') { + log('executeCommand: afterInjection, plid='+plid); + var pl = padAudioPlaylist(); + if (window.audioPlayer && pl) { + updatePlaylist(getPlaylist(pl)); + } else { + clearPlaylist(); + } + return; + } + + log('executeCommand:', command, plid); + // var self = this; + + if (!window.audioPlayer || !padAudioPlaylist()) { + log('[executeCommand] audioplayer or playlist not found'); + stManager.add(['audioplayer.js'], function() { + executeAfterPadLoading(function() { + log('[executeCommand] after execafterpadloading, window.audioPlayer:', window.audioPlayer); + wrapAudioMethods(); + + var plist = padAudioPlaylist(); + if (plist) { + log('[executeCommand] after exec...: send updatePlaylist() with plist'); + updatePlaylist(getPlaylist(plist)); + } + + if (command == 'playpause' || command == 'next' || command == 'prev') { + log('[executeCommand] after exec...: simple command'); + var id = getPlayFirstId(); + if (id) { + log('[executeCommand] after exec...: found id='+id+', playAudioNew() now'); + playAudioNew(id); + } else if (plist && plist.start) { + log('[executeCommand] after exec...: found plist.start, playAudioNew() now'); + playAudioNew(plist.start); + } + } else if (startsWith(command, 'operateTrack:')) { // TODO this is new fix + var id = parseInt(command.replace('operateTrack:')); + log('[executeCommand] after exec...: got operateTrack, id='+id); + if (!plist[id]) { + log('[executeCommand] after exec...: after got operateTrack: plist[id] not found, send new pl to app'); + //self.clearPlaylist(); + updatePlaylist(getPlaylist(plist)); + if (plist.start) { + log('[executeCommand] after exec...: got operateTrack, pl not found... ... play plist.start now'); + playAudioNew(plist.start); + } + } else { + log('[executeCommand] after exec...: got operateTrack, it is found, playAudioNew() now'); + playAudioNew(id); + } + } + }); + }); + return; + } + + function evaluateCommand(command) { + switch (command) { + case 'next': + case 'prev': + case 'playpause': + if (audioPlayer.id) { + if (command == 'next') next(); + else if (command == 'prev') prev(); + else if (command == 'playpause') playPause(); + } else { + var id = getPlayFirstId(); + if (id) playId(id); + } + break; + + default: + if (startsWith(command, 'operateTrack:')) { + log('[executeCommand] got operateTrack;'); + var id = command.replace('operateTrack:', ''), pl = padAudioPlaylist(); + if (pl[id] !== undefined) { + log('[executeCommand] got operateTrack; track is found, playAudioNew() now'); + //playAudioNew(id); + //audioPlayer.operate(id); + playId(id); + } else { + log('[executeCommand] got operateTrack; track not found, updatePlaylist with pl:', pl); + updatePlaylist(getPlaylist(pl)); + var id = getPlayFirstId(); + if (id) { + log('[executeCommand] got operateTrack; play id from getPlayFirstId() now'); + playId(id); + } + } + } + break; + } + } + + if (plid != _lastPlaylistId) { + log('[executeCommand] plid does not match; plid='+plid+', _lastPlaylistId='+_lastPlaylistId); + var pl = padAudioPlaylist(); + if (pl) { + updatePlaylist(getPlaylist(pl), true); + log('[executeCommand] plid does not match, sent updatePlaylist() with pl:', pl); + + if (plid == 0) { + evaluateCommand(command); + } else { + if (['next', 'prev', 'playpause'].indexOf(command) != -1) { + var id = audioPlayer.id || pl.start || getPlayFirstId(); + if (id) { + playId(id); + } + } + } + } + } else { + evaluateCommand(command); + } + } + + function setTrackId(id, _status) { + _status = _status || (audioPlayer.player.paused() ? 'pause' : 'play'); + clearTimeout(_setTrackIdTimeout); + + var check = function() { + if (audioPlayer.player) { + sendOperateTrack(id, _status); + } else { + _setTrackIdTimeout = setTimeout(check, 200); + } + }; + check(); + } + + function sendOperateTrack(id, _status) { + log('[sendOperateTrack]', id, _status); + toApp('operateTrack', { + 'id': id, + 'status': _status, + 'playlistId': _lastPlaylistId + }); + } + + function setToOperateQueue(id, _status) { + var q = _operateQueue; + for (var i = 0; i < q.length; i++) { + var track = q[i]; + if (track[0] == id) { + track[1] = _status; + return; + } + } + q.push([id, _status]); + } + + function existsInCurrentPlaylist(id) { + return _lastPlaylistSummary && _lastPlaylistSummary.indexOf(id) != -1; + } + + function processOperateQueue(pl) { + log('[processOperateQueue]'); + var q = _operateQueue; + while (q.length) { + var track = q.shift(); + log('[processOperateQueue] track:', track[0]); + if (pl[track[0]] !== undefined) { + log('[processOperateQueue] track', track[0], 'found, send it now'); + sendOperateTrack(track[0], track[1]); + } + } + } + + function clearOperateQueue() { + _operateQueue = []; + } + + function printPlaylist() { + var pl = padAudioPlaylist(); + if (pl) { + for (var k in pl) { + log(pl[k][5] + ' - ' + pl[k][6]); + } + } + } + + function getPlaylist(_pl) { + _pl = _pl || padAudioPlaylist(); + var pl = null; + if (_pl) { + var start = _pl.start, pl = []; + var nextId = start; + do { + if (_pl[nextId]) { + _pl[nextId]._vkpcId = nextId; + pl.push(_pl[nextId]); + nextId = _pl[nextId]._next; + } + } while (nextId != '' && nextId !== undefined && nextId != start); + } + return pl; + } + + // force=true is used when plids not match + function updatePlaylist(pl, force) { + var tracks = [], summary = [], title; + if (pl) { + for (var k = 0; k < pl.length; k++) { + tracks.push({ + id: pl[k]._vkpcId, + artist: decodeEntities(pl[k][5]), + title: decodeEntities(pl[k][6]), + duration: pl[k][4] + }); + summary.push(pl[k]._vkpcId); + } + + summary = summary.join(';'); + + log("updatePlaylist: _lastPlaylistSummary:", _lastPlaylistSummary, 'summary:', summary); + if (force || _lastPlaylistSummary === null || _lastPlaylistSummary !== summary) { + log('[updatePlaylist] last summary not matched;', _lastPlaylistSummary, summary); + var activeId = '', activeStatus = ''; + var vkpl = padAudioPlaylist(); + var plTitle = (window.audioPlaylist && window.audioPlaylist.htitle) || vkpl.htitle; + if (audioPlayer.id && vkpl[audioPlayer.id] !== undefined) { + activeId = audioPlayer.id; + _watchGraphicsChange = true; + + activeStatus = getPlayerStatus(true) ? 'play' : 'pause'; + _watchGraphicsChange = true; + } + + _lastPlaylistSummary = summary; + _lastPlaylistId = random(100000, 1000000); + + log("[updatePlaylist] send pl with id="+_lastPlaylistId+', activeId='+activeId+', activeStatus='+activeStatus+' to app'); + try { + toApp('updatePlaylist', { + tracks: tracks, + title: parsePlaylistTitle(plTitle) || "", + id: _lastPlaylistId, + active: { 'id': activeId, 'status': activeStatus }, + browser: getBrowser() + }); + } catch(e) { + log('[updatePlaylist] exception:', e, e.stack); + } + + processOperateQueue(pl); + } + } + } + + function clearPlaylist(no_send, called_from) { + called_from = called_from || ""; + log('[clearPlaylist] (called from: '+called_from+')'); + + _lastPlaylistSummary = null; + _lastPlaylistId = 0; + if (!no_send) { + toApp('clearPlaylist', {}); + } + } + + function checkPlaylist() { + var pl = padAudioPlaylist(); + if (!pl) { + clearPlaylist(true, 'checkPlaylist'); + } + } + + function parsePlaylistTitle(str) { + str = str || ""; + str = trim(str); + if (str == '') return str; + + var starts = { + 0: '\u0421\u0435\u0439\u0447\u0430\u0441 \u0438\u0433\u0440\u0430\u0435\u0442 \u2014 ', // Сейчас играет ru + 100: '\u041d\u044b\u043d\u0447\u0435 \u0438\u0433\u0440\u0430\u0435\u0442\u044a\u2014 ', // Нынче играетъ re + 3: 'Now playing \u2014 ', // en + 1: '\u0417\u0430\u0440\u0430\u0437 \u0437\u0432\u0443\u0447\u0438\u0442\u044c \u2014 ', // Зараз звучить ua + 777: '\u041f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043b\u0430\u0441\u0442\u0438\u043d\u043a\u0430 \u00ab' // Проигрывается пластинка su + }; + var ends = { + 0: ' \\| [0-9]+ \u0430\u0443\u0434\u0438\u043e\u0437\u0430[^\\s]+$', + 3: ' \\| [0-9]+ audio [^\\s]+$', + 1: ' \\| [0-9]+ \u0430\u0443\u0434\u0456\u043e\u0437\u0430[^\\s]+$', + 100: ' \\| [0-9]+ \u043a\u043e\u043c\u043f\u043e\u0437[^\\s]+$', + 777: ' \\| [0-9]+ \u0433\u0440\u0430\u043c\u0437\u0430[^\\s]+$' + }; + + if (window.vk && vk.lang !== undefined) { + log('1', starts[vk.lang] !== undefined, startsWith(str, starts[vk.lang]), str, starts[vk.lang], str.indexOf(starts[vk.lang])); + if (starts[vk.lang] !== undefined && startsWith(str, starts[vk.lang])) { + str = str.substring(starts[vk.lang].length); + } + + if (ends[vk.lang] !== undefined) { + var regex = new RegExp(ends[vk.lang], 'i'); + if (str.match(regex)) str = str.replace(regex, ''); + } + } + + return stripTags(trim(str)); + } + + function afterInjection() { + log("after injection"); + var pl = getPlaylist(); + if (pl) updatePlaylist(pl); + } + + function next() { + audioPlayer.nextTrack(true, !window.audioPlaylist) + /*if (audioPlayer.controls && audioPlayer.controls.pd && audioPlayer.controls.pd.next) { + audioPlayer.controls.pd.next.click(); + } else { + audioPlayer.nextTrack(true, !window.audioPlaylist) + }*/ + } + + function prev() { + audioPlayer.prevTrack(true, !window.audioPlaylist); + /*if (audioPlayer.controls && audioPlayer.controls.pd && audioPlayer.controls.pd.prev) { + audioPlayer.controls.pd.prev.click(); + } else { + audioPlayer.prevTrack(true, !window.audioPlaylist); + }*/ + } + + function getPlayFirstId() { + var id = currentAudioId() || ls.get('audio_id') || (window.audioPlaylist && audioPlaylist.start); + return id || null; + } + + function playFirst() { + var id = getPlayFirstId(); + + if (id) playId(id); + else { + var plist = padAudioPlaylist(); + if (plist && plist.start) { + playId(plist.start); + } else { + executeAfterPadLoading(function() { + var plist = padAudioPlaylist(); + if (plist && plist.start) { + playId(plist.start); + } + }); + } + } + } + + function executeAfterPadLoading(f) { + Pads.show('mus'); + window.onPlaylistLoaded = function() { + if (f) { + try { + f(); + } catch(e) {} + } + setTimeout(function() { + Pads.show('mus'); + }, 10); + } + } + + function getPlayerStatus(justStarted) { + if (!audioPlayer.player) return false; + try { + var pl = audioPlayer.player; + if (pl && pl.music && pl.music.buffered && !pl.music.buffered.length && justStarted) return true; + } catch (e) { + return true; + } + + return audioPlayer.player && !audioPlayer.player.paused(); + } + + function pauseForSafari() { + if (window.audioPlayer && audioPlayer.player) audioPlayer.pauseTrack(); + } + + function playPause() { + if (window.audioPlayer && audioPlayer.player) { + if (audioPlayer.player.paused()) { + audioPlayer.playTrack(); + } else { + audioPlayer.pauseTrack(); + } + } + } + + function operateTrack(id) { + if (id == audioPlayer.id) { + playPause(); + } else { + audioPlayer.operate(id); + } + } + + function playId(id) { + if (window.audioPlayer) audioPlayer.operate(id); + else playAudioNew(id); + } + + function getLastInstanceId() { + var id = null, pp = ls.get('pad_playlist'); + if (pp && pp.source) id = pp.source; + //return [id, ls.get('vkpc_lastid')]; + return id; + } + + this.executeCommand = executeCommand; + + this.getParams = function(command) { + if (command != 'afterInjection') { + checkPlaylist(); + } + var havePlayer = window.audioPlayer !== undefined; + var havePlaylist = havePlayer && (window.padAudioPlaylist && !!padAudioPlaylist()); + + return { + havePlayer: havePlayer, + havePlaylist: havePlaylist, + isPlaying: window.audioPlayer && window.audioPlayer.player && !window.audioPlayer.player.paused(), + tabId: window.curNotifier && curNotifier.instance_id, + trackId: window.audioPlayer && audioPlayer.id, + playlistId: havePlaylist ? _lastPlaylistId : 0, + lsSourceId: getLastInstanceId() + }; + }; + + this.init = function(sid) { + if (_checkPlaylistTimer === null) { + _checkPlaylistTimer = setInterval(function() { + if ((_lastPlaylistId || _lastPlaylistSummary) && !padAudioPlaylist()) { + clearPlaylist(true, 'timer'); // TODO func + } + }, 1000); + } + + if (!window.__wrappedByVKPC && window.audioPlayer && window.ls && window.stManager) { + if (!stManager.__done) { + stManager.__done = stManager.done; + stManager.done = function(fn) { + if (fn == 'audioplayer.js') { + wrapAudioMethods(); // TODO func + } + stManager.__done.apply(stManager, arguments); + }; + } + + wrapAudioMethods(); + + if (!ls.__set) { + ls.__set = ls.set; + ls.set = function(k, v) { + ls.__set.apply(ls, arguments); + if (k == 'pad_playlist') { + log('pad_playlist updated:', v); + updatePlaylist(getPlaylist(v)); // TODO func + } + }; + } + if (!ls.__remove) { + ls.__remove = ls.remove; + ls.remove = function(k, v) { + ls.__remove.apply(ls, arguments); + if (k == 'pad_playlist') { + log('pad_playlist removed from ls'); + //self.clearPlaylist(true, 'ls.remove'); + // self.clearPlaylist(); + } + }; + } + + window.__wrappedByVKPC = true; + } + + if (sid === _sid) { + return; + } + if (_sid !== null) { + clear(); // TODO + } + _sid = sid; + + log('(re)inited OK'); + }; + + this.getSID = function() { + return _sid; + }; + + this.getLastPlaylistID = function() { + return _lastPlaylistId; + }; + + this.getLastInstanceId = getLastInstanceId; + this.clearPlaylist = clearPlaylist; + this.parsePlaylistTitle = parsePlaylistTitle; +}; // window.VKPC = ... + +if (!window.DOMContentLoaded) { + window.console && console.log && console.log("[VKPC] !window.DOMContentLoaded, exising"); + return; +} + +window.DOMContentLoaded(function() { + if (window.vk) { + document.addEventListener("VKPCBgMessage", receiveMessage, false); + sendMessage({cmd: 'register'}); + } +}); + +window.addEventListener("focus", function(e) { + _isFocused = true; +}, false); +window.addEventListener("blur", function(e) { + _isFocused = false +}, false); + +// send message to background +function sendMessage(json) { + var cdata = createCData(JSON.stringify(json)); + document.getElementById('utils').appendChild(cdata); + + var evt = document.createEvent("Events"); + evt.initEvent("VKPCInjectedMessage", true, false); + cdata.dispatchEvent(evt); +} + +// received message from background +function receiveMessage(e) { + // log('receiveMessage', e); + var target = e.target, json = JSON.parse(target.data || "{}"); + + switch (json.cmd) { + case "afterInjection": + VKPC.init(json.data.sid); + + var params = VKPC.getParams(json.data.command); + params.isFocused = _isFocused; + + sendMessage({ + cmd: "afterInjection", + data: params, + id: json.id + }); + break; + + case "vkpc": + switch (json.command) { + case "clearPlaylist": + VKPC.clearPlaylist(true, "as"); + break; + + default: + VKPC.executeCommand(json.command, json.playlistId); + break; + } + + break; + } + + try { + remove(target); + } catch (e) {} +} + +// wrapper for sendMessage() to send message directly to VKPC.app +function toApp(command, data) { + var json = { + // bg: 1, + cmd: "to_app", + data: { + command: command, + data: data + } + }; + sendMessage(json); +} + +})(); diff --git a/firefox/install.rdf b/firefox/install.rdf new file mode 100644 index 0000000..e9ef2e9 --- /dev/null +++ b/firefox/install.rdf @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>vkpc@ch1p.com</em:id> + <em:name>VK Player Controller</em:name> + <em:version>3.0.1</em:version> + <em:type>2</em:type> + <em:creator>ch1p</em:creator> + <em:description>This is a part of VK Player Controller for OSX. For more information, please visit https://ch1p.com/vkpc/</em:description> + <em:iconURL>chrome://vkpc/content/icon.png</em:iconURL> + <em:bootstrap>true</em:bootstrap> + + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:minVersion>4.0</em:minVersion> + <em:maxVersion>99.0</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> |