// 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