--- /dev/null
+make UI not so crappy
+
+support keyboard shortcuts
+
+playlist support
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+function Mpc(socket, cb_update_state) {
+ this._socket = socket;
+ this._cb_update_state = cb_update_state;
+ this._queue = ['init'];
+ this.state = {};
+}
+
+Mpc.log = function(msg, obj) {
+ console.log('mpc: ' + msg, obj);
+}
+
+Mpc.prototype.send = function(msg) {
+ this._queue.push(msg);
+ this._socket.send(msg, function(x) {
+ Mpc.log('send: ' + msg + ':', x);
+ });
+}
+
+Mpc.prototype.recv_msg = function(lines) {
+ curr = this._queue.shift();
+ Mpc.log('recv: [' + curr + ']:', lines.join('\n'));
+ curr = curr.split(' ');
+
+ switch (curr[0]) {
+ // Needs to return a list of dicts (see above for dicts).
+ //case 'playlistinfo':
+ case 'currentsong':
+ case 'stats':
+ case 'status':
+ state = {};
+ lines.forEach(function(line) {
+ i = line.indexOf(':');
+ if (i == -1)
+ return; // Ignores the OK line
+ key = line.substr(0, i);
+ val = line.substr(i + 2);
+ state[key] = val;
+ });
+ this.state = state;
+ this._cb_update_state(state);
+ break;
+ default:
+ this._cb_update_state(lines, curr);
+ break;
+ }
+}
+
+Mpc.prototype.recv = function(msg) {
+ /* We can get back a bunch of responses in a row, so parse them out */
+ /* XXX: Do we have to handle partial reads ? like long playlists ... */
+ lines = msg.split('\n');
+ var i = 0;
+ while (i < lines.length) {
+ if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ') {
+ this.recv_msg(lines.splice(0, i + 1));
+ i = 0;
+ } else
+ ++i;
+ }
+}
+
+/*
+ * Command generator helpers.
+ */
+
+Mpc.__make_send_void = function(cmd) {
+ return function() { this.send(cmd); }
+}
+
+Mpc.__make_send_arg1 = function(cmd) {
+ return function(a1) {
+ if (a1 === undefined)
+ Mpc.log(cmd + ': function requires one argument');
+ else
+ this.send(cmd + ' ' + a1);
+ }
+}
+
+Mpc.__make_send_arg2 = function(cmd) {
+ return function(a1, a2) {
+ if (a1 === undefined || a2 === undefined)
+ Mpc.log(cmd + ': function requires two arguments');
+ else
+ this.send(cmd + ' ' + a1 + ' ' + a2);
+ }
+}
+
+Mpc.__make_send_opt = function(cmd) {
+ return function(arg) {
+ if (arg === undefined)
+ arg = '';
+ this.send(cmd + ' ' + arg);
+ };
+}
+
+Mpc.__make_send_range = function(cmd, min, max, def) {
+ return function(arg) {
+ if (arg === undefined)
+ arg = def;
+ if (arg >= min && arg <= max)
+ this.send(cmd + ' ' + arg);
+ else
+ Mpc.log(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
+ };
+}
+
+/*
+ * Querying MPD's status
+ * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
+ */
+
+// clearerror
+Mpc.prototype.clearerror = Mpc.__make_send_void('clearerror');
+// currentsong
+Mpc.prototype.currentsong = Mpc.__make_send_void('currentsong');
+// idle [SUBSYSTEMS...]
+// TODO
+// status
+Mpc.prototype.status = Mpc.__make_send_void('status');
+// stats
+Mpc.prototype.stats = Mpc.__make_send_void('stats');
+
+/*
+ * Playback options
+ * http://www.musicpd.org/doc/protocol/ch03s02.html
+ */
+
+// consume {STATE}
+Mpc.prototype.consume = Mpc.__make_send_range('consume', 0, 1, 1);
+// crossfade {SECONDS}
+Mpc.prototype.consume = Mpc.__make_send_arg1('crossfade');
+// mixrampdb {deciBels}
+Mpc.prototype.mixrampdb = Mpc.__make_send_arg1('mixrampdb');
+// mixrampdelay {SECONDS|nan}
+// Note: Probably should handle javascript NaN here.
+Mpc.prototype.mixrampdelay = Mpc.__make_send_arg1('mixrampdelay');
+// random {STATE}
+Mpc.prototype.random = Mpc.__make_send_range('random', 0, 1, 1);
+// repeat {STATE}
+Mpc.prototype.repeat = Mpc.__make_send_range('repeat', 0, 1, 1);
+// setvol {VOL}
+Mpc.prototype.setvol = Mpc.__make_send_range('setvol', 0, 100);
+// single {STATE}
+Mpc.prototype.single = Mpc.__make_send_range('single', 0, 1, 1);
+// replay_gain_mode {MODE}
+Mpc.prototype.replay_gain_mode = Mpc.__make_send_arg1('replay_gain_mode');
+// replay_gain_status
+
+/*
+ * Controlling playback
+ * http://www.musicpd.org/doc/protocol/ch03s03.html
+ */
+
+// next
+Mpc.prototype.next = Mpc.__make_send_void('next');
+// pause {PAUSE}
+Mpc.prototype.pause = Mpc.__make_send_range('pause', 0, 1, 1);
+// play [SONGPOS]
+Mpc.prototype.play = Mpc.__make_send_opt('play');
+// playid [SONGID]
+Mpc.prototype.playid = Mpc.__make_send_opt('playid');
+// previous
+Mpc.prototype.previous = Mpc.__make_send_void('previous');
+// seek {SONGPOS} {TIME}
+Mpc.prototype.seek = Mpc.__make_send_arg2('seek');
+// seekid {SONGID} {TIME}
+Mpc.prototype.seekid = Mpc.__make_send_arg2('seekid');
+// seekcur {TIME}
+Mpc.prototype.seekcur = Mpc.__make_send_arg1('seek');
+// stop
+Mpc.prototype.stop = Mpc.__make_send_void('stop');
+
+/*
+ * Connection settings
+ * http://www.musicpd.org/doc/protocol/ch03s08.html
+ */
+
+// close
+Mpc.prototype.close = Mpc.__make_send_void('close');
+// kill
+Mpc.prototype.kill = Mpc.__make_send_void('kill');
+// password {PASSWORD}
+Mpc.prototype.password = Mpc.__make_send_arg1('password');
+// ping
+Mpc.prototype.ping = Mpc.__make_send_void('ping');
--- /dev/null
+/*
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Boris Smus (smus@chromium.org)
+*/
+
+(function(exports) {
+
+ // Define some local variables here.
+ var socket = chrome.socket;
+
+ /**
+ * Creates an instance of the client
+ *
+ * @param {String} host The remote host to connect to
+ * @param {Number} port The port to connect to at the remote host
+ */
+ function TcpClient(host, port) {
+ this.host = host;
+ this.port = port;
+
+ // Callback functions.
+ this.callbacks = {
+ connect: null, // Called when socket is connected.
+ disconnect: null, // Called when socket is disconnected.
+ recv: null, // Called when client receives data from server.
+ sent: null // Called when client sends data to server.
+ };
+
+ // Socket.
+ this.socketId = null;
+ this.isConnected = false;
+
+ log('initialized tcp client');
+ }
+
+ /**
+ * Connects to the TCP socket, and creates an open socket.
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-create
+ * @param {Function} callback The function to call on connection
+ */
+ TcpClient.prototype.connect = function(callback) {
+ socket.create('tcp', {}, this._onCreate.bind(this));
+
+ // Register connect callback.
+ this.callbacks.connect = callback;
+ };
+
+ /**
+ * Sends a message down the wire to the remote side
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-write
+ * @param {String} msg The message to send
+ * @param {Function} callback The function to call when the message has sent
+ */
+ TcpClient.prototype.sendMessage = function(msg, callback) {
+ this._stringToArrayBuffer(msg + '\n', function(arrayBuffer) {
+ socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this));
+ }.bind(this));
+
+ // Register sent callback.
+ this.callbacks.sent = callback;
+ };
+
+ /**
+ * Sets the callback for when a message is received
+ *
+ * @param {Function} callback The function to call when a message has arrived
+ */
+ TcpClient.prototype.addResponseListener = function(callback) {
+ // Register received callback.
+ this.callbacks.recv = callback;
+ };
+
+ /**
+ * Disconnects from the remote side
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect
+ */
+ TcpClient.prototype.disconnect = function() {
+ socket.disconnect(this.socketId);
+ this.isConnected = false;
+ };
+
+ /**
+ * The callback function used for when we attempt to have Chrome
+ * create a socket. If the socket is successfully created
+ * we go ahead and connect to the remote side.
+ *
+ * @private
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-connect
+ * @param {Object} createInfo The socket details
+ */
+ TcpClient.prototype._onCreate = function(createInfo) {
+ this.socketId = createInfo.socketId;
+ if (this.socketId > 0) {
+ socket.connect(this.socketId, this.host, this.port, this._onConnectComplete.bind(this));
+ this.isConnected = true;
+ } else {
+ error('Unable to create socket');
+ }
+ };
+
+ /**
+ * The callback function used for when we attempt to have Chrome
+ * connect to the remote side. If a successful connection is
+ * made then polling starts to check for data to read
+ *
+ * @private
+ * @param {Number} resultCode Indicates whether the connection was successful
+ */
+ TcpClient.prototype._onConnectComplete = function(resultCode) {
+ // Start polling for reads.
+ setInterval(this._periodicallyRead.bind(this), 500);
+
+ if (this.callbacks.connect) {
+ log('connect complete');
+ this.callbacks.connect();
+ }
+ log('onConnectComplete');
+ };
+
+ /**
+ * Checks for new data to read from the socket
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-read
+ */
+ TcpClient.prototype._periodicallyRead = function() {
+ socket.read(this.socketId, null, this._onDataRead.bind(this));
+ };
+
+ /**
+ * Callback function for when data has been read from the socket.
+ * Converts the array buffer that is read in to a string
+ * and sends it on for further processing by passing it to
+ * the previously assigned callback function.
+ *
+ * @private
+ * @see TcpClient.prototype.addResponseListener
+ * @param {Object} readInfo The incoming message
+ */
+ TcpClient.prototype._onDataRead = function(readInfo) {
+ // Call received callback if there's data in the response.
+ if (readInfo.resultCode > 0 && this.callbacks.recv) {
+ log('onDataRead');
+ // Convert ArrayBuffer to string.
+ this._arrayBufferToString(readInfo.data, function(str) {
+ this.callbacks.recv(str);
+ }.bind(this));
+ }
+ };
+
+ /**
+ * Callback for when data has been successfully
+ * written to the socket.
+ *
+ * @private
+ * @param {Object} writeInfo The outgoing message
+ */
+ TcpClient.prototype._onWriteComplete = function(writeInfo) {
+ log('onWriteComplete');
+ // Call sent callback.
+ if (this.callbacks.sent) {
+ this.callbacks.sent(writeInfo);
+ }
+ };
+
+ /**
+ * Converts an array buffer to a string
+ *
+ * @private
+ * @param {ArrayBuffer} buf The buffer to convert
+ * @param {Function} callback The function to call when conversion is complete
+ */
+ TcpClient.prototype._arrayBufferToString = function(buf, callback) {
+ var bb = new Blob([new Uint8Array(buf)]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsText(bb);
+ };
+
+ /**
+ * Converts a string to an array buffer
+ *
+ * @private
+ * @param {String} str The string to convert
+ * @param {Function} callback The function to call when conversion is complete
+ */
+ TcpClient.prototype._stringToArrayBuffer = function(str, callback) {
+ var bb = new Blob([str]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsArrayBuffer(bb);
+ };
+
+ /**
+ * Wrapper function for logging
+ */
+ function log(msg) {
+ //console.log('tcp-client: ', msg);
+ }
+
+ /**
+ * Wrapper function for error logging
+ */
+ function error(msg) {
+ console.error('tcp-client: ', msg);
+ }
+
+ exports.TcpClient = TcpClient;
+
+})(window);
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('main.html', {
+ bounds: {
+ width: 300,
+ height: 120
+ }
+ });
+});
--- /dev/null
+<!-- Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. -->
+<!doctype html>
+<html>
+
+<head>
+<title>Media Player Client</title>
+<script src='js/tcp-client.js'></script>
+<script src='js/mpc.js'></script>
+<script src='main.js'></script>
+<style>
+table {
+ margin: 0px;
+ padding: 0px;
+ border-collapse: collapse;
+}
+body {
+ margin: 0px;
+ padding: 0px;
+}
+td {
+ white-space: nowrap;
+}
+div#status {
+ white-space: nowrap;
+}
+
+table.tabs td {
+ padding-left: 1em;
+ border: black solid 1px;
+ border-top: 0px;
+ font-size: smaller;
+ font-family: sans-serif;
+ background-color: #d0d0d0;
+}
+table.tabs td.selected {
+ background-color: white;
+}
+
+/* Get the tabs to float at the bottom */
+html, body {
+ height: 100%;
+}
+div#body {
+ min-height: 100%;
+}
+div.main {
+ overflow: auto;
+ padding-bottom: 1.3em;
+}
+div#footer {
+ position: relative;
+ margin-top: -1.3em;
+ height: 1.3em;
+ clear: both;
+}
+</style>
+</head>
+
+<body>
+
+<div id='body'>
+
+<div class='main' id='main.controls'>
+<table>
+ <tr>
+ <td>
+ <input type='button' id='previous' value='⇦'><input type='button' id='stop' value='◻'><input type='button' id='pause' value='◫'><input type='button' id='play' value='▷'><input type='button' id='next' value='⇨'>
+ </td>
+ <td style='whitespace:collapse'>
+ <input type='button' id='repeat' value='∞'><input type='button' id='random' value='®'><input type='button' id='single' value='§'><input type='button' id='consume' value='©'>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input type='range' id='setvol' min='0' max='100' value='0'>
+ </td>
+ <td>
+ <input type='range' id='seekcur' min='0' max='100' value='0'>
+ </td>
+ </tr>
+ <tr>
+ <td colspan=2>
+ <span id='status'>Loading...</span>
+ </td>
+ </tr>
+</table>
+</div>
+
+<div class='main' id='main.options' style='display: none'>
+<table>
+ <tr>
+ <td>Host:</td>
+ <td><input type='text' id='host' value='192.168.0.2'></td>
+ </tr>
+ <tr>
+ <td>Port:</td>
+ <td><input type='number' id='port' value='6600'></td>
+ </tr>
+ <tr>
+ <td colspan=2>
+ <input type='button' id='connect' value='connect'>
+
+ Sync Settings:<input type='checkbox' id='sync' checked></td>
+ </tr>
+</table>
+</div>
+
+<div class='main' id='main.metadata' style='display: none'>
+<div id='metadata'></div>
+</div>
+
+</div>
+
+<div id='footer'>
+<table class='tabs'>
+ <tr>
+ <td id='tab.controls' class='selected'>Controls</td>
+ <td id='tab.metadata'>Metadata</td>
+ <td id='tab.options'>Options</td>
+ </tr>
+</table>
+</div>
+
+</body>
+</html>
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+/* Globals to allow easy manipulation via javascript console */
+var mpc;
+var tcpclient;
+
+function TcpClientSender(tcpclient) {
+ this.tcpclient = tcpclient;
+}
+TcpClientSender.prototype.send = function(data, cb) {
+ this.tcpclient.sendMessage(data, cb);
+}
+
+function tramp_mpc_recv(data) {
+ mpc.recv(data);
+}
+
+function sync_storage(sync) {
+ return sync ? chrome.storage.sync : chrome.storage.local;
+}
+
+window.onload = function() {
+ var local_keys = [
+ 'sync',
+ ];
+ var sync_keys = [
+ 'host', 'port',
+ ];
+ var options = {
+ 'host': '192.168.0.2',
+ 'port': 6600,
+ 'sync': true,
+ };
+
+ chrome.storage.local.get(local_keys, function(settings) {
+ local_keys.forEach(function(key) {
+ if (key in settings)
+ options[key] = settings[key]
+ });
+
+ var storage = sync_storage(options['sync']);
+ storage.get(sync_keys, function(settings) {
+ sync_keys.forEach(function(key) {
+ if (key in settings)
+ options[key] = settings[key];
+ });
+
+ init_ui(local_keys, sync_keys, options);
+ mpc_connect();
+ });
+ });
+};
+
+function mpc_refresh() {
+ mpc.status();
+ mpc.currentsong();
+}
+
+function mpc_connect(host, port) {
+ if (typeof(host) != 'string') {
+ host = window['opts_host'].value;
+ port = parseInt(window['opts_port'].value);
+ }
+
+ if (mpc != undefined) {
+ console.log('disconnecting');
+ update_ui('disconnect');
+ delete mpc;
+ tcpclient.disconnect();
+ delete tcpclient;
+ }
+
+ update_ui('init');
+ tcpclient = new TcpClient(host, port);
+ tcpclient.connect(function() {
+ var mpc_sender = new TcpClientSender(tcpclient);
+ tcpclient.addResponseListener(tramp_mpc_recv);
+ mpc = new Mpc(mpc_sender, update_ui);
+ console.log('connected to ' + host + ':' + port);
+ mpc_refresh();
+ });
+}
+
+function tramp_mpc_consume() {
+ var val = zo(!getToggleButton(this));
+ mpc.consume(val);
+ setToggleButton(this, val);
+}
+function tramp_mpc_next() { mpc.next(); }
+function tramp_mpc_pause() { mpc.pause(); }
+function tramp_mpc_play() { mpc.play(); }
+function tramp_mpc_previous() { mpc.previous(); }
+function tramp_mpc_random() {
+ var val = zo(!getToggleButton(this));
+ mpc.random(val);
+ setToggleButton(this, val);
+}
+function tramp_mpc_repeat() {
+ var val = zo(!getToggleButton(this));
+ mpc.repeat(val);
+ setToggleButton(this, val);
+}
+function tramp_mpc_seekcur() { mpc.seekcur(this.value); }
+function tramp_mpc_setvol() { mpc.setvol(this.value); }
+function tramp_mpc_single() {
+ var val = zo(!getToggleButton(this));
+ mpc.single(val);
+ setToggleButton(this, val);
+}
+function tramp_mpc_stop() { mpc.stop(); }
+
+function zo(val) {
+ return val ? 1 : 0;
+}
+function szo(val) {
+ return val == '0' ? 0 : 1;
+}
+function getToggleButton(btn) {
+ return btn.style.borderStyle == 'inset';
+}
+function setToggleButton(btn, val) {
+ if (val === undefined)
+ val = !getToggleButton(btn);
+ btn.style.borderStyle = val ? 'inset' : '';
+}
+
+function show_page(page) {
+ if (typeof(page) != 'string')
+ page = this.id.split('.')[1];
+
+ var eles = document.getElementsByClassName('main');
+ for (var i = 0; i < eles.length; ++i) {
+ var ele = eles[i];
+ var dis = 'none';
+ var cls = '';
+ if (ele.id == 'main.' + page) {
+ dis = '';
+ cls = 'selected';
+ }
+ ele.style.display = dis;
+ document.getElementById('tab.' + ele.id.split('.')[1]).className = cls;
+ }
+}
+
+function update_local_settings() {
+ var setting = {};
+ setting[this.id] = this.checked;
+ chrome.storage.local.set(setting);
+}
+
+function update_sync_settings() {
+ var setting = {};
+ setting[this.id] = this.value;
+ var storage = sync_storage(window['opts_sync'].checked);
+ storage.set(setting);
+}
+
+function init_ui(local_keys, sync_keys, options) {
+ /* Setup footer */
+ [
+ 'controls', 'metadata', 'options',
+ ].forEach(function(id) {
+ document.getElementById('tab.' + id).onclick = show_page;
+ });
+
+ /* Setup control tab */
+ ui_mpc_status = document.getElementById('status');
+ ui_mpc_metadata = document.getElementById('metadata');
+ [
+ 'consume', 'next', 'pause', 'play', 'previous', 'random', 'repeat',
+ 'seekcur', 'setvol', 'single', 'stop',
+ ].forEach(function(id) {
+ var ele = window['ui_mpc_' + id] = document.getElementById(id);
+ ele.onclick = window['tramp_mpc_' + id];
+ ele.title = id;
+ });
+
+ /* Setup options tab */
+ document.getElementById('connect').onclick = mpc_connect;
+ local_keys.forEach(function(id) {
+ var ele = window['opts_' + id] = document.getElementById(id);
+ ele.checked = options[id];
+ ele.onchange = update_local_settings;
+ });
+ sync_keys.forEach(function(id) {
+ var ele = window['opts_' + id] = document.getElementById(id);
+ ele.value = options[id];
+ ele.oninput = update_sync_settings;
+ });
+}
+
+function update_ui(state, cmd) {
+ if (typeof(state) == 'string') {
+ ui_mpc_status.innerText = ({
+ 'disconnect': 'Disconnecting...',
+ 'init': 'Connecting...',
+ })[state];
+ return;
+ }
+
+ if (Array.isArray(state)) {
+ /*
+ switch (cmd[0]) {
+ case 'setvol':
+ case 'seekcur':
+ break;
+ default:
+ mpc_refresh();
+ }
+ */
+ return;
+ }
+
+ if ('file' in state) {
+ // Hack: should be a real object.
+ ui_mpc_metadata.innerText = state['file'];
+ return;
+ }
+
+ var time = state.time.split(':');
+ window['ui_mpc_seekcur'].max = time[1];
+ window['ui_mpc_seekcur'].value = time[0];
+
+ window['ui_mpc_setvol'].value = state.volume;
+ [
+ 'consume', 'random', 'repeat', 'single',
+ ].forEach(function(id) {
+ setToggleButton(window['ui_mpc_' + id], szo(state[id]));
+ });
+
+ ui_mpc_status.innerText = ({
+ 'play': 'Playing',
+ 'pause': 'Paused',
+ 'stop': 'Stopped',
+ })[state.state];
+}
--- /dev/null
+#!/bin/bash -e
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+case $1 in
+-h|--help)
+ echo "Usage: $0 [rev]"
+ exit 0
+ ;;
+esac
+
+json_value() {
+ local key=$1
+ sed -n -r \
+ -e '/^[[:space:]]*"'"${key}"'"/s|.*:[[:space:]]*"([^"]*)",?$|\1|p' \
+ manifest.json
+}
+
+PN=$(json_value name | sed 's:[[:space:]]:_:g' | tr '[:upper:]' '[:lower:]')
+PV=$(json_value version)
+rev=${1:-0}
+PVR="${PV}.${rev}"
+P="${PN}-${PVR}"
+
+rm -rf "${P}"
+mkdir "${P}"
+
+while read line ; do
+ [[ ${line} == */* ]] && mkdir -p "${P}/${line%/*}"
+ ln "${line}" "${P}/${line}"
+done < <(sed 's:#.*::' manifest.files)
+cp manifest.json "${P}/"
+
+sed -i \
+ -e '/"version"/s:"[^"]*",:"'${PVR}'",:' \
+ "${P}/manifest.json"
+
+zip="${P}.zip"
+zip -r "${zip}" "${P}"
+rm -rf "${P}"
+du -b "${zip}"
--- /dev/null
+images/icon-128x128.png
+js/mpc.js
+js/tcp-client.js
+launcher.js
+main.js
+main.html
--- /dev/null
+{
+ "manifest_version": 2,
+ "minimum_chrome_version": "24",
+ "name": "Music Player Client",
+ "version": "1.0.1",
+ "description": "Control a Music Player Daemon (MPD)",
+ "icons": {
+ "128": "images/icon-128x128.png"
+ },
+ "app": {
+ "background": {
+ "scripts": ["launcher.js"]
+ }
+ },
+ "offline_enabled": true,
+ "permissions": [
+ "storage",
+ {"socket": ["tcp-connect"]}
+ ]
+}