Video Syncronization Prototyping Complete.
This commit is contained in:
parent
0b68db1265
commit
6dc9ad7b34
10 changed files with 286 additions and 20 deletions
146
www/js/channel/mediaHandler.js
Normal file
146
www/js/channel/mediaHandler.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/*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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
class mediaHandler{
|
||||
constructor(client, player, media){
|
||||
//Get parents
|
||||
this.client = client;
|
||||
this.player = player;
|
||||
this.syncTolerance = 1;
|
||||
this.syncDelta = 6;
|
||||
|
||||
//Ingest media object from server
|
||||
this.startMedia(media);
|
||||
}
|
||||
|
||||
startMedia(media){
|
||||
//If we properly ingested the media
|
||||
if(this.ingestMedia(media)){
|
||||
//Build the video player
|
||||
this.buildPlayer();
|
||||
|
||||
//Call the start function
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
buildPlayer(){
|
||||
//Create player
|
||||
this.video = document.createElement('video');
|
||||
//Append it to page
|
||||
this.player.videoContainer.appendChild(this.video);
|
||||
//Reset player lock
|
||||
this.lock = false;
|
||||
}
|
||||
|
||||
destroyPlayer(){
|
||||
//Remove player from page
|
||||
this.video.remove();
|
||||
//Null out video property
|
||||
this.video = null;
|
||||
}
|
||||
|
||||
ingestMedia(media){
|
||||
//Set now playing
|
||||
this.nowPlaying = media;
|
||||
|
||||
//return true to signify success
|
||||
return true;
|
||||
}
|
||||
|
||||
start(){
|
||||
}
|
||||
|
||||
sync(timestamp){
|
||||
}
|
||||
|
||||
end(){
|
||||
this.nowPlaying = null;
|
||||
this.destroyPlayer();
|
||||
}
|
||||
|
||||
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;
|
||||
//set lock property
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
getRatio(){
|
||||
return this.video.videoWidth / this.video.videoHeight;
|
||||
}
|
||||
}
|
||||
|
||||
class nullHandler extends mediaHandler{
|
||||
constructor(client, player){
|
||||
//Call derived constructor
|
||||
super(client, player, {});
|
||||
}
|
||||
|
||||
start(){
|
||||
//Lock the player
|
||||
super.setPlayerLock(true);
|
||||
|
||||
//Set the static placeholder
|
||||
this.video.src = '/video/static.webm';
|
||||
|
||||
//Set video title
|
||||
this.player.title.textContent = 'NULL';
|
||||
|
||||
//play the placeholder video
|
||||
this.video.play();
|
||||
}
|
||||
}
|
||||
|
||||
class rawFileHandler extends mediaHandler{
|
||||
constructor(client, player, media){
|
||||
//Call derived constructor
|
||||
super(client, player, media);
|
||||
}
|
||||
|
||||
start(){
|
||||
//Set video
|
||||
this.video.src = this.nowPlaying.id;
|
||||
|
||||
//Set video title
|
||||
this.player.title.textContent = this.nowPlaying.title;
|
||||
|
||||
//Unlock player
|
||||
super.setPlayerLock(false);
|
||||
|
||||
//play video
|
||||
this.video.play();
|
||||
}
|
||||
|
||||
sync(timestamp){
|
||||
//Check if timestamp evenly devides into sync delta, effectively only checking for sync every X seconds
|
||||
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){
|
||||
//If we need to sync, then sync the video!
|
||||
this.video.currentTime = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,15 +27,17 @@ class player{
|
|||
|
||||
//elements
|
||||
this.playerDiv = document.querySelector("#media-panel-div");
|
||||
this.videoContainer = document.querySelector("#media-panel-video-container")
|
||||
this.navBar = document.querySelector("#navbar");
|
||||
this.video = document.querySelector("#media-panel-video");
|
||||
this.uiBar = document.querySelector("#media-panel-head-div");
|
||||
this.title = document.querySelector("#media-panel-title-span");
|
||||
this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon");
|
||||
this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon");
|
||||
this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon");
|
||||
|
||||
//run setup functions
|
||||
this.setupInput();
|
||||
this.defineListeners();
|
||||
}
|
||||
|
||||
setupInput(){
|
||||
|
|
@ -50,6 +52,52 @@ class player{
|
|||
this.cinemaModeIcon.addEventListener("click", ()=>{this.toggleCinemaMode()});
|
||||
}
|
||||
|
||||
defineListeners(){
|
||||
this.client.socket.on("play", this.play.bind(this));
|
||||
this.client.socket.on("sync", this.sync.bind(this));
|
||||
this.client.socket.on("end", this.end.bind(this));
|
||||
}
|
||||
|
||||
play(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 raw-file compatible source
|
||||
if(data.media.type == 'ia' || data.media.type == 'raw'){
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
//Re-size to aspect since video may now be a different size
|
||||
this.client.chatBox.resizeAspect();
|
||||
}
|
||||
|
||||
end(){
|
||||
//Call the media handler finisher
|
||||
this.mediaHandler.end();
|
||||
|
||||
//Replace it with a null handler
|
||||
this.mediaHandler = new nullHandler(client, this);
|
||||
}
|
||||
|
||||
sync(data){
|
||||
this.mediaHandler.sync(data.timestamp);
|
||||
}
|
||||
|
||||
popUI(event){
|
||||
this.toggleUI(true);
|
||||
clearTimeout(this.uiTimer);
|
||||
|
|
@ -94,7 +142,14 @@ class player{
|
|||
this.popUI();
|
||||
}
|
||||
|
||||
//This way other classes don't need to worry about media handler
|
||||
getRatio(){
|
||||
return this.video.videoWidth / this.video.videoHeight;
|
||||
//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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue