quantumcode-ai-architect / components /self-talk-recorder.js
muboboev's picture
Подэтап 4.3 — Self-Talk Journal
98feb93 verified
class SelfTalkRecorder extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.container {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.video-preview {
width: 100%;
height: 300px;
background: #1e293b;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.placeholder {
text-align: center;
color: #64748b;
}
.controls {
display: flex;
gap: 1rem;
}
button {
flex: 1;
padding: 0.75rem;
border-radius: 0.5rem;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.record-btn {
background: #7c3aed;
color: white;
border: none;
}
.record-btn:hover {
background: #6d28d9;
}
.record-btn.recording {
background: #dc2626;
animation: pulse 1.5s infinite;
}
.stop-btn {
background: #1e293b;
color: white;
border: 1px solid #334155;
}
.stop-btn:hover {
background: #334155;
}
.timer {
font-size: 1.25rem;
font-weight: 600;
color: #7c3aed;
text-align: center;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
</style>
<div class="container">
<h2 class="text-xl font-bold gradient-text mb-2">Record Your Self-Talk</h2>
<p class="text-slate-300 mb-4">Speak freely for 1 minute about your thoughts, feelings, or affirmations.</p>
<div class="video-preview">
<video id="videoPreview" autoplay muted></video>
<div class="placeholder" id="videoPlaceholder">
<i data-feather="video" class="w-12 h-12 mx-auto mb-2"></i>
<p>Video preview will appear here</p>
</div>
</div>
<div class="timer" id="timer">01:00</div>
<div class="controls">
<button class="record-btn" id="recordBtn">
<i data-feather="mic"></i> Start Recording
</button>
<button class="stop-btn" id="stopBtn" disabled>
<i data-feather="square"></i> Stop
</button>
</div>
</div>
`;
this.mediaRecorder = null;
this.recordedChunks = [];
this.countdownInterval = null;
this.timeLeft = 60;
this.setupEventListeners();
feather.replace();
}
setupEventListeners() {
const recordBtn = this.shadowRoot.getElementById('recordBtn');
const stopBtn = this.shadowRoot.getElementById('stopBtn');
const videoPreview = this.shadowRoot.getElementById('videoPreview');
const videoPlaceholder = this.shadowRoot.getElementById('videoPlaceholder');
const timer = this.shadowRoot.getElementById('timer');
recordBtn.addEventListener('click', async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
videoPreview.srcObject = stream;
videoPlaceholder.style.display = 'none';
videoPreview.style.display = 'block';
this.mediaRecorder = new MediaRecorder(stream);
this.recordedChunks = [];
this.mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data);
}
};
this.mediaRecorder.onstop = () => {
const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
this.saveRecording(blob);
};
this.mediaRecorder.start(100);
recordBtn.classList.add('recording');
recordBtn.disabled = true;
stopBtn.disabled = false;
// Start countdown
this.timeLeft = 60;
this.countdownInterval = setInterval(() => {
this.timeLeft--;
const minutes = Math.floor(this.timeLeft / 60);
const seconds = this.timeLeft % 60;
timer.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (this.timeLeft <= 0) {
this.stopRecording();
}
}, 1000);
} catch (error) {
console.error('Error accessing media devices:', error);
alert('Could not access camera/microphone. Please check permissions.');
}
});
stopBtn.addEventListener('click', () => this.stopRecording());
}
stopRecording() {
if (this.countdownInterval) {
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
const videoPreview = this.shadowRoot.getElementById('videoPreview');
const stream = videoPreview.srcObject;
stream.getTracks().forEach(track => track.stop());
this.shadowRoot.getElementById('recordBtn').classList.remove('recording');
this.shadowRoot.getElementById('recordBtn').disabled = false;
this.shadowRoot.getElementById('stopBtn').disabled = true;
this.shadowRoot.getElementById('timer').textContent = '01:00';
}
}
async saveRecording(blob) {
// In a real app, this would upload to S3/CDN via your backend
console.log('Recording saved:', blob);
alert('Recording saved successfully! It will appear in your journal entries.');
// Simulate adding to entries list
const entriesContainer = document.getElementById('entriesContainer');
if (entriesContainer) {
const newEntry = {
id: Date.now().toString(),
date: new Date().toISOString().split('T')[0],
duration: '1:00',
thumbnail: 'http://static.photos/people/320x240/' + Math.floor(Math.random() * 10),
mood: 'New'
};
entriesContainer.insertAdjacentHTML('afterbegin', `
<div class="glass-card entry-card p-6 cursor-pointer">
<div class="relative pb-[56.25%] mb-4 overflow-hidden rounded-lg">
<img src="${newEntry.thumbnail}" alt="Entry thumbnail" class="absolute h-full w-full object-cover">
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-3">
<div class="text-white font-medium">${newEntry.duration}</div>
</div>
</div>
<div class="flex justify-between items-center">
<div>
<h3 class="font-bold">${newEntry.date}</h3>
<p class="text-sm text-slate-400">${newEntry.mood}</p>
</div>
<button class="text-indigo-400 hover:text-indigo-300">
<i data-feather="play"></i>
</button>
</div>
</div>
`);
feather.replace();
}
}
}
customElements.define('self-talk-recorder', SelfTalkRecorder);