您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Turn those beastly 4chan threads in an easy to use, easy to watch stream of content, given to you in a gallery-like format. Batteries not included
当前为
// ==UserScript== // @name stream4chan // @namespace http://tampermonkey.net/ // @version 4.3 // @description Turn those beastly 4chan threads in an easy to use, easy to watch stream of content, given to you in a gallery-like format. Batteries not included // @author Lauchlan105 // @match http://boards.4chan.org/*/thread/* // @grant none // ==/UserScript== ////////////////// // # Settings # // ////////////////// ////////////////// // # Settings # // ////////////////// /* fix resizing (again) remove (S) for shuffle change settings font color make video size change with gallery prevent arrow from being pushed outside */ var settingsArray = [ //Loop whole thread true, //Play automatically true, //Shuffle on startup false, //Play Webms true, //Show Webm controls true, //Play webm sound true, //Play Gifs true, //Gif duration (Seconds) 3, //Play Images true, //Image duration (Seconds) 3, //Open Stream4chan on startup true, //Override Individual media? true, //Select Media randomly false ]; /////////////////// // # Variables # // /////////////////// //Placeholder variables var globalTimeout; var webm; var gif; var png; var jpg; var SOT; var EOT; var noneSelected; var allContent; var usedContent; var currentContent; //////////////////////////// // # Media Object Model # // //////////////////////////// class Media{ constructor(thumb, source, id){ // local scope variable for object access via video/thumbnail elements var obj = this; this.position = 0; this.playcount = 0; this.prevMedia = noneSelected; this.nextMedia = noneSelected; this.id = id === undefined ? "" : id; this.thumb = document.createElement('img'); this.thumb.src = thumb; this.thumb.setAttribute('class','sfc-slide-preview'); this.type = mediaType(source); this.resizeTimeout = setTimeout(function(){}, 0); //Handles deleted files/invalid media if(this.type === undefined || this.type === null){ //Force null for simpler conditionals this.type = null; console.log('Media could not be made. One or more of the following are invalid...'); console.log(' Given Arguments:'); console.log(' thumb: ' + thumb); console.log(' source: ' + source); console.log(' id: ' + id); console.log(''); console.log(' All functions will be assigned placeholders'); function placeHolderFunction(calledFunction){ el_stage.innerHTML = calledFunction + ' cannot be called on comment with id ' + this.id; console.log(calledFunction + ' cannot be called on object with id ' + this.id); return; } this.unhighlight = function(){ placeHolderFunction('unhiglight'); }; this.highlight = function(){ placeHolderFunction('highlight'); }; this.deselect = function(){ placeHolderFunction('deselect'); }; this.resize = function(){ placeHolderFunction('resize'); }; this.select = function(){ placeHolderFunction('select'); }; this.pause = function(){ placeHolderFunction('pause'); }; this.play = function(){ placeHolderFunction('play'); }; return false; } if(this.type === webm){ this.media = document.createElement('video'); this.media.setAttribute("id", "sfc-webm"); this.media.setAttribute("controls",""); this.media.setAttribute("loop",""); this.media.loop = true; this.media.setAttribute("autoplay",""); this.media.autoplay = false; this.media.setAttribute("preload",""); this.media.preload = "none"; }else if(this.type === png || this.type === gif || this.type === jpg){ this.media = document.createElement('img'); this.media.setAttribute("id","sfc-img"); this.media.alt = source; } this.media.setAttribute("class", "sfc-media"); this.media.src = source; /////////////////// //MEDIA FUNCTIONS// /////////////////// this.play = function(){ if(obj !== SOT && obj !== EOT && obj !== noneSelected){ if(obj.type == webm){ obj.media.volume = op_playSound.checked ? obj.media.volume : 0; obj.media.play(); }else if(obj.type == gif || obj.type == jpg || obj.type == png){ obj.media.src = obj.media.alt; updateAutoplay(); } } }; this.pause = function(){ clearTimeout(globalTimeout); if(obj.type == webm){ obj.media.pause(); }else if(obj.type == gif || obj.type == jpg || obj.type == png){ obj.media.src = obj.thumb.src; } }; /////////////////////// //THUMBNAIL FUNCTIONS// /////////////////////// var highlight = function(){ obj.thumb.style.border = "2px solid gainsboro"; /* //Scroll into view var pc = document.getElementById('pc' + id.substr(id.lastIndexOf('p') + 1)); pc.scrollIntoView(); //Set container style to mimic focused content pc.style.background = '#f0e0d6'; pc.style.border = '1px solid #D99F91!important'; */ }; var unhighlight = function(){ obj.thumb.style.border = "2px solid transparent"; /* //Remove focused content styles var pc = document.getElementById('pc' + id.substr(id.lastIndexOf('p') + 1)); pc.style.background = '#F0C0B0!important'; pc.style.border = '1px solid #D9BFB7'; */ }; /////////////////////////// //MISCELLANEOUS FUNCTIONS// /////////////////////////// this.select = function(){ obj.playcount++; //Deselect active content if(currentContent !== null){ currentContent.deselect(); } //Set currentContent to this object currentContent = obj; //Highlight thumbnail border highlight(); //Add media to stage obj.media.controls = op_controls.checked; el_stage.appendChild(obj.media); //Play Media obj.play(); //resize media obj.resize(); //Update auto playing updateAutoplay(); //Moves gallery so selected object is centered obj.slideGallery(); //Update counter in top right updateCounter(); //Assign and load neighbouring media obj.getNeighbours(); if(obj.prevMedia.type === webm){ obj.prevMedia.media.load(); }else if(obj.nextMedia.type === webm){ obj.nextMedia.media.load(); } }; this.deselect = function(){ obj.pause(); obj.media.currentTime = 0; unhighlight(); el_stage.innerHTML = ""; currentContent = null; //Reset prev and next media obj.prevMedia = noneSelected; obj.nextMedia = noneSelected; }; this.thumb.onclick = function(){ if(obj.thumb.style.border == "2px solid gainsboro"){ obj.deselect(); }else{ obj.select(); } }; this.resize = function(){ clearTimeout(obj.resizeTimeout); //Recursive resize function //repeats every {interval} seconds //if interval is undefined or < 0.01, default to 0.01 function execResize(interval){ //if interval is below 0.01, set to minimum 0.01 if(interval){ if(interval < 0.05) interval = 0.05; } if(isShown(el_sfc)){ if(obj === currentContent){ var setByWidth = true; console.log('resizing'); //Set to max width obj.media.style.width = window.innerWidth - (el_stagePrev.clientWidth + el_stageNext.clientWidth) + 'px'; obj.media.style.height = 'auto'; //if media height exceeds the stage height if(obj.media.clientHeight > el_stage.clientHeight){ //Set to max height instead obj.media.style.height = el_stage.clientHeight + 'px'; obj.media.style.width = 'auto'; setByWidth = false; } if(setByWidth){ // if full width, set height padding obj.media.style.marginLeft = '0px'; var difHeight = (el_stage.clientHeight - obj.media.clientHeight)/2; var topMarg = (difHeight) - ( difHeight%1 ); //Minus any decimals obj.media.style.marginTop = topMarg + 'px'; }else{ // if full height, set width padding obj.media.style.marginTop = '0px'; var difWidth = (el_stage.clientWidth - obj.media.clientWidth)/2; var leftMarg = (difWidth) - ( difWidth%1 ); //Minus any decimals obj.media.style.marginLeft = leftMarg + 'px'; } if(interval){ obj.resizeTimeout = setTimeout(function(){ execResize(interval); }, interval*1000); }else{ return; } }else{ clearTimeout(obj.resizeTimeout); } }else{ clearTimeout(obj.resizeTimeout); } return; } //Continue resizing every 1 second till video ends execResize(0.2); }; this.slideGallery = function(){ //resets slider transform el_internalSlider.style.transform = ''; //distance from the left of the browser to the middle of the thumbnail var middleOfThumb = getPageTopLeft(obj.thumb).left + (obj.thumb.clientWidth*1.5); //The distance between left side of browser and vertical middle of browser var middleOfWindow = window.innerWidth/2; //Displacement from middle var displacement = middleOfWindow - middleOfThumb; //Include the current position of the internal slider var crntSliderValue = getPageTopLeft(el_internalSlider).left; displacement += crntSliderValue; //Apply change el_internalSlider.style.transform = 'translateX( ' + displacement + 'px)'; } this.previous = function(){ obj.prevMedia.select(); } this.next = function(){ obj.nextMedia.select(); } this.getNeighbours = function(){ if(!canPlay(obj)){ //If the current object isn't in usedContent -> prev = last in used content, next = first in used content obj.prevMedia = usedContent[usedContent.length - 1].select(); obj.nextMedia = usedContent[0].select(); }else if(op_random.checked){ obj.prevMedia = getRandom(); obj.nextMedia = getRandom(obj.prevMedia); }else if(op_loopAll.checked){ obj.prevMedia = obj.position === 0 ? usedContent[usedContent.length - 1] : usedContent[obj.position - 1]; obj.nextMedia = obj.position === usedContent.length - 1 ? usedContent[0] : usedContent[obj.position + 1]; }else if(!op_loopAll.checked){ obj.prevMedia = obj.position === 0 ? obj : usedContent[obj.position - 1]; obj.nextMedia = obj.position === usedContent.length - 1 ? obj : usedContent[obj.position + 1]; } } this.media.getObj = function(){ return obj; }; this.thumb.getObj = function(){ return obj; }; return true; } } ////////////// // # Main # // ////////////// (function(){ //Removes default padding document.body.style.padding = "0px"; insertElements(); initVars(); initPlaceholders(); startInteractions(); startEventListeners(); var validSettings = applyDefaulSettings(); if( !validSettings.valid ){ console.log(validSettings.valid); for(var i = 0; i < validSettings.messages.length; i++){ console.log(validSettings.messages[i]); el_stage.innerHTML = '<h1>' + validSettings.messages[i] + '</h1>'; } return; } //Show and hide gallery to force load thumbnails //without this .select() does not work until gallery is shown showGallery(true); showGallery(false); magicMouse(false); sfc.style.display = "none"; //Force hide SFC showSFC(false); //Force styles to be ready for fade in //If shuffled on start up -> shuffle if(settingsArray[2]){ shuffle(true); } //If open on startup is selected -> open on startup if(settingsArray[10]){ showSFC(true); usedContent[0].select(); } })(); ///////////////////////////////// // # Initial Setup Functions # // ///////////////////////////////// //Insert html for buttons and modal function insertElements(){ //Start Button var btn1 = '[<a id="sfc-start" href="#">start</a>]'; var btn2 = '[<a id="sfc-resume" href="#">resume</a>]'; //Add span to nav var nav = document.getElementsByClassName('navLinks desktop'); for(var i = 0; i < nav.length; i++){ var span = document.createElement('span'); span.innerHTML = btn1 + " " + btn2; span.className = 'sfc-nav'; span.style.display = nav[i].style.display; nav[i].parentNode.insertBefore(span, nav[i]); nav[i].parentNode.insertBefore(document.getElementById('op'), nav[i]); } var html = '<!-- SETTINGS --> <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet"> <div id="sfc-settings-column"> <div id="sfc-settings-row"> <div id="sfc-settings-panel"> Settings <div id="sfc-settings-exit"></div> <!-- GENERAL SETTINGS --> <div> <p class="sfc-settings-title"> General </p> <div id="sfc-option"> <input id="stream4chan-loopAll" class="SFC-input" type="checkbox"> Loop whole thread (L) </div> <div id="sfc-option"> <input id="stream4chan-auto" class="SFC-input" type="checkbox"> Play Automatically (A) </div> <div id="sfc-option"> <input id="stream4chan-random" class="SFC-input" type="checkbox"> Random (R) </div> <div id="sfc-option"> <input id="stream4chan-shuffle" class="SFC-input" type="button" value=" Shuffle "> (S) </div> </div> <!-- WEBM SETTINGS --> <div> <p class="sfc-settings-title"> Webm </p> <div id="sfc-option"> <input id="stream4chan-webms" class="SFC-input" type="checkbox"> Play Webms (W) </div> <div id="sfc-option"> <input id="stream4chan-controls" class="SFC-input" type="checkbox"> Show Controls (C) </div> <div id="sfc-option"> <input id="stream4chan-playSound" class="SFC-input" type="checkbox"> Play sound (S) </div> </div> <!-- GIF SETTINGS --> <div> <p class="sfc-settings-title"> Gif </p> <div id="sfc-option"> <input id="stream4chan-gifs" class="SFC-input" type="checkbox"> Play Gifs (G) </div> <div id="sfc-option"> <input id="stream4chan-gif-duration" class="SFC-input" type="number" min="1" max="60" value="3" step="1"> Gif Duration (up/down) </div> </div> <!-- IMG SETTINGS --> <div> <p class="sfc-settings-title"> Images </p> <div id="sfc-option"> <input id="stream4chan-imgs" class="SFC-input" type="checkbox"> Play Images (I) </div> <div id="sfc-option"> <input id="stream4chan-img-duration" class="SFC-input" type="number" min="1" max="60" value="3" step="1"> Image Duration (Shift + up/down) </div> </div> </div> </div> </div> <!-- Main --> <div id="sfc-main"> <div id="sfc-main-prev" class="sfc-util prev"></div> <div id="sfc-stage"> </div> <div id="sfc-utility"> <div id="sfc-main-settings" class="sfc-util settings"></div> <div id="sfc-main-gallery" class="sfc-util gallery"></div> </div> <div id="sfc-counter"> <p id="sfc-counter-first">1</p> / <p id="sfc-counter-second">2</p> </div> <div id="sfc-main-next" class="sfc-util next"></div> </div> <!-- Gallery Slider --> <div id="sfc-gallery"> <div id="sfc-gallery-prev" class="sfc-util prev"></div> <div id="sfc-slider"> <div id="sfc-slider-internal"> </div> </div> <div id="sfc-gallery-next" class="sfc-util next"></div> </div> <!-- Hidden Cursor Overlay --> <span id="sfc-magicMouse"> </span>'; // var html = '<!-- SETTINGS --> <div id="sfc-settings-column"><div id="sfc-settings-row"> <div id="sfc-settings-panel"> Settings <div id="sfc-settings-exit"></div> <!-- GENERAL SETTINGS --> <div> <p class="sfc-settings-title"> General </p> <div id="sfc-option"> <input id="stream4chan-loopAll" class="SFC-input" type="checkbox"> Loop whole thread (L) </div> <div id="sfc-option"> <input id="stream4chan-auto" class="SFC-input" type="checkbox"> Play Automatically (A) </div> <div id="sfc-option"> <input id="stream4chan-random" class="SFC-input" type="checkbox"> Random (R) </div> <div id="sfc-option"> <input id="stream4chan-shuffle" class="SFC-input" type="button" value=" Shuffle "> (S) </div> </div> <!-- WEBM SETTINGS --> <div> <p class="sfc-settings-title"> Webm </p> <div id="sfc-option"> <input id="stream4chan-webms" class="SFC-input" type="checkbox"> Play Webms (W) </div> <div id="sfc-option"> <input id="stream4chan-controls" class="SFC-input" type="checkbox"> Show Controls (C) </div> <div id="sfc-option"> <input id="stream4chan-playSound" class="SFC-input" type="checkbox"> Play sound (S) </div> </div> <!-- GIF SETTINGS --> <div> <p class="sfc-settings-title"> Gif </p> <div id="sfc-option"> <input id="stream4chan-gifs" class="SFC-input" type="checkbox"> Play Gifs (G) </div> <div id="sfc-option"> <input id="stream4chan-gif-duration" class="SFC-input" type="number" min="1" max="60" value="3" step="1"> Gif Duration (up/down) </div> </div> <!-- IMG SETTINGS --> <div> <p class="sfc-settings-title"> Images </p> <div id="sfc-option"> <input id="stream4chan-imgs" class="SFC-input" type="checkbox"> Play Images (I) </div> <div id="sfc-option"> <input id="stream4chan-img-duration" class="SFC-input" type="number" min="1" max="60" value="3" step="1"> Image Duration (Shift + up/down) </div> </div> </div> </div> </div> <!-- Main --> <div id="sfc-main"> <div id="sfc-main-prev" class="sfc-util prev"></div> <div id="sfc-stage"> </div> <div id="sfc-utility"> <div id="sfc-main-settings" class="sfc-util settings"></div> <div id="sfc-main-gallery" class="sfc-util gallery"></div> </div> <div id="sfc-main-next" class="sfc-util next"></div> </div> <!-- Gallery Slider --> <div id="sfc-gallery"> <div id="sfc-gallery-prev" class="sfc-util prev"></div> <div id="sfc-slider"> <div id="sfc-slider-internal"> </div> </div> <div id="sfc-gallery-next" class="sfc-util next"></div> </div>'; // var css = '<!-- CSS --> <style> body, div, img, a, span, html, p{ margin: 0px; border: 0px; padding: 0px; } .sfc-nav{ height: auto; width: auto; } #sfc{ opacity: 1; -moz-transition: opacity 0.50s ease-in-out; -webkit-transition: opacity 0.50s ease-in-out; -o-transition: opacity 0.50s ease-in-out; -ms-transition: opacity 0.50s ease-in-out; transition: opacity 0.50s ease-in-out; } #sfc-main { width: 100%; height: 100%; z-index: 500; position: fixed; top: 0; display: flex; flex-flow: row; background-color: rgba(0,0,0,0.9 ); -webkit-box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); -moz-box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); } #sfc-gallery{ width: 100%; height: auto; z-index: 1000; position: fixed; bottom: 0; display: flex; flex-flow: row; background-color: rgba(0,0,0,0.35); transform: translateY(100%); -webkit-transition: transform 0.4s ease-in-out 0.05s; transition: transform 0.4s ease-in-out 0.05s; } #sfc-settings-column{ height: 100vh; z-index: 1500; top: 0; display: none; flex-flow: column; position: fixed; background-color: rgba(0,0,0,0.7); font-family: "PT Sans", sans-serif; font-size: 16pt; } #sfc-settings-row{ width: 100vw; display: flex; flex-flow: row; flex: 1 1 auto; } #sfc-settings-panel{ margin: auto; padding: 20px; flex: 0 1 auto; background-color: rgba(255,255,255,0.8); } .sfc-settings-title{ margin-top: 7%; text-decoration: underline; } #sfc-settings-exit { width: 20px; height: 20px; opacity: 0.3; float: right; background-image: url("http://www.myiconfinder.com/uploads/iconsets/4c515d45f6a8c4fe16e448a692a9370d.png"); background-size: contain; -webkit-transition: opacity 0.2s linear 0.05s, visibility 0s; transition: opacity 0.2s linear 0.05s, visibility 0s; } #sfc-settings-exit:hover{ opacity: 1; } #sfc-stage, #sfc-slider{ flex: 1 1 auto; margin: 5px; } #sfc-slider-internal{ width: 0px; } #sfc-utility{ position: fixed; bottom: 0; -webkit-transition: transform 0.4s ease-in-out 0.05s; transition: transform 0.4s ease-in-out 0.05s; } .sfc-util{ opacity: 0.1; width: 3vw; height: 3vw; background-color: rgba(255,255,255,0.6); background-clip: content-box; border-radius: 50%; background-size: 1.5vw; background-repeat: no-repeat; background-position:center; margin: auto; padding: 5px; -webkit-transition: opacity 0.2s linear 0.05s, visibility 0s; transition: opacity 0.2s linear 0.05s, visibility 0s; } .sfc-util:hover{ opacity: 1; } .gallery, .settings{ background-size: 2.2vw; } .gallery{ background-image: url("https://maxcdn.icons8.com/Android_L/PNG/24/Photo_Video/gallery-24.png"); } .settings{ background-image: url("https://maxcdn.icons8.com/Android_L/PNG/24/Very_Basic/settings-24.png"); } .prev, .next { flex: 0 0 3vw; background-image: url("http://www.dsetechnology.co.uk/images/disclose-arrow.png"); } .prev{ transform: rotate(180deg); } .sfc-slide-preview{ height: 6vh; width: auto; border: 2px solid transparent; -webkit-transition: border 0.2s linear 0.05s, visibility 0s; transition: border 0.2s linear 0.05s, visibility 0s; } .sfc-slide-preview:hover { border: 2px solid white; } </style> '; var css = '<style> <!-- CSS --> <style> body, div, img, a, span, html, p{ margin: 0px; border: 0px; padding: 0px; } #sfc-magicMouse { z-index: 1000; position: fixed; top: 0px; height: 100vh; width: 100vw; cursor: none; } #sfc-counter { min-height: ; position: fixed; top: 0px; right: 0px; color: rgba(255,255,255,0.3); font-family: \'Montserrat\', sans-serif; } #sfc-counter p { display: inline; } .sfc-nav{ height: auto; width: auto; } #sfc{ opacity: 1; -moz-transition: opacity 0.50s ease-in-out; -webkit-transition: opacity 0.50s ease-in-out; -o-transition: opacity 0.50s ease-in-out; -ms-transition: opacity 0.50s ease-in-out; transition: opacity 0.50s ease-in-out; } #sfc-main { width: 100%; height: 100%; z-index: 500; position: fixed; top: 0; display: flex; flex-flow: row; background-color: rgba(0,0,0,0.9 ); -webkit-box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); -moz-box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); box-shadow: inset 0px 0px 300px 28px rgba(0,0,0,1); } #sfc-gallery{ width: 100%; height: auto; z-index: 1000; position: fixed; bottom: 0; display: flex; flex-flow: row; background-color:rgba(0,0,0,0.35); transform: translateY(100%); -webkit-transition: transform 0.4s ease-in-out 0.05s; transition: transform 0.4s ease-in-out 0.05s; } #sfc-settings-column{ height: 100vh; z-index: 1500; top: 0; display: none; flex-flow: column; position: fixed; background-color: rgba(0,0,0,0.7); font-family: "PT Sans", sans-serif; font-size: 16pt; } #sfc-settings-row{ width: 100vw; display: flex; flex-flow: row; flex: 1 1 auto; } #sfc-settings-panel{ margin: auto; padding: 20px; flex: 0 1 auto; background-color: rgba(255,255,255,0.8); } .sfc-settings-title{ margin-top: 7%; text-decoration: underline; } #sfc-settings-exit { width: 20px; height: 20px; opacity: 0.3; float: right; background-image: url("http://www.myiconfinder.com/uploads/iconsets/4c515d45f6a8c4fe16e448a692a9370d.png"); background-size: contain; -webkit-transition: opacity 0.2s linear 0.05s, visibility 0s; transition: opacity 0.2s linear 0.05s, visibility 0s; } #sfc-settings-exit:hover{ opacity: 1; } #sfc-stage, #sfc-slider{ flex: 1 1 auto; margin: 5px; } #sfc-slider-internal{ width: 0px; -webkit-transition: transform 0.4s ease-in-out 0.05s; transition: transform 0.4s ease-in-out 0.05s; } #sfc-utility{ position: fixed; bottom: 0; -webkit-transition: transform 0.4s ease-in-out 0.05s; transition: transform 0.4s ease-in-out 0.05s; } .sfc-util{ opacity: 0.1; width: 3vw; height: 3vw; background-color: rgba(255,255,255,0.6); background-clip: content-box; border-radius: 50%; background-size: 1.5vw; background-repeat: no-repeat; background-position:center; margin: auto; padding: 5px; -webkit-transition: opacity 0.2s linear 0.05s, visibility 0s; transition: opacity 0.2s linear 0.05s, visibility 0s; } .sfc-util:hover{ opacity: 1; } .gallery, .settings{ background-size: 2.2vw; } .gallery{ background-image: url("https://maxcdn.icons8.com/Android_L/PNG/24/Photo_Video/gallery-24.png"); } .settings{ background-image: url("https://maxcdn.icons8.com/Android_L/PNG/24/Very_Basic/settings-24.png"); } .prev, .next { flex: 0 0 3vw; background-image: url("http://www.dsetechnology.co.uk/images/disclose-arrow.png"); } .prev{ transform: rotate(180deg); } .sfc-slide-preview{ height: 6vh; width: auto; border: 2px solid transparent; -webkit-transition: border 0.2s linear 0.05s, visibility 0s; transition: border 0.2s linear 0.05s, visibility 0s; } .sfc-slide-preview:hover { border: 2px solid white; } </style>'; var sfc = document.createElement('div'); sfc.setAttribute('id','sfc'); sfc.innerHTML = html + css; var target = document.getElementsByClassName('thread'); for(i = 0; i < target.length; i++){ target[i].prepend(sfc); } } //Create and initialize global variables for easy access to HTML elements function initVars(){ //Custom function to find elements while //alerting console of errors in case of null || undefined function getEl(elName){ var temp = document.getElementById(elName); if(temp === null || temp === undefined){ temp = document.getElementsByClassName(elName)[0]; if(temp === null || temp === undefined){ console.log('### ERROR ###'); console.log('initVars: getEl(\'' + elName +'\') returned... '); console.log(temp); } } return temp; } //Main Page el_startBtn = getEl('sfc-start'); el_resumeBtn = getEl('sfc-resume'); //Modal el_sfc = getEl('sfc'); el_magicMouse = getEl('sfc-magicMouse'); //Stage Area el_stage = getEl('sfc-stage'); el_stagePrev = getEl('sfc-main-prev'); el_stageNext = getEl('sfc-main-next'); //Utility buttons el_util = getEl("sfc-utility"); el_galleryBtn = getEl("sfc-main-gallery"); el_settingsBtn = getEl("sfc-main-settings"); el_counter_first = getEl("sfc-counter-first"); el_counter_second = getEl("sfc-counter-second"); //Gallery Area el_gallery = getEl("sfc-gallery"); el_slider = getEl('sfc-slider'); el_internalSlider = getEl('sfc-slider-internal'); el_internalSlider.style.transform = "translateX(0px)"; el_sliderPrev = getEl('sfc-gallery-prev'); el_sliderNext = getEl('sfc-gallery-next'); //Settings and option area el_settings = getEl("sfc-settings-column"); el_settingsExit = getEl("sfc-settings-exit"); op_loopAll = getEl('stream4chan-loopAll'); op_auto = getEl('stream4chan-auto'); op_random = getEl('stream4chan-random'); op_shuffle = getEl('stream4chan-shuffle'); op_webms = getEl('stream4chan-webms'); op_controls = getEl('stream4chan-controls'); op_playSound = getEl('stream4chan-playSound'); op_gifs = getEl('stream4chan-gifs'); op_gif_duration = getEl('stream4chan-gif-duration'); op_imgs = getEl('stream4chan-imgs'); op_img_duration = getEl('stream4chan-img-duration'); } //Inititialize values to placeholder variables function initPlaceholders(){ //Type placeholders. Less quotations in code webm = 'webm'; gif = 'gif'; png = 'png'; jpg ='jpg'; //Start of thread //Object based placeholder for the beginning of the thread (used when loopAll is unchecked) SOT = new Media("https://dummyimage.com/1920x1080/000000/ffffff.png","https://dummyimage.com/1920x1080/000000/ffffff.png"); SOT.media.src = "https://dummyimage.com/1920x1080/000000/ffffff.png&text=Start+of+thread"; SOT.thumb.src = "https://dummyimage.com/480x270/000000/ffffff.png&text=Start+of+thread"; SOT.type = "SOT"; //End of thread //Object based placeholder for the end of the thread (used when loopAll is unchecked) EOT = new Media("https://dummyimage.com/1920x1080/000000/ffffff.png","https://dummyimage.com/1920x1080/000000/ffffff.png"); EOT.media.src = "https://dummyimage.com/1920x1080/000000/ffffff.png&text=End+of+thread"; EOT.thumb.src = "https://dummyimage.com/480x270/000000/ffffff.png&text=End+of+thread"; EOT.type = "EOT"; //Object based placeholder for when there is no applicable media found or nothing is selected noneSelected = new Media("https://dummyimage.com/1920x1080/000000/ffffff.png","https://dummyimage.com/1920x1080/000000/ffffff.png"); noneSelected.media.src = "https://dummyimage.com/1920x1080/000000/ffffff.png&text=No+Media+Selected"; noneSelected.thumb.src = "https://dummyimage.com/480x270/000000/ffffff.png&text=No+Media+Selected"; allContent = getContent(); usedContent = getUsedContent(); currentContent = noneSelected; } //Links keyboard controls with functions and updates sfc accordinglys function startEventListeners(){ window.onresize = function(){ // updateGallery(); currentContent.resize(); currentContent.slideGallery(); } op_loopAll.onchange = updateGallery; op_controls.onchange = function(){ if(currentContent.type == webm){ currentContent.media.controls = op_controls.checked; } }; op_playSound.onchange = function(){ if(currentContent.type == webm){ currentContent.media.volume = op_playSound.checked ? 1 : 0; } }; op_webms.onchange = updateGallery; op_gifs.onchange = updateGallery; op_imgs.onchange = updateGallery; op_auto.onchange = updateAutoplay; op_shuffle.onclick = shuffle; el_stagePrev.onclick = previous; el_stageNext.onclick = next; document.onkeydown = function(event){ switch(event.keyCode){ //Space Key case 32: if(currentContent !== null){ //if paused if(currentContent.type == webm){ if(currentContent.media.paused){ currentContent.play(); }else{ currentContent.pause(); } }else{ if(currentContent.media.src == currentContent.thumb.src){ currentContent.play(); }else{ currentContent.pause(); } } } break; //Enter Key case 13: if(event.ctrlKey) return; if(!isShown(el_sfc)){ if(event.altKey){ el_resumeBtn.onclick(); }else{ el_startBtn.onclick(); } } break; //Esc Key case 27: if(isShown(el_settings)){ showSettings(false); }else if(isShown(el_gallery)){ showGallery(false); }else if(isShown(el_sfc)){ showSFC(false); } break; //Left arrow Key case 37: if(!event.altKey) previous(); break; //Right arrow Key case 39: if(!event.altKey) next(); break; //Up arrow Key case 38: if(event.shiftKey){ op_img_duration.value++; }else{ op_gif_duration.value++; } break; //Down arrow Key case 40: if(event.shiftKey){ op_img_duration.value--; }else{ op_gif_duration.value--; } break; //L Key case 76: op_loopAll.checked = !op_loopAll.checked; op_loopAll.onchange(); break; //A Key case 65: op_auto.checked = !op_auto.checked; op_auto.onchange(); break; //R Key case 82: if(!event.ctrlKey) op_random.checked = !op_random.checked; break; //Q Key case 81: break; //S Key case 83: op_playSound.checked = !op_playSound.checked; op_playSound.onchange(); break; //W Key case 87: op_webms.checked = !op_webms.checked; op_webms.onchange(); break; //C Key case 67: op_controls.checked = !op_controls.checked; op_controls.onchange(); break; //G Key case 71: op_gifs.checked = !op_gifs.checked; op_gifs.onchange(); break; //I Key case 73: op_imgs.checked = !op_imgs.checked; op_imgs.onchange(); break; //Backspace case 8: settingsArray[11] = !settingsArray[11]; break; //Print what was typed into console default: var temp = ""; if(event.shiftKey){ temp += "Shift + "; } if(event.altKey){ temp += "Alt + "; } if(event.ctrlKey){ temp += "Ctrl + "; } temp += event.keyCode; console.log(temp); } } el_gallery.onwheel = function(event){ //This number, when used with // if(event.wheelDelta > 0 ){ // }else{ // console.log('scrolling down'); // el_internalSlider.style.transform = 'translateX(' + (getPageTopLeft(el_internalSlider).left) + 'px)'; // } } el_stage.onclick = function(e) { if (e.target === this){ currentContent.pause(); showSFC(false); return; } }; } ////////////////////////////////////////////// // # SFC Control and Animation Functions # // ////////////////////////////////////////////// //Links functions with page controllers //eg: making gallery button show/hide gallery function startInteractions(){ //Apply functionality: click start to show modal and play first media item el_startBtn.onclick = function(){ showSFC(true); if(op_auto.checked){ usedContent[0].select(); } }; el_resumeBtn.onclick = function(){ showSFC(true); currentContent.resize(); currentContent.play(); updateAutoplay(); }; //Apply functionality: click gallery button to show/hide gallery el_gallery.style.transform = "translateY(100%)"; el_galleryBtn.onclick = showGallery; //Apply functionality: click settings button to show settings //Click exit button to exit settings el_settingsBtn.onclick = function(){ showSettings(true); }; el_settingsExit.onclick = function(){ showSettings(false); }; } //Applies default settings // • Default settings are on line 5 function applyDefaulSettings(){ var messages = Array(); //Loop whole thread if(settingsArray[0] !== true && settingsArray[0] !== false){ messages.push("Loop whole thread"); }else{ op_loopAll.checked = settingsArray[0]; } //Play automatically if(settingsArray[1] !== true && settingsArray[1] !== false){ messages.push("Play automatically"); }else{ op_auto.checked = settingsArray[1]; } //Shuffle on startup if(settingsArray[2] !== true && settingsArray[2] !== false){ messages.push("Shuffle on startup"); }else{ op_shuffle.value = settingsArray[2] ? "Unshuffle" : "Shuffle"; } //Play Webms if(settingsArray[3] !== true && settingsArray[3] !== false){ messages.push("Play Webms"); }else{ op_webms.checked = settingsArray[3]; } //Show Webm controls if(settingsArray[4] !== true && settingsArray[4] !== false){ messages.push("Show Webm controls"); }else{ op_controls.checked = settingsArray[4]; } //Play webm sound if(settingsArray[5] !== true && settingsArray[5] !== false){ messages.push("Play webm sound"); }else{ op_playSound.checked = settingsArray[5]; } //Play Gifs if(settingsArray[6] !== true && settingsArray[6] !== false){ messages.push("Play Gifs"); }else{ op_gifs.checked = settingsArray[6]; } //Gif duration (Seconds) if( isNaN(settingsArray[7]) ){ messages.push("Gif duration (Seconds)"); }else{ op_gif_duration.value = settingsArray[7]; } //Play Images if(settingsArray[8] !== true && settingsArray[8] !== false){ messages.push("Play Images"); }else{ op_imgs.checked = settingsArray[8]; } //Image duration (Seconds) if( isNaN(settingsArray[9]) ){ messages.push("Image duration (Seconds)"); }else{ op_img_duration.value = settingsArray[9]; } //Open Stream4chan on startup if(settingsArray[10] !== true && settingsArray[10] !== false){ messages.push("Open Stream4chan on startup"); } //Override Individual Media if(settingsArray[11] !== true && settingsArray[11] !== false){ messages.push("Override Individual Media"); } //Select Media randomly if(settingsArray[12] !== true && settingsArray[12] !== false){ messages.push("Select Media Randomly"); }else{ op_random.checked = settingsArray[12]; } return { valid: (messages.length <= 0), messages: messages }; } //Toggles showing the modal function showSFC(bool){ function show(){ document.body.style.overflow = "hidden"; el_sfc.style.display = "block"; setTimeout(function(){ el_sfc.style.opacity = 1; magicMouse(true); }, 40); return true; } function hide(){ showGallery(false); showSettings(false); currentContent.pause(); el_sfc.style.opacity = 0; el_sfc.addEventListener("transitionend", function() { if(el_sfc.style.opacity == 0){ el_sfc.style.display = "none"; el_sfc.removeEventListener("transitionend", function(){}, false); document.body.style.overflow = "auto"; showMouse(false); } }, false); return true; } if(bool === true){ show(); }else if (bool === false){ hide(); }else if (isShown(el_sfc)){ show(); }else{ hide(); } return false; } //Toggles showing the gallery function showGallery(bool){ function show(){ //Sets internal gallery slider to appropriate width //'if' statements causes this to only fire once updateGallery(); el_gallery.style.transform = "translateY(0px)"; el_util.style.transform = "translateY(-" + el_gallery.clientHeight + "px)"; return true; } function hide(){ el_gallery.style.transform = "translateY(100%)"; el_util.style.transform = "translateY(0)"; return true; } if(bool === true){ show(); }else if(bool === false){ hide(); }else if(el_gallery.style.transform == "translateY(100%)"){ show(); }else{ hide(); } return false; } //Toggles showing the settings function showSettings(bool){ function show(){ el_settings.style.display = "flex"; return true; } function hide(){ el_settings.style.display = "none"; return true; } if(bool === true){ show(); }else if(bool === false){ hide(); }else if(el_settings.style.display == "none" || el_settings.style.display === ""){ show(); }else{ hide(); } return false; } //Parse through el_sfc, el_settings or el_gallery //Return boolean indicating it's state function isShown(el){ if(el === el_sfc){ return !(el_sfc.style.display == "none"); } if(el === el_settings){ return !(el_settings.style.display == "none" || el_settings.style.display === ""); } if(el === el_gallery){ return !(el_gallery.style.transform == "translateY(100%)"); } } ///////////////////////////////////////// // # Media and Media Array Functions # // ///////////////////////////////////////// function previous(){ currentContent.previous(); } function next(){ currentContent.next(); } //Updates usedContent array, populates gallery and re-adjusts width function updateGallery(contentToUse){ //Update contents of usedContent array var validParams = contentToUse !== null && contentToUse !== undefined; usedContent = validParams ? contentToUse : getUsedContent(); //Recalculate the previous and next media currentContent.getNeighbours(); //Change currentContent to closest valid content if(currentContent !== noneSelected && currentContent !== null){ if(!canPlay(currentContent)){ var newContent = null; var crntContentFound = false; //Find currentContent in allContent array and set newContent to next valid content for(var i = 0; i <= allContent.length; i++){ //Keep i within bounds if settingsArray[0] (loop whole thread) i = settingsArray[0] ? i%allContent.length : i; //Terminates loop if out of bounds if( i === allContent.length) break; //If crntContent is reached again, nothing was found if(crntContentFound && allContent[i] === currentContent) break; //If content matches, crntContent has been found if(allContent[i] === currentContent) crntContentFound = true; //If playable content has been found after crntcontent, save to newContent if(crntContentFound && canPlay(allContent[i])){ newContent = allContent[i]; break; } } //If newContent is null, change to noneSelected newContent = newContent === null ? noneSelected : newContent; newContent.select(); } } //Clear contents and width of internalSlider el_internalSlider.innerHTML = ""; el_internalSlider.style.width = "-1px"; //Add all thumbnails to internalSlider for(var i = 0; i < usedContent.length; i++){ el_internalSlider.appendChild(usedContent[i].thumb); el_internalSlider.style.width = (el_internalSlider.clientWidth + (usedContent[i].thumb.clientWidth*1.2) ) + "px"; } //Scroll seleceted media into gallery view again currentContent.slideGallery(); updateCounter(); //Trigger height calculations without changing gallery state // showGallery(isShown(el_gallery)); } //Returns media type when given source function mediaType(input){ if(input === undefined){ console.log('Error: mediaType input argument was undefined'); }else if(input === null){ console.log('Error: mediaType input argument was null'); }else{ var temp = input.toString(); temp = temp.substr(temp.lastIndexOf('.') + 1); if(temp == webm) return webm; if(temp == gif) return gif; if(temp == png) return png; if(temp == jpg) return jpg; } //Last Resort return null; } //Returns if current user settings permits the playing of parsed object function canPlay(mediaObj){ var objType = mediaObj.type; var canPlay = false; //Return 'checked' value of checkbox corresponding to the object type switch(objType){ case webm: return op_webms.checked; case gif: return op_gifs.checked; case png: case jpg: return op_imgs.checked; case "SOT": case "EOT": return !op_loopAll.checked; break; default: return false; } // return (objType == webm && op_webms.checked) || (objType == gif && op_gifs.checked) || ( (objType == png || objType == jpg) && op_imgs.checked ) || (objType == "SOT" && !op_loopAll.checked) || (objType == "EOT" && !op_loopAll.checked); } //Applies autoplay based on user settings function updateAutoplay(){ //Clear timeout to avoid timeout overlaps and //unwanted function calls clearTimeout(globalTimeout); if(currentContent.type == webm){ //Loop media (incase auto is not turned on) currentContent.media.loop = true; //If it is turned on, set to false and await end of video if(op_auto.checked){ currentContent.media.loop = false; currentContent.media.onended = next; } }else if(currentContent.type == gif){ //If auto is checked apply according timeout if(op_auto.checked){ globalTimeout = setTimeout(next, op_gif_duration.value*1000); } }else if(currentContent.type == png || currentContent.type == jpg){ //If auto is checked apply according timeout if(op_auto.checked){ globalTimeout = setTimeout(next, op_img_duration.value*1000); } } } //Returns array of ALL elemnts. Including SOT, EOT and noneSelected //Also sets the onclick method for the thumbnail in the default thread function getContent(){ var temp = []; var elements = document.getElementsByClassName('fileThumb'); //Pushes 'start of thread' placeholder temp.push(SOT); //Loops over all media elements in thread //and pushes them to temp array for(var i = 0; i < elements.length; i++){ var vidSrc = elements[i].href; var imgSrc = elements[i].getElementsByTagName('img')[0].src; var id = elements[i].parentNode.parentNode.id; var x = new Media(imgSrc, vidSrc, id); function playThis(){ showSFC(true); x.select(); } temp.push(x); //Change clicking the video element to open SFC var href = elements[i].getElementsByTagName('img')[0].parentNode; href.setAttribute('oldHref', href.href); href.oldHref = href.href; href.setAttribute('imgSrc', imgSrc); href.imgSrc = imgSrc; href.href = imgSrc; href.onclick = function(event){ inThreadClick(event); }; } //Pushes 'end of thread' placeholder temp.push(EOT); return temp; } //Returns all media permitted to play by user settings function getUsedContent(){ var temp = []; var count = 0; for(var i = 0; i < allContent.length; i++){ if(canPlay(allContent[i])){ temp.push(allContent[i]); temp[count].position = count; count++; } } return temp; } //Shuffles the usedContent array function shuffle(){ //Direction is shuffle/unshuffle var shuffled = op_shuffle.value === "Unshuffled"; if(!shuffled){ var newUsedContent = []; var max, min = 0; //Push a random index from the usedContent into a new array while(usedContent.length > 0){ //Get random index max = usedContent.length - 1; var rand = Math.floor(Math.random() * (max - min + 1)) + min; //remove one from old and add to new newUsedContent.push(usedContent.splice(rand, 1)[0]); //Update positions newUsedContent[newUsedContent.length - 1].position = newUsedContent.length - 1; } //Overwrite the old and update gallery usedContent = newUsedContent; updateGallery(usedContent); op_shuffle.value = "Unshuffle"; }else { updateGallery(); op_shuffle.value = "Shuffle"; } } function getRandom(exclude){ //Array of media with lowest view count var lowestPlayCount = []; //Fill array with least viewed content for(var i = 0; i < usedContent.length; i++){ //If placeholder media -> continue if(usedContent[i] === SOT || usedContent[i] === EOT || usedContent[i] === exclude){ continue; } //If array is empty -> add current media if(lowestPlayCount.length === 0){ lowestPlayCount.push(usedContent[i]); continue; } //If new lowest found -> erase and push if(usedContent[i].playcount < lowestPlayCount[0].playcount){ lowestPlayCount = []; lowestPlayCount.push(usedContent[i]); continue; } //If matching view count -> push if(usedContent[i].playcount === lowestPlayCount[0].playcount){ lowestPlayCount.push(usedContent[i]); continue; } } //Select random media from array var min = 0; var max = lowestPlayCount.length - 1; var rand = Math.floor(Math.random() * (max - min + 1)) + min; console.log("Random"); console.log(lowestPlayCount); console.log(""); return lowestPlayCount[rand]; } function updateCounter(){ el_counter_first.innerHTML = currentContent.position + 1; el_counter_second.innerHTML = usedContent.length; } /////////////////////// // # Miscellaneous # // /////////////////////// //This function is called when a thumbnail in the thread is clicked function inThreadClick(event, videoHref){ if(event.target === null) return; if(settingsArray[11]){ var hrefContainer = event.target.parentNode; var currentID = hrefContainer.parentNode.parentNode.id; setTimeout( function(){ hrefContainer.parentNode.classList.remove("image-expanded"); }, 0.0001); for(var j = 0; j < allContent.length; j++){ if(allContent[j].id === currentID){ showSFC(true); allContent[j].select(); } } }else{ event.target.href = event.target.oldHref; event.target.click(); } } function magicMouse(bool){ var timer = setTimeout(function(){},0); var showMouse = function(){ console.log("showing"); el_magicMouse.style.display = "none"; } var hideMouse = function(){ console.log("hiding"); el_magicMouse.style.display = "block"; document.body.style.cursor = "auto"; } var resetTimer = function(){ console.log("resetting"); showMouse(); clearTimeout(timer); timer = setTimeout(hideMouse, 500); } if(bool === true){ document.body.onmousemove = resetTimer; }else{ document.body.onmousemove = null; } } function getPageTopLeft(el) { var rect = el.getBoundingClientRect(); var docEl = document.documentElement; return { left: rect.left + (window.pageXOffset || docEl.scrollLeft || 0), top: rect.top + (window.pageYOffset || docEl.scrollTop || 0) }; } //Convert css style strings to integer values (top, right, bottom and left) function getCSSValues(styleStr){ var values = []; do{ //Find match var match = styleStr.match("\\d+[a-zA-Z]{2}"); var foundString = match[0]; //Remove all characters before and including the match styleStr = styleStr.substr( match.index + foundString.length, styleStr.length - 1); values.push(parseInt(foundString, 10)); }while(styleStr.length > 3); //If below 3 than it's not a valid style value return values; }