You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

286 lines
8.3 KiB

// Define a debug mode variable
let debugMode = true
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: {
// 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 preset
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`)
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)
num++
}
}
videos.forEach((video) => {
video.addEventListener("loadedmetadata", handleVideoLoaded)
video.addEventListener("ended", handleVideoEnded)
})
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)
let blendModes = ["screen","overlay","difference","lighten"]
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 {
ctx.globalCompositeOperation = blendModes[i % blendModes.length]
}
// 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);
let corners = [[0, 0], [1, 1], [0, 1], [1, 0]]; // Top-left, bottom-right, top-right, bottom-left
videos.forEach((video, i) => {
// Get the corner coordinates for the current video
const corner = corners[i]
// 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)
// 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, ' '))
}
// Helper function to format time in HH:MM:SS format
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() {
aspectRatio = Math.max(
videos[0].videoWidth / videos[0].videoHeight,
videos[1].videoWidth / videos[1].videoHeight
)
canvas.width = window.innerWidth
canvas.height = window.innerWidth / aspectRatio
}
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)
// Check if the pressed key is the one you want to bind
if (event.key === 'Enter') {
showText = !showText
}
if (event.key === 'ArrowDown') {
loadVideos(-1)
}
if (event.key === 'ArrowUp') {
loadVideos(1)
}
})
getSourceFiles().then(()=>{
loadVideos(videosToLoad)
updateCanvasSize()
})