// Check if client is using a mobile phone const getMobileOS = () => { const ua = navigator.userAgent if (/android/i.test(ua)) { return "Android" } else if (/iPad|iPhone|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) { return "iOS" } return "Other" } const isRunningOn = getMobileOS(); if (isRunningOn != 'Other') { alert('Whoop, it seems that you attempt to access this website on a ' + isRunningOn + ' mobile/tablet, which might not work properly. You should better use a computer instead.'); } // Loads the Youtube 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 resp = check_url(url); if (resp < 0) { alert('Wrong URL...'); console.log('Nothing to load!'); } else if (resp == 0) { 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 { alert('Wrong URL...'); console.log('Nothing to load!'); } } else if (resp == 1) { // fetch_bandcamp_url(url); alert('Wrong URL...'); 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); } cue(trackID) { this.player.cueVideoById(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) < 20) { 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; } function check_url(url) { let test = url.indexOf('youtu'); if (test > -1 && test < 13) { return 0; //It's most likely a youtube link } else if (url.indexOf('.bandcamp') > -1 || url.indexOf('/album/') > -1 || url.indexOf('/track/') > -1) { return 1; //It's most likely a bandcamp link } else { return -1; //Wrong link } } // 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); // console.log('match ' + match); let matchIndex = url.match(regIndex); // console.log('matchindex ' + matchIndex); // console.log('regexp ' + match.match(/=LL([^#\&\?]*)/)); let index = 0; if (matchIndex != null) { index = matchIndex[1] - 1; } if (match != null && match[1].lenght > 2) { // lenght > 2 prevents from trying to load a "list=LL" (liked videos of signed-in user) 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; } } function fetch_bandcamp_url(url) { let iframe = document.createElement('iframe'); iframe.setAttribute('class','hidden'); iframe.setAttribute('src',url); playerB.node.parentNode.appendChild(iframe); console.log(iframe); let scrap = iframe.contentWindow.document.querySelector('script[data-tralbum]'); console.log('scrap: ' + scrap); } // Thumbnails: https://i1.ytimg.com/vi/{trackID}/mqdefault.jpg // Converts values from 0-255 to 00-ff function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } // ----------------------------------- // BANDCAMP Tests // ----------------------------------- // let surl = 'https://bandcamp.com/api/bcsearch_public_api/1/autocomplete_elastic'; // let req = {"search_text": "techno", // "search_filter": "", // "full_page": false, // "fan_id": null // }; // let bcURL = 'https://clett.bandcamp.com/track/placenta'; // let request = new Request(bcURL); // let headers = new Headers({ // 'X-Requested-With': 'com.bandcamp.android', // 'Content-Type': 'application/json', // 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; Unknown Device)', // 'Host': 'bandcamp.com', // }); // // let data; // fetch(bcURL, { // method: 'GET', // headers: headers // }) // .then(response => response.json()) // .then(data => console.log('data: ' + data)); // console.log('response: ' + data); // console.log('data: ' + data); // let html = async await fetch(bcURL).text(); // html as text // let doc = new DOMParser().parseFromString(html, 'text/html'); // doc.title; doc.body; // let Http = new XMLHttpRequest(); // Http.open("GET", bcURL); // Http.send(); // // Http.onreadystatechange = (e) => { // console.log(Http.responseText) // } // ----------------------------------- // BACKGROUND // ----------------------------------- let areStarsShooting = true; let shootingStarWrapper = document.getElementsByClassName('shooting-stars-wrapper')[0]; let shootingStar; function newShootingStar() { shootingStar = document.createElement('div'); let topStart = Math.floor(0.6 * window.innerHeight * Math.random()); let leftStart = Math.floor(window.innerWidth * (0.1 + 0.8 * Math.random())); let duration = 1000 + Math.floor(Math.random() * 5000); let distance = 200 + Math.floor(Math.random() * 500); let angle = Math.floor(Math.random() * 30) - 15; let direction = Math.random() < 0.5 ? -1 : 1; shootingStar.style.top = topStart + 'px'; shootingStar.style.left = leftStart + 'px'; // root.style.setProperty('--shooting-star-duration', duration + "px"); // shootingStarCSS.insertRule(':root{--shooting-star-duration: '+ duration + 'ms}'); document.documentElement.style.setProperty('--shooting-star-duration', duration + "ms"); document.documentElement.style.setProperty('--shooting-star-translate', distance + "px"); document.documentElement.style.setProperty('--shooting-star-angle', angle + "deg"); document.documentElement.style.setProperty('--shooting-star-direction', direction); shootingStarWrapper.appendChild(shootingStar); shootingStar.classList.add('shooting-star'); setTimeout(() => { shootingStar.remove(); // let nextIn = 10; let nextIn = 7000 + Math.floor(Math.random() * 5000); if (areStarsShooting) { setTimeout(() => { newShootingStar(); }, nextIn); } }, duration); // setTimeout('newShootingStar()', duration + nextIn); } newShootingStar(); document.getElementById('shooting-stars-enable').innerHTML = '⭐️Disable shooting stars⭐️'; document.getElementById('shooting-stars-enable').addEventListener('click', () => { if (areStarsShooting) { document.getElementById('shooting-stars-enable').innerHTML = '⭐️Enable shooting stars⭐️'; areStarsShooting = false; } else { document.getElementById('shooting-stars-enable').innerHTML = '⭐️Disable shooting stars⭐️'; areStarsShooting = true; newShootingStar(); } }); // Enable/disable shooting stars (with P5JS background) // if (typeof processingSketch !== 'undefined') { // let separatorSpan = document.createElement('span'); // separatorSpan.innerHTML = ' - '; // document.getElementById('shooting-stars-enable').after(separatorSpan); // document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️'; // } // document.getElementById('shooting-stars-enable').addEventListener('click', () => { // if (processingIsOn) { // processingSketch.frameRate(0); // document.getElementById('shooting-stars-enable').innerHTML = 'Enable shooting stars⭐️'; // processingIsOn = false; // } else { // processingSketch.frameRate(24); // document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️'; // processingIsOn = true; // } // }); /*------------------------------------------------------- - EMAIL OBFUSCATION - found here http://www.grall.name/posts/1/antiSpam-emailAddressObfuscation.html -------------------------------------------------------*/ document.getElementById('contact-me').addEventListener('click', (e) => { var y = decode("pbagnpg@gsypy.klm"); //https://rot13.com/ e.target.setAttribute("href", "mailto:" + y); }) function decode(a) { // ROT13 : a Caesar cipher // letter -> letter' such that code(letter') = (code(letter) + 13) modulo 26 return a.replace(/[a-zA-Z]/g, function(c){ return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26); }) }; /** SEARCH WITH YOUTUBE API */ let lastSearchQuery; let results; gapi.load("client", loadClient); // https://developers.google.com/youtube/v3/docs/search/list function loadClient() { gapi.client.setApiKey("AIzaSyCuz5tGc9oDxw6RZnpibakYFHzMvsd8bUs"); return gapi.client.load("https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest") .then(function() { console.log("GAPI client loaded for API"); }, function(err) { console.error("Error loading GAPI client for API", err); }); } function execute(query) { // Make sure the client is loaded before calling this method. return gapi.client.youtube.search.list({ "part": [ "snippet" ], "maxResults": 15, "q": query, "type": [ "video" ], "fields": "items(id/videoId,snippet(title,thumbnails/medium))" }) .then(function(response) { if (response.status == 200) { results = response.result; localStorage.setItem('sres', JSON.stringify(results)); displaySearchResults(results); } // Handle the results here (response.result has the parsed body). console.log("Response", response); }, function(err) { console.error("Execute error", err); alert("Whoops, something went wrong. \n Maybe all of my daily Youtube Data API requests have been used. Try again later! \n Maybe it's something else. Try again later?"); }); } const searchForm = document.getElementById('search-bar'); const searchQueryObj = document.getElementById('search-query'); const searchButtonObj = document.getElementById('search-button'); const searchResultsObj = document.getElementById('search-results'); //When the page has loaded, store the hidden search-item class div as a template for future searchs const searchResultItemObj = searchResultsObj.querySelector('.search-item').cloneNode(true); searchResultsObj.querySelector('.search-item').remove(); searchForm.addEventListener('submit', (e) => { e.preventDefault(); // We handle the form submit by ourselve if (searchQueryObj.value != lastSearchQuery) { // prevent useless requests console.log('Search for: ' + searchQueryObj.value); execute(searchQueryObj.value); lastSearchQuery = searchQueryObj.value; } return false; }); function displaySearchResults(res) { // First we clean previous search results while (searchResultsObj.firstChild) { searchResultsObj.removeChild(searchResultsObj.firstChild); } res.items.forEach((item) => { let itemElmt = searchResultItemObj.cloneNode(true); itemElmt.querySelector('.search-item-info img').setAttribute('src', item.snippet.thumbnails.medium.url); itemElmt.querySelector('.search-item-info span').innerHTML = item.snippet.title; itemElmt.querySelector('.search-item-info a').setAttribute('href', 'https://www.youtube.com/watch?v=' + item.id.videoId) itemElmt.dataset.id = item.id.videoId; itemElmt.querySelector('.load-to-A').addEventListener('click', () => { playerA.cue(item.id.videoId); }); itemElmt.querySelector('.load-to-B').addEventListener('click', () => { playerB.cue(item.id.videoId); }); console.log(itemElmt); searchResultsObj.appendChild(itemElmt); }); } // For testing purpose results = JSON.parse(localStorage.getItem('sres')); displaySearchResults(results);