|
|
|
|
import EventEmitter from './EventEmitter.js';
|
|
|
|
|
|
|
|
|
|
// Define a debug mode variable
|
|
|
|
|
let debugMode = true
|
|
|
|
|
|
|
|
|
|
const eventEmitter = new EventEmitter(debugMode);
|
|
|
|
|
|
|
|
|
|
const canvas = document.getElementById("videoCanvas")
|
|
|
|
|
const ctx = canvas.getContext("2d")
|
|
|
|
|
|
|
|
|
|
let videosToLoad = 2
|
|
|
|
|
const maxVideosToLoad = 9
|
|
|
|
|
const videos = []
|
|
|
|
|
let videosLoaded = 0
|
|
|
|
|
|
|
|
|
|
let aspectRatio = 1
|
|
|
|
|
let cachedVideos = []
|
|
|
|
|
|
|
|
|
|
let showText = true
|
|
|
|
|
|
|
|
|
|
const parameterStore = {
|
|
|
|
|
blendModeParams: {
|
|
|
|
|
mode: "screen",
|
|
|
|
|
opacity: 1,
|
|
|
|
|
},
|
|
|
|
|
filterParams: {
|
|
|
|
|
grayscale: 0,
|
|
|
|
|
blur: 0,
|
|
|
|
|
},
|
|
|
|
|
transformParams: {
|
|
|
|
|
scale: 1,
|
|
|
|
|
// Additional parameters specific to the tile mode or other transformation
|
|
|
|
|
tilePositionX: 0, // X-coordinate for positioning the tiled video
|
|
|
|
|
tilePositionY: 0, // Y-coordinate for positioning the tiled video
|
|
|
|
|
tileScaleX: 1, // Scale factor along the X-axis for the tiled video
|
|
|
|
|
tileScaleY: 1, // Scale factor along the Y-axis for the tiled video
|
|
|
|
|
},
|
|
|
|
|
presets: {
|
|
|
|
|
default: {
|
|
|
|
|
blendModeParams: { mode: "screen", opacity: 1 },
|
|
|
|
|
},
|
|
|
|
|
sepia: {
|
|
|
|
|
blendModeParams: { mode: "overlay", opacity: 0.5 },
|
|
|
|
|
},
|
|
|
|
|
tile: {
|
|
|
|
|
tilePositionX: 0,
|
|
|
|
|
tilePositionY: 0,
|
|
|
|
|
tileScaleX: 1,
|
|
|
|
|
tileScaleY: 1,
|
|
|
|
|
},
|
|
|
|
|
// Add more presets as needed
|
|
|
|
|
},
|
|
|
|
|
selectedPreset: "default",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function applyPreset(presetName) {
|
|
|
|
|
const preset = parameterStore.presets[presetName];
|
|
|
|
|
if (preset) {
|
|
|
|
|
for (const [category, categoryParams] of Object.entries(preset)) {
|
|
|
|
|
// Check if the category exists in the parameter store
|
|
|
|
|
if (parameterStore.hasOwnProperty(category)) {
|
|
|
|
|
// Iterate over each parameter in the category
|
|
|
|
|
for (const [paramKey, paramValue] of Object.entries(categoryParams)) {
|
|
|
|
|
// Update the corresponding parameter in the parameter store
|
|
|
|
|
parameterStore[category][paramKey] = paramValue;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error(`Category "${category}" not found in parameter store.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Update selected preset
|
|
|
|
|
parameterStore.selectedPreset = presetName;
|
|
|
|
|
} else {
|
|
|
|
|
console.error(`Preset "${presetName}" not found.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function debugLog(...args) {
|
|
|
|
|
if (debugMode) {
|
|
|
|
|
console.log(...args)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadVideos(num) {
|
|
|
|
|
debugLog(`Loading ${num} videos`)
|
|
|
|
|
if (num < 0) {
|
|
|
|
|
num = Math.max(num,-Math.abs(maxVideosToLoad))
|
|
|
|
|
} else {
|
|
|
|
|
num = Math.min(num,Math.abs(maxVideosToLoad))
|
|
|
|
|
}
|
|
|
|
|
while (num > 0) {
|
|
|
|
|
let video = createVideoElement()
|
|
|
|
|
video.addEventListener("loadedmetadata", handleVideoLoaded)
|
|
|
|
|
video.addEventListener("ended", handleVideoEnded)
|
|
|
|
|
video.addEventListener("error", handleVideoEnded)
|
|
|
|
|
video.src = selectRandomVideo(video)
|
|
|
|
|
videos.push(video)
|
|
|
|
|
num--
|
|
|
|
|
}
|
|
|
|
|
while (num < 0) {
|
|
|
|
|
let video = videos.pop()
|
|
|
|
|
video.removeEventListener("loadedmetadata", handleVideoLoaded)
|
|
|
|
|
video.removeEventListener("ended", handleVideoEnded)
|
|
|
|
|
video.removeEventListener("error", handleVideoEnded)
|
|
|
|
|
num++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createVideoElement() {
|
|
|
|
|
const video = document.createElement("video")
|
|
|
|
|
video.autoplay = true
|
|
|
|
|
video.muted = true
|
|
|
|
|
return video
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleVideoLoaded() {
|
|
|
|
|
videosLoaded++
|
|
|
|
|
if (videosLoaded === videos.length) {
|
|
|
|
|
updateCanvasSize()
|
|
|
|
|
drawVideos()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleVideoEnded(event) {
|
|
|
|
|
const video = event.target
|
|
|
|
|
//debugLog(video,event)
|
|
|
|
|
selectRandomVideo(video)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawVideos() {
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
|
|
|
|
|
|
|
videos.forEach((video,i) => {
|
|
|
|
|
// Calculate scaling factor to fit the canvas
|
|
|
|
|
const scale = Math.max(canvas.width/video.videoWidth, canvas.height/video.videoHeight)
|
|
|
|
|
|
|
|
|
|
// Calculate scaled dimensions
|
|
|
|
|
const scaledWidth = video.videoWidth * scale
|
|
|
|
|
const scaledHeight = video.videoHeight * scale
|
|
|
|
|
|
|
|
|
|
// Calculate horizontal and vertical centering offset
|
|
|
|
|
const offsetX = (canvas.width-scaledWidth)/2
|
|
|
|
|
const offsetY = (canvas.height-scaledHeight)/2
|
|
|
|
|
|
|
|
|
|
// Use default blend mode for first video (i=0), repeat the rest of blendModes
|
|
|
|
|
if (i === 0) {
|
|
|
|
|
ctx.globalCompositeOperation = "source-over"
|
|
|
|
|
} else {
|
|
|
|
|
// Using values from parameterStore
|
|
|
|
|
ctx.globalCompositeOperation = parameterStore.blendModeParams.mode
|
|
|
|
|
ctx.globalAlpha = parameterStore.blendModeParams.opacity
|
|
|
|
|
}
|
|
|
|
|
video.globalCompositeOperation = ctx.globalCompositeOperation || "default"
|
|
|
|
|
|
|
|
|
|
// Draw the current video frame onto the canvas
|
|
|
|
|
ctx.drawImage(video, offsetX, offsetY, scaledWidth, scaledHeight)
|
|
|
|
|
|
|
|
|
|
// Reset composite operation
|
|
|
|
|
ctx.globalCompositeOperation = "source-over"
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// If showText is true, draw debug text
|
|
|
|
|
if (showText) {
|
|
|
|
|
drawDebugText()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Request next frame
|
|
|
|
|
requestAnimationFrame(drawVideos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function drawDebugText() {
|
|
|
|
|
// Set font style and color
|
|
|
|
|
ctx.font = "16px Arial"
|
|
|
|
|
ctx.fillStyle = "white"
|
|
|
|
|
let padding = 20
|
|
|
|
|
|
|
|
|
|
// Calculate the position for the active preset text at the top center of the canvas
|
|
|
|
|
const presetText = `${parameterStore.selectedPreset}`;
|
|
|
|
|
const textWidth = ctx.measureText(presetText).width;
|
|
|
|
|
const centerX = canvas.width / 2;
|
|
|
|
|
const positionX = centerX - textWidth / 2;
|
|
|
|
|
const positionY = padding;
|
|
|
|
|
|
|
|
|
|
// Set text alignment to center horizontally
|
|
|
|
|
ctx.textAlign = "center";
|
|
|
|
|
ctx.textBaseline = "top";
|
|
|
|
|
|
|
|
|
|
// Draw the active preset text
|
|
|
|
|
ctx.fillText(`${presetText}`, positionX, positionY);
|
|
|
|
|
ctx.fillText(`${canvas.width}x${canvas.height} (${aspectRatio.toFixed(2)})`, positionX, positionY+20);
|
|
|
|
|
|
|
|
|
|
let corners = [[0, 0], [1, 1], [0, 1], [1, 0]]; // Top-left, bottom-right, bottom-left, top-right
|
|
|
|
|
|
|
|
|
|
videos.forEach((video, i) => {
|
|
|
|
|
// Get the corner coordinates for the current video
|
|
|
|
|
const corner = corners[i % corners.length]
|
|
|
|
|
|
|
|
|
|
// Calculate the position of the text in the corner
|
|
|
|
|
let positionX = corner[0] ? canvas.width : padding
|
|
|
|
|
let positionY = corner[1] ? canvas.height : padding
|
|
|
|
|
|
|
|
|
|
// Adjust position to ensure text is within the canvas bounds
|
|
|
|
|
//positionX = Math.max(0, Math.min(positionX, canvas.width - ctx.measureText(getFilename(video.src)).width))
|
|
|
|
|
positionY = Math.max(0, Math.min(positionY, canvas.height - padding*3))
|
|
|
|
|
|
|
|
|
|
// Set text alignment based on corner
|
|
|
|
|
ctx.textAlign = corner[0] ? "right" : "left"
|
|
|
|
|
ctx.textBaseline = corner[1] ? "bottom" : "top"
|
|
|
|
|
|
|
|
|
|
// Draw debug text for the video
|
|
|
|
|
ctx.fillText(getFilename(video.src), positionX, positionY)
|
|
|
|
|
ctx.fillText(`Dimensions: ${video.videoWidth}x${video.videoHeight} ()`, positionX, positionY + 20)
|
|
|
|
|
ctx.fillText(formatTime(video.currentTime) + "/" + formatTime(video.duration), positionX, positionY + 40)
|
|
|
|
|
ctx.fillText(`${video.globalCompositeOperation}` ,positionX,positionY+60)
|
|
|
|
|
// Add more debug information as needed
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to extract filename from full path
|
|
|
|
|
function getFilename(src) {
|
|
|
|
|
const parts = src.split('/')
|
|
|
|
|
return decodeURIComponent(parts[parts.length - 1].replace(/\%20/g, ' '))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatTime(seconds) {
|
|
|
|
|
const hours = Math.floor(seconds / 3600);
|
|
|
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
|
const remainingSeconds = Math.floor(seconds % 60);
|
|
|
|
|
return `${(hours ? hours.toString().padStart(2, '0') + ':' : '')}` +
|
|
|
|
|
`${minutes.toString().padStart(2, '0')}:`+
|
|
|
|
|
`${remainingSeconds.toString().padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateCanvasSize() {
|
|
|
|
|
let maxAspectRatio = 0;
|
|
|
|
|
|
|
|
|
|
// Calculate the maximum aspect ratio among all videos
|
|
|
|
|
for (let i = 0; i < videos.length; i++) {
|
|
|
|
|
const videoAspectRatio = videos[i].videoWidth / videos[i].videoHeight;
|
|
|
|
|
maxAspectRatio = Math.max(maxAspectRatio, videoAspectRatio);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canvas.width = window.innerWidth;
|
|
|
|
|
canvas.height = window.innerWidth / maxAspectRatio;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener("resize", updateCanvasSize)
|
|
|
|
|
|
|
|
|
|
function getSourceFiles() {
|
|
|
|
|
debugLog("Getting source files")
|
|
|
|
|
return fetch("/api/videos")
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(videoFiles => {
|
|
|
|
|
debugLog("Success")
|
|
|
|
|
cachedVideos = videoFiles;
|
|
|
|
|
})
|
|
|
|
|
.catch(error => console.error("Error fetching videos:", error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectRandomVideo(videoElement) {
|
|
|
|
|
if (cachedVideos.length) {
|
|
|
|
|
const currentSrc = videoElement.src;
|
|
|
|
|
const filteredVideos = cachedVideos.filter(video => video.src !== currentSrc);
|
|
|
|
|
if (filteredVideos.length > 0) {
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * filteredVideos.length);
|
|
|
|
|
const randomVideo = filteredVideos[randomIndex];
|
|
|
|
|
videoElement.src = randomVideo.src;
|
|
|
|
|
} else {
|
|
|
|
|
debugLog("No other videos available.");
|
|
|
|
|
getSourceFiles().then(()=>{
|
|
|
|
|
selectRandomVideo(videoElement)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
debugLog("Cache empty, doing new request")
|
|
|
|
|
getSourceFiles().then(()=>{
|
|
|
|
|
selectRandomVideo(videoElement)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function(event) {
|
|
|
|
|
console.log('keyDown',event)
|
|
|
|
|
if (event.key === 'Enter') {
|
|
|
|
|
showText = !showText
|
|
|
|
|
}
|
|
|
|
|
if (event.key === 'ArrowDown') {
|
|
|
|
|
loadVideos(-1)
|
|
|
|
|
}
|
|
|
|
|
if (event.key === 'ArrowUp') {
|
|
|
|
|
loadVideos(1)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
getSourceFiles().then(()=>{
|
|
|
|
|
loadVideos(videosToLoad)
|
|
|
|
|
updateCanvasSize()
|
|
|
|
|
})
|