Fix public file share handler: include audio MIME types for inline viewing
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user