From aec4fd0272b9b2c5134cbea95019c5be821cf209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sun, 25 Jan 2026 02:29:53 +0100 Subject: [PATCH] Fix public file share handler: include audio MIME types for inline viewing --- .../lib/pages/public_file_viewer.dart | 118 ++++++++++++------ go_cloud/internal/http/routes.go | 4 +- 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/b0esche_cloud/lib/pages/public_file_viewer.dart b/b0esche_cloud/lib/pages/public_file_viewer.dart index cec971b..368ab94 100644 --- a/b0esche_cloud/lib/pages/public_file_viewer.dart +++ b/b0esche_cloud/lib/pages/public_file_viewer.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:web/web.dart' as web; +import 'dart:ui_web' as ui_web; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:video_player/video_player.dart'; import 'package:flutter/foundation.dart' show kIsWeb; @@ -26,6 +27,7 @@ class _PublicFileViewerState extends State { Map? _fileData; VideoPlayerController? _videoController; List? _pdfBytes; + String? _videoViewType; @override void initState() { @@ -54,16 +56,9 @@ class _PublicFileViewerState extends State { await _loadPdfBytes(); } - // Initialize video controller if it's a video file + // Initialize video player if it's a video file if (_isVideoFile()) { - try { - await _initializeVideoPlayer(); - } catch (e) { - setState(() { - _error = - 'Video format not supported in browser. Please download the file.'; - }); - } + await _initializeVideoPlayer(); } } catch (e) { setState(() { @@ -96,34 +91,48 @@ class _PublicFileViewerState extends State { } Future _initializeVideoPlayer() async { - final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; - if (url != null) { - String videoUrl = url; - if (kIsWeb) { - // For web, fetch bytes and create blob URL to avoid CORS issues - try { - final apiClient = getIt(); - final uri = Uri.parse(url); - final path = uri.path + (uri.query.isNotEmpty ? '?${uri.query}' : ''); - final bytes = await apiClient.getBytes(path); - final mimeType = - _fileData?['capabilities']?['mimeType'] ?? 'video/mp4'; - final blob = web.Blob( - [Uint8List.fromList(bytes)] as dynamic, - web.BlobPropertyBag(type: mimeType), - ); - videoUrl = web.URL.createObjectURL(blob); - } catch (e) { - // Fallback to direct URL - videoUrl = url; - } + if (!kIsWeb) { + // For mobile, use VideoPlayerController + final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; + if (url != null) { + _videoController = VideoPlayerController.networkUrl(Uri.parse(url)); + await _videoController!.initialize(); + setState(() {}); + } + } else { + // For web, use HTML video element + final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; + if (url != null) { + _videoViewType = 'public-video-viewer-${widget.token.hashCode}'; + _registerVideoViewFactory(url); + setState(() {}); } - _videoController = VideoPlayerController.networkUrl(Uri.parse(videoUrl)); - await _videoController!.initialize(); - setState(() {}); } } + void _registerVideoViewFactory(String videoUrl) { + ui_web.platformViewRegistry.registerViewFactory(_videoViewType!, (int viewId) { + final videoElement = web.HTMLVideoElement() + ..src = videoUrl + ..controls = true + ..autoplay = false + ..crossOrigin = 'anonymous' + ..style.width = '100%' + ..style.height = '100%' + ..style.objectFit = 'contain'; + + videoElement.onError.listen((event) { + if (mounted) { + setState(() { + _error = 'Video format not supported or could not be loaded. Please download the file.'; + }); + } + }); + + return videoElement; + }); + } + String? _getViewUrl() { return _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; } @@ -201,13 +210,42 @@ class _PublicFileViewerState extends State { ), ); } - } else if (_isVideoFile() && _videoController != null) { - return Expanded( - child: AspectRatio( - aspectRatio: _videoController!.value.aspectRatio, - child: VideoPlayer(_videoController!), - ), - ); + } else if (_isVideoFile()) { + if (kIsWeb && _videoViewType != null) { + // Use HTML video element for web + return Expanded( + child: Container( + color: Colors.black, + child: HtmlElementView(viewType: _videoViewType!), + ), + ); + } else if (!kIsWeb && _videoController != null) { + // Use VideoPlayer for mobile + return Expanded( + child: AspectRatio( + aspectRatio: _videoController!.value.aspectRatio, + child: VideoPlayer(_videoController!), + ), + ); + } else if (_error != null) { + return Expanded( + child: Center( + child: Text( + _error!, + style: TextStyle(color: AppTheme.primaryText), + textAlign: TextAlign.center, + ), + ), + ); + } else { + return const Expanded( + child: Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(AppTheme.accentColor), + ), + ), + ); + } } else if (_isAudioFile()) { return Center( child: AudioPlayerBar( diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 3d2c0b1..67c7145 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -2952,8 +2952,8 @@ func publicFileShareHandler(w http.ResponseWriter, r *http.Request, db *database Token: viewerToken, } - // Set view URL for PDFs and videos (for inline viewing) - if isPdf || strings.HasPrefix(mimeType, "video/") { + // Set view URL for PDFs, videos, and audio (for inline viewing) + if isPdf || strings.HasPrefix(mimeType, "video/") || strings.HasPrefix(mimeType, "audio/") { viewerSession.ViewUrl = viewPath }