Finished up with player UI-Bar functionality, including 'reload' and 'sync' controls.

This commit is contained in:
rainbow napkin 2025-01-17 06:02:39 -05:00
parent 6dc9ad7b34
commit f38eae170d
7 changed files with 222 additions and 50 deletions

View file

@ -39,10 +39,10 @@ module.exports = class{
const mediaObj = queuedMedia.fromMedia(inputMedia, new Date().getTime()); const mediaObj = queuedMedia.fromMedia(inputMedia, new Date().getTime());
//Start playback //Start playback
this.play(mediaObj); this.start(mediaObj);
} }
play(mediaObj){ start(mediaObj){
//Silently end the media //Silently end the media
this.end(true); this.end(true);
@ -109,11 +109,11 @@ module.exports = class{
//If a socket is specified //If a socket is specified
if(socket != null){ if(socket != null){
//Send data out to specified socket //Send data out to specified socket
socket.emit("play", data); socket.emit("start", data);
//Otherwise //Otherwise
}else{ }else{
//Send that shit out to the entire channel //Send that shit out to the entire channel
this.server.io.in(this.channel.name).emit("play", data); this.server.io.in(this.channel.name).emit("start", data);
} }
} }

View file

@ -45,7 +45,7 @@ module.exports = class{
//Pull media list //Pull media list
const mediaList = await this.yankMedia(data.url); const mediaList = await this.yankMedia(data.url);
//Get active channel from server/socket //Get active channel from server/socket
const chan = this.server.activeChannels.get(socket.chan) const chan = this.server.activeChannels.get(socket.chan);
//Queue the first media object given //Queue the first media object given
chan.queue.queueMedia(mediaList[0]); chan.queue.queueMedia(mediaList[0]);
}catch(err){ }catch(err){
@ -54,11 +54,11 @@ module.exports = class{
} }
async yankMedia(url){ async yankMedia(url){
const pullType = await this.getMediaType(url) const pullType = await this.getMediaType(url);
if(pullType == 'ia'){ if(pullType == 'ia'){
//Create empty list to hold media objects //Create empty list to hold media objects
const mediaList = [] const mediaList = [];
//Pull metadata from IA //Pull metadata from IA
const mediaInfo = await iaUtil.fetchMetadata(url); const mediaInfo = await iaUtil.fetchMetadata(url);
@ -69,17 +69,17 @@ module.exports = class{
//pull filename from path //pull filename from path
const name = path[path.length - 1]; const name = path[path.length - 1];
//Construct link from pulled info //Construct link from pulled info
const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}` const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}`;
//Create new media object from file info //Create new media object from file info
mediaList.push(new media(name, name, link, 'ia', Number(file.length))); mediaList.push(new media(name, name, link, 'ia', Number(file.length)));
} }
//return media object list //return media object list
return mediaList return mediaList;
}else{ }else{
//return null to signify a bad url //return null to signify a bad url
return null return null;
} }
} }
@ -87,7 +87,7 @@ module.exports = class{
//Check if we have a valid url //Check if we have a valid url
if(!validator.isURL(url)){ if(!validator.isURL(url)){
//If not toss the fucker out //If not toss the fucker out
return null return null;
} }
//If we have link to a resource from archive.org //If we have link to a resource from archive.org

View file

@ -16,16 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<div class="media-panel" id="media-panel-div"> <div class="media-panel" id="media-panel-div">
<div class="media-panel panel-head-div" id="media-panel-head-div"> <div class="media-panel panel-head-div" id="media-panel-head-div">
<i class="media-panel panel-head-element bi-caret-down-fill" id="media-panel-div-toggle-icon"></i> <i title="Hide Player" class="media-panel panel-head-element bi-caret-down-fill" id="media-panel-div-toggle-icon"></i>
<p class="media-panel panel-head-element" id="media-panel-title-paragraph">Currently Playing: <span id="media-panel-title-span">NULL</span></p> <p class="media-panel panel-head-element" id="media-panel-title-paragraph"> - </p>
<span class="media-panel panel-head-spacer-span" id="media-panel-head-spacer-span"></span> <span class="media-panel panel-head-spacer-span" id="media-panel-head-spacer-span"></span>
<i class="media-panel panel-head-element bi-arrow-repeat" id="media-panel-sync-icon"></i> <i title="Synchronize" class="media-panel panel-head-element bi-arrow-repeat" id="media-panel-sync-icon"></i>
<i class="media-panel panel-head-element bi-aspect-ratio-fill" id="media-panel-aspect-lock-icon"></i> <i title="Lock Chat Size to Video Aspect Ratio" class="media-panel panel-head-element bi-aspect-ratio-fill" id="media-panel-aspect-lock-icon"></i>
<i class="media-panel panel-head-element bi-film" id="media-panel-cinema-mode-icon"></i> <i title="Cinema Mode" class="media-panel panel-head-element bi-film" id="media-panel-cinema-mode-icon"></i>
<i class="media-panel panel-head-element bi-arrows-vertical" id="media-panel-flip-vertical-icon"></i> <i title="Horizontal Flip" class="media-panel panel-head-element bi-arrows-vertical" id="media-panel-flip-vertical-icon"></i>
<i class="media-panel panel-head-element bi-arrows" id="media-panel-flip-horizontal-icon"></i> <i title="Vertical Flip" class="media-panel panel-head-element bi-arrows" id="media-panel-flip-horizontal-icon"></i>
<i class="media-panel panel-head-element bi-arrow-clockwise" id="media-panel-reload-icon"></i> <i title="Reload Media" class="media-panel panel-head-element bi-arrow-clockwise" id="media-panel-reload-icon"></i>
<i class="media-panel panel-head-element bi-chat-right-dots-fill" id="media-panel-show-chat-icon"></i> <i title="Show Chat" class="media-panel panel-head-element bi-chat-right-dots-fill" id="media-panel-show-chat-icon"></i>
</div> </div>
<div id="media-panel-video-container"> <div id="media-panel-video-container">
</div> </div>

View file

@ -52,21 +52,13 @@ div#media-panel-div{
#media-panel-head-div{ #media-panel-head-div{
position: absolute; position: absolute;
z-index: 1;
height: 3em; height: 3em;
right: 0; right: 0;
left: 0; left: 0;
top: 0; top: 0;
} }
video#media-panel-video{
flex: 1;
min-height: 0;
}
#media-panel-sync-button{
height: 1.5em;
}
#media-panel-title-paragraph{ #media-panel-title-paragraph{
font-size: 1.2em; font-size: 1.2em;
} }
@ -90,6 +82,11 @@ div#chat-panel-main-div{
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
transform: scaleX(1) scaleY(1);
}
#media-panel-video-container video{
height: 100%
} }
.drag-handle{ .drag-handle{

View file

@ -138,6 +138,11 @@ textarea{
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.positive{
color: var(--focus0-alt0);
text-shadow: var(--focus-glow0);
}
.danger-button{ .danger-button{
background-color: var(--danger0); background-color: var(--danger0);
color: var(--accent1); color: var(--accent1);

View file

@ -19,8 +19,8 @@ class mediaHandler{
//Get parents //Get parents
this.client = client; this.client = client;
this.player = player; this.player = player;
this.syncTolerance = 1;
this.syncDelta = 6; this.lastTimestamp = 0;
//Ingest media object from server //Ingest media object from server
this.startMedia(media); this.startMedia(media);
@ -64,7 +64,21 @@ class mediaHandler{
start(){ start(){
} }
sync(timestamp){ sync(timestamp = this.lastTimestamp){
}
reload(){
//Get current timestamp
const timestamp = this.video.currentTime;
//Load video from source
this.video.load();
//Set it back to the proper time
this.video.currentTime = timestamp;
//Play the video
this.video.play();
} }
end(){ end(){
@ -72,6 +86,12 @@ class mediaHandler{
this.destroyPlayer(); this.destroyPlayer();
} }
play(){
}
pause(){
}
setPlayerLock(lock){ setPlayerLock(lock){
//toggle controls //toggle controls
this.video.controls = !lock; this.video.controls = !lock;
@ -89,6 +109,13 @@ class mediaHandler{
getRatio(){ getRatio(){
return this.video.videoWidth / this.video.videoHeight; return this.video.videoWidth / this.video.videoHeight;
}
getTimestamp(){
}
setVideoTitle(title){
this.player.title.textContent = `Currently Playing: ${title}`;
} }
} }
@ -100,13 +127,13 @@ class nullHandler extends mediaHandler{
start(){ start(){
//Lock the player //Lock the player
super.setPlayerLock(true); this.setPlayerLock(true);
//Set the static placeholder //Set the static placeholder
this.video.src = '/video/static.webm'; this.video.src = '/video/static.webm';
//Set video title //Set video title manually
this.player.title.textContent = 'NULL'; this.player.title.textContent = 'Channel Off Air';
//play the placeholder video //play the placeholder video
this.video.play(); this.video.play();
@ -117,6 +144,17 @@ class rawFileHandler extends mediaHandler{
constructor(client, player, media){ constructor(client, player, media){
//Call derived constructor //Call derived constructor
super(client, player, media); super(client, player, media);
//Since this media type has no way to tell between the user and code seek events, we need a flag to mark them
this.selfSeek = false;
//Define listeners
this.defineListeners();
}
defineListeners(){
this.video.addEventListener('pause', this.onPause.bind(this));
this.video.addEventListener('seeking', this.onSeek.bind(this));
} }
start(){ start(){
@ -124,23 +162,55 @@ class rawFileHandler extends mediaHandler{
this.video.src = this.nowPlaying.id; this.video.src = this.nowPlaying.id;
//Set video title //Set video title
this.player.title.textContent = this.nowPlaying.title; this.setVideoTitle(this.nowPlaying.title);
//Unlock player //Unlock player
super.setPlayerLock(false); this.setPlayerLock(false);
//play video //play video
this.video.play(); this.video.play();
} }
sync(timestamp){ play(){
//Check if timestamp evenly devides into sync delta, effectively only checking for sync every X seconds this.video.play();
if(timestamp % this.syncDelta == 0){ }
//Get absolute difference between syncronization timestamp and actual video timestamp, and check if it's over the sync tolerance
if(Math.abs(timestamp - this.video.currentTime) > this.syncTolerance){ pause(){
//If we need to sync, then sync the video! this.video.pause();
this.video.currentTime = timestamp; }
}
sync(timestamp = this.lastTimestamp){
//Set self seek flag
this.selfSeek = true;
//Set current video time based on timestamp received from server
this.video.currentTime = timestamp;
}
reload(){
//Throw self seek flag to make sure we don't un-sync the player
this.selfSeek = true;
//Call derived reload function
super.reload();
}
onPause(event){
this.player.unlockSync();
}
onSeek(event){
//If the video was seeked out-side of code
if(!this.selfSeek){
this.player.unlockSync();
} }
//reset self seek flag
this.selfSeek = false;
}
getTimestamp(){
//Return current timestamp
return this.video.currentTime;
} }
} }

View file

@ -21,6 +21,7 @@ class player{
//booleans //booleans
this.onUI = false; this.onUI = false;
this.syncLock = true;
//timers //timers
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false); this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
@ -30,14 +31,23 @@ class player{
this.videoContainer = document.querySelector("#media-panel-video-container") this.videoContainer = document.querySelector("#media-panel-video-container")
this.navBar = document.querySelector("#navbar"); this.navBar = document.querySelector("#navbar");
this.uiBar = document.querySelector("#media-panel-head-div"); this.uiBar = document.querySelector("#media-panel-head-div");
this.title = document.querySelector("#media-panel-title-span"); this.title = document.querySelector("#media-panel-title-paragraph");
this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon"); this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon");
this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon"); this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon");
this.syncIcon = document.querySelector("#media-panel-sync-icon");
this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon"); this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon");
this.flipYIcon = document.querySelector("#media-panel-flip-vertical-icon")
this.flipXIcon = document.querySelector("#media-panel-flip-horizontal-icon")
this.reloadIcon = document.querySelector("#media-panel-reload-icon");
//Numbers
this.syncTolerance = 1;
this.syncDelta = 6;
//run setup functions //run setup functions
this.setupInput(); this.setupInput();
this.defineListeners(); this.defineListeners();
this.lockSync();
} }
setupInput(){ setupInput(){
@ -47,18 +57,23 @@ class player{
this.uiBar.addEventListener("mouseleave", ()=>{this.setOnUI(false)}); this.uiBar.addEventListener("mouseleave", ()=>{this.setOnUI(false)});
//UIBar/header icons //UIBar/header icons
//Don't bind these, they want an argument that isn't an event :P
this.showVideoIcon.addEventListener("click", ()=>{this.toggleVideo()}); this.showVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
this.hideVideoIcon.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.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));
} }
defineListeners(){ defineListeners(){
this.client.socket.on("play", this.play.bind(this)); this.client.socket.on("start", this.start.bind(this));
this.client.socket.on("sync", this.sync.bind(this)); this.client.socket.on("sync", this.sync.bind(this));
this.client.socket.on("end", this.end.bind(this)); this.client.socket.on("end", this.end.bind(this));
} }
play(data){ start(data){
//If we have an active media handler //If we have an active media handler
if(this.mediaHandler != null){ if(this.mediaHandler != null){
//End the media handler //End the media handler
@ -86,6 +101,32 @@ class player{
this.client.chatBox.resizeAspect(); this.client.chatBox.resizeAspect();
} }
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 && difference > this.syncTolerance && this.syncLock){
//If we need to sync, then sync the video!
this.mediaHandler.sync(timestamp);
}
//Collect last timestamp
this.mediaHandler.lastTimestamp = timestamp;
}
}
reload(){
if(this.mediaHandler != null){
this.mediaHandler.reload();
}
}
end(){ end(){
//Call the media handler finisher //Call the media handler finisher
this.mediaHandler.end(); this.mediaHandler.end();
@ -94,8 +135,67 @@ class player{
this.mediaHandler = new nullHandler(client, this); this.mediaHandler = new nullHandler(client, this);
} }
sync(data){ lockSync(){
this.mediaHandler.sync(data.timestamp); //Light up the sync icon to show that we're actively synchronized
this.syncIcon.classList.add('positive');
//Enable syncing
this.syncLock = true;
//If we have a media handler
if(this.mediaHandler != null){
//Sync to last timestamp
this.mediaHandler.sync();
//Play
this.mediaHandler.play();
}
}
unlockSync(){
//Unlight the sync icon
this.syncIcon.classList.remove('positive');
//Disable syncing
this.syncLock = false;
}
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)';
}
}
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)';
}
} }
popUI(event){ popUI(event){