summaryrefslogtreecommitdiff
path: root/firefox
diff options
context:
space:
mode:
Diffstat (limited to 'firefox')
-rw-r--r--firefox/bootstrap.js73
-rw-r--r--firefox/chrome.manifest2
-rw-r--r--firefox/content/icon.pngbin0 -> 7780 bytes
-rw-r--r--firefox/content/module.jsm569
-rw-r--r--firefox/content/overlay.js8
-rw-r--r--firefox/content/overlay.xul6
-rw-r--r--firefox/content/vkpc.js769
-rw-r--r--firefox/install.rdf22
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
new file mode 100644
index 0000000..6667635
--- /dev/null
+++ b/firefox/content/icon.png
Binary files differ
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>