Commit 987acffa authored by Marko's avatar Marko
Browse files

Initial commit for version 0.2

parents
{
"env": {
"browser": true,
"node": true,
"es6": false
},
"extends": "eslint:recommended",
"globals": {
"window": true,
"document": true,
"chrome": true,
"require": true
},
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"semi": [
"error",
"always"
],
"no-console": "off"
}
}
\ No newline at end of file
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/cli/shrinkwrap#caveats
node_modules
bower_components
# Debug log from npm
npm-debug.log
.DS_Store
.history
nbproject
.idea
build
\ No newline at end of file
# Volume Control for Bandcamp Player
## Foreword
I got really tired of not being able to control the volume of Bandcamp audio players so I thought about trying to fix that for myself. This extension is primarily made for my own personal use, but I figured if someone else can use it as well, why not try and help.
## How this thing works
The extension works by searching for HTML5 audio players on any Bandcamp page (including the embedded players on other websites), hooking into their HTML markup where the buttons (well, Play/Pause button) and progress slider are and adding in a volume slider that can control said player's volume.
The slider takes some of the styles from Bandcamp's own player to fit the visual style so it should look fairly consistent. It's nothing too fancy, but it gets the job done. The volume slider handle is a bit more rounded to make it easier to discern it from the regular song progress bar.
The volume is saved every time it's changed and the value should persist across multiple players. So if you save a volume at 42% on one page and open up another one (it doesn't matter if it's embedded or not), the volume should default to 42% in that one, too. This eliminates the need to constantly reset it if you're browsing multiple pages quickly. Note that if there are multiple embedded players in the same page, changing volume of one will **not** instantly affect any other. Also, only the last value changed (regardless of the player) will be saved.
Note that currently on Bandcamp's Discover page one slider controls **all** audio players until I figure out which is which.
## Permissions
The extension only requires `storage` permissions (so it can save the volume level), and access to the content of any Bandcamp (`https://*.bandcamp.com/`) page (so it can find the player, markup, etc.).
## License
Copyright 2017
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.
\ No newline at end of file
'use_strict';
(function () {
init();
function init() {
if (playerHasErrors()) { return; }
var type = determinePlayerType();
if (!typeIsSupported(type)) {
return;
}
var player = findPlayer(),
$markupContainer = findVolumeControlContainer(type),
$markupWrapper = findVolumeControlWrapperFromMarkupContainerByType($markupContainer, type),
markup = generateVolumeControkMarkupByType(type),
clicked = false,
deltaX = 0;
if (!player) { return; }
$markupWrapper.innerHTML += markup;
var $handle = $markupWrapper.querySelector('#bk_volume_handle'),
$progBar = $markupWrapper.querySelector('.progbar_empty'),
$volume = $markupWrapper.querySelector('#bk_volume_value');
adjustPlayerMarkupByType(type);
$handle.addEventListener('mousedown', mouseDown);
document.addEventListener('mousemove', mouseMove);
window.addEventListener('mouseup', mouseUp);
restoreVolume(function (volume) {
player.volume = volume;
$handle.style.left = normaliseHandlePosition(player.volume) + 'px';
displayVolumeByType(type);
});
function mouseDown(e) {
clicked = true;
deltaX = e.offsetX;
return false;
}
function mouseMove(e) {
if (clicked) {
var progBarLeft = getLeftOffset($progBar),
posX = e.pageX - progBarLeft - deltaX;
if (posX < 0) {
posX = 0;
}
if (posX > $progBar.clientWidth - $handle.clientWidth) {
posX = $progBar.clientWidth - $handle.clientWidth;
}
$handle.style.left = (posX) + 'px';
player.volume = normaliseHandleVolume(posX);
displayVolumeByType(type);
}
}
function mouseUp() {
clicked = false;
saveVolume(player.volume);
}
function normaliseHandleVolume(position) {
var handleWidth = $handle.clientWidth,
progbarWidth = $progBar.clientWidth,
trackWidth = progbarWidth - handleWidth,
volume = position / trackWidth;
if (volume < 0) {
volume = 0;
}
else if (volume > 1) {
volume = 1;
}
return volume;
}
function normaliseHandlePosition(volume) {
var handleWidth = $handle.clientWidth,
progbarWidth = $progBar.clientWidth,
trackWidth = progbarWidth - handleWidth,
position = trackWidth * volume;
return position;
}
function displayVolumeByType(type) {
var vol = Math.round(player.volume * 100);
switch (type) {
case 'page':
case 'large':
case 'small':
$volume.textContent = 'Vol: ' + vol + '%';
break;
}
}
}
function playerHasErrors() {
var errors = document.querySelectorAll('.inline_player .error'),
hasError = false;
errors.forEach(function (item) {
if (item.style.visibility === 'visible') {
hasError = true;
return;
}
});
return hasError;
}
function determinePlayerType() {
var url = window.location.href,
type;
if (url.indexOf('EmbeddedPlayer') > -1) {
if (url.indexOf('size=large') > -1 &&
url.indexOf('artwork=small') < 0) {
type = 'large';
}
else if (url.indexOf('size=large') > -1 &&
url.indexOf('artwork=small') > -1 ||
url.indexOf('artwork=none') > -1) {
type = 'small';
}
else {
type = 'unsupported';
}
}
return type || 'page';
}
function typeIsSupported(type) {
switch (type) {
case 'large':
case 'small':
case 'page':
return true;
}
return false;
}
function findPlayer() {
return document.querySelector('audio');
}
function findVolumeControlContainer(type) {
var $controlContainer;
switch (type) {
case 'large':
$controlContainer = document.querySelector('.inline_player #artarea');
break;
case 'small':
$controlContainer = document.querySelector('.inline_player #infolayer .info');
break;
case 'page':
default:
$controlContainer = document.querySelector('.inline_player');
break;
}
return $controlContainer;
}
function findVolumeControlWrapperFromMarkupContainerByType(container, type) {
var $controlWrapper;
switch (type) {
case 'large':
$controlWrapper = container;
break;
case 'small':
$controlWrapper = container;
break;
case 'page':
$controlWrapper = container.querySelector('tbody');
break;
}
return $controlWrapper;
}
function generateVolumeControkMarkupByType(type) {
var $markup = '';
switch (type) {
case 'large':
$markup += '<div class="bk_volume_wrapper" style="position: absolute; width: 100%; padding: 6px; bottom: 2.4%;">'
+ '<div class="progbar" style="width: 78%"><div class="progbar_empty" style="position: relative"><div class="thumb" id="bk_volume_handle"></div></div></div>'
+ '<span id="bk_volume_value" class="bk_volume_value" style="position: absolute; right: 8px; border-radius: 2px; background: rgba(0, 0, 0, 0.72); color: #fff; padding: 4px; font-size: 0.9em; bottom: -4px;"></span>'
+ '</div>';
break;
case 'small':
$markup += '<div class="bk_volume_wrapper" style="position: absolute; width: 100%; padding: 6px 12px 6px 67px; bottom: 3%;">'
+ '<div class="progbar_wrapper" style="position: relative; width: 100%; margin: 0; padding: 0;">'
+ '<div class="progbar" style="width: 72%"><div class="progbar_empty" style="position: relative"><div class="thumb" id="bk_volume_handle"></div></div></div>'
+ '<span id="bk_volume_value" class="bk_volume_value" style="position: absolute; right: 8px; border-radius: 2px; background: rgba(0, 0, 0, 0.72); color: #fff; padding: 4px; font-size: 11px; line-height: 1; bottom: -6px;"></span>'
+ '</div>'
+ '</div>';
break;
case 'page':
default:
$markup += '<tr class="bk_volume_wrapper">'
+ '<td></td>'
+ '<td><div class="progbar"><div class="progbar_empty" style="position: relative"><div class="thumb" id="bk_volume_handle"></div></div></div></td>'
+ '<td><span id="bk_volume_value" class="bk_volume_value"></span></td>'
+ '</tr>';
}
return $markup;
}
function adjustPlayerMarkupByType(type) {
switch (type) {
case 'large':
document.querySelector('#artarea #big_play_button').style.bottom = '10%';
document.querySelector('#artarea .logo').style.bottom = '10%';
break;
case 'small':
document.querySelector('#infolayer .logo .icon').style.top = '2px';
document.querySelector('#infolayer #maintext').style.marginTop = '0';
document.querySelector('#infolayer #linkarea').style.marginTop = '0';
document.querySelector('#infolayer #currenttitlerow').style.marginTop = '0';
document.querySelector('#infolayer #currenttitlerow').style.paddingTop = '0';
document.querySelector('#big_play_button').style.bottom = '28px';
break;
}
}
function getLeftOffset(element) {
var rect = element.getBoundingClientRect();
return rect.left + document.body.scrollLeft;
}
function saveVolume(volume) {
chrome.storage.local.set({ 'bk_bandcamp_volume': volume }, function () {
// Volume saved yay!
});
}
function restoreVolume(callback) {
chrome.storage.local.get('bk_bandcamp_volume', function (values) {
var vol = +(values['bk_bandcamp_volume']);
if (typeof vol !== 'number' || (vol > 1 || vol < 0)) {
vol = 0.72;
}
if (typeof callback === 'function') {
callback(vol);
}
});
}
})();
\ No newline at end of file
# Volume Control for Bandcamp Player
## Foreword
I got really tired of not being able to control the volume of Bandcamp audio players so I thought about trying to fix that for myself. This extension is primarily made for my own personal use, but I figured if someone else can use it as well, why not try and help.
## How this thing works
The extension works by searching for HTML5 audio players on any Bandcamp page (including the embedded players on other websites), hooking into their HTML markup where the buttons (well, Play/Pause button) and progress slider are and adding in a volume slider that can control said player's volume.
The slider takes some of the styles from Bandcamp's own player to fit the visual style so it should look fairly consistent. It's nothing too fancy, but it gets the job done. The volume slider handle is a bit more rounded to make it easier to discern it from the regular song progress bar.
The volume is saved every time it's changed and the value should persist across multiple players. So if you save a volume at 42% on one page and open up another one (it doesn't matter if it's embedded or not), the volume should default to 42% in that one, too. This eliminates the need to constantly reset it if you're browsing multiple pages quickly. Note that if there are multiple embedded players in the same page, changing volume of one will **not** affect any other. Also, only the last value changed (regardless of the player) will be saved.
Note that on Bandcamp's Discover page one slider controls **all** audio players until I figure out which is which.
## Permissions
The extension only requires `storage` permissions (so it can save the volume level), and access to the content of any Bandcamp (`https://*.bandcamp.com/`) page (so it can find the player, markup, etc.).
## License
Copyright 2017
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.
\ No newline at end of file
var gulp = require('gulp'),
bump = require('gulp-bump'),
concat = require('gulp-concat'),
del = require('del'),
header = require('gulp-header'),
footer = require('gulp-footer'),
gulpif = require('gulp-if'),
gutil = require('gulp-util'),
seq = require('run-sequence'),
stripDebug = require('gulp-strip-debug'),
uglify = require('gulp-uglify');
var env = process.env.NODE_ENV || 'dev', // prod | dev
buildDir = './build',
files = {
js: [
'./src/lib/**/*',
'./src/index.js',
],
meta: [
'./src/meta/**/*'
]
};
gulp.task('clean', function () {
return del(buildDir, { force: true });
});
gulp.task('publish:version', function () {
if (env === 'dev') {
return;
}
gulp.src('./package.json')
.pipe(bump({ type: 'patch' }))
.pipe(gulp.dest('./'));
gulp.src('./src/meta/manifest.json')
.pipe(bump({ type: 'patch' }))
.pipe(gulp.dest('./src/meta'));
});
gulp.task('publish', function () {
seq('clean', 'publish:version', 'compile', 'meta');
});
gulp.task('compile', function () {
return gulp.src(files.js)
.pipe(concat('content.js'))
.pipe(header('(function () {\n\n\'use strict\';\n\n'))
.pipe(footer('\n})();\n'))
.pipe(gulpif(env === 'prod', stripDebug()))
.pipe(gulpif(env === 'prod', uglify().on('error', gutil.log)))
.pipe(gulp.dest(buildDir));
});
gulp.task('meta', function () {
return gulp.src(files.meta)
.pipe(gulp.dest(buildDir));
});
gulp.task('build', function () {
seq('clean', ['compile', 'meta']);
});
gulp.task('watch', function () {
gulp.watch(files.meta, { debounceDelay: 500 }, ['meta']);
gulp.watch(files.js, { debounceDelay: 500 }, ['compile']);
});
{
"name": "bandcamp-volume-control",
"version": "0.2.1",
"description": "It's a volume control for Bandcamp audio player. The extension adds another slider (somewhere around the one that controls the track progress) to control the volume.",
"main": "src/index.js",
"scripts": {
"test": "echo \"No tests made yet. YOLO!\" && exit 1",
"build-prod": "NODE_ENV=prod gulp build",
"build-dev": "NODE_ENV=dev gulp build",
"publish": "NODE_ENV=prod gulp publish",
"watch": "NODE_ENV=dev gulp watch",
"dev": "NODE_ENV=dev gulp build watch"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/markoooo/bandcamp-volume-control.git"
},
"keywords": [
"bandcamp",
"audio",
"volume",
"extension",
"webextension",
"browser"
],
"author": "butterknight",
"license": "Apache-2.0",
"bugs": {
"url": "https://gitlab.com/markoooo/bandcamp-volume-control/issues"
},
"homepage": "https://gitlab.com/markoooo/bandcamp-volume-control#README",
"devDependencies": {
"del": "^2.2.2",
"gulp": "^3.9.1",
"gulp-bump": "^2.5.1",
"gulp-concat": "^2.6.1",
"gulp-footer": "^1.0.5",
"gulp-header": "^1.8.8",
"gulp-if": "^2.0.2",
"gulp-strip-debug": "^1.1.0",
"gulp-uglify": "^2.0.0",
"gulp-util": "^3.0.8",
"run-sequence": "^1.2.2"
}
}
var BVC = new BandcampVolumeControl();
BVC.init();
\ No newline at end of file
var BandcampVolumeControl = function () {
console.log('Bandcamp Volume Control loaded!');
};
BandcampVolumeControl.prototype.init = function () {
var BVC = this;
this.type = this.findType();
this.players = this.findPlayers();
this.markupControl = MarkupControlFactory.getMarkupControlByType(this.type);
this.markupControl.activate(this);
this.storage = new StorageService();
this.storage.restoreVolume(function (volume) {
BVC.updateVolume.call(BVC, volume);
});
console.log('Bandcamp Volume Control activated!');
};
BandcampVolumeControl.prototype.findType = function () {
var url = window.location.href,
type;
if (isEmbededPlayer()) {
if (isLargePlayer()) {
type = 'large';
}
else if (isSmallPlayer()) {
type = 'small';
}
else {
type = 'unsupported';
throw new Error("Sorry, but this player type isn't currently supported.");
}
}
else {
if (document.getElementById('discover')) {
type = 'discover';
}
else if (document.getElementById('trackInfo')) {
type = 'page';
}
else {
type = 'unsupported';
throw new Error("Sorry, but this player type isn't currently supported.");
}
}
return type;
function isEmbededPlayer() {
return url.indexOf('EmbeddedPlayer') > -1;
}
function isLargePlayer() {
return url.indexOf('size=large') > -1
&& url.indexOf('artwork') < 0;
}
function isSmallPlayer() {
return url.indexOf('size=large') > -1 && url.indexOf('artwork=small') > -1
|| url.indexOf('size=large') > -1 && url.indexOf('artwork=none') > -1;
}
};