screenstream-studio / script.js
Tuxifan's picture
CROW_ROUTE(app, "/")([]() {
8876673 verified
class ScreenStreamApp {
constructor() {
this.thumbnailInterval = null;
this.durationInterval = null;
this.isRecording = false;
this.init();
}
init() {
this.bindEvents();
this.loadRecordings();
this.checkRecordingStatus();
}
bindEvents() {
document.getElementById('startBtn').addEventListener('click', () => this.startRecording());
document.getElementById('stopBtn').addEventListener('click', () => this.stopRecording());
}
async checkRecordingStatus() {
try {
const response = await fetch('/duration');
if (response.ok) {
this.isRecording = true;
this.updateUIForRecording();
this.startLivePreview();
this.startDurationTimer();
} else {
this.isRecording = false;
this.updateUIForIdle();
this.stopLivePreview();
this.stopDurationTimer();
}
} catch (error) {
console.error('Error checking recording status:', error);
this.isRecording = false;
this.updateUIForIdle();
}
}
async startRecording() {
const filename = document.getElementById('filename').value.trim() || 'recording_' + Date.now();
const format = document.getElementById('format').value;
try {
const response = await fetch(`/start/${filename}?format=${format}`, {
method: 'POST'
});
if (response.status === 204) {
this.isRecording = true;
this.updateUIForRecording();
this.startLivePreview();
this.startDurationTimer();
this.showNotification('Recording started successfully!', 'success');
} else {
this.showNotification('Failed to start recording', 'error');
}
} catch (error) {
console.error('Error starting recording:', error);
this.showNotification('Error starting recording', 'error');
}
}
async stopRecording() {
try {
const response = await fetch('/stop', {
method: 'POST'
});
if (response.status === 204) {
this.isRecording = false;
this.updateUIForIdle();
this.stopLivePreview();
this.stopDurationTimer();
this.showNotification('Recording stopped successfully!', 'success');
this.loadRecordings();
} else {
this.showNotification('Failed to stop recording', 'error');
}
} catch (error) {
console.error('Error stopping recording:', error);
this.showNotification('Error stopping recording', 'error');
}
}
updateUIForRecording() {
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusDot').classList.remove('hidden');
document.getElementById('statusText').textContent = 'Recording';
document.getElementById('thumbnail').classList.remove('hidden');
document.getElementById('noFeed').classList.add('hidden');
}
updateUIForIdle() {
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('statusDot').classList.add('hidden');
document.getElementById('statusText').textContent = 'Not Recording';
document.getElementById('thumbnail').classList.add('hidden');
document.getElementById('noFeed').classList.remove('hidden');
document.getElementById('duration').textContent = '';
}
startLivePreview() {
this.stopLivePreview();
this.updateThumbnail();
this.thumbnailInterval = setInterval(() => this.updateThumbnail(), 1000);
}
stopLivePreview() {
if (this.thumbnailInterval) {
clearInterval(this.thumbnailInterval);
this.thumbnailInterval = null;
}
}
async updateThumbnail() {
const thumbnail = document.getElementById('thumbnail');
try {
const response = await fetch('/thumbnail');
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
thumbnail.src = url + '?t=' + Date.now();
}
} catch (error) {
console.error('Error updating thumbnail:', error);
}
}
startDurationTimer() {
this.stopDurationTimer();
this.updateDuration();
this.durationInterval = setInterval(() => this.updateDuration(), 1000);
}
stopDurationTimer() {
if (this.durationInterval) {
clearInterval(this.durationInterval);
this.durationInterval = null;
}
}
async updateDuration() {
try {
const response = await fetch('/duration');
if (response.ok) {
const duration = await response.text();
document.getElementById('duration').textContent = this.formatDuration(parseInt(duration));
}
} catch (error) {
console.error('Error updating duration:', error);
}
}
formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
async loadRecordings() {
try {
const response = await fetch('/videos');
const videos = await response.json();
this.renderRecordings(videos);
} catch (error) {
console.error('Error loading recordings:', error);
}
}
renderRecordings(videos) {
const container = document.getElementById('recordingsList');
if (videos.length === 0) {
container.innerHTML = `
<div class="text-center py-8 text-gray-500">
<i data-feather="folder" class="w-12 h-12 mx-auto mb-4"></i>
<p>No recordings found</p>
</div>
`;
return;
}
container.innerHTML = videos.map(video => `
<div class="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:border-primary transition-colors">
<div class="flex items-center gap-3">
<i data-feather="video" class="text-primary"></i>
<div>
<h3 class="font-semibold text-gray-800">${video}</h3>
<p class="text-sm text-gray-500">Click to download</p>
</div>
</div>
<div class="flex gap-2">
<a href="/recording/${video}"
class="bg-primary hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-1">
<i data-feather="download"></i>
Download
</a>
<button onclick="app.deleteRecording('${video}')"
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-1">
<i data-feather="trash-2"></i>
Delete
</button>
</div>
</div>
`).join('');
feather.replace();
}
async deleteRecording(filename) {
if (!confirm(`Are you sure you want to delete "${filename}"?`)) {
return;
}
try {
const response = await fetch(`/recording/${filename}`, {
method: 'DELETE'
});
if (response.status === 204) {
this.showNotification('Recording deleted successfully!', 'success');
this.loadRecordings();
} else {
this.showNotification('Failed to delete recording', 'error');
}
} catch (error) {
console.error('Error deleting recording:', error);
this.showNotification('Error deleting recording', 'error');
}
}
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transform transition-transform duration-300 translate-x-full ${
type === 'success' ? 'bg-secondary' :
type === 'error' ? 'bg-red-500' :
'bg-primary'
} text-white font-medium`;
notification.textContent = message;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Remove after delay
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.app = new ScreenStreamApp();
});