Fix download button centering and width, fix audio/video loading by using blob URLs for web
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as 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 '../theme/app_theme.dart';
|
import '../theme/app_theme.dart';
|
||||||
import '../services/api_client.dart';
|
import '../services/api_client.dart';
|
||||||
import '../injection.dart';
|
import '../injection.dart';
|
||||||
@@ -97,7 +98,24 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
|
|||||||
Future<void> _initializeVideoPlayer() async {
|
Future<void> _initializeVideoPlayer() async {
|
||||||
final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl'];
|
final url = _fileData?['viewUrl'] ?? _fileData?['downloadUrl'];
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
_videoController = VideoPlayerController.networkUrl(Uri.parse(url));
|
String videoUrl = url;
|
||||||
|
if (kIsWeb) {
|
||||||
|
// For web, fetch bytes and create blob URL to avoid CORS issues
|
||||||
|
try {
|
||||||
|
final apiClient = getIt<ApiClient>();
|
||||||
|
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([bytes], mimeType);
|
||||||
|
videoUrl = web.URL.createObjectURL(blob);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to direct URL
|
||||||
|
videoUrl = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_videoController = VideoPlayerController.networkUrl(Uri.parse(videoUrl));
|
||||||
await _videoController!.initialize();
|
await _videoController!.initialize();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@@ -192,6 +210,7 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
|
|||||||
child: AudioPlayerBar(
|
child: AudioPlayerBar(
|
||||||
fileName: _fileData!['fileName'] ?? 'Audio',
|
fileName: _fileData!['fileName'] ?? 'Audio',
|
||||||
fileUrl: viewUrl,
|
fileUrl: viewUrl,
|
||||||
|
mimeType: _fileData?['capabilities']?['mimeType'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (_isDocumentFile()) {
|
} else if (_isDocumentFile()) {
|
||||||
@@ -266,11 +285,14 @@ class _PublicFileViewerState extends State<PublicFileViewer> {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
leading: _fileData != null
|
leading: _fileData != null
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, top: 4),
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 56,
|
||||||
child: ModernGlassButton(
|
child: ModernGlassButton(
|
||||||
onPressed: _downloadFile,
|
onPressed: _downloadFile,
|
||||||
child: const Icon(Icons.download, size: 18),
|
child: const Icon(Icons.download, size: 18),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ import 'package:just_audio/just_audio.dart'
|
|||||||
class AudioPlayerBar extends StatefulWidget {
|
class AudioPlayerBar extends StatefulWidget {
|
||||||
final String fileName;
|
final String fileName;
|
||||||
final String fileUrl;
|
final String fileUrl;
|
||||||
|
final String? mimeType;
|
||||||
final VoidCallback? onClose;
|
final VoidCallback? onClose;
|
||||||
|
|
||||||
const AudioPlayerBar({
|
const AudioPlayerBar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.fileName,
|
required this.fileName,
|
||||||
required this.fileUrl,
|
required this.fileUrl,
|
||||||
|
this.mimeType,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,10 +65,12 @@ class _AudioPlayerBarState extends State<AudioPlayerBar>
|
|||||||
String? _errorMsg;
|
String? _errorMsg;
|
||||||
Future<void> _initAudio() async {
|
Future<void> _initAudio() async {
|
||||||
try {
|
try {
|
||||||
await _audioPlayer.setUrl(widget.fileUrl);
|
|
||||||
|
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
// Web implementation
|
// Web implementation
|
||||||
|
await (_audioPlayer as dynamic).setUrl(
|
||||||
|
widget.fileUrl,
|
||||||
|
mimeType: widget.mimeType,
|
||||||
|
);
|
||||||
_durationSubscription = _audioPlayer.durationStream.listen((d) {
|
_durationSubscription = _audioPlayer.durationStream.listen((d) {
|
||||||
if (d != null) {
|
if (d != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -104,6 +108,9 @@ class _AudioPlayerBarState extends State<AudioPlayerBar>
|
|||||||
await _audioPlayer.play();
|
await _audioPlayer.play();
|
||||||
} else {
|
} else {
|
||||||
// Mobile implementation (just_audio)
|
// Mobile implementation (just_audio)
|
||||||
|
await _audioPlayer.setAudioSource(
|
||||||
|
AudioSource.uri(Uri.parse(widget.fileUrl)),
|
||||||
|
);
|
||||||
_audioPlayer.durationStream.firstWhere((d) => d != null).then((
|
_audioPlayer.durationStream.firstWhere((d) => d != null).then((
|
||||||
d,
|
d,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as web;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import '../services/api_client.dart';
|
||||||
|
import '../injection.dart';
|
||||||
|
|
||||||
class AudioPlayer {
|
class AudioPlayer {
|
||||||
web.HTMLAudioElement? _audioElement;
|
web.HTMLAudioElement? _audioElement;
|
||||||
|
String? _blobUrl;
|
||||||
final StreamController<Duration> _positionController =
|
final StreamController<Duration> _positionController =
|
||||||
StreamController<Duration>.broadcast();
|
StreamController<Duration>.broadcast();
|
||||||
final StreamController<Duration> _durationController =
|
final StreamController<Duration> _durationController =
|
||||||
@@ -41,13 +44,19 @@ class AudioPlayer {
|
|||||||
_errorSubscription = null;
|
_errorSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setUrl(String url) async {
|
Future<void> setUrl(String url, {String? mimeType}) async {
|
||||||
// Clean up any existing subscriptions
|
// Clean up any existing subscriptions
|
||||||
_disposeSubscriptions();
|
_disposeSubscriptions();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final apiClient = getIt<ApiClient>();
|
||||||
|
final path = url.replaceFirst(apiClient.baseUrl, '');
|
||||||
|
final bytes = await apiClient.getBytes(path);
|
||||||
|
final blob = web.Blob([bytes], mimeType ?? 'audio/mpeg');
|
||||||
|
final blobUrl = web.URL.createObjectURL(blob);
|
||||||
|
|
||||||
_audioElement = web.HTMLAudioElement();
|
_audioElement = web.HTMLAudioElement();
|
||||||
_audioElement!.src = url;
|
_audioElement!.src = blobUrl;
|
||||||
_audioElement!.crossOrigin = 'anonymous'; // Handle CORS
|
_audioElement!.crossOrigin = 'anonymous'; // Handle CORS
|
||||||
|
|
||||||
// Set up event listeners and store subscriptions
|
// Set up event listeners and store subscriptions
|
||||||
@@ -115,6 +124,11 @@ class AudioPlayer {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_disposeSubscriptions();
|
_disposeSubscriptions();
|
||||||
_audioElement?.pause();
|
_audioElement?.pause();
|
||||||
|
if (_blobUrl != null) {
|
||||||
|
web.URL.revokeObjectURL(_blobUrl!);
|
||||||
|
_blobUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
_audioElement = null;
|
_audioElement = null;
|
||||||
_positionController.close();
|
_positionController.close();
|
||||||
_durationController.close();
|
_durationController.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user