aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
451 lines (448 loc) • 15.5 kB
JavaScript
'use client';
import { jsx } from 'react/jsx-runtime';
import React, { useState, useRef, useEffect, useCallback, useContext, createContext } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
const getMediaDevicesApi = () => typeof navigator !== 'undefined' && navigator.mediaDevices ? navigator.mediaDevices : undefined;
const getMediaRecorderCtor = () => {
if (typeof window !== 'undefined' && window.MediaRecorder) {
return window.MediaRecorder;
}
if (typeof MediaRecorder !== 'undefined') {
return MediaRecorder;
}
return undefined;
};
const isTestEnvironment = typeof process !== "undefined" && !!process.env.JEST_WORKER_ID;
let hasWarnedForDevices = false;
let hasWarnedForRecorder = false;
const MediaContext = /*#__PURE__*/createContext(null);
// Mock transcription service
const mockTranscriptionService = {
async transcribe(mediaFile) {
// Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 2000));
// Mock transcript data based on media type and duration
const duration = mediaFile.duration || 120;
const entries = [];
const mockContent = ["Welcome to our presentation about advanced media components.", "Today we'll explore the capabilities of modern web media players.", "Our system includes automatic transcription and subtitle generation.", "Users can search through transcripts and jump to specific moments.", "The AI can identify speakers and analyze sentiment in real-time.", "Chapter markers help organize content for better navigation.", "Quality adaptation ensures smooth playback on all devices.", "Analytics provide insights into user engagement patterns.", "This technology represents the future of interactive media."];
const speakers = ['Alice Johnson', 'Bob Smith', 'Dr. Sarah Chen'];
for (let i = 0; i < Math.min(mockContent.length, Math.floor(duration / 15)); i++) {
const startTime = i * 15;
const endTime = Math.min(startTime + 12, duration);
entries.push({
id: `transcript_${i}`,
startTime,
endTime,
text: mockContent[i] || "Additional content continues...",
speaker: speakers[i % speakers.length],
confidence: 0.85 + Math.random() * 0.15,
keywords: ['media', 'transcription', 'AI', 'technology'].slice(0, Math.floor(Math.random() * 3) + 1),
sentiment: ['positive', 'neutral', 'negative'][Math.floor(Math.random() * 3)]
});
}
return entries;
},
async generateSummary(transcript) {
await new Promise(resolve => setTimeout(resolve, 1500));
return `This ${transcript.length > 5 ? 'comprehensive' : 'brief'} presentation covers advanced media components including automatic transcription, AI-powered analysis, and interactive features. Key topics include speaker identification, sentiment analysis, and user engagement tracking.`;
},
async identifySpeakers(transcript) {
await new Promise(resolve => setTimeout(resolve, 1000));
const speakers = [...new Set(transcript.map(entry => entry.speaker).filter(Boolean))];
return speakers;
}
};
const MediaProvider = ({
children
}) => {
const [mediaFiles, setMediaFiles] = useState([]);
const [playbackState, setPlaybackState] = useState(null);
const [transcripts, setTranscripts] = useState({});
const [analytics, setAnalytics] = useState({});
const [isRecording, setIsRecording] = useState(false);
const [recordingDevices, setRecordingDevices] = useState([]);
const mediaRecorderRef = useRef(null);
const recordedChunksRef = useRef([]);
// Initialize recording devices
useEffect(() => {
const devicesApi = getMediaDevicesApi();
if (!devicesApi?.enumerateDevices) {
if (!hasWarnedForDevices && !isTestEnvironment) {
console.warn("Media devices API is not available in this environment; skipping device enumeration.");
}
hasWarnedForDevices = true;
setRecordingDevices([]);
return;
}
const getDevices = async () => {
try {
const devices = await devicesApi.enumerateDevices();
setRecordingDevices(devices.filter(device => device.kind === "audioinput" || device.kind === "videoinput"));
} catch (error) {
console.error("Error getting media devices:", error);
}
};
getDevices();
}, []);
const addMediaFile = useCallback(file => {
setMediaFiles(prev => [...prev, file]);
// Initialize analytics
setAnalytics(prev => ({
...prev,
[file.id]: {
totalViews: 0,
averageWatchTime: 0,
completionRate: 0,
engagementPoints: [],
heatmapData: [],
deviceStats: {},
locationStats: {}
}
}));
}, []);
const removeMediaFile = useCallback(id => {
setMediaFiles(prev => prev.filter(file => file.id !== id));
setTranscripts(prev => {
const newTranscripts = {
...prev
};
delete newTranscripts[id];
return newTranscripts;
});
setAnalytics(prev => {
const newAnalytics = {
...prev
};
delete newAnalytics[id];
return newAnalytics;
});
}, []);
const getMediaFile = useCallback(id => {
return mediaFiles.find(file => file.id === id);
}, [mediaFiles]);
const updateMediaFile = useCallback((id, updates) => {
setMediaFiles(prev => prev.map(file => file.id === id ? {
...file,
...updates
} : file));
}, []);
const play = useCallback(mediaId => {
const mediaFile = getMediaFile(mediaId);
if (mediaFile) {
setPlaybackState(prev => ({
...prev,
mediaId,
isPlaying: true,
isLoading: false,
isError: false,
currentTime: prev?.mediaId === mediaId ? prev.currentTime : 0,
duration: mediaFile.duration || 0,
volume: prev?.volume || 1,
playbackRate: prev?.playbackRate || 1,
quality: prev?.quality || 'auto',
isFullscreen: prev?.isFullscreen || false,
isMuted: prev?.isMuted || false,
buffered: null
}));
trackEngagement(mediaId, playbackState?.currentTime || 0, 'play');
}
}, [getMediaFile, playbackState]);
const pause = useCallback(() => {
setPlaybackState(prev => prev ? {
...prev,
isPlaying: false
} : null);
if (playbackState) {
trackEngagement(playbackState.mediaId, playbackState.currentTime, 'pause');
}
}, [playbackState]);
const stop = useCallback(() => {
setPlaybackState(null);
}, []);
const seekTo = useCallback(time => {
setPlaybackState(prev => prev ? {
...prev,
currentTime: time
} : null);
if (playbackState) {
trackEngagement(playbackState.mediaId, time, 'seek');
}
}, [playbackState]);
const setVolume = useCallback(volume => {
setPlaybackState(prev => prev ? {
...prev,
volume
} : null);
}, []);
const setPlaybackRate = useCallback(rate => {
setPlaybackState(prev => prev ? {
...prev,
playbackRate: rate
} : null);
}, []);
const toggleMute = useCallback(() => {
setPlaybackState(prev => prev ? {
...prev,
isMuted: !prev.isMuted
} : null);
}, []);
const toggleFullscreen = useCallback(() => {
setPlaybackState(prev => prev ? {
...prev,
isFullscreen: !prev.isFullscreen
} : null);
if (playbackState) {
trackEngagement(playbackState.mediaId, playbackState.currentTime, 'fullscreen');
}
}, [playbackState]);
const setQuality = useCallback(quality => {
setPlaybackState(prev => prev ? {
...prev,
quality
} : null);
}, []);
const generateTranscript = useCallback(async mediaId => {
const mediaFile = getMediaFile(mediaId);
if (!mediaFile) return;
try {
const transcript = await mockTranscriptionService.transcribe(mediaFile);
setTranscripts(prev => ({
...prev,
[mediaId]: transcript
}));
} catch (error) {
console.error('Error generating transcript:', error);
}
}, [getMediaFile]);
const searchTranscript = useCallback((mediaId, query) => {
const transcript = transcripts[mediaId] || [];
const lowercaseQuery = query.toLowerCase();
return transcript.filter(entry => entry.text.toLowerCase().includes(lowercaseQuery) || entry.keywords?.some(keyword => keyword.toLowerCase().includes(lowercaseQuery)));
}, [transcripts]);
const getTranscriptAtTime = useCallback((mediaId, time) => {
const transcript = transcripts[mediaId] || [];
return transcript.find(entry => time >= entry.startTime && time <= entry.endTime);
}, [transcripts]);
const setActiveChapter = useCallback((mediaId, chapterId) => {
setPlaybackState(prev => prev && prev.mediaId === mediaId ? {
...prev,
activeChapter: chapterId
} : prev);
}, []);
const getChapterAtTime = useCallback((mediaId, time) => {
const mediaFile = getMediaFile(mediaId);
if (!mediaFile?.chapters) return undefined;
return mediaFile.chapters.find(chapter => time >= chapter.startTime && time <= chapter.endTime);
}, [getMediaFile]);
const setActiveSubtitle = useCallback((mediaId, subtitleId) => {
setPlaybackState(prev => prev && prev.mediaId === mediaId ? {
...prev,
activeSubtitle: subtitleId
} : prev);
}, []);
const generateSubtitles = useCallback(async (mediaId, language) => {
const transcript = transcripts[mediaId];
if (!transcript) return;
const subtitles = transcript.map(entry => ({
id: entry.id,
startTime: entry.startTime,
endTime: entry.endTime,
text: entry.text,
speaker: entry.speaker
}));
updateMediaFile(mediaId, {
subtitles: [...(getMediaFile(mediaId)?.subtitles || []), {
id: `subtitle_${language}`,
language,
label: language.toUpperCase(),
content: subtitles
}]
});
}, [transcripts, getMediaFile, updateMediaFile]);
const trackView = useCallback((mediaId, watchTime) => {
setAnalytics(prev => ({
...prev,
[mediaId]: {
...prev[mediaId],
totalViews: (prev[mediaId]?.totalViews || 0) + 1,
averageWatchTime: ((prev[mediaId]?.averageWatchTime || 0) + watchTime) / 2
}
}));
}, []);
const trackEngagement = useCallback((mediaId, time, type) => {
setAnalytics(prev => ({
...prev,
[mediaId]: {
...prev[mediaId],
engagementPoints: [...(prev[mediaId]?.engagementPoints || []), {
time,
engagement: type === 'play' ? 1 : 0.5
}].slice(-100) // Keep last 100 engagement points
}
}));
}, []);
const generateSummary = useCallback(async mediaId => {
const transcript = transcripts[mediaId];
if (!transcript) return '';
return await mockTranscriptionService.generateSummary(transcript);
}, [transcripts]);
const identifySpeakers = useCallback(async mediaId => {
const transcript = transcripts[mediaId];
if (!transcript) return [];
return await mockTranscriptionService.identifySpeakers(transcript);
}, [transcripts]);
const extractKeywords = useCallback(async mediaId => {
const transcript = transcripts[mediaId];
if (!transcript) return [];
const allKeywords = transcript.flatMap(entry => entry.keywords || []);
return [...new Set(allKeywords)];
}, [transcripts]);
const analyzeSentiment = useCallback(async mediaId => {
const transcript = transcripts[mediaId];
if (!transcript) return {};
const sentimentMap = {};
transcript.forEach(entry => {
if (entry.sentiment) {
sentimentMap[entry.startTime] = entry.sentiment;
}
});
return sentimentMap;
}, [transcripts]);
const startRecording = useCallback(async (type, options) => {
const mediaDevicesApi = getMediaDevicesApi();
if (!mediaDevicesApi?.getUserMedia) {
if (!hasWarnedForDevices && !isTestEnvironment) {
console.warn("Media devices API is not available in this environment; recording is disabled.");
}
hasWarnedForDevices = true;
return;
}
const MediaRecorderCtor = getMediaRecorderCtor();
if (!MediaRecorderCtor) {
if (!hasWarnedForRecorder && !isTestEnvironment) {
console.warn("MediaRecorder API is not available in this environment; recording is disabled.");
}
hasWarnedForRecorder = true;
return;
}
try {
const constraints = {
audio: type === "audio" || type === "video",
video: type === "video",
...options?.constraints
};
const stream = await mediaDevicesApi.getUserMedia(constraints);
const mediaRecorder = new MediaRecorderCtor(stream);
recordedChunksRef.current = [];
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
recordedChunksRef.current.push(event.data);
}
};
mediaRecorderRef.current = mediaRecorder;
setIsRecording(true);
mediaRecorder.start();
} catch (error) {
console.error("Error starting recording:", error);
}
}, []);
const stopRecording = useCallback(async () => {
if (!mediaRecorderRef.current || !isRecording) return null;
return new Promise(resolve => {
mediaRecorderRef.current.onstop = () => {
const blob = new Blob(recordedChunksRef.current, {
type: 'video/webm'
});
const url = URL.createObjectURL(blob);
const mediaFile = {
id: `recording_${Date.now()}`,
type: 'video',
src: url,
title: 'New Recording',
size: blob.size,
format: 'webm'
};
addMediaFile(mediaFile);
setIsRecording(false);
resolve(mediaFile);
};
mediaRecorderRef.current.stop();
});
}, [isRecording, addMediaFile]);
const pauseRecording = useCallback(() => {
if (mediaRecorderRef.current && isRecording) {
mediaRecorderRef.current.pause();
}
}, [isRecording]);
const resumeRecording = useCallback(() => {
if (mediaRecorderRef.current && isRecording) {
mediaRecorderRef.current.resume();
}
}, [isRecording]);
const value = {
mediaFiles,
addMediaFile,
removeMediaFile,
getMediaFile,
updateMediaFile,
playbackState,
setPlaybackState,
play,
pause,
stop,
seekTo,
setVolume,
setPlaybackRate,
toggleMute,
toggleFullscreen,
setQuality,
transcripts,
generateTranscript,
searchTranscript,
getTranscriptAtTime,
setActiveChapter,
getChapterAtTime,
setActiveSubtitle,
generateSubtitles,
analytics,
trackView,
trackEngagement,
generateSummary,
identifySpeakers,
extractKeywords,
analyzeSentiment,
isRecording,
recordingDevices,
startRecording,
stopRecording,
pauseRecording,
resumeRecording
};
return jsx(MediaContext.Provider, {
"data-glass-component": true,
value: value,
children: children
});
};
const GlassMediaProvider = /*#__PURE__*/React.forwardRef(({
className,
children,
...rest
}, ref) => jsx("div", {
ref: ref,
className: cn("glass-media-provider", className),
...rest,
children: jsx(MediaProvider, {
children: children
})
}));
GlassMediaProvider.displayName = "GlassMediaProvider";
const useMedia = () => {
const context = useContext(MediaContext);
if (!context) {
throw new Error('useMedia must be used within a MediaProvider');
}
return context;
};
export { GlassMediaProvider, MediaProvider, useMedia };
//# sourceMappingURL=GlassMediaProvider.js.map