import 'dart:io'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import '../app_progress_indicator.dart'; /// Plays a local video (via Chewie) or audio file (via [_AudioControls]). /// Reports an inline "format not supported" message when the platform can't /// initialize the file. class MediaPlayer extends StatefulWidget { final String path; final bool isAudio; final String? filename; const MediaPlayer({ super.key, required this.path, required this.isAudio, this.filename, }); @override State createState() => _MediaPlayerState(); } class _MediaPlayerState extends State { VideoPlayerController? _video; ChewieController? _chewie; Object? _initError; @override void initState() { super.initState(); _initialize(); } Future _initialize() async { final controller = VideoPlayerController.file(File(widget.path)); try { await controller.initialize(); } on Object catch (e) { await controller.dispose(); if (!mounted) return; setState(() => _initError = e); return; } if (!mounted) { await controller.dispose(); return; } if (widget.isAudio) { controller.addListener(_onAudioTick); setState(() => _video = controller); } else { setState(() { _video = controller; _chewie = ChewieController( videoPlayerController: controller, autoPlay: false, looping: false, allowFullScreen: true, allowPlaybackSpeedChanging: true, ); }); } } void _onAudioTick() { if (mounted) setState(() {}); } @override void dispose() { _video?.removeListener(_onAudioTick); _chewie?.dispose(); _video?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (_initError != null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48), const SizedBox(height: 12), Text( widget.isAudio ? 'Audio kann nicht abgespielt werden' : 'Video kann nicht abgespielt werden', style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Format wird auf diesem Gerät nicht unterstützt. Über das Menü kannst du die Datei in einer anderen App öffnen.', style: Theme.of(context).textTheme.bodySmall, textAlign: TextAlign.center, ), ], ), ), ); } if (_video == null) { return const Center(child: AppProgressIndicator.large()); } if (widget.isAudio) { return _AudioControls( controller: _video!, filename: widget.filename ?? '', ); } return Chewie(controller: _chewie!); } } class _AudioControls extends StatelessWidget { final VideoPlayerController controller; final String filename; const _AudioControls({required this.controller, required this.filename}); String _format(Duration d) { final m = d.inMinutes.remainder(60).toString().padLeft(2, '0'); final s = d.inSeconds.remainder(60).toString().padLeft(2, '0'); if (d.inHours > 0) return '${d.inHours}:$m:$s'; return '$m:$s'; } @override Widget build(BuildContext context) { final value = controller.value; final duration = value.duration; final position = value.position; final maxMs = duration.inMilliseconds == 0 ? 1 : duration.inMilliseconds; final posMs = position.inMilliseconds.clamp(0, maxMs).toDouble(); return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.audiotrack, size: 96, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 24), Text( filename, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), const SizedBox(height: 32), Slider( min: 0, max: maxMs.toDouble(), value: posMs, onChanged: (v) => controller.seekTo(Duration(milliseconds: v.toInt())), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _format(position), style: Theme.of(context).textTheme.bodySmall, ), Text( _format(duration), style: Theme.of(context).textTheme.bodySmall, ), ], ), ), const SizedBox(height: 24), FloatingActionButton( heroTag: 'audioPlayPause', onPressed: () { if (value.isPlaying) { controller.pause(); } else { controller.play(); } }, child: Icon(value.isPlaying ? Icons.pause : Icons.play_arrow), ), ], ), ), ); } }