Skip to content

Instantly share code, notes, and snippets.

@hinell hinell/SC.Tracks.js
Created Apr 28, 2019

Embed
What would you like to do?
/********************
Name : SC.Tracks snippet
Version : 0.1.2
Last-Modified : 18.04.19
Description :
The programm walks over tracks:
Tracks.nodes = [
1track
2track <---- tracks.current
3track
4track
5track
]
History:
0.1.2
* Added confirmation dialog when accidentally clicking relocation
**********************/
text = [
`irritated`
, `centering`
, `Storm Thoughts`
, `Mattias spelar modulsynt VIII`
, `Dave Smith (Sequential) OB6`
, `fire in the cathedral`
// , `Aeternum I`
// , `SB410`
// , `The Outer Edge`
// , `Luullahan jotta on lysti olla`
// , `Evolutions`
// , `Distillery Road`
// , `Mildred`
// , `Lauvene - Infinity`
// , `Soul Elemental`
// , `Bones - ChangeOfScenery`
// , `Keeping On`
// , `Break Through`
// , `Where The River Flows`
// , `Boson Spin - Ambient Bite 390`
// , `Ragnarok`
// , `Subliminal`
// , `Vertrautheit`
// , `Par collision d'étoiles`
// , `Trio Initiate Disquiet0367`
// , `Going Forward While Looking Back`
// , `L12`
// , `Another Carefree Day On The Nostromo`
// , `Boson Spin - Ambient Bite 387`
]
console.clear()
// Utils
HTMLElement.prototype.$ = function(){return this.querySelector.apply(this,arguments) };
HTMLElement.prototype.$$= function(){return this.querySelectorAll.apply(this,arguments)};
String.prototype.contains = function(str){ return new RegExp(str).test(this) }
Array.prototype.forEach =
Array.prototype.each = function (fn ,this_){
if(this.length <=0 || fn == undefined) return;
let i = 0
if (this_) fn = fn.bind(this_);
while (i < this.length) {
fn(this[i],i++)
}
};
Set.prototype.toArray = function(){
let arr = [], iterator = this.values(), current;
while(!(current = iterator.next() ).done) arr.push(current.value) ;
return arr
};
Error.prototype.throw = function(){
throw this
}
NodeList.prototype.addEventListener = function (name, handler){
[].slice.call(this).forEach(el => el.addEventListener(name, handler))
}
NodeList.prototype.removeEventListener = function (name, handler){
[].slice.call(this).forEach(el => el.removeEventListener(name, handler))
}
var NodeListX = class {
constructor(nodes){
this.nodes = [].slice.call(nodes);
}
filter(fn, that){ return new NodeListX(this.nodes.filter(fn, that)) }
addEventListener(name, handler){
if (!this.nodes.length) { return }
this.nodes.forEach(el => el.addEventListener(name, handler))
}
removeEventListener(name, handler){
if (!this.nodes.length) { return }
this.nodes.forEach(el => el.removeEventListener(name, handler))
}
concat(nodelistx) {
return new NodeListX(this.nodes.concat(nodelistx.nodes))
}
}
// Major classes
// Class which runs certain function "fn" at fixed interval of time
var Observer = function(fn,frequency){
this.fn = fn
this.fr = frequency
}
Observer.prototype.observe =
Observer.prototype.start = function (){
if(this.interval || this.running) {return};
this.running = true;
this.interval = setInterval(function(){
this.fn()
}.bind(this),this.fr)
return this
}
Observer.prototype.disconnect =
Observer.prototype.stop =
Observer.prototype.cancel = function(){
if(this.running && this.interval) {
clearInterval(this.interval);
return this.running = this.interval = false;
}
return this.running
}
var Button = class {
constructor (tag, id, style) {
this.el = document.getElementById(id) || document.createElement(tag || 'span')
this.el.id = id;
this.el.style = style || '';
this.defaultStyle = style;
}
insertInto (el){ el.appendChild(el) }
listen(event,fn){ this.el.addEventListener(event, fn) }
label(str) { this.el.innerText = str || ''}
style(st){
this.el.style = this.defaultStyle+st;
}
}
var SCButton = Button;
var Waveforms = function(selector){ this.selector = selector }
Waveforms.prototype.hide = function(){
if (this.hidden) {return}
this.hidden = true
style = document.querySelector('style').sheet;
style.insertRule((this.selector || '.waveform')+' {display: none}',style.cssRules.length)
}
var Track = class {
constructor(e){
this.e = e
this.mouseClickEvent = new MouseEvent('click', {bubble: true, cancelable: true, composed: false})
}
play(){ this.toggle(); return this }
toggle(){
this.e.querySelector(Track.playClassName)
.dispatchEvent(this.mouseClickEvent)
return this
}
}
Track.playClassName = '.sound__header .sc-button-play';
// Events listeners:
// tracks.onRunning
// tracks.onStop
// tracks.onBad
// tracks.onFound
// tracks.onFetch
var Tracks = function(el,waveformselector,opt){
this.update(el);
this.current = this.nodes[0];
this.currentIndex = 0; // current checked node index of the tracks
// this.maxnodes = opt.nodeslimit == void 0 ? 127 : opt.nodeslimit // maximum nodes to load & to enumerate over
this.maxnodes = opt.nodeslimit;
this.direction = 'wn' // searching direction: up or wn (down)
this.waveforms = new Waveforms(waveformselector);
// Tracks removal config
this.remove = opt.nodeRemove == void 0 ? false : opt.nodeRemove
this.hide = opt.nodeHide == void 0 ? true : opt.nodeHide
this.tracksLImit = opt.tracksLimit || 200;
// Removing extr tracks
this.removedTracks= 0;
this.extraTrackRemoveObject = new Observer(function(){
itemsCount = this.maxTracks;
if (this.tracksLImit
&& this.nodes.length > this.tracksLImit
&& this.currentIndex > this.tracksLImit - 1
) {
let tracksToRemove = [];
tracksToRemove = [].slice.call(tracks.nodes, 0, this.tracksLImit)
tracksToRemove.forEach(e => {
e.parentElement.removeChild(e)
});
this.removedTracks += tracksToRemove.length;
console.log(`Total tracks removed: %d`, this.removedTracks);
}
}.bind(this), 2000)
// fetching triggers soundcloud tracks dowload
this.fetching = new Observer(function(){
// console.clear()
// console.log('FETCHING IN <= NODES',this.fetchedtimes,this.nodes.length)
// console.log(this.current)
this.fetchedtimes ? this.fetchedtimes++ : (this.fetchedtimes = 1);
this.maxnodes && this.fetchedtimes >= (this.maxnodes * 5) && this.fetching.cancel()
this.nodes.length >= this.maxnodes && this.fetching.cancel();
this.current.nextElementSibling || this._fetch()
// play last track everytime we get the tracks feed
/*let t = this.nodes[this.nodes.length - 1];
if(t) {
new Track(t).toggle()
}*/
// this._fetch()
}.bind(this),100*4)
// Most important part.
// Checking method iterates over fetched tracks
// and checks match for the track we have provided by Tracks.find() method
this.checking = new Observer(this.check = function(){
this._match()
if(this.found) {
// once found let's start to play
/*
try {
new Track(this.current).toggle()
} catch (e){
console.log('Whooops. Play is failed!', e)
}
*/
this.onFound && this.onFound();
console.clear()
console.log('IT IS FOUND :) ',this.nodes.length);
console.log(this.current)
this.checking.cancel();
this.fetching.cancel()
return
}
// if(this._hasNext()) return this._walk();
if(this._hasNext()) {
this.onRunning && this.onRunning(this);
this._walk();
}
// else { this.direction == 'wn' && this.nodes.length < this.maxnodes && this._fetch()};
// FINALLY: NOTHING FOUND
if(!this._hasNext() && this.nodes.length >= this.maxnodes){
console.clear()
console.log('NOTHING FOUND :( )',this.nodes.length)
this.onBad && this.onBad(this);
window.scroll(0,0)
this.checking.cancel();
this.fetching.cancel()
return
}
}.bind(this),100*1.5);
// MAIN LOOOP with observers
this.observers = [
this.checking
, this.fetching
, this.extraTrackRemoveObject
]
}
// Resets nodes to the specified element's children
Tracks.prototype.update = function(el){
this.el = el;
if(!el) {
this.nodes = [];
return
}
this.className = '.'+el.className.split(/\s/g).join('.')
this.nodes = el.children;
}
Tracks.prototype._fetch = function(limit){
let lastPosition = window.scrollY;
window.scroll(0,9999999)
// window.scroll(0,lastPosition);
this.onFetch && setTimeout(this.onFetch, 1000);
}
Tracks.prototype._match = function(selector){
this.remove && (this.checked = this.checked == void 0 ? 0 : ++this.checked)
let textelem = this.current.querySelector(this.selector || selector);
textelem || new Error('Invalid selector: the target for string search hasn\'t been found! ').throw()
if(this.str instanceof Array) {
return this.found = this.str.some(searchString => new RegExp(searchString).test(textelem.innerText) )
}
this.found = textelem.innerText.contains(this.str)
}
// True if a sibling of the current node exists
Tracks.prototype._hasNext= function(){
switch (this.direction){
case 'up': return this.current.previousElementSibling;
case 'wn': return this.current.nextElementSibling;
}
}
// Walking next
Tracks.prototype._walk = function(){
let previous;
this.current.style.display == 'none' && (this.current.style.display = '')
switch (this.direction){
case 'up': this.current = this.current.previousElementSibling; break;
case 'wn':
if(this.remove) {
previous = this.current;
this.current = this.current.nextElementSibling;
previous.parentElement.removeChild(previous)
}
if(this.hide) {
this.current.style.display = 'none';
this.current = this.current.nextElementSibling;
}
if(!this.remove && !this.hide) {
this.current.style.border = 'none'
this.current.removeAttribute('style')
this.current = this.current.nextElementSibling
this.current.style.border = 'inset .6em #ff5500';
}
}
this.currentIndex = [].slice.call(this.nodes).indexOf(this.current);
}
Tracks.prototype.find = function(searchstring,selector,direction = 'wn'){
this.str = searchstring || this.str || new Error('Invalid argument: string required!').throw()
this.selector = selector || this.selector || new Error('Invalid argument: selector for string search required!').throw()
this.direction= direction || this.direction|| 'wn';
console.log('SEARCHING ',this.direction == 'wn' ? 'DOWN' : 'UP')
}
Tracks.prototype.pause =
Tracks.prototype.stop = function(){
if(!this.el) { return }
this.running = false
this.observers.forEach(e => e.stop() );
this.onStop && this.onStop(this);
}
Tracks.prototype.start = function(){
if(!this.el){
this.running = false;
return
}
if(this.nodes.length !== this.el.children.length){
this.pause()
this.reset()
}
this.running = true
this.observers.forEach(e => e.start() );
}
Tracks.prototype.toggle = function(){
if(!this.el) { return }
this.observers.every(o => o.running)
? this.pause()
: this.start()
}
// RESET SEARCH. Tip: uset it only after pausing
Tracks.prototype.reset = function(){
this.current = this.nodes[this.currentIndex = 0];
}
Tracks.prototype.show = function(){
[].slice.call(this.nodes).each(e => e.style.display = '')
}
if(!!tracks) { tracks.pause(); tracks = undefined }
var tracks,
// RESET & STOP BUTTON
resetButton, stopButton;
resetButton = new SCButton('span', 'snippetResetButtonTrack', 'cursor: pointer');
resetButton.el.innerText = "R";
resetButton.el.title = "Reset button. Press to reset search to 0";
resetButton.listen('click', () => {
tracks.pause();
tracks.reset();
stopButton.label('PROCEED: ' + tracks.currentIndex);
stopButton.el.style = '';
})
stopButton = new SCButton('span', 'trackFinderStopButton', 'width: 9em; cursor: pointer');
stopButton.el.innerText = 'START SEARCH';
stopButton.el.onclick = () => tracks.toggle();
stopButton.el.title = 'Start/Pause button'
nextButton = new SCButton('span', 'trackPassOverButton', 'cursor: pointer')
nextButton.el.innerText = 'N';
nextButton.el.title = 'Proceed to the next track';
nextButton.el.onclick = function(){
let next = tracks.current.nextElementSibling;
if(!next) { return }
tracks.current = next;
tracks.start();
}
currentTrackButton = new SCButton('span', 'snippetShowCurrentTruck', 'cursor: pointer;' )
currentTrackButton.el.innerText = 'CR';
currentTrackButton.el.title = 'Scroll current track into view'
currentTrackButton.el.onclick = function(){
tracks.current.scrollIntoView();
tracks.current.style.border = 'inset .6em #ff5500';
setTimeout(() => tracks.current.style.border = '', 5000 )
};
[resetButton, stopButton, nextButton, currentTrackButton]
.forEach(b => b.el.className = 'header__link header__proUpsell');
// Program which prevents from following links and shows
// Confirmation dialog
// No arguments
var allLinks = new NodeListX(allLinks || []);
var confirmRelocationInit = function(){
let confirmRelocation;
allLinks.removeEventListener(`click`, confirmRelocation);
let links = new NodeListX(document.querySelectorAll(`a`));
links = links.filter((el) => {
const excludePattern = /Play|Pause|Stop|Continue|Repeat/g;
return !(excludePattern.test(el.title) || el.classList.contains(`compactTrackList__moreLink`) )
});
confirmRelocation = function (e) {
e.stopPropagation()
dialogIsopen = true;
const el = e.currentTarget;
const msg = `
You are currently in search mode. Are you sure you want to go to ${el && el.href}?
(press cancel to stay here)`;
const allowedToRelocate = confirm(msg);
if (!allowedToRelocate) {
e.preventDefault();
}
};
links.addEventListener(`click`, confirmRelocation);
allLinks = links;
};
var tracksClassName = '.lazyLoadingList__list.sc-list-nostyle.sc-clearfix';
var initTrackSearch = function(){
// Guard from accident relocation
document.querySelector('div.header__middle').style='display: none';
var panel = document.querySelector('.'+'header__right sc-clearfix'.split(' ').join('.'))
// INITIALIZING BUTTONS
var buttons = [nextButton, resetButton, stopButton, currentTrackButton];
buttons.reverse().forEach(function(button){
let buttonEl = document.getElementById(button.el.id)
if(!buttonEl) {
panel.insertAdjacentElement('afterbegin', button.el);
}
})
// resetButton.el.style = stopButton.el.style = ''; // display
if(document.location.pathname === '/stream'){
var tracklist = document.querySelector(tracksClassName);
if(tracks) {
tracks.update(tracklist)
} else {
tracks = new Tracks(tracklist,'.waveform',{
nodeslimit : 100*10
,nodeHide : false // Hide nodes
});
tracks.waveforms.hide();
}
tracks.onRunning = function(){
stopButton.label('...' + tracks.currentIndex);
stopButton.style('cursor: wait')
}
tracks.onBad = function(tracks){
stopButton.label('BAD :('+tracks.currentIndex)
}
tracks.onFound = function(){
stopButton.label(`SUCCESS ${this.currentIndex}:`);
var removeHighlight = function(){
this.current.style.border = ''
}
// Highlight found node
currentTrackButton.el.onclick()
this.current.addEventListener('mouseover', removeHighlight)
setTimeout(function(){
this.current.removeEventListener('mouseover', removeHighlight)
}.bind(this))
// window.scroll(0,this.foundnode.offsetTop)
// window.scroll(0,this.current.getClientRects()[0].top+window.pageYOffset);
}
tracks.onStop = function(){
stopButton.label('RESTART: ' + tracks.currentIndex);
stopButton.style('')
}
tracks.onFetch = confirmRelocationInit;
confirmRelocationInit();
textsearchselector = '.soundTitle__usernameTitleContainer'
// text
;tracks.find(text,textsearchselector,'wn')
// ;tracks.toggle()
} else {
;
(resetButtonOk) && (resetButton.el.style = 'display: none'); // hide
(stopButtonOk) && (stopButton.el.style = 'display: none');
tracks && tracks.update(undefined);
document.querySelector('div.header__middle').style='display:';
}
}
if (window.location.pathname !== `/stream`) {
const stream = `https://soundcloud.com/stream`;
window.location.assign(stream)
alert(`You are about to go to the right page.\n Don't forget to run scripts!`)
} else {
var tracksFindingInterval;
if(tracksFindingInterval){
console.log('Elements update interval cleared!')
clearInterval(tracksFindingInterval)
};
tracksFindingInterval = setInterval(initTrackSearch, 5000);
initTrackSearch();
;test = function(str, str2){ return new RegExp(str).test(str2 || str) };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.