JSDoc for www/js/channel/*.js complete. Just need to hadnle ww/js/channel/panels.

This commit is contained in:
rainbow napkin 2025-09-04 20:11:23 -04:00
parent ac06f839ea
commit c0f219276f
91 changed files with 38653 additions and 104 deletions

2611
www/doc/client/cPanel.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@
<h2><span class="attribs"><span class="type-signature"></span></span>channel<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Class for object containing base code for the Canopy channel client.</div>
<div class="class-description">Class containing base code for the Canopy channel client.</div>
</header>
@ -1248,13 +1248,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -43,7 +43,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class for object containing base code for the Canopy channel client.
* Class containing base code for the Canopy channel client.
*/
class channel{
/**
@ -283,13 +283,13 @@ const client = new channel();</code></pre>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

671
www/doc/client/chat.js.html Normal file
View file

@ -0,0 +1,671 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chat.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chat.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class which represents Canopy Chat Box UI
*/
class chatBox{
/**
* Instantiates a new Chat Box object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client
/**
* Whether or not chat-size should be locked to current media aspect ratio
*/
this.aspectLock = true;
/**
* Whether or not the chat box should auto-scroll on new chat
*/
this.autoScroll = true;
/**
* Chat Buffer Scroll Top on last scroll
*/
this.lastPos = 0;
/**
* Height of Chat Buffer on last scroll
*/
this.lastHeight = 0;
/**
* Width of Chat Buffer on last scroll
*/
this.lastWidth = 0;
/**
* Click-Dragger Object for handling dynamic chat/video split re-sizing
*/
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-drag-handle", "#chat-panel-div");
/**
* Command Pre-Processor Object
*/
this.commandPreprocessor = new commandPreprocessor(client);
/**
* Chat Post-Processor Object
*/
this.chatPostprocessor = new chatPostprocessor(client);
//Element Nodes
/**
* Chat Panel Container Div
*/
this.chatPanel = document.querySelector("#chat-panel-div");
/**
* High Level Selector
*/
this.highSelect = document.querySelector("#chat-panel-high-level-select");
/**
* Flair Selector
*/
this.flairSelect = document.querySelector("#chat-panel-flair-select");
/**
* Chat Buffer Div
*/
this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
/**
* Chat Prompt
*/
this.chatPrompt = document.querySelector("#chat-panel-prompt");
/**
* Auto-Complete Placeholder
*/
this.autocompletePlaceholder = document.querySelector("#chat-panel-prompt-autocomplete-filler");
/**
* Auto-Complete Display
*/
this.autocompleteDisplay = document.querySelector("#chat-panel-prompt-autocomplete-display");
/**
* Settings Panel Icon
*/
this.settingsIcon = document.querySelector("#chat-panel-settings-icon");
/**
* Admin Panel Icon
*/
this.adminIcon = document.querySelector("#chat-panel-admin-icon");
/**
* Emote Icon
*/
this.emoteIcon = document.querySelector("#chat-panel-emote-icon");
/**
* Send Chat/Command Button
*/
this.sendButton = document.querySelector("#chat-panel-send-button");
/**
* Aspect Lock Icon
* Seems weird to stick this in here, but the split is dictated by chat width :P
*/
this.aspectLockIcon = document.querySelector("#media-panel-aspect-lock-icon");
/**
* Hide Chat Icon
*/
this.hideChatIcon = document.querySelector("#chat-panel-div-hide");
/**
* Show Chat Icon
*/
this.showChatIcon = document.querySelector("#media-panel-show-chat-icon");
//Setup functions
this.setupInput();
this.defineListeners();
this.sizeToAspect();
}
/**
* Defines input-related event listeners
*/
setupInput(){
//Chat bar
this.chatPrompt.addEventListener("keydown", this.send.bind(this));
this.chatPrompt.addEventListener("keydown", this.tabComplete.bind(this));
this.chatPrompt.addEventListener("input", this.displayAutocomplete.bind(this));
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
this.sendButton.addEventListener("click", this.send.bind(this));
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new settingsPanel(client))});
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new queuePanel(client))});
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
//Header icons
this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this));
this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()});
this.highSelect.addEventListener("change", this.setHighLevel.bind(this));
this.flairSelect.addEventListener("change", this.setFlair.bind(this));
//Clickdragger/Resize
this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this));
this.clickDragger.handle.addEventListener("clickdrag", this.handleAutoScroll.bind(this));
window.addEventListener("resize", this.resizeAspect.bind(this));
//chatbuffer
this.chatBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
}
/**
* Defines network-related event listners
*/
defineListeners(){
this.client.socket.on("chatMessage", this.displayChat.bind(this));
this.client.socket.on("clearChat", this.clearChat.bind(this));
}
/**
* Clears chat on command from server
* @param {Object} data - Data from server
*/
clearChat(data){
//If we where passed a user to check
if(data.user != null){
var clearedChats = document.querySelectorAll(`.chat-entry-${data.user}`);
}else{
var clearedChats = document.querySelectorAll('.chat-entry');
}
//For each chat found
clearedChats.forEach((chat) => {
//fuckin' nukem!
chat.remove();
});
}
/**
* Receives, Post-Processes, and Displays chat messages from server
* @param {Object} data De-hydrated chat object from server
*/
displayChat(data){
//Create chat-entry span
var chatEntry = document.createElement('span');
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${data.user}`);
//Create high-level label
var highLevel = document.createElement('p');
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level","high-level");
highLevel.textContent = utils.unescapeEntities(`${data.highLevel}`);
chatEntry.appendChild(highLevel);
//If we're not using classic flair
if(data.flair != "classic"){
//Use flair
var flair = `flair-${data.flair}`;
//Otherwise
}else{
//Pull user's assigned color from the color map
var flair = this.client.userList.colorMap.get(data.user);
}
//Create username label
var userLabel = document.createElement('p');
userLabel.classList.add("chat-panel-buffer", "chat-entry-username", );
//Create color span
var flairSpan = document.createElement('span');
flairSpan.classList.add("chat-entry-flair-span", flair);
flairSpan.innerHTML = data.user;
//Inject flair span into user label before the colon
userLabel.innerHTML = `${flairSpan.outerHTML}: `;
//Append user label
chatEntry.appendChild(userLabel);
//Create chat body
var chatBody = document.createElement('p');
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
chatEntry.appendChild(chatBody);
//Append the post-processed chat-body to the chat buffer
this.chatBuffer.appendChild(this.chatPostprocessor.postprocess(chatEntry, data));
//Set size to aspect on launch
this.resizeAspect();
}
/**
* Concatinate Text into Chat Prompt
* @param {String} text - Text to Concatinate
*/
catChat(text){
this.chatPrompt.value += text;
this.displayAutocomplete();
}
/**
* Calls a toke command out with a specified user
* @param {String} user - User to toke with
*/
tokeWith(user){
this.commandPreprocessor.preprocess(user == this.client.user.user ? "!toke up fuckers" : `!toke up ${user}`);
}
/**
* Pre-processes and sends text from chat prompt to server
* @param {Event} event - Event passed down from Event Handler
*/
send(event){
if((!event || !event.key || event.key == "Enter") &amp;&amp; this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value);
//Clear our prompt and autocomplete nodes
this.chatPrompt.value = "";
this.autocompletePlaceholder.innerHTML = '';
this.autocompleteDisplay.innerHTML = '';
}
}
/**
* Displays auto-complete text against current prompt input
* @param {Event} event - Event passed down from Event Handler
*/
displayAutocomplete(event){
//Find current match
const match = this.checkAutocomplete();
//Set placeholder to space out the autocomplete display
//Use text content because it's unescaped, and while this only effects local users, it'll keep someone from noticing and whinging about it
this.autocompletePlaceholder.textContent = this.chatPrompt.value;
//Set the autocomplete display
this.autocompleteDisplay.textContent = match.match.replace(match.word, '');
}
/**
* Called upon tab-complete
* @param {Event} event - Event passed down from Event Handler
*/
tabComplete(event){
//If we hit tab or this isn't a keyboard event
if(event.key == "Tab" || event.key == null){
//Prevent default action
event.preventDefault();
//return focus to the chat prompt
this.chatPrompt.focus();
//Grab autocompletion match
const match = this.checkAutocomplete();
//If we have a match
if(match.match != ''){
//Autocomplete the current word
this.chatPrompt.value += match.match.replace(match.word, '');
//Clear out the autocomplete display
this.autocompleteDisplay.innerHTML = '';
}
}
}
/**
* Checks string input against auto-complete dictionary to generate the best guess as to what the user is typing
* @param {String} input - Current input from Chat Prompt
* @returns {Object} Object containing word we where handed and the match we found
*/
checkAutocomplete(input = this.chatPrompt.value){
//Rebuild this fucker every time because it really doesn't take that much compute power and emotes/used tokes change
//Worst case we could store it persistantly and update as needed but I think that might be much
const dictionary = this.commandPreprocessor.buildAutocompleteDictionary();
//Split our input by whitespace
const splitInput = input.split(/\s/g);
//Get the current word we're working on
const word = splitInput[splitInput.length - 1];
let matches = [];
//Run through dictionary sets
for(let set of Object.keys(dictionary)){
//Go through the current definitions of the current dictionary set
//I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix
//and also directly push it into a shared array :P
for(let cmd of dictionary[set].cmds){
//Append the proper prefix/postfix to the current command
const definition = (`${dictionary[set].prefix}${cmd[0]}${dictionary[set].postfix}`);
//if definition starts with the current word and the command is enabled
if((word == '' ? false : definition.indexOf(word) == 0) &amp;&amp; cmd[1]){
//Add definition to match list
matches.push(definition);
}
}
}
//If we found jack shit
if(matches.length == 0){
//Return jack shit
return {
match: '',
word
};
//If we got something
}else{
//return our top match
return {
match: matches[0],
word
};
}
}
/**
* Handles initial client meta-data dump from server upon connection
* @param {Object} data - Data dump from server
*/
handleClientInfo(data){
this.updateFlairSelect(data.flairList, data.user.flair);
this.updateHighSelect(data.user.highLevel);
}
/**
* Sets user high-level
* @param {Event} event - Event passed down from Event Handler
*/
setHighLevel(event){
const highLevel = event.target.value;
this.client.socket.emit("setHighLevel", {highLevel});
}
/**
* Sets user flair
* @param {Event} event - Event passed down from Event Handler
*/
setFlair(event){
const flair = event.target.value;
this.client.socket.emit("setFlair", {flair});
}
/**
* Handles High-Level updates from the server
* @param {Number} highLevel - High Level to Set
*/
updateHighSelect(highLevel){
this.highSelect.value = highLevel;
}
/**
* Handles flair updates from the server
* @param {Array} fliarList - List of flairs to put into flair select
* @param {String} fliar - Flair to set
*/
updateFlairSelect(flairList, flair){
//clear current flair select
this.flairSelect.innerHTML = "";
//For each flair in flairlist
flairList.forEach((flair) => {
//Create an option
var flairOption = document.createElement('option');
//Set the name and innerHTML
flairOption.value = flair.name;
flairOption.textContent = utils.unescapeEntities(flair.displayName);
//Append it to the select
this.flairSelect.appendChild(flairOption);
});
//Set the selected flair in the UI
this.flairSelect.value = flair;
//Re-style the UI, do this in two seperate steps in-case we're running for the first time and have nothing to replace.
this.flairSelect.className = this.flairSelect.className.replace(/flair-\S*/, "");
this.flairSelect.classList.add(`flair-${flair}`);
}
/**
* Locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
lockAspect(event){
//prevent the user from breaking shit :P
if(this.chatPanel.style.display != "none"){
this.aspectLock = true;
this.aspectLockIcon.style.display = "none";
this.sizeToAspect();
}
}
/**
* Un-locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
unlockAspect(event){
//Disable aspect lock
this.aspectLock = false;
//Show aspect lock icon
this.aspectLockIcon.style.display = "inline";
}
L /**
* Re-sizes chat back to aspect ratio on window re-size when chat box is aspect locked
* Also prevents horizontal scroll-bars from chat/window resizing
* @param {Event} event - Event passed down from Event Handler
*/
resizeAspect(event){
const playerHidden = this.client.player.playerDiv.style.display == "none";
//If the aspect is locked and the player is hidden
if(this.aspectLock &amp;&amp; !playerHidden){
this.sizeToAspect();
//Otherwise
}else{
//Fix the clickDragger on userlist
this.client.userList.clickDragger.fixCutoff();
}
//Autoscroll chat in-case we fucked it up
this.handleAutoScroll();
}
L /**
* Re-sizes chat box relative to media aspect ratio
*/
sizeToAspect(){
if(this.chatPanel.style.display != "none"){
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
const targetChatWidth = window.innerWidth - targetVidWidth;
//This should be changeable in settings later on, for now it defaults to 20%
const limit = window.innerWidth * .2;
//Set width to target or 20vh depending on whether or not we've hit the width limit
this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : '20vh';
//Fix busted layout
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
//This sometimes gets called before userList ahs been initiated :p
if(this.client.userList != null){
this.client.userList.clickDragger.fixCutoff();
}
}
}
/**
* Toggles Chat Box UX
* @param {Boolean} show - Whether or not to show Chat Box UX
*/
toggleUI(show = !this.chatPanel.checkVisibility()){
if(show){
this.chatPanel.style.display = "flex";
this.showChatIcon.style.display = "none";
this.client.player.hideVideoIcon.style.display = "flex";
this.client.userList.clickDragger.fixCutoff();
}else{
this.chatPanel.style.display = "none";
this.showChatIcon.style.display = "flex";
this.client.player.hideVideoIcon.style.display = "none";
}
}
/**
* Handles Video Toggling
* @param {Boolean} show - Whether or not the video is currently being hidden
*/
handleVideoToggle(show){
//If we're enabling the video
if(show){
//Show hide chat icon
this.hideChatIcon.style.display = "flex";
//Re-enable the click dragger
this.clickDragger.enabled = true;
//Lock the chat to aspect ratio of the video, to make sure the chat width isn't breaking shit
this.lockAspect();
//If we're disabling the video
}else{
//Hide hide hide hide hide hide chat icon
this.hideChatIcon.style.display = "none";
//Need to clear the width from the split, or else it doesn't display properly
this.chatPanel.style.flexBasis = "100%";
//Disable the click dragger
this.clickDragger.enabled = false;
}
}
/**
* Handles scrolling within the chat buffer
* @param {Event} event - Event passed down from Event Handler
*/
scrollHandler(event){
//If we're just starting out
if(this.lastPos == 0){
//Set last pos for the first time
this.lastPos = this.chatBuffer.scrollTop;
}
//Calculate scroll delta
const deltaY = this.chatBuffer.scrollTop - this.lastPos;
//Grab visible bounding rect so we don't have to do it again (can't use offset because someone might zoom in :P)
const bufferRect = this.chatBuffer.getBoundingClientRect();
const bufferHeight = Math.round(bufferRect.height);
const bufferWidth = Math.round(bufferRect.width);
if(this.lastHeight == 0){
this.lastHeight = bufferHeight;
}
if(this.lastWidth == 0){
this.lastWidth = bufferWidth;
}
//If we're scrolling up
if(deltaY &lt; 0){
//If we have room to scroll, and we didn't resize
if(this.chatBuffer.scrollHeight > bufferHeight &amp;&amp; (this.lastWidth == bufferWidth &amp;&amp; this.lastHeight == bufferHeight)){
//Disable auto scrolling
this.autoScroll = false;
}else{
this.handleAutoScroll();
}
//Otherwise if the difference between the chat buffers scroll height and offset height is equal to the scroll top
//(Because it is scrolled all the way down)
}else if((this.chatBuffer.scrollHeight - bufferHeight) == this.chatBuffer.scrollTop){
this.autoScroll = true;
}
//Set last post/size for next the run
this.lastPos = this.chatBuffer.scrollTop;
this.lastHeight = bufferHeight;
this.lastWidth = bufferWidth;
}
/**
* Auto-scrolls chat buffer when new chats are entered.
*/
handleAutoScroll(){
//If autoscroll is enabled
if(this.autoScroll){
//Set chatBuffer scrollTop to the difference between scrollHeight and buffer height (scroll to the bottom)
this.chatBuffer.scrollTop = this.chatBuffer.scrollHeight - Math.round(this.chatBuffer.getBoundingClientRect().height);
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

4659
www/doc/client/chatBox.html Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,680 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: chatPostprocessor.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: chatPostprocessor.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class contianing client-side message post-processing code
*/
class chatPostprocessor{
/**
* Instantiates a new Chat Post-Processor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client;
}
/**
* Post-Processes a single message from the server and returns a presntable DOM Node
* @param {Node} chatEntry - Chat entry generated by initial chatBox method
* @param {Object} rawData - Raw data from server
* @returns {Node} Post-Processed Chat Entry
*/
postprocess(chatEntry, rawData){
//Create empty array to hold filter spans
this.filterSpans = [];
//Set raw message data
this.rawData = rawData;
//Set current chat nodes
this.chatEntry = chatEntry;
this.chatBody = this.chatEntry.querySelector(".chat-entry-body");
//Split the chat message into an array of objects representing each word/chunk
this.splitMessage();
//Process Qoutes
this.processQoute();
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
this.processLinks();
//Inject clickable command examples
this.processCommandExamples();
//Inject clickable channel names
this.processChannelNames();
//Inject clickable usernames
this.processUsernames();
//Detect inline spoilers
this.processSpoilers();
//Detect inline strikethrough
this.processStrikethrough();
//Detect inline bold text
this.processBold();
//Detect inline italics
this.processItalics();
//Inject whitespace into long ass-words
this.addWhitespace();
//Handle non-standard chat types
this.handleChatType();
//Inject the pre-processed chat hyper-text into the chatEntry node
this.injectBody();
//Return the pre-processed node
return this.chatEntry;
}
/**
* Splits message into an array of Word Objects for further processing
*/
splitMessage(){
//Create an empty array to hold the body
this.messageArray = [];
//Unescape any sanatized char codes as we use .textContent for double-safety, and to prevent splitting of char codes
//Split string by word-boundries on words and non-word boundries around whitespace, with negative lookaheads to exclude file seperators so we don't split link placeholders, and dashes so we dont split usernames and other things
//Also split by any invisble whitespace as a crutch to handle mushed links/emotes
//If we can one day figure out how to split non-repeating special chars instead of special chars with whitespace, that would be perf, unfortunately my brain hasn't rotted enough to understand regex like that just yet.
const splitString = utils.unescapeEntities(this.rawData.msg).split(/(?&lt;!-)(?&lt;!␜)(?=\w)\b|(?!-)(?&lt;=\w)\b|(?=\s)\B|(?&lt;=\s)\B|/g);
//for each word in the splitstring
splitString.forEach((string) => {
//create a word object
const wordObj = {
string: string,
filterClasses: [],
type: "word"
}
//Add it to our body array
this.messageArray.push(wordObj);
});
}
/**
* Injects word objects into chat-entry as proper DOM Nodes
*/
injectBody(){
//Create an empty array to hold the objects to inject
const injectionArray = [];
//For each word object
this.messageArray.forEach((wordObj) => {
if(wordObj.type == 'word'){
//Create span node
const span = document.createElement('span');
//Set span filter classes
span.classList.add(...wordObj.filterClasses);
//Set span text
span.textContent = wordObj.string;
//Inject node into array
injectionArray.push(span);
}else if(wordObj.type == 'link'){
//Create a link node from our link
const link = document.createElement('a');
link.classList.add('chat-link', ...wordObj.filterClasses);
link.href = wordObj.link;
link.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
link.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, link);
}else if(wordObj.type == 'deadLink'){
//Create a text span node from our link
const badLink = document.createElement('a');
badLink.classList.add('chat-dead-link', 'danger-link', ...wordObj.filterClasses);
badLink.href = wordObj.link;
badLink.target = "_blank";
//Use textContent to be safe since links can't be escaped serverside
badLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, badLink);
}else if(wordObj.type == 'malformedLink'){
//Create a text span node from our link
const malformedLink = document.createElement('span');
malformedLink.classList.add('chat-malformed-link', ...wordObj.filterClasses);
//Use textContent to be safe since links can't be escaped (this is why we don't just add it using injectString)
//arguably we could sanatize malformed links serverside since they're never actually used as links
malformedLink.textContent = wordObj.link;
//Append node to chatBody
combineNode(wordObj, malformedLink);
}else if(wordObj.type == 'image'){
//Create an img node from our link
const img = document.createElement('img');
img.classList.add('chat-img', ...wordObj.filterClasses);
img.src = wordObj.link;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
img.title = `[${emote.name}]`;
}
//Append node to chatBody
combineNode(wordObj, img);
}else if(wordObj.type == 'video'){
//Create a video node from our link
const vid = document.createElement('video');
vid.classList.add('chat-video', ...wordObj.filterClasses);
vid.src = wordObj.link;
vid.controls = false;
vid.autoplay = true;
vid.loop = true;
vid.muted = true;
//Look for an emote by link since emotes are tx'd as bare links
const emote = this.client.chatBox.commandPreprocessor.getEmoteByLink(wordObj.link);
//If this is a known emote
if(emote != null){
//Set the hover text to the emote's name
vid.title = `[${emote.name}]`;
}
combineNode(wordObj, vid);
}else if(wordObj.type == 'command'){
//Create link node
const link = document.createElement('a');
//Set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.command;
//Add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.commandPreprocessor.preprocess(wordObj.command)});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else if(wordObj.type == "username"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add(wordObj.color, ...wordObj.filterClasses);
//Set href and inner text
link.href = "javascript:";
link.textContent = wordObj.string;
//add chatbox functionality
link.addEventListener('click', () => {this.client.chatBox.chatPrompt.value += `${wordObj.string} `});
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else if(wordObj.type == "channel"){
//Create link node
const link = document.createElement('a');
//set class
link.classList.add('chat-link', ...wordObj.filterClasses);
//Set href and inner text
link.href = `/c/${wordObj.chan}`;
link.target = "_blank"
link.textContent = wordObj.string;
//We don't have to worry about injecting this into whitespace since there shouldn't be any here.
injectionArray.push(link);
}else{
console.warn("Unknown chat postprocessor word type:");
console.warn(wordObj);
}
});
//For each item found in the injection array
for(let itemIndex in injectionArray){
const item = injectionArray[itemIndex];
//Currently this doesnt support multiple overlapping span-type filters
//not a huge deal since we only have once (spoiler)
//All others can be applied per-node without any usability side effects
const curFilter = this.filterSpans.filter(filterFilters)[0];
let appendBody = this.chatBody;
//If we have a filter span
if(curFilter != null){
//If we're beggining the array
if(itemIndex == curFilter.index[0]){
//Create the span
appendBody = document.createElement('span');
//Label it for what it is
appendBody.classList.add(curFilter.class);
//Add it to the chat body
this.chatBody.appendChild(appendBody);
//Otherwise
}else{
//Use the existing span
appendBody = (this.chatBody.children[this.chatBody.children.length - 1]);
}
}
//Append the node to our chat body
appendBody.appendChild(item);
function filterFilters(filter){
//If the index is within the filter span
return filter.index[0] &lt;= itemIndex &amp;&amp; filter.index[1] >= itemIndex;
}
}
//Like string.replace except it actually injects the node so we can keep things like event handlers
function combineNode(wordObj, node, placeholder = '␜'){
//Split string by the placeholder so we can keep surrounding whitespace
const splitWord = wordObj.string.split(placeholder, 2);
//Create combined node
const combinedSpan = document.createElement('span');
//Add the first part of the text
combinedSpan.textContent = splitWord[0];
//Add in the requestd node
combinedSpan.appendChild(node);
//Finish it off with the last bit of text
combinedSpan.insertAdjacentText('beforeend', splitWord[1]);
//Add to injection array as three nested items to keep arrays lined up
injectionArray.push(combinedSpan);
}
}
/**
* Processes qouted text in chat
*/
processQoute(){
//If the message starts off with '>'
if(this.messageArray[0].string[0] == '>'){
this.chatBody.classList.add("qoute");
}
}
/**
* Processes clickable command examples in chat
*/
processCommandExamples(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word
const lastChar = wordObj.string[wordObj.string.length - 1];
//if the last char is !
if(lastChar == '!' || lastChar == '/'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
const command = lastChar + nextWord.string;
//Take out the command marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "command",
string: nextWord.string,
filterClasses: [],
command: command
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable channel names in chat
*/
processChannelNames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Get last char of current word with slashes pounds
const lastChar = wordObj.string[wordObj.string.length - 1];
const secondLastChar = wordObj.string[wordObj.string.length - 2];
//if the last char is # and the second to last char isn't &amp; or # (avoid spoilers)
if(lastChar == '#' &amp;&amp; secondLastChar != '#'){
//get next word
const nextWord = this.messageArray[wordIndex + 1];
//if we have another word
if(nextWord != null){
//Take out the chan marker
this.messageArray[wordIndex].string = wordObj.string.slice(0,-1);
const commandObj = {
type: "channel",
string: lastChar + nextWord.string,
filterClasses: [],
chan: nextWord.string
}
this.messageArray[wordIndex + 1] = commandObj;
}
}
}
});
}
/**
* Processes clickable username callouts in chat
*/
processUsernames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Check for user and get their color
const color = this.client.userList.colorMap.get(wordObj.string);
//If the current word is the username of a connected user
if(color != null){
//Mark it as so
this.messageArray[wordIndex].type = "username";
//Store their color
this.messageArray[wordIndex].color = color;
}
}
});
}
/**
* Injects invisible whitespace in long-ass words to prevent fucking up the chat buffer size
*/
addWhitespace(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
//if the word object hasn't been pre-processed elsewhere
if(wordObj.type == "word"){
//Create an empty array to hold our word
var wordArray = [];
//For each character in the string of the current word object
this.messageArray[wordIndex].string.split("").forEach((char, charIndex) => {
//push the current character to the wordArray
wordArray.push(char);
//After eight characters
if(charIndex > 8){
//Push an invisible line-break character between every character
wordArray.push("");
}
});
//Join the wordArray into a single string, and use it to set the current wordObject's string
this.messageArray[wordIndex].string = wordArray.join("");
}
});
}
/**
* Searches for text in-between a specific delimiter and runs a given callback against it
*
* Internal command used by several text filters to prevent code re-writes
* @param {String} delimiter - delimiter to search string by
* @param {Function} cb - Callback function to run against found strings
* @returns {Array} - list of found instances of filter
*/
processFilter(delimiter, cb){
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
const foundFilters = [];
//Spoiler detection stage
//For each word object in the message array
main: for(let wordIndex = 0; wordIndex &lt; this.messageArray.length; wordIndex++){
//Get the current word object
const wordObj = this.messageArray[wordIndex];
//If its a regular word and contains '##'
if(wordObj.type == 'word' &amp;&amp; wordObj.string.match(utils.escapeRegex(delimiter))){
//Crawl through detected spoilers
for(let spoiler of foundFilters){
//If the current word object is part of a detected spoiler
if(wordIndex == spoiler[0] || wordIndex == spoiler[1]){
//ignore it and continue on to the next word object
continue main;
}
}
//Crawl throw word objects after the current one
for(let endIndex = (wordIndex + 1); endIndex &lt; this.messageArray.length; endIndex++){
//Get the current end object
const endObj = this.messageArray[endIndex];
//If its a regular word and contains '##'
if(endObj.type == 'word' &amp;&amp; endObj.string.match(utils.escapeRegex(delimiter))){
//Setup the found filter array
const foundFilter = [wordIndex, endIndex];
//Scrape out delimiters
wordObj.string = wordObj.string.replaceAll(delimiter,'');
endObj.string = endObj.string.replaceAll(delimiter,'');
//Add it to the list of detected filters
foundFilters.push(foundFilter);
//Run the filter callback
cb(foundFilter)
//Break the nested end-detection loop
break;
}
}
}
}
return foundFilters;
}
/**
* Processes in-line spoilers
*/
processSpoilers(){
//Process spoilers using '##' delimiter
this.processFilter('##', (foundSpoiler)=>{
//For each found spoiler add it to the list of found filter spans
this.filterSpans.push({class: "spoiler", index: [foundSpoiler[0] + 1, foundSpoiler[1] - 1], delimiters: [foundSpoiler[0], foundSpoiler[1]]});
});
}
/**
* Processes in-line Strike-through
*/
processStrikethrough(){
//Process strikethrough's using '~~' delimiter
this.processFilter('~~', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("strikethrough");
}
})
}
/**
* Processes in-line Bold/Strong text
*/
processBold(){
//Process strong text using '*' delimiter
this.processFilter('**', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("bold");
}
})
}
/**
* Processes in-line Italics
*/
processItalics(){
//Process italics using '__' delimiter
this.processFilter('*', (foundStrikethrough)=>{
for(let wordIndex = foundStrikethrough[0]; wordIndex &lt; foundStrikethrough[1]; wordIndex++){
this.messageArray[wordIndex].filterClasses.push("italics");
}
})
}
/**
* Processes clickable links and embedded media
*/
processLinks(){
//If we don't have links
if(this.rawData.links == null){
//Don't bother
return;
}
//For every link received in this message
this.rawData.links.forEach((link, linkIndex) => {
//For every word obj in the message array
this.messageArray.forEach((wordObj, wordIndex) => {
//Check current wordobj for link (placeholder may contain whitespace with it)
if(wordObj.string.match(`␜${linkIndex}`)){
//Set current word object in the body array to the new link object
this.messageArray[wordIndex] = {
//Don't want to use a numbered placeholder to make this easier during body injection
//but we also don't want to clobber any surrounding whitespace
string: wordObj.string.replace(`␜${linkIndex}`, '␜'),
link: link.link,
type: link.type,
filterClasses: []
}
}
})
});
}
/**
* Marks chat nodes in-case of non-standard chat types
*/
handleChatType(){
if(this.rawData.type == "whisper"){
//add whisper class
this.chatBody.classList.add('whisper');
}else if(this.rawData.type == "announcement"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//Get the username and make it into an announcement title (little hacky but this *IS* postprocessing)
const userNode = this.chatEntry.querySelector('.chat-entry-username');
userNode.textContent = `${userNode.textContent.slice(0,-2)} Announcement`;
//Add/remove relevant classes
userNode.classList.remove('chat-entry-username');
userNode.classList.add('announcement-title');
this.chatBody.classList.add('announcement-body');
this.chatEntry.classList.add('announcement');
}else if(this.rawData.type == "toke"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("toke");
}else if(this.rawData.type == "tokewhisper"){
//Squash the high-level
this.chatEntry.querySelector('.high-level').remove();
//remove the username
this.chatEntry.querySelector('.chat-entry-username').remove();
//Add toke/tokewhisper class
this.chatBody.classList.add("tokewhisper","serverwhisper");
}else if(this.rawData.type == "spoiler"){
//Set whole-body spoiler
this.chatBody.classList.add("spoiler");
}else if(this.rawData.type == "strikethrough"){
//Set whole-body spoiler
this.chatBody.classList.add("strikethrough");
}else if(this.rawData.type == "bold"){
//Set whole-body spoiler
this.chatBody.classList.add("bold");
}else if(this.rawData.type == "italics"){
//Set whole-body spoiler
this.chatBody.classList.add("italics");
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -30,7 +30,7 @@
<h2><span class="attribs"><span class="type-signature"></span></span>commandPreprocessor<span class="signature">(client)</span><span class="type-signature"></span></h2>
<div class="class-description">Class for object containing chat and command pre-processing logic</div>
<div class="class-description">Class containing chat and command pre-processing logic</div>
</header>
@ -105,7 +105,7 @@
<td class="description last">Parent client mgmt object</td>
<td class="description last">Parent client Management Object</td>
</tr>
@ -1906,13 +1906,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -43,12 +43,12 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class for object containing chat and command pre-processing logic
* Class containing chat and command pre-processing logic
*/
class commandPreprocessor{
/**
* Instantiates a new commandPreprocessor object
* @param {channel} client - Parent client mgmt object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
@ -328,7 +328,7 @@ class commandPreprocessor{
}
/**
* Class for Object which contains logic for client-side commands
* Class which contains logic for client-side commands
*/
class commandProcessor{
/**
@ -365,13 +365,13 @@ class commandProcessor{
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -30,7 +30,7 @@
<h2><span class="attribs"><span class="type-signature"></span></span>commandProcessor<span class="signature">(client)</span><span class="type-signature"></span></h2>
<div class="class-description">Class for Object which contains logic for client-side commands</div>
<div class="class-description">Class which contains logic for client-side commands</div>
</header>
@ -415,13 +415,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -0,0 +1,524 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: cpanel.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: cpanel.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class containing code for managing the Canopy Panel UX
*/
class cPanel{
/**
* Instantiates a new Canopy Panel Management object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management object
*/
this.client = client;
/**
* Active Panel Object
*/
this.activePanel = null;
/**
* Pinned Panel Object
*/
this.pinnedPanel = null;
/**
* Popped Panel Objects
*/
this.poppedPanels = [];
/**
* Click-Dragger object for re-sizable active panel
*/
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false, null, false);
/**
* Click-Dragger object for re-sizable pinned panel
*/
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
//Element Nodes
//Active Panel
/**
* Active Panel Container
*/
this.activePanelDiv = document.querySelector("#cpanel-active-div");
/**
* Active Panel Title
*/
this.activePanelTitle = document.querySelector("#cpanel-active-title");
/**
* Active Title Document Div
*/
this.activePanelDoc = document.querySelector("#cpanel-active-doc");
/**
* Active Panel Pin Icon
*/
this.activePanelPinIcon = document.querySelector("#cpanel-active-pin-icon");
/**
* Active Panel Pop-Out Icon
*/
this.activePanelPopoutIcon = document.querySelector("#cpanel-active-popout-icon");
/**
* Active Panel Close Icon
*/
this.activePanelCloseIcon = document.querySelector("#cpanel-active-close-icon");
//Pinned Panel
/**
* Pinned Panel Contianer
*/
this.pinnedPanelDiv = document.querySelector("#cpanel-pinned-div");
/**
* Pinned Panel Title
*/
this.pinnedPanelTitle = document.querySelector("#cpanel-pinned-title");
/**
* Pinned Panel Document Div
*/
this.pinnedPanelDoc = document.querySelector("#cpanel-pinned-doc");
/**
* Pinned Panel Un-Pin Icon
*/
this.pinnedPanelUnpinIcon = document.querySelector("#cpanel-pinned-unpin-icon");
/**
* Pinned Panel Pop-Out Icon
*/
this.pinnedPanelPopoutIcon = document.querySelector("#cpanel-pinned-popout-icon");
/**
* Pinned Panel Close Icon
*/
this.pinnedPanelCloseIcon = document.querySelector("#cpanel-pinned-close-icon");
this.setupInput();
}
/**
* Defines input-related event listeners
*/
setupInput(){
this.activePanelCloseIcon.addEventListener("click", this.hideActivePanel.bind(this));
this.activePanelPinIcon.addEventListener("click", this.pinPanel.bind(this));
this.activePanelPopoutIcon.addEventListener("click", this.popActivePanel.bind(this));
this.pinnedPanelCloseIcon.addEventListener("click", this.hidePinnedPanel.bind(this));
this.pinnedPanelUnpinIcon.addEventListener("click", this.unpinPanel.bind(this));
this.pinnedPanelPopoutIcon.addEventListener("click", this.popPinnedPanel.bind(this));
}
/**
* Sets Active Panel
* @param {panelObj} panel - Panel Object to set as active
* @param {String} panelBody - innerHTML of Panel, pulls from panelObj.getPage() if empty
*/
async setActivePanel(panel, panelBody){
//Set active panel
this.activePanel = panel;
//Grab panel hypertext content and load it into div
this.activePanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.activePanel.getPage() : panelBody;
//Display panel
this.activePanelDiv.style.display = "flex";
this.activePanelTitle.textContent = this.activePanel.name;
//Call panel initialization function
this.activePanel.panelDocument = this.activePanelDoc;
this.activePanel.docSwitch();
}
/**
* Hides active panel
* @param {Event} event - Event passed down from Input Handler
* @param {Boolean} keepAlive - Prevents closing panel if true
*/
hideActivePanel(event, keepAlive = false){
if(!keepAlive){
this.activePanel.closer();
}
//Hide the panel
this.activePanelDiv.style.display = "none";
//Clear out the panel
this.activePanelDoc.innerHTML = '';
//Set active panel to null
this.activePanel = null;
}
/**
* Pins active panel
*/
pinPanel(){
this.setPinnedPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Pop's out active panel
*/
popActivePanel(){
this.popPanel(this.activePanel, this.activePanelDoc.innerHTML);
this.hideActivePanel(null, true);
}
/**
* Sets pinned panel
* @param {panelObj} panel - Panel Object to apply to panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
*/
async setPinnedPanel(panel, panelBody){
//Set pinned panel
this.pinnedPanel = panel;
//Set Title
this.pinnedPanelTitle.textContent = this.pinnedPanel.name;
//Grab panel hypertext content and load it into div
this.pinnedPanelDoc.innerHTML = (panelBody == null || panelBody == "") ? await this.pinnedPanel.getPage() : panelBody;
//Display panel
this.pinnedPanelDiv.style.display = "flex";
//Call panel initialization function
this.pinnedPanel.panelDocument = this.pinnedPanelDoc;
this.pinnedPanel.docSwitch();
//Resize to window/content
this.pinnedPanelDragger.fixCutoff();
}
/**
* Hides pinned panel
* @param {Event} event - Passed down input event
* @param {Boolean} keepAlive - Prevents panel.closer() from running if true
*/
hidePinnedPanel(event, keepAlive = false){
this.pinnedPanelDiv.style.display = "none";
if(!keepAlive){
this.pinnedPanel.closer();
}
this.pinnedPanel = null;
}
/**
* Sets pinned panel to active
*/
unpinPanel(){
this.setActivePanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops pinned panel
*/
popPinnedPanel(){
this.popPanel(this.pinnedPanel, this.pinnedPanelDoc.innerHTML);
this.hidePinnedPanel(null, true);
}
/**
* Pops a new pop-out panel
* @param {panelObj} panel - panelObj to apply to the panel
* @param {String} panelBody - Raw HTML to inject into panel body, injects panel default if left to null
*/
popPanel(panel, panelBody){
var newPanel = new poppedPanel(panel, panelBody, this)
this.poppedPanels.push(newPanel);
}
}
/**
* Template Class for other Classes for Objects which represent a single Canopy Panel
*/
class panelObj{
/**
* Instantiates a new Panel Object
* @param {channel} client - Parent client Management Object
* @param {String} name - Panel Name
* @param {String} pageURL - Panel Default Page URL
* @param {Document} panelDocument - Panel Document
*/
constructor(client, name = "Placeholder Panel", pageURL = "/panel/placeholder", panelDocument = window.document){
/**
* Panel Name
*/
this.name = name;
/**
* Panel Default Page URL
*/
this.pageURL = pageURL;
/**
* Panel Document
*/
this.panelDocument = panelDocument;
/**
* Current root document panel doc lives within
*/
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
/**
* Parent Client Management object
*/
this.client = client;
}
/**
* Fetches panel page from the server
* @returns {String} Raw panel doc HTML
*/
async getPage(){
var response = await fetch(this.pageURL,{
method: "GET",
});
return await response.text();
}
/**
* Handles Document/Panel Changes
*/
docSwitch(){
//Set owner doc
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
}
/**
* Called upon panel close/exit
*/
closer(){
}
}
/**
* Class which represents a single instance of a popped-out panel
*/
class poppedPanel{
/**
* Instantiates a new Popped Panel Object
* @param {panelObj} panel - Panel Object to apply to Popped Panel
* @param {String} panelBody - Raw HTML to inject into panel body, defaults to panel page if null
* @param {cPanel} cPanel - Parent Canopy Panel Management Object
*/
constructor(panel, panelBody, cPanel){
/**
* Panel Object to apply to Popped Panel
*/
this.panel = panel;
/**
* Raw HTML to inject into panel body, defaults to panel page if null
*/
this.panelBody = panelBody;
/**
* Browser Window taken up by the Popped Panel
*/
this.window = null;
/**
* Popped Panel Container Div
*/
this.pinnedPanelDiv = null;
/**
* Popped Panel Title
*/
this.pinnedPanelTitle = null;
/**
* Popped Panel Document Div
*/
this.pinnedPanelDoc = null;
/**
* Popped Panel Close Icon
*/
this.pinnedPanelCloseIcon = null;
/**
* Parent Canopy Panel Management Object
*/
this.cPanel = cPanel;
/**
* Disables this.panel.closer() calls from this.closer()
*/
this.keepAlive = false;
//Continue constructor asynchrnously
this.asyncConstructor();
}
/**
* Continuation of constructor method for asynchronous function calls
*/
async asyncConstructor(){
//Set panel body properly
this.panelBody = (this.panelBody == null || this.panelBody == "") ? await this.panel.getPage() : this.panelBody;
//Pop the panel
this.popContainer();
}
/**
* Pops/Opens container window upon start
*/
popContainer(){
//Set Window Object
this.window = window.open("/panel/popoutContainer","",`menubar=no,height=850,width=600`);
this.window.addEventListener("load", this.fillContainer.bind(this));
}
/**
* Fills container window with Popped Panel container elements
*/
fillContainer(){
//Set Element Nodes
this.panelDiv = this.window.document.querySelector("#cpanel-div");
this.panelTitle = this.window.document.querySelector("#cpanel-title");
this.panelDoc = this.window.document.querySelector("#cpanel-doc");
this.panelPopinIcon = this.window.document.querySelector("#cpanel-popin-icon");
this.panelPinIcon = this.window.document.querySelector("#cpanel-pin-icon");
//Set Window Title
this.window.document.title = this.window.document.title.replace("NULL_POPOUT", `${this.panel.name} (${client.channelName})`);
//Set Panel Content
this.panelTitle.innerText = this.panel.name;
this.panelDoc.innerHTML = this.panelBody;
//Set panel object document and call the related function
this.panel.panelDocument = this.window.document;
this.panel.docSwitch();
this.setupInput();
}
/**
* Defines default input-related popped-panel Event Listeners
*/
setupInput(){
this.panelPopinIcon.addEventListener("click", this.unpop.bind(this));
this.panelPinIcon.addEventListener("click", this.pin.bind(this));
this.window.addEventListener("unload", this.closer.bind(this));
}
/**
* Called upon close/exit of panel
*/
closer(){
if(!this.keepAlive){
this.panel.closer();
}
this.cPanel.poppedPanels.splice(this.cPanel.poppedPanels.indexOf(this),1);
}
/**
* Un-pops panel into active-panel slot
*/
unpop(){
//Set active panel
this.cPanel.setActivePanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
//Close the popped window
this.window.close();
}
/**
* Pins panel next to chat
*/
pin(){
this.cPanel.setPinnedPanel(this.panel, this.panelDoc.innerHTML);
this.keepAlive = true;
this.window.close();
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -202,13 +202,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

2928
www/doc/client/hlsBase.html Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -81,13 +81,13 @@ This new codebase intends to solve the following issues with the current CyTube
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,844 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: mediaHandler.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: mediaHandler.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/*
* Base class for all Canopy Media Handlers
*
* This is little more than a interface class
*/
class mediaHandler{
/**
* Instantiates a new Media Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Parent Canopy Player Object
*/
this.player = player;
/**
* Media Handler Source Type
*/
this.type = type
/*
* Denotes wether a seek call was made by the syncing function
*/
this.selfAct = false;
/*
* Contains the last received time stamp
*/
this.lastTimestamp = 0;
//Ingest media object from server
this.startMedia(media);
}
/**
* Ingests media nd starts playback
* @param {Object} media - Media object from server
*/
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
//Call the start function
this.start();
}
}
/**
* Builds video player element
*/
buildPlayer(){
//Reset player lock
this.lock = false;
}
/**
* Destroys video player element
*/
destroyPlayer(){
//Null out video property
this.video = null;
}
/**
* Ingests media object from server
* @param {Object} media - Media object from the server
* @returns {Boolean} True upon success
*/
ingestMedia(media){
//Set now playing
this.nowPlaying = media;
//return true to signify success
return true;
}
/**
* Starts video playback
*/
start(){
this.setVideoTitle(this.nowPlaying.title);
}
/**
* Syncronizes timestamp based on timestamp received from server
* @param {Number} timestamp - Current video timestamp in seconds
*/
sync(timestamp = this.lastTimestamp){
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set self act flag
this.selfAct = true;
}
}
/**
* Reloads media player
*/
reload(){
//Get current timestamp
const timestamp = this.video.currentTime;
//Throw self act flag to make sure we don't un-sync the player
this.selfAct = true;
}
/**
* Handles media end
*/
end(){
//Null out current media
this.nowPlaying = null;
//Throw self act to prevent unlock on video end
this.selfAct = true;
//Destroy the player
this.destroyPlayer();
}
/**
* Plays video
*/
play(){
}
/**
* Pauses video
*/
pause(){
}
/**
* Toggles player control lockout
* @param {Boolean} lock - Whether or not to lock-out user control of video
*/
setPlayerLock(lock){
//set lock property
this.lock = lock;
}
/**
* Calculates Aspect Ratio of media
* @returns {Number} Media Aspect Ratio as Floating Point number
*/
getRatio(){
return 4/3;
}
/**
* Gets current timestamp from video
* @returns {Number} Media Timestamp in seconds
*/
getTimestamp(){
return 0;
}
/**
* Sets player title
* @param {String} title - Title to set
*/
setVideoTitle(title){
this.player.title.textContent = `Currently Playing: ${title}`;
}
/**
* Called once all video metadata has properly been fetched
* @param {Event} event - Event passed down by event handler
*/
onMetadataLoad(event){
//Resize aspect (if locked), since the video doesn't properly report it's resolution until it's been loaded
this.client.chatBox.resizeAspect();
}
/**
* Called on media pause
* @param {Event} event - Event passed down by event handler
*/
onPause(event){
//If the video was paused out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
this.selfAct = false;
}
/**
* Called on media volume change
* @param {Event} event - Event passed down by event handler
*/
onVolumeChange(event){
}
/**
* Called on media seek
* @param {Event} event - Event passed down by event handler
*/
onSeek(event){
//If the video was seeked out-side of code
if(!this.selfAct){
this.player.unlockSync();
}
//reset self act flag
this.selfAct = false;
}
/**
* Called on media buffer
* @param {Event} event - Event passed down by event handler
*/
onBuffer(){
this.selfAct = true;
}
}
/**
* Class containing basic building blocks for anything that touches a &lt;video> tag
* @extends mediaHandler
*/
class rawFileBase extends mediaHandler{
/**
* Instantiates a new rawFileBase object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
super(client, player, media, type);
}
/**
* Defines input-related event listeners
*/
defineListeners(){
//Resize to aspect on metadata load
this.video.addEventListener('loadedmetadata', this.onMetadataLoad.bind(this));
this.video.addEventListener('volumechange', this.onVolumeChange.bind(this));
}
buildPlayer(){
//Create player
this.video = document.createElement('video');
//Enable controls
this.video.controls = true;
//Append it to page
this.player.videoContainer.appendChild(this.video);
//Run derived method
super.buildPlayer();
}
destroyPlayer(){
//Stops playback
this.video.pause();
//Remove player from page
this.video.remove();
//Run derived method
super.destroyPlayer();
}
reload(){
//Call derived method
super.reload();
//Load video from source
this.video.load();
//Set it back to the proper time
this.video.currentTime = this.lastTimestamp;
//Play the video
this.video.play();
}
setPlayerLock(lock){
//toggle controls
this.video.controls = !lock;
//Only toggle mute if we're locking, or if we're unlocking after being locked
//If this is ran twice without locking we don't want to surprise unmute on the user
if(lock || this.lock){
//toggle mute
this.video.muted = lock;
}
//toggle looping
this.video.loop = lock;
//Run derived method
super.setPlayerLock(lock);
}
getRatio(){
return this.video.videoWidth / this.video.videoHeight;
}
onVolumeChange(event){
//Pull volume from video
this.player.volume = this.video.volume;
}
}
/**
* Off air static 'player'
* @extends rawFileBase
*/
class nullHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
*/
constructor(client, player){
//Call derived constructor
super(client, player, {}, null);
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
//Disable right clicking
this.video.addEventListener('contextmenu', (e)=>{e.preventDefault()});
}
start(){
//call derived start function
super.start();
//Lock the player
this.setPlayerLock(true);
//Set the static placeholder
this.video.src = '/video/static.webm';
//play the placeholder video
this.video.play();
}
setVideoTitle(title){
this.player.title.textContent = `Channel Off Air`;
}
}
/**
* Basic building blocks needed for proper time-synchronized raw-file playback
* @extends rawFileBase
*/
class rawFileHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'raw');
//Define listeners
this.defineListeners();
}
defineListeners(){
//Run derived method
super.defineListeners();
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
start(){
//Call derived start
super.start();
//Set video
this.video.src = this.nowPlaying.rawLink;
//Set video volume
this.video.volume = this.player.volume;
//Unlock player
this.setPlayerLock(false);
//play video
this.video.play();
}
play(){
this.video.play();
}
pause(){
this.video.pause();
}
sync(timestamp = this.lastTimestamp){
//Call derived sync
super.sync(timestamp);
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
//Set current video time based on timestamp received from server
this.video.currentTime = timestamp;
}
}
getTimestamp(){
//Return current timestamp
return this.video.currentTime;
}
}
/**
* Handles Youtube playback via the official YT embed (gross)
* @extends mediaHandler
*/
class youtubeEmbedHandler extends mediaHandler{
/**
* Instantiates a new Youtube Embed Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'ytEmbed');
//Set flag to notify functions when the player is actually ready
this.ready = false;
//Create property to hold video iframe for easy access
this.iframe = null;
}
//custom start media function since we want the youtube player to call the start function once it's ready
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
//Build the video player
this.buildPlayer();
}
}
buildPlayer(){
//If the embed API hasn't loaded
if(!this.client.ytEmbedAPILoaded){
//Complain and stop
return console.warn("youtubeEmbedHandler.buildPlayer() Called before YT Iframe API Loaded, waiting on refresh to rebuild...");
}
//Create temp div for yt api to replace
const tempDiv = document.createElement('div');
//Name the div
tempDiv.id = "youtube-embed-player"
//Append it to the video container
this.player.videoContainer.appendChild(tempDiv);
//Create a new youtube player using the official YT iframe-embed api
this.video = new YT.Player('youtube-embed-player', {
//Inject video id
videoId: this.nowPlaying.id,
events: {
'onReady': this.start.bind(this),
'onStateChange': this.onStateChange.bind(this)
}
});
//Call derived function
super.buildPlayer();
}
start(){
//Call derived start function
super.start();
//Set volume based on player volume
this.video.setVolume(this.player.volume * 100);
//Kick the video off
this.video.playVideo();
//Pull iframe
this.iframe = this.video.getIframe()
//Throw the ready flag
this.ready = true;
}
destroyPlayer(){
//If we've had enough time to create a player frame
if(this.ready){
//Pull volume from player before destroying since google didn't give us a volume change event like a bunch of dicks
this.player.volume = (this.video.getVolume() / 100);
//Use the embed api's built in destroy function
this.video.destroy();
}
//Check the f̶r̶i̶d̶g̶e video container for leftovers
const leftovers = this.player.videoContainer.querySelector("#youtube-embed-player");
//If we have any leftovers
if(leftovers != null){
//Nukem like last nights chicken
leftovers.remove();
}
//Call derived destroy function
super.destroyPlayer();
}
sync(timestamp = this.lastTimestamp){
//If we're not ready
if(!this.ready){
//Kick off a timer to wait it out and try again l8r
setTimeout(this.sync.bind(this), 100);
//If it failed, tell randy to fuck off
return;
}
//Seek to timestamp, allow buffering
this.video.seekTo(timestamp, true);
}
reload(){
//if we're ready
if(this.ready){
//re-load the video by id
this.video.loadVideoById(this.nowPlaying.id);
}
}
play(){
//If we're ready
if(this.ready){
//play the video
this.video.playVideo();
}
}
pause(){
//If we're ready
if(this.ready){
//pause the video
this.video.pauseVideo();
}
}
getRatio(){
//TODO: Implement a type-specific metadata property object in the media class to hold type-sepecifc meta-data
//Alternatively we could fill in resolution information from the raw link
//However keeping embedded functionality dependant on raw-links seems like bad practice
}
getTimestamp(){
//If we're ready
if(this.ready){
//Return the timestamp
return this.video.getCurrentTime();
}
//If we fall through, simply report that the video hasn't gone anywhere yet
return 0;
}
setVideoTitle(){
//Clear out the player title so that youtube's baked in title can do it's thing.
//This will be replaced once we complete the full player control and remove the defualt youtube UI
this.player.title.textContent = "";
}
/**
* Generic handler for state changes since google is a dick
*/
onStateChange(event){
switch(event.data){
//video unstarted
case -1:
return;
//video ended
case 0:
return;
//video playing
case 1:
return;
//video paused
case 2:
super.onPause(event);
return;
//video buffering
case 3:
//There is no good way to tell slow connections apart from user seeking
//This will be easier to implement once we get custom player controls up
//super.onSeek(event);
return;
//video queued
case 5:
return;
//bad status code
default:
return;
}
}
setPlayerLock(lock){
super.setPlayerLock(lock);
if(this.ready){
this.iframe.style.pointerEvents = (lock ? "none" : "");
}
}
}
/**
* Base HLS Media handler for handling all HLS related media
* @extends rawFileBase
*/
class hlsBase extends rawFileBase{
/**
* Instantiates a new HLS Base object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
//Call derived constructor
super(client, player, media, type);
}
buildPlayer(){
//Call derived buildPlayer function
super.buildPlayer();
//Instantiate HLS object
this.hls = new Hls();
//Load HLS Stream
this.hls.loadSource(this.nowPlaying.url);
//Attatch hls object to video element
this.hls.attachMedia(this.video);
//Bind onMetadataLoad to MANIFEST_PARSED
this.hls.on(Hls.Events.MANIFEST_PARSED, this.onMetadataLoad.bind(this));
}
end(){
//Stop hls.js from loading any more of the stream
this.hls.stopLoad();
//Call derived method
super.end();
}
onMetadataLoad(){
//Call derived method
super.onMetadataLoad();
}
start(){
//Call derived method
super.start();
//Start the video
this.video.play();
}
}
/**
* HLS Livestream Handler
* @extends hlsBase
*/
class hlsLiveStreamHandler extends hlsBase{
/**
* Instantiates a new HLS Live Stream Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, "livehls");
//Create variable to determine if we need to resync after next seek
this.reSync = false;
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeked', this.onSeek.bind(this));
this.video.addEventListener('waiting', this.onBuffer.bind(this));
}
sync(){
//Kick the video back on if it was paused
this.video.play();
//Pull video duration
const duration = this.video.duration;
//Ignore bad timestamps
if(duration > 0){
//Seek to the end to sync up w/ the livestream
this.video.currentTime = duration;
}
}
setVideoTitle(title){
//Add title as text content for security :P
this.player.title.textContent = `: ${title}`;
//Create glow span
const glowSpan = document.createElement('span');
//Fill glow span content
glowSpan.textContent = "🔴LIVE";
//Set glowspan class
glowSpan.classList.add('critical-danger-text');
//Inject glowspan into title in a way that allows it to be easily replaced
this.player.title.prepend(glowSpan);
}
onBuffer(event){
//Call derived function
super.onBuffer(event);
//If we're synced by the end of buffering
if(this.player.syncLock){
//Throw flag to manually sync since this works entirely differently from literally every other fucking media source
this.reSync = true;
}
}
onSeek(event){
//Call derived method
super.onSeek(event);
//If we stopped playing the video
if(this.video == null){
//Don't worry about it
return;
}
//Calculate distance to end of stream
const difference = this.video.duration - this.video.currentTime;
//If we where buffering under sync lock
if(this.reSync){
//Set reSync to false
this.reSync = false;
//If the difference is bigger than streamSyncTolerance
if(difference > this.player.streamSyncTolerance){
//Sync manually since we have no timestamp, and therefore the player won't do it for us
this.sync();
}
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,918 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: panelObj</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: panelObj</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h2>
<div class="class-description">Template Class for other Classes for Objects which represent a single Canopy Panel</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="panelObj"><span class="type-signature"></span>new panelObj<span class="signature">(client, name, pageURL, panelDocument)</span><span class="type-signature"></span></h4>
<div class="description">
Instantiates a new Panel Object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>client</code></td>
<td class="type">
<span class="param-type"><a href="channel.html">channel</a></span>
</td>
<td class="default">
</td>
<td class="description last">Parent client Management Object</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
Placeholder Panel
</td>
<td class="description last">Panel Name</td>
</tr>
<tr>
<td class="name"><code>pageURL</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="default">
/panel/placeholder
</td>
<td class="description last">Panel Default Page URL</td>
</tr>
<tr>
<td class="name"><code>panelDocument</code></td>
<td class="type">
<span class="param-type">Document</span>
</td>
<td class="default">
</td>
<td class="description last">Panel Document</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line263">line 263</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="client"><span class="type-signature"></span>client<span class="type-signature"></span></h4>
<div class="description">
Parent Client Management object
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line295">line 295</a>
</li></ul></dd>
</dl>
<h4 class="name" id="name"><span class="type-signature"></span>name<span class="type-signature"></span></h4>
<div class="description">
Panel Name
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line275">line 275</a>
</li></ul></dd>
</dl>
<h4 class="name" id="ownerDoc"><span class="type-signature"></span>ownerDoc<span class="type-signature"></span></h4>
<div class="description">
Current root document panel doc lives within
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line290">line 290</a>
</li></ul></dd>
</dl>
<h4 class="name" id="pageURL"><span class="type-signature"></span>pageURL<span class="type-signature"></span></h4>
<div class="description">
Panel Default Page URL
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line280">line 280</a>
</li></ul></dd>
</dl>
<h4 class="name" id="panelDocument"><span class="type-signature"></span>panelDocument<span class="type-signature"></span></h4>
<div class="description">
Panel Document
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line285">line 285</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="closer"><span class="type-signature"></span>closer<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Called upon panel close/exit
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line321">line 321</a>
</li></ul></dd>
</dl>
<h4 class="name" id="docSwitch"><span class="type-signature"></span>docSwitch<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Handles Document/Panel Changes
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line313">line 313</a>
</li></ul></dd>
</dl>
<h4 class="name" id="getPage"><span class="type-signature">(async) </span>getPage<span class="signature">()</span><span class="type-signature"> &rarr; {String}</span></h4>
<div class="description">
Fetches panel page from the server
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="cpanel.js.html">cpanel.js</a>, <a href="cpanel.js.html#line302">line 302</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
Raw panel doc HTML
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">String</span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

3383
www/doc/client/player.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: player.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: player.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class which represents Canopy Player UX
*/
class player{
/**
* Instantiates a new Canopy Player object
* @param {channel} client - Parent client Management Object
*/
constructor (client){
/**
* Parent CLient Management Object
*/
this.client = client;
/**
* Whether or not the mouse cursor is floating over player UX
*/
this.onUI = false;
/**
* Whether or not player scrub is locked to sync signal from the server
*/
this.syncLock = true;
/**
* Player UX Stow-Away timer
*/
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
//elements
/**
* Top-Level Player Container Div
*/
this.playerDiv = document.querySelector("#media-panel-div");
/**
* Player Element Container Div
*/
this.videoContainer = document.querySelector("#media-panel-video-container")
/**
* Page Nav-Par
*/
this.navBar = document.querySelector("#navbar");
/**
* Auto-Hiding Player UI
*/
this.uiBar = document.querySelector("#media-panel-head-div");
/**
* Player Title Label
*/
this.title = document.querySelector("#media-panel-title-paragraph");
/**
* Player Show Video Icon
*/
this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon");
/**
* Player Hide Video Icon
*/
this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon");
/**
* Player Syncronization Icon
*/
this.syncIcon = document.querySelector("#media-panel-sync-icon");
/**
* Player Cinema-Mode Icon
*/
this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon");
/**
* Player Filp Video Y Icon
*/
this.flipYIcon = document.querySelector("#media-panel-flip-vertical-icon")
/**
* Player Flip Video X Icon
*/
this.flipXIcon = document.querySelector("#media-panel-flip-horizontal-icon")
/**
* Player Media Reload Icon
*/
this.reloadIcon = document.querySelector("#media-panel-reload-icon");
/**
* Tolerance between timestamp from server and actual media before corrective seek for pre-recorded media
*/
this.syncTolerance = 0.4;
/**
* Tolerance in livestream delay before corrective seek to live.
*
* Might seem weird to keep this here instead of the HLS handler, but remember we may want to support other livestream services in the future...
*/
this.streamSyncTolerance = 2;
/**
* Forced time to wait between sync checks, heavily decreases chance of seek-banging without reducing syncornization accuracy
*/
this.syncDelta = 6;
/**
* Current Player Volume
*/
this.volume = 1;
//run setup functions
this.setupInput();
this.defineListeners();
}
/**
* Defines Input-Related Event Listeners for the player
*/
setupInput(){
//UIBar Movement Detection
this.playerDiv.addEventListener("mousemove", this.popUI.bind(this));
this.uiBar.addEventListener("mouseenter", ()=>{this.setOnUI(true)});
this.uiBar.addEventListener("mouseleave", ()=>{this.setOnUI(false)});
//UIBar/header icons
//Don't bind these, they want an argument that isn't an event :P
this.showVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
this.hideVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
this.syncIcon.addEventListener("click", this.lockSync.bind(this));
this.cinemaModeIcon.addEventListener("click", ()=>{this.toggleCinemaMode()});
this.flipYIcon.addEventListener('click', this.flipY.bind(this));
this.flipXIcon.addEventListener('click', this.flipX.bind(this));
this.reloadIcon.addEventListener("click", this.reload.bind(this));
}
/**
* Define Network-Related Event Listeners for the player
*/
defineListeners(){
this.client.socket.on("start", this.start.bind(this));
this.client.socket.on("sync", this.sync.bind(this));
this.client.socket.on("end", this.end.bind(this));
this.client.socket.on("updateCurrentRawFile", this.updateCurrentRawFile.bind(this));
}
/**
* Handles command from server to start media
* @param {Object} data - Media Metadata from server
*/
start(data){
//If we have an active media handler
if(this.mediaHandler != null){
//End the media handler
this.mediaHandler.end();
}
//Ignore null media
if(data.media == null){
//Set null handler
this.mediaHandler = new nullHandler(client, this);
//Otherwise
}else{
//If we have a youtube video and the official embedded iframe player is selected
if(data.media.type == 'yt' &amp;&amp; localStorage.getItem("ytPlayerType") == 'embed'){
//Create a new yt handler for it
this.mediaHandler = new youtubeEmbedHandler(this.client, this, data.media);
//Sync to time stamp
this.mediaHandler.sync(data.timestamp);
//If we have an HLS Livestream
}else if(data.media.type == "livehls"){
//Create a new HLS Livestream Handler for it
this.mediaHandler = new hlsLiveStreamHandler(this.client, this, data.media);
}else if(data.media.type == 'dm'){
this.mediaHandler = new hlsDailymotionHandler(this.client, this, data.media);
//Otherwise, if we have a raw-file compatible source
}else if(data.media.type == 'ia' || data.media.type == 'raw' || data.media.type == 'yt' || data.media.type == 'dm'){
//Create a new raw file handler for it
this.mediaHandler = new rawFileHandler(client, this, data.media);
//Sync to time stamp
this.mediaHandler.sync(data.timestamp);
}else{
this.mediaHandler = new nullHandler(client, this);
}
}
//Lock synchronization since everyone starts at 0, and update the UI
this.lockSync();
//Re-size to aspect since video may now be a different size
this.client.chatBox.resizeAspect();
//Sync off of starter time stamp
this.mediaHandler.sync(data.timestamp);
}
/**
* Handles synchronization command from server
* @param {Object} data - Syncrhonization Data from Server
*/
sync(data){
if(this.mediaHandler != null){
//Get timestamp
const timestamp = data.timestamp;
//Get difference between server and local timestamp
const difference = Math.abs(timestamp - this.mediaHandler.getTimestamp());
//Check if timestamp evenly devides into sync delta, effectively only checking for sync every X seconds
//Check if the difference between timestamps is larger than the sync tolerance
//Lastly, check to make sure we have sync lock
if(timestamp % this.syncDelta == 0 &amp;&amp; difference > this.syncTolerance &amp;&amp; this.syncLock){
//If we need to sync, then sync the video!
this.mediaHandler.sync(timestamp);
}
//Collect last timestamp
this.mediaHandler.lastTimestamp = timestamp;
}
}
/**
* Reloads the media player
*/
reload(){
if(this.mediaHandler != null){
this.mediaHandler.reload();
}
}
/**
* Handles End-Media Commands from the Server
*/
end(){
//Call the media handler finisher
this.mediaHandler.end();
//Replace it with a null handler
this.mediaHandler = new nullHandler(client, this);
//Re-lock sync since we're probably gonna start new media soon anywho, and we need to update the UI anywho
this.lockSync();
}
/**
* Handles Raw-File Metadata Updates from the Server
* @param {Object} data - Updadated Raw-File link from Server
*/
updateCurrentRawFile(data){
//typecheck the media handler to see if we really need to do any of this shit, if not...
if(this.mediaHandler.type == 'ytEmbed'){
//Ignore it
return;
}
//Grab current item from media handler
const currentItem = this.mediaHandler.nowPlaying;
//Update raw link
currentItem.rawLink = data.file;
//Re-start the item
this.start({media: currentItem});
}
/**
* Locks player seek to synced timestamp from the server
*/
lockSync(){
//Enable syncing
this.syncLock = true;
if(this.mediaHandler != null &amp;&amp; this.mediaHandler.type != null){
//Light up the sync icon to show that we're actively synchronized
this.syncIcon.classList.add('positive');
//Sync to last timestamp
this.mediaHandler.sync();
//Play
this.mediaHandler.play();
}else{
//Unlight the sync icon since there is nothing to sync
this.syncIcon.classList.remove('positive');
}
}
/**
* Un-locks player seek to synced timestamp from the server
*/
unlockSync(){
//Unlight the sync icon since we're no longer actively synced
this.syncIcon.classList.remove('positive');
//Disable syncing
this.syncLock = false;
}
/**
* Flips the video horizontally
*/
flipX(){
//I'm lazy
const transform = this.videoContainer.style.transform;
//If we we're specifically set to un-mirrored
if(transform.match("scaleX(1)")){
//mirror it
this.videoContainer.style.transfrom = transform.replace('scaleX(1)', 'scaleX(-1)');
//If we're currently mirrored
}else if(transform.match("scaleX(-1)")){
//Un-mirror
this.videoContainer.style.transfrom = transform.replace('scaleX(-1)', 'scaleX(1)');
//Otherwise, if it's untouched
}else{
//Mirror it
this.videoContainer.style.transform += 'scaleX(-1)';
}
}
/**
* Flips the video vertically
*/
flipY(){
//I'm lazy
const transform = this.videoContainer.style.transform;
//If we we're specifically set to un-mirrored
if(transform.match("scaleY(1)")){
//mirror it
this.videoContainer.style.transfrom = transform.replace('scaleY(1)', 'scaleY(-1)');
//If we're currently mirrored
}else if(transform.match("scaleY(-1)")){
//Un-mirror
this.videoContainer.style.transfrom = transform.replace('scaleY(-1)', 'scaleY(1)');
//Otherwise, if it's untouched
}else{
//Mirror it
this.videoContainer.style.transform += 'scaleY(-1)';
}
}
/**
* Displays UI after player-related input
* @param {Event} event - Event passed through by event handler
*/
popUI(event){
this.toggleUI(true);
clearTimeout(this.uiTimer);
if(!this.onUI){
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
}
}
/**
* Toggles UI-Bar on or off
* @param {Boolean} show - Whether or not to show the UI-Bar. Defaults to toggle if left unspecified.
*/
toggleUI(show = this.uiBar.style.display == "none"){
this.uiBar.style.display = show ? "flex" : "none";
}
/**
* Toggles video on or off
* @param {Boolean} show - Whether or not to show the video player. Defaults to toggle if left unspecified
*/
toggleVideo(show = !this.playerDiv.checkVisibility()){
if(show){
this.playerDiv.style.display = "flex";
this.showVideoIcon.style.display = "none";
}else{
this.playerDiv.style.display = "none";
this.showVideoIcon.style.display = "flex";
}
//Tell chatbox to handle this shit
this.client.chatBox.handleVideoToggle(show);
}
/**
* Toggles Cinema Mode on or off
* @param {Boolean} cinema - Whether or not to enter Cinema Mode. Defaults to toggle if left unspecified
*/
toggleCinemaMode(cinema = !this.navBar.checkVisibility()){
if(cinema){
this.navBar.style.display = "flex";
}else{
this.navBar.style.display = "none";
}
//Resize the video if we're aspect locked
this.client.chatBox.resizeAspect();
}
/**
* Informs the class when the user's mouse curosr enters and leaves the UI area
* @param {Boolean} onUI - Whether or not onUI should be toggled true
*/
setOnUI(onUI){
this.onUI = onUI;
this.popUI();
}
/**
* Calculates ratio of current media object
* @returns {Number} Current media aspect ratio as a single floating point number
*/
getRatio(){
//If we have no media handler
if(this.mediaHandler == null){
//Return a 4/3 aspect to get a decent chat size
return 4/3;
}else{
return this.mediaHandler.getRatio();
}
}
}</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@
<h2><span class="attribs"><span class="type-signature"></span></span>userList<span class="signature">(client)</span><span class="type-signature"></span></h2>
<div class="class-description">Class for object containing logic behind userlist UX</div>
<div class="class-description">Class containing logic behind userlist UX</div>
</header>
@ -1185,13 +1185,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -43,7 +43,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;https://www.gnu.org/licenses/>.*/
/**
* Class for object containing logic behind userlist UX
* Class containing logic behind userlist UX
*/
class userList{
/**
@ -246,13 +246,13 @@ class userList{
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="channel.html">channel</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="userList.html">userList</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="cPanel.html">cPanel</a></li><li><a href="channel.html">channel</a></li><li><a href="chatBox.html">chatBox</a></li><li><a href="chatPostprocessor.html">chatPostprocessor</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="hlsBase.html">hlsBase</a></li><li><a href="hlsLiveStreamHandler.html">hlsLiveStreamHandler</a></li><li><a href="mediaHandler.html">mediaHandler</a></li><li><a href="nullHandler.html">nullHandler</a></li><li><a href="panelObj.html">panelObj</a></li><li><a href="player.html">player</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="userList.html">userList</a></li><li><a href="youtubeEmbedHandler.html">youtubeEmbedHandler</a></li></ul><h3>Global</h3><ul><li><a href="global.html#onYouTubeIframeAPIReady">onYouTubeIframeAPIReady</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:53 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:44 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

File diff suppressed because it is too large Load diff

View file

@ -786,7 +786,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -196,7 +196,7 @@ module.exports = activeChannel;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -347,7 +347,7 @@ module.exports = channelManager;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -81,7 +81,7 @@ module.exports = chat;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -178,7 +178,7 @@ module.exports = chatBuffer;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -376,7 +376,7 @@ module.exports = chatHandler;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -473,7 +473,7 @@ module.exports = commandPreprocessor;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -334,7 +334,7 @@ module.exports = connectedUser;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -83,7 +83,7 @@ module.exports = media;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1180,7 +1180,7 @@ module.exports = playlistHandler;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1795,7 +1795,7 @@ module.exports = queue;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -165,7 +165,7 @@ module.exports = queuedMedia;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -273,7 +273,7 @@ module.exports = tokebot;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1991,7 +1991,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -329,7 +329,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -829,7 +829,7 @@ Left here since it seems like good form anywho, since this would be a private, o
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -3686,7 +3686,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1246,7 +1246,7 @@ These arrays are used to handle further command/chat processing
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1831,7 +1831,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -1879,7 +1879,7 @@ Having to crawl through these sockets is that. Because the other ways seem more
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -7377,7 +7377,7 @@ Warns server admin against unsafe config options.
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -87,7 +87,7 @@ This new codebase intends to solve the following issues with the current CyTube
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -352,7 +352,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -5108,7 +5108,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -5805,7 +5805,7 @@ Called auto-magically by the Synchronization Timer
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -936,7 +936,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -101,7 +101,7 @@ module.exports = channelBanSchema;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -169,7 +169,7 @@ module.exports = channelPermissionSchema;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -934,7 +934,7 @@ module.exports = mongoose.model("channel", channelSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -96,7 +96,7 @@ module.exports = chatSchema;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -96,7 +96,7 @@ module.exports = mediaSchema;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -124,7 +124,7 @@ module.exports = mediaSchema.discriminator('saved', playlistMediaProperties);</c
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -174,7 +174,7 @@ module.exports = playlistSchema;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -113,7 +113,7 @@ module.exports = mediaSchema.discriminator('queued', queuedProperties);</code></
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -164,7 +164,7 @@ module.exports = mongoose.model("emote", emoteSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -118,7 +118,7 @@ module.exports = mongoose.model("flair", flairSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -356,7 +356,7 @@ module.exports = mongoose.model("permissions", permissionSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -240,7 +240,7 @@ module.exports = mongoose.model("statistics", statSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -160,7 +160,7 @@ module.exports = mongoose.model("tokeCommand", tokeCommandSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -222,7 +222,7 @@ module.exports = mongoose.model("emailChange", emailChangeSchema);
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -198,7 +198,7 @@ module.exports = mongoose.model("passwordReset", passwordResetSchema);
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -521,7 +521,7 @@ module.exports = mongoose.model("userBan", userBanSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -888,7 +888,7 @@ module.exports.userModel = mongoose.model("user", userSchema);</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -841,7 +841,7 @@ I would now, but I don't want to break shit in a comment-only commit.
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -118,7 +118,7 @@ module.exports.verify = async function(payload, uniqueSecret = ''){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -108,7 +108,7 @@ module.exports.securityCheck = function(){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -103,7 +103,7 @@ module.exports.hashIP = function(ip){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -146,7 +146,7 @@ module.exports.markLink = async function(link){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -207,7 +207,7 @@ module.exports.errorMiddleware = function(err, req, res, next){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -140,7 +140,7 @@ module.exports.sendAddressVerification = async function(requestDB, userDB, newEm
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -154,7 +154,7 @@ module.exports.fetchMetadata = async function(fullID, title){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -193,7 +193,7 @@ module.exports.getMediaType = async function(url){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -186,7 +186,7 @@ async function ytdlpFetch(link, format = 'b'){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -69,7 +69,7 @@ module.exports.escapeRegex = function(string){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -105,7 +105,7 @@ module.exports.kickoff = function(){
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -236,7 +236,7 @@ module.exports.maxAttempts = maxAttempts;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Sep 03 2025 07:51:51 GMT-0400 (Eastern Daylight Time)
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Thu Sep 04 2025 20:09:42 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for object containing base code for the Canopy channel client.
* Class containing base code for the Canopy channel client.
*/
class channel{
/**

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for Object which represents Canopy Chat Box UI
* Class which represents Canopy Chat Box UI
*/
class chatBox{
/**
@ -24,7 +24,7 @@ class chatBox{
*/
constructor(client){
/**
* Parent CLient Management Object
* Parent Client Management Object
*/
this.client = client
@ -69,20 +69,75 @@ class chatBox{
this.chatPostprocessor = new chatPostprocessor(client);
//Element Nodes
/**
* Chat Panel Container Div
*/
this.chatPanel = document.querySelector("#chat-panel-div");
/**
* High Level Selector
*/
this.highSelect = document.querySelector("#chat-panel-high-level-select");
/**
* Flair Selector
*/
this.flairSelect = document.querySelector("#chat-panel-flair-select");
/**
* Chat Buffer Div
*/
this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
/**
* Chat Prompt
*/
this.chatPrompt = document.querySelector("#chat-panel-prompt");
/**
* Auto-Complete Placeholder
*/
this.autocompletePlaceholder = document.querySelector("#chat-panel-prompt-autocomplete-filler");
/**
* Auto-Complete Display
*/
this.autocompleteDisplay = document.querySelector("#chat-panel-prompt-autocomplete-display");
/**
* Settings Panel Icon
*/
this.settingsIcon = document.querySelector("#chat-panel-settings-icon");
/**
* Admin Panel Icon
*/
this.adminIcon = document.querySelector("#chat-panel-admin-icon");
/**
* Emote Icon
*/
this.emoteIcon = document.querySelector("#chat-panel-emote-icon");
/**
* Send Chat/Command Button
*/
this.sendButton = document.querySelector("#chat-panel-send-button");
//Seems weird to stick this in here, but the split is dictated by chat width :P
/**
* Aspect Lock Icon
* Seems weird to stick this in here, but the split is dictated by chat width :P
*/
this.aspectLockIcon = document.querySelector("#media-panel-aspect-lock-icon");
/**
* Hide Chat Icon
*/
this.hideChatIcon = document.querySelector("#chat-panel-div-hide");
/**
* Show Chat Icon
*/
this.showChatIcon = document.querySelector("#media-panel-show-chat-icon");
//Setup functions
@ -91,6 +146,9 @@ class chatBox{
this.sizeToAspect();
}
/**
* Defines input-related event listeners
*/
setupInput(){
//Chat bar
this.chatPrompt.addEventListener("keydown", this.send.bind(this));
@ -118,11 +176,18 @@ class chatBox{
this.chatBuffer.addEventListener('scroll', this.scrollHandler.bind(this));
}
/**
* Defines network-related event listners
*/
defineListeners(){
this.client.socket.on("chatMessage", this.displayChat.bind(this));
this.client.socket.on("clearChat", this.clearChat.bind(this));
}
/**
* Clears chat on command from server
* @param {Object} data - Data from server
*/
clearChat(data){
//If we where passed a user to check
if(data.user != null){
@ -138,6 +203,10 @@ class chatBox{
});
}
/**
* Receives, Post-Processes, and Displays chat messages from server
* @param {Object} data De-hydrated chat object from server
*/
displayChat(data){
//Create chat-entry span
var chatEntry = document.createElement('span');
@ -186,15 +255,27 @@ class chatBox{
this.resizeAspect();
}
/**
* Concatinate Text into Chat Prompt
* @param {String} text - Text to Concatinate
*/
catChat(text){
this.chatPrompt.value += text;
this.displayAutocomplete();
}
/**
* Calls a toke command out with a specified user
* @param {String} user - User to toke with
*/
tokeWith(user){
this.commandPreprocessor.preprocess(user == this.client.user.user ? "!toke up fuckers" : `!toke up ${user}`);
}
/**
* Pre-processes and sends text from chat prompt to server
* @param {Event} event - Event passed down from Event Handler
*/
send(event){
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value);
@ -205,6 +286,10 @@ class chatBox{
}
}
/**
* Displays auto-complete text against current prompt input
* @param {Event} event - Event passed down from Event Handler
*/
displayAutocomplete(event){
//Find current match
const match = this.checkAutocomplete();
@ -216,6 +301,10 @@ class chatBox{
this.autocompleteDisplay.textContent = match.match.replace(match.word, '');
}
/**
* Called upon tab-complete
* @param {Event} event - Event passed down from Event Handler
*/
tabComplete(event){
//If we hit tab or this isn't a keyboard event
if(event.key == "Tab" || event.key == null){
@ -239,6 +328,11 @@ class chatBox{
}
}
/**
* Checks string input against auto-complete dictionary to generate the best guess as to what the user is typing
* @param {String} input - Current input from Chat Prompt
* @returns {Object} Object containing word we where handed and the match we found
*/
checkAutocomplete(input = this.chatPrompt.value){
//Rebuild this fucker every time because it really doesn't take that much compute power and emotes/used tokes change
//Worst case we could store it persistantly and update as needed but I think that might be much
@ -287,27 +381,48 @@ class chatBox{
}
}
/**
* Handles initial client meta-data dump from server upon connection
* @param {Object} data - Data dump from server
*/
handleClientInfo(data){
this.updateFlairSelect(data.flairList, data.user.flair);
this.updateHighSelect(data.user.highLevel);
}
/**
* Sets user high-level
* @param {Event} event - Event passed down from Event Handler
*/
setHighLevel(event){
const highLevel = event.target.value;
this.client.socket.emit("setHighLevel", {highLevel});
}
/**
* Sets user flair
* @param {Event} event - Event passed down from Event Handler
*/
setFlair(event){
const flair = event.target.value;
this.client.socket.emit("setFlair", {flair});
}
/**
* Handles High-Level updates from the server
* @param {Number} highLevel - High Level to Set
*/
updateHighSelect(highLevel){
this.highSelect.value = highLevel;
}
/**
* Handles flair updates from the server
* @param {Array} fliarList - List of flairs to put into flair select
* @param {String} fliar - Flair to set
*/
updateFlairSelect(flairList, flair){
//clear current flair select
this.flairSelect.innerHTML = "";
@ -331,6 +446,10 @@ class chatBox{
this.flairSelect.classList.add(`flair-${flair}`);
}
/**
* Locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
lockAspect(event){
//prevent the user from breaking shit :P
if(this.chatPanel.style.display != "none"){
@ -340,6 +459,10 @@ class chatBox{
}
}
/**
* Un-locks chat-size to aspect ratio of media
* @param {Event} event - Event passed down from Event Handler
*/
unlockAspect(event){
//Disable aspect lock
this.aspectLock = false;
@ -348,6 +471,11 @@ class chatBox{
this.aspectLockIcon.style.display = "inline";
}
L /**
* Re-sizes chat back to aspect ratio on window re-size when chat box is aspect locked
* Also prevents horizontal scroll-bars from chat/window resizing
* @param {Event} event - Event passed down from Event Handler
*/
resizeAspect(event){
const playerHidden = this.client.player.playerDiv.style.display == "none";
@ -364,6 +492,9 @@ class chatBox{
this.handleAutoScroll();
}
L /**
* Re-sizes chat box relative to media aspect ratio
*/
sizeToAspect(){
if(this.chatPanel.style.display != "none"){
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
@ -384,6 +515,10 @@ class chatBox{
}
}
/**
* Toggles Chat Box UX
* @param {Boolean} show - Whether or not to show Chat Box UX
*/
toggleUI(show = !this.chatPanel.checkVisibility()){
if(show){
this.chatPanel.style.display = "flex";
@ -397,6 +532,10 @@ class chatBox{
}
}
/**
* Handles Video Toggling
* @param {Boolean} show - Whether or not the video is currently being hidden
*/
handleVideoToggle(show){
//If we're enabling the video
if(show){
@ -421,6 +560,10 @@ class chatBox{
}
}
/**
* Handles scrolling within the chat buffer
* @param {Event} event - Event passed down from Event Handler
*/
scrollHandler(event){
//If we're just starting out
if(this.lastPos == 0){
@ -465,6 +608,9 @@ class chatBox{
this.lastWidth = bufferWidth;
}
/**
* Auto-scrolls chat buffer when new chats are entered.
*/
handleAutoScroll(){
//If autoscroll is enabled
if(this.autoScroll){

View file

@ -13,11 +13,28 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class contianing client-side message post-processing code
*/
class chatPostprocessor{
/**
* Instantiates a new Chat Post-Processor object
* @param {channel} client - Parent client Management Object
*/
constructor(client){
/**
* Parent Client Management Object
*/
this.client = client;
}
/**
* Post-Processes a single message from the server and returns a presntable DOM Node
* @param {Node} chatEntry - Chat entry generated by initial chatBox method
* @param {Object} rawData - Raw data from server
* @returns {Node} Post-Processed Chat Entry
*/
postprocess(chatEntry, rawData){
//Create empty array to hold filter spans
this.filterSpans = [];
@ -30,6 +47,7 @@ class chatPostprocessor{
//Split the chat message into an array of objects representing each word/chunk
this.splitMessage();
//Process Qoutes
this.processQoute();
//Re-Hydrate and Inject links and embedded media into un-processed placeholders
@ -62,13 +80,16 @@ class chatPostprocessor{
//Handle non-standard chat types
this.handleChatType();
//Inject the pre-processed chat into the chatEntry node
//Inject the pre-processed chat hyper-text into the chatEntry node
this.injectBody();
//Return the pre-processed node
return this.chatEntry;
}
/**
* Splits message into an array of Word Objects for further processing
*/
splitMessage(){
//Create an empty array to hold the body
this.messageArray = [];
@ -93,6 +114,9 @@ class chatPostprocessor{
});
}
/**
* Injects word objects into chat-entry as proper DOM Nodes
*/
injectBody(){
//Create an empty array to hold the objects to inject
const injectionArray = [];
@ -285,6 +309,9 @@ class chatPostprocessor{
}
}
/**
* Processes qouted text in chat
*/
processQoute(){
//If the message starts off with '>'
if(this.messageArray[0].string[0] == '>'){
@ -292,6 +319,9 @@ class chatPostprocessor{
}
}
/**
* Processes clickable command examples in chat
*/
processCommandExamples(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
@ -324,6 +354,9 @@ class chatPostprocessor{
});
}
/**
* Processes clickable channel names in chat
*/
processChannelNames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
@ -356,6 +389,9 @@ class chatPostprocessor{
});
}
/**
* Processes clickable username callouts in chat
*/
processUsernames(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
@ -375,6 +411,9 @@ class chatPostprocessor{
});
}
/**
* Injects invisible whitespace in long-ass words to prevent fucking up the chat buffer size
*/
addWhitespace(){
//for each word object in the body
this.messageArray.forEach((wordObj, wordIndex) => {
@ -400,6 +439,14 @@ class chatPostprocessor{
});
}
/**
* Searches for text in-between a specific delimiter and runs a given callback against it
*
* Internal command used by several text filters to prevent code re-writes
* @param {String} delimiter - delimiter to search string by
* @param {Function} cb - Callback function to run against found strings
* @returns {Array} - list of found instances of filter
*/
processFilter(delimiter, cb){
//Create empty array to hold spoilers (keep this seperate at first for internal function use)
const foundFilters = [];
@ -451,6 +498,9 @@ class chatPostprocessor{
return foundFilters;
}
/**
* Processes in-line spoilers
*/
processSpoilers(){
//Process spoilers using '##' delimiter
this.processFilter('##', (foundSpoiler)=>{
@ -459,6 +509,9 @@ class chatPostprocessor{
});
}
/**
* Processes in-line Strike-through
*/
processStrikethrough(){
//Process strikethrough's using '~~' delimiter
this.processFilter('~~', (foundStrikethrough)=>{
@ -468,6 +521,9 @@ class chatPostprocessor{
})
}
/**
* Processes in-line Bold/Strong text
*/
processBold(){
//Process strong text using '*' delimiter
this.processFilter('**', (foundStrikethrough)=>{
@ -477,6 +533,9 @@ class chatPostprocessor{
})
}
/**
* Processes in-line Italics
*/
processItalics(){
//Process italics using '__' delimiter
this.processFilter('*', (foundStrikethrough)=>{
@ -486,6 +545,9 @@ class chatPostprocessor{
})
}
/**
* Processes clickable links and embedded media
*/
processLinks(){
//If we don't have links
if(this.rawData.links == null){
@ -513,6 +575,9 @@ class chatPostprocessor{
});
}
/**
* Marks chat nodes in-case of non-standard chat types
*/
handleChatType(){
if(this.rawData.type == "whisper"){
//add whisper class

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for object containing chat and command pre-processing logic
* Class containing chat and command pre-processing logic
*/
class commandPreprocessor{
/**
@ -300,7 +300,7 @@ class commandPreprocessor{
}
/**
* Class for Object which contains logic for client-side commands
* Class which contains logic for client-side commands
*/
class commandProcessor{
/**

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for Object containing code for managing the Canopy Panel UX
* Class containing code for managing the Canopy Panel UX
*/
class cPanel{
/**
@ -323,7 +323,7 @@ class panelObj{
}
/**
* Class for Objects which represent a single instance of a popped-out panel
* Class which represents a single instance of a popped-out panel
*/
class poppedPanel{
/**

View file

@ -14,26 +14,53 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//This is little more than a interface class
/*
* Base class for all Canopy Media Handlers
*
* This is little more than a interface class
*/
class mediaHandler{
/**
* Instantiates a new Media Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
//Get parents
/**
* Parent Client Management Object
*/
this.client = client;
/**
* Parent Canopy Player Object
*/
this.player = player;
//Set handler type
/**
* Media Handler Source Type
*/
this.type = type
//Denotes wether a seek call was made by the syncing function
/*
* Denotes wether a seek call was made by the syncing function
*/
this.selfAct = false;
//Set last received timestamp to 0
/*
* Contains the last received time stamp
*/
this.lastTimestamp = 0;
//Ingest media object from server
this.startMedia(media);
}
/**
* Ingests media nd starts playback
* @param {Object} media - Media object from server
*/
startMedia(media){
//If we properly ingested the media
if(this.ingestMedia(media)){
@ -45,16 +72,27 @@ class mediaHandler{
}
}
/**
* Builds video player element
*/
buildPlayer(){
//Reset player lock
this.lock = false;
}
/**
* Destroys video player element
*/
destroyPlayer(){
//Null out video property
this.video = null;
}
/**
* Ingests media object from server
* @param {Object} media - Media object from the server
* @returns {Boolean} True upon success
*/
ingestMedia(media){
//Set now playing
this.nowPlaying = media;
@ -63,10 +101,17 @@ class mediaHandler{
return true;
}
/**
* Starts video playback
*/
start(){
this.setVideoTitle(this.nowPlaying.title);
}
/**
* Syncronizes timestamp based on timestamp received from server
* @param {Number} timestamp - Current video timestamp in seconds
*/
sync(timestamp = this.lastTimestamp){
//Skip sync calls that won't seek so we don't pointlessly throw selfAct
if(timestamp != this.video.currentTime){
@ -75,6 +120,9 @@ class mediaHandler{
}
}
/**
* Reloads media player
*/
reload(){
//Get current timestamp
const timestamp = this.video.currentTime;
@ -83,6 +131,9 @@ class mediaHandler{
this.selfAct = true;
}
/**
* Handles media end
*/
end(){
//Null out current media
this.nowPlaying = null;
@ -94,32 +145,64 @@ class mediaHandler{
this.destroyPlayer();
}
/**
* Plays video
*/
play(){
}
/**
* Pauses video
*/
pause(){
}
/**
* Toggles player control lockout
* @param {Boolean} lock - Whether or not to lock-out user control of video
*/
setPlayerLock(lock){
//set lock property
this.lock = lock;
}
/**
* Calculates Aspect Ratio of media
* @returns {Number} Media Aspect Ratio as Floating Point number
*/
getRatio(){
return 4/3;
}
/**
* Gets current timestamp from video
* @returns {Number} Media Timestamp in seconds
*/
getTimestamp(){
return 0;
}
/**
* Sets player title
* @param {String} title - Title to set
*/
setVideoTitle(title){
this.player.title.textContent = `Currently Playing: ${title}`;
}
/**
* Called once all video metadata has properly been fetched
* @param {Event} event - Event passed down by event handler
*/
onMetadataLoad(event){
//Resize aspect (if locked), since the video doesn't properly report it's resolution until it's been loaded
this.client.chatBox.resizeAspect();
}
/**
* Called on media pause
* @param {Event} event - Event passed down by event handler
*/
onPause(event){
//If the video was paused out-side of code
if(!this.selfAct){
@ -129,9 +212,17 @@ class mediaHandler{
this.selfAct = false;
}
/**
* Called on media volume change
* @param {Event} event - Event passed down by event handler
*/
onVolumeChange(event){
}
/**
* Called on media seek
* @param {Event} event - Event passed down by event handler
*/
onSeek(event){
//If the video was seeked out-side of code
if(!this.selfAct){
@ -142,17 +233,34 @@ class mediaHandler{
this.selfAct = false;
}
/**
* Called on media buffer
* @param {Event} event - Event passed down by event handler
*/
onBuffer(){
this.selfAct = true;
}
}
//Basic building blocks for anything that touches a <video> tag
/**
* Class containing basic building blocks for anything that touches a <video> tag
* @extends mediaHandler
*/
class rawFileBase extends mediaHandler{
/**
* Instantiates a new rawFileBase object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
super(client, player, media, type);
}
/**
* Defines input-related event listeners
*/
defineListeners(){
//Resize to aspect on metadata load
this.video.addEventListener('loadedmetadata', this.onMetadataLoad.bind(this));
@ -222,8 +330,16 @@ class rawFileBase extends mediaHandler{
}
}
//Off air static 'player'
/**
* Off air static 'player'
* @extends rawFileBase
*/
class nullHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
*/
constructor(client, player){
//Call derived constructor
super(client, player, {}, null);
@ -258,8 +374,17 @@ class nullHandler extends rawFileBase{
}
}
//Basic building blocks needed for proper time-synchronized raw-file playback
/**
* Basic building blocks needed for proper time-synchronized raw-file playback
* @extends rawFileBase
*/
class rawFileHandler extends rawFileBase{
/**
* Instantiates a new Null Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'raw');
@ -319,7 +444,17 @@ class rawFileHandler extends rawFileBase{
}
}
/**
* Handles Youtube playback via the official YT embed (gross)
* @extends mediaHandler
*/
class youtubeEmbedHandler extends mediaHandler{
/**
* Instantiates a new Youtube Embed Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, 'ytEmbed');
@ -469,7 +604,9 @@ class youtubeEmbedHandler extends mediaHandler{
this.player.title.textContent = "";
}
//Generic handler for state changes since google is a dick
/**
* Generic handler for state changes since google is a dick
*/
onStateChange(event){
switch(event.data){
//video unstarted
@ -509,7 +646,18 @@ class youtubeEmbedHandler extends mediaHandler{
}
}
/**
* Base HLS Media handler for handling all HLS related media
* @extends rawFileBase
*/
class hlsBase extends rawFileBase{
/**
* Instantiates a new HLS Base object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
* @param {String} type - Media Handler Source Type
*/
constructor(client, player, media, type){
//Call derived constructor
super(client, player, media, type);
@ -554,7 +702,17 @@ class hlsBase extends rawFileBase{
}
}
/**
* HLS Livestream Handler
* @extends hlsBase
*/
class hlsLiveStreamHandler extends hlsBase{
/**
* Instantiates a new HLS Live Stream Handler object
* @param {channel} client - Parent Client Management Object
* @param {player} player - Parent Canopy Player Object
* @param {Object} media - De-hydrated media object from server
*/
constructor(client, player, media){
//Call derived constructor
super(client, player, media, "livehls");

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for objects which represent Canopy Player UX
* Class which represents Canopy Player UX
*/
class player{
/**

View file

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
/**
* Class for object containing logic behind userlist UX
* Class containing logic behind userlist UX
*/
class userList{
/**