FIX: Enhance video playback and implement smart aspect ratio window sizing
- Replace ReactPlayer with native HTML5 video for better MP4 support - Add proper MIME type mapping for all video formats (mp4, webm, mkv, avi, mov, wmv, flv) - Implement smart window sizing based on media dimensions - Auto-adjust window size to match image/video aspect ratio with constraints - Add media dimension detection for images (naturalWidth/Height) and videos (videoWidth/Height) - Center windows automatically when resizing for media content - Apply intelligent scaling with max viewport limits (90% width, 80% height) - Preserve minimum window sizes and add padding for UI elements - Enhanced error handling and debug logging for video playback 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ interface DraggableWindowProps {
|
||||
isMaximized?: boolean;
|
||||
zIndex?: number;
|
||||
onFocus?: () => void;
|
||||
targetSize?: { width: number; height: number };
|
||||
}
|
||||
|
||||
export function DraggableWindow({
|
||||
@@ -35,6 +36,7 @@ export function DraggableWindow({
|
||||
isMaximized = false,
|
||||
zIndex = 1000,
|
||||
onFocus,
|
||||
targetSize,
|
||||
}: DraggableWindowProps) {
|
||||
const { t } = useTranslation();
|
||||
// Window state
|
||||
@@ -55,6 +57,40 @@ export function DraggableWindow({
|
||||
const windowRef = useRef<HTMLDivElement>(null);
|
||||
const titleBarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Handle target size changes for media files
|
||||
useEffect(() => {
|
||||
if (targetSize && !isMaximized) {
|
||||
const maxWidth = Math.min(window.innerWidth * 0.9, 1200);
|
||||
const maxHeight = Math.min(window.innerHeight * 0.8, 800);
|
||||
|
||||
// Calculate appropriate window size maintaining aspect ratio
|
||||
let newWidth = Math.min(targetSize.width + 50, maxWidth); // Add padding for UI
|
||||
let newHeight = Math.min(targetSize.height + 150, maxHeight); // Add padding for header/footer
|
||||
|
||||
// If still too large, scale down maintaining aspect ratio
|
||||
if (newWidth > maxWidth || newHeight > maxHeight) {
|
||||
const widthRatio = maxWidth / newWidth;
|
||||
const heightRatio = maxHeight / newHeight;
|
||||
const scale = Math.min(widthRatio, heightRatio);
|
||||
|
||||
newWidth = Math.floor(newWidth * scale);
|
||||
newHeight = Math.floor(newHeight * scale);
|
||||
}
|
||||
|
||||
// Ensure minimum size
|
||||
newWidth = Math.max(newWidth, minWidth);
|
||||
newHeight = Math.max(newHeight, minHeight);
|
||||
|
||||
setSize({ width: newWidth, height: newHeight });
|
||||
|
||||
// Center the window
|
||||
setPosition({
|
||||
x: Math.max(0, (window.innerWidth - newWidth) / 2),
|
||||
y: Math.max(0, (window.innerHeight - newHeight) / 2)
|
||||
});
|
||||
}
|
||||
}, [targetSize, isMaximized, minWidth, minHeight]);
|
||||
|
||||
// Handle window focus
|
||||
const handleWindowClick = useCallback(() => {
|
||||
onFocus?.();
|
||||
|
||||
@@ -52,6 +52,7 @@ import { defaultKeymap, history, historyKeymap, toggleComment } from "@codemirro
|
||||
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
||||
import { PhotoProvider, PhotoView } from "react-photo-view";
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
import ReactPlayer from "react-player";
|
||||
|
||||
interface FileItem {
|
||||
name: string;
|
||||
@@ -73,6 +74,7 @@ interface FileViewerProps {
|
||||
onContentChange?: (content: string) => void;
|
||||
onSave?: (content: string) => void;
|
||||
onDownload?: () => void;
|
||||
onMediaDimensionsChange?: (dimensions: { width: number; height: number }) => void;
|
||||
}
|
||||
|
||||
// Get official icon for programming languages
|
||||
@@ -277,6 +279,7 @@ export function FileViewer({
|
||||
onContentChange,
|
||||
onSave,
|
||||
onDownload,
|
||||
onMediaDimensionsChange,
|
||||
}: FileViewerProps) {
|
||||
const { t } = useTranslation();
|
||||
const [editedContent, setEditedContent] = useState(content);
|
||||
@@ -664,9 +667,18 @@ export function FileViewer({
|
||||
alt={file.name}
|
||||
className="max-w-full max-h-full object-contain rounded-lg shadow-sm cursor-pointer hover:shadow-lg transition-shadow"
|
||||
style={{ maxHeight: "calc(100vh - 200px)" }}
|
||||
onLoad={() => {
|
||||
onLoad={(e) => {
|
||||
setImageLoading(false);
|
||||
setImageLoadError(false);
|
||||
|
||||
// Get natural dimensions and notify parent
|
||||
const img = e.currentTarget;
|
||||
if (onMediaDimensionsChange && img.naturalWidth && img.naturalHeight) {
|
||||
onMediaDimensionsChange({
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight
|
||||
});
|
||||
}
|
||||
}}
|
||||
onError={() => {
|
||||
setImageLoading(false);
|
||||
@@ -763,16 +775,79 @@ export function FileViewer({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Video file preview */}
|
||||
{/* Video file preview with enhanced HTML5 support */}
|
||||
{fileTypeInfo.type === "video" && !showLargeFileWarning && (
|
||||
<div className="p-6 flex items-center justify-center h-full">
|
||||
<video
|
||||
controls
|
||||
className="max-w-full max-h-full rounded-lg shadow-sm"
|
||||
src={`data:video/*;base64,${content}`}
|
||||
>
|
||||
Your browser does not support video playback.
|
||||
</video>
|
||||
<div className="w-full max-w-4xl">
|
||||
{(() => {
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || '';
|
||||
const mimeType = (() => {
|
||||
switch (ext) {
|
||||
case 'mp4': return 'video/mp4';
|
||||
case 'webm': return 'video/webm';
|
||||
case 'mkv': return 'video/x-matroska';
|
||||
case 'avi': return 'video/x-msvideo';
|
||||
case 'mov': return 'video/quicktime';
|
||||
case 'wmv': return 'video/x-ms-wmv';
|
||||
case 'flv': return 'video/x-flv';
|
||||
default: return 'video/mp4';
|
||||
}
|
||||
})();
|
||||
|
||||
const videoUrl = `data:${mimeType};base64,${content}`;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<video
|
||||
controls
|
||||
className="w-full rounded-lg shadow-sm"
|
||||
style={{
|
||||
maxHeight: "calc(100vh - 200px)",
|
||||
backgroundColor: "#000"
|
||||
}}
|
||||
preload="metadata"
|
||||
onError={(e) => {
|
||||
console.error('Video playback error:', e.currentTarget.error);
|
||||
}}
|
||||
onLoadStart={() => {
|
||||
console.log('Video loading started...');
|
||||
}}
|
||||
onLoadedMetadata={(e) => {
|
||||
const video = e.currentTarget;
|
||||
console.log('Video metadata loaded, dimensions:', video.videoWidth, 'x', video.videoHeight);
|
||||
|
||||
// Get video dimensions and notify parent
|
||||
if (onMediaDimensionsChange && video.videoWidth && video.videoHeight) {
|
||||
onMediaDimensionsChange({
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight
|
||||
});
|
||||
}
|
||||
}}
|
||||
onCanPlay={() => {
|
||||
console.log('Video can start playing');
|
||||
}}
|
||||
>
|
||||
<source src={videoUrl} type={mimeType} />
|
||||
<div className="text-center text-muted-foreground p-4">
|
||||
<AlertCircle className="w-8 h-8 mx-auto mb-2" />
|
||||
<p>Your browser does not support video playback for this format.</p>
|
||||
{onDownload && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onDownload}
|
||||
className="mt-2 flex items-center gap-2 mx-auto"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download to play externally
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ export function FileWindow({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const [pendingContent, setPendingContent] = useState<string>("");
|
||||
const [mediaDimensions, setMediaDimensions] = useState<{ width: number; height: number } | undefined>();
|
||||
const autoSaveTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const currentWindow = windows.find((w) => w.id === windowId);
|
||||
@@ -365,6 +366,12 @@ export function FileWindow({
|
||||
focusWindow(windowId);
|
||||
};
|
||||
|
||||
// Handle media dimensions change
|
||||
const handleMediaDimensionsChange = (dimensions: { width: number; height: number }) => {
|
||||
console.log('Media dimensions received:', dimensions);
|
||||
setMediaDimensions(dimensions);
|
||||
};
|
||||
|
||||
if (!currentWindow) {
|
||||
return null;
|
||||
}
|
||||
@@ -383,6 +390,7 @@ export function FileWindow({
|
||||
onFocus={handleFocus}
|
||||
isMaximized={currentWindow.isMaximized}
|
||||
zIndex={currentWindow.zIndex}
|
||||
targetSize={mediaDimensions}
|
||||
>
|
||||
<FileViewer
|
||||
file={file}
|
||||
@@ -393,6 +401,7 @@ export function FileWindow({
|
||||
onContentChange={handleContentChange}
|
||||
onSave={(newContent) => handleSave(newContent)}
|
||||
onDownload={handleDownload}
|
||||
onMediaDimensionsChange={handleMediaDimensionsChange}
|
||||
/>
|
||||
</DraggableWindow>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user