canopy/www/doc/client/player.js.html

484 lines
17 KiB
HTML

<!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="addURLPopup.html">addURLPopup</a></li><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="clearPopup.html">clearPopup</a></li><li><a href="commandPreprocessor.html">commandPreprocessor</a></li><li><a href="commandProcessor.html">commandProcessor</a></li><li><a href="defaultTitlesPopup.html">defaultTitlesPopup</a></li><li><a href="emotePanel.html">emotePanel</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="newPlaylistPopup.html">newPlaylistPopup</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="playlistManager.html">playlistManager</a></li><li><a href="poppedPanel.html">poppedPanel</a></li><li><a href="queuePanel.html">queuePanel</a></li><li><a href="rawFileBase.html">rawFileBase</a></li><li><a href="rawFileHandler.html">rawFileHandler</a></li><li><a href="renamePopup.html">renamePopup</a></li><li><a href="reschedulePopup.html">reschedulePopup</a></li><li><a href="schedulePopup.html">schedulePopup</a></li><li><a href="settingsPanel.html">settingsPanel</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 Sat Sep 06 2025 00:30:26 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>