Mix Youtube videos like a true DJ! https://dj.tflcl.xyz
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
5.8 KiB

// Loads the IFrame Player API code asynchronously.
let tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
let firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
//THE DJ CROSSFADER!!!
let crossfader = document.querySelector('#crossfader');
class player {
trackID;
player;
crossfaderCoef;
constructor(id) {
this.id = id;
this.iframe = document.querySelector('#'+this.id);
this.node = this.iframe.parentNode;
this.volume = this.node.querySelector('.volume-slider').value;
this.volSliderVal = this.node.querySelector('.volume-slider-value');
this.volVal = this.node.querySelector('.volume-value')
this.volSliderVal.innerHTML = this.volume + '%';
this.loadButton = this.node.querySelector('.load-button');
this.speedSlider = this.node.querySelector('.speed-slider');
this.speedSlider.nextElementSibling.querySelector('.speed-value').innerHTML = this.speedSlider.value;
}
init(trackID) {
this.trackID = trackID;
let src = `https://www.youtube-nocookie.com/embed/${trackID}?disablekb=1&enablejsapi=1&playsinline=1&autoplay=0&rel=0&modestbranding=1&iv_load_policy=3&version=3`;
this.iframe.setAttribute('src', src);
this.player = new YT.Player(this.id, {
events: {
'onReady': this.onPlayerReady.bind(this),
'onStateChange': this.onPlayerStateChange.bind(this),
'onError': this.onPlayerError.bind(this)
}
});
}
onPlayerReady(e) {
this.player.setVolume(this.volume);
this.node.querySelector('.volume-slider').addEventListener('input', (e) => {
this.volume = e.target.value;
this.volSliderVal.innerHTML = this.volume + '%';
this.volumeUpdate();
});
this.loadButton.addEventListener('click', () => {
let url = prompt('Enter a YouTube URL (track or playlist):', 'https://www.youtube.com/watch?v=' + this.trackID);
if (url) {
let newTrackID = get_video_id(url);
let newPlaylistID = get_playlist_id(url);
if (newPlaylistID[0] != 0) {
console.log('playlistid to load: ' + newPlaylistID);
this.loadPlaylist(newPlaylistID);
} else if (newTrackID != 0) {
console.log('track to load: ' + newTrackID);
this.load(newTrackID);
} else {
console.log('Nothing to load!');
}
}
});
this.speedSlider.addEventListener('input', (e) => {
this.speedSlider.nextElementSibling.querySelector('.speed-value').innerHTML = e.target.value;
this.player.setPlaybackRate(Number(e.target.value));
});
this.volumeUpdate();
}
onPlayerStateChange(e) {
console.log(this.id + ' state change: ' + e.data);
}
onPlayerError(e) {
console.log(this.id + ' - Error: ' + e.data);
}
load(trackID) {
this.player.loadVideoById(trackID);
}
loadPlaylist(playlistID) {
//First element should be the playlist ID, second element the index of the video to play
let list = playlistID[0];
let index = playlistID[1];
this.player.loadPlaylist({
list: list,
listType: 'playlist',
index: index
});
}
volumeUpdate() {
let outputVolume = Math.round(this.volume * this.crossfaderCoef);
this.player.setVolume(outputVolume);
this.volVal.innerHTML = outputVolume + '%';
let boxShadowStyle = '0 0 3em #ddbddd'
this.iframe.style.boxShadow = boxShadowStyle + componentToHex(Math.round(this.crossfaderCoef*255));
}
}
let playerA = new player('playerA');
let playerB = new player('playerB');
function onYouTubeIframeAPIReady() {
playerA.init('anWaxGNDRJ4')
playerB.init('PhEuAuhH418');
}
calcCrossfaderCoefs(crossfader.value);
crossfader.addEventListener('input', (e) => {
//Makes the slider stick on the middle
if (Math.abs(crossfader.value - 500) < 15) {
crossfader.value = 500;
}
calcCrossfaderCoefs(crossfader.value);
playerA.volumeUpdate();
playerB.volumeUpdate();
// updateCrossfader(crossfader.value);
});
function calcCrossfaderCoefs(input) {
let normVal = input / 1000;
//Classic 'transition' curve
let coefB = Math.min(normVal*2, 1);
let coefA = Math.min((1-normVal)*2, 1);
//Cheap smooth curve
// let coefB = normVal ** 2;
// let coefA = (1 - normVal) ** 2;
//Amplitude-preserving curve (but not power)
// let coefB = (normVal ** 2) * (3 - 2 * normVal);
// let coefA = 1 - coefB;
playerA.crossfaderCoef = coefA;
playerB.crossfaderCoef = coefB;
}
// From https://stackoverflow.com/a/55616161/14182148
function get_playlist_id(url) {
let VID_REGEX = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
let regPlaylist = /[?&]list=([^#\&\?]+)/;
let regIndex = /[?&]index=([^#]+)/;
let match = url.match(regPlaylist);
let matchIndex = url.match(regIndex);
let index = 0;
if (matchIndex != null) {
index = matchIndex[1] - 1;
}
if (match != null) {
return [match[1], index];
} else {
return [0,0];
}
}
function get_video_id(url) { //originally 'video_id_from_playlist'
let VID_REGEX = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
let video_id = url.match(VID_REGEX);
if (video_id != null) {
return video_id[2];
} else {
return 0;
}
}
// From https://thomaspark.co/projects/needledrop/ by Thomas Park
// function getYouTubeID(url){
// url = url.split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
// return (url[2] !== undefined) ? url[2].split(/[^0-9a-z_\-]/i)[0] : false;
// }
// function get_video_id(url) {
// url = url.split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
// return (url[2] !== undefined) ? url[2].split(/[^0-9a-z_\-]/i)[0] : url[0];
// }
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
// Thumbnails: https://i1.ytimg.com/vi/{trackID}/mqdefault.jpg