Fix public file share handler: include audio MIME types for inline viewing

This commit is contained in:
Leon Bösche
2026-01-25 02:29:53 +01:00
parent 91a9759874
commit aec4fd0272
2 changed files with 80 additions and 42 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:web/web.dart' as web; import 'package:web/web.dart' as web;
import 'dart:ui_web' as ui_web;
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
@@ -26,6 +27,7 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
Map<String, dynamic>? _fileData; Map<String, dynamic>? _fileData;
VideoPlayerController? _videoController; VideoPlayerController? _videoController;
List<int>? _pdfBytes; List<int>? _pdfBytes;
String? _videoViewType;
@override @override
void initState() { void initState() {
@@ -54,16 +56,9 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
await _loadPdfBytes(); await _loadPdfBytes();
} }
// Initialize video controller if it's a video file // Initialize video player if it's a video file
if (_isVideoFile()) { if (_isVideoFile()) {
try { await _initializeVideoPlayer();
await _initializeVideoPlayer();
} catch (e) {
setState(() {
_error =
'Video format not supported in browser. Please download the file.';
});
}
} }
} catch (e) { } catch (e) {
setState(() { setState(() {
@@ -96,34 +91,48 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
} }
Future<void> _initializeVideoPlayer() async { Future<void> _initializeVideoPlayer() async {
final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; if (!kIsWeb) {
if (url != null) { // For mobile, use VideoPlayerController
String videoUrl = url; final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl'];
if (kIsWeb) { if (url != null) {
// For web, fetch bytes and create blob URL to avoid CORS issues _videoController = VideoPlayerController.networkUrl(Uri.parse(url));
try { await _videoController!.initialize();
final apiClient = getIt<ApiClient>(); setState(() {});
final uri = Uri.parse(url); }
final path = uri.path + (uri.query.isNotEmpty ? '?${uri.query}' : ''); } else {
final bytes = await apiClient.getBytes(path); // For web, use HTML video element
final mimeType = final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl'];
_fileData?['capabilities']?['mimeType'] ?? 'video/mp4'; if (url != null) {
final blob = web.Blob( _videoViewType = 'public-video-viewer-${widget.token.hashCode}';
[Uint8List.fromList(bytes)] as dynamic, _registerVideoViewFactory(url);
web.BlobPropertyBag(type: mimeType), setState(() {});
);
videoUrl = web.URL.createObjectURL(blob);
} catch (e) {
// Fallback to direct URL
videoUrl = url;
}
} }
_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() { String? _getViewUrl() {
return _fileData?['viewUrl'] ?? _fileData?['downloadUrl']; return _fileData?['viewUrl'] ?? _fileData?['downloadUrl'];
} }
@@ -201,13 +210,42 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
), ),
); );
} }
} else if (_isVideoFile() && _videoController != null) { } else if (_isVideoFile()) {
return Expanded( if (kIsWeb && _videoViewType != null) {
child: AspectRatio( // Use HTML video element for web
aspectRatio: _videoController!.value.aspectRatio, return Expanded(
child: VideoPlayer(_videoController!), 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<Color>(AppTheme.accentColor),
),
),
);
}
} else if (_isAudioFile()) { } else if (_isAudioFile()) {
return Center( return Center(
child: AudioPlayerBar( child: AudioPlayerBar(

View File

@@ -2952,8 +2952,8 @@ func publicFileShareHandler(w http.ResponseWriter, r *http.Request, db *database
Token: viewerToken, Token: viewerToken,
} }
// Set view URL for PDFs and videos (for inline viewing) // Set view URL for PDFs, videos, and audio (for inline viewing)
if isPdf || strings.HasPrefix(mimeType, "video/") { if isPdf || strings.HasPrefix(mimeType, "video/") || strings.HasPrefix(mimeType, "audio/") {
viewerSession.ViewUrl = viewPath viewerSession.ViewUrl = viewPath
} }