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.
203 lines
5.8 KiB
203 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
|
|
|