177 lines
4.9 KiB
Dart
177 lines
4.9 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:audioplayers/audioplayers.dart' as ap;
|
|
import 'package:audioplayers/audioplayers.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class AudioPlayer extends StatefulWidget {
|
|
/// Path from where to play recorded audio
|
|
final String source;
|
|
|
|
/// Callback when audio file should be removed
|
|
/// Setting this to null hides the delete button
|
|
final VoidCallback onDelete;
|
|
|
|
const AudioPlayer({
|
|
super.key,
|
|
required this.source,
|
|
required this.onDelete,
|
|
});
|
|
|
|
@override
|
|
AudioPlayerState createState() => AudioPlayerState();
|
|
}
|
|
|
|
class AudioPlayerState extends State<AudioPlayer> {
|
|
static const double _controlSize = 56;
|
|
static const double _deleteBtnSize = 24;
|
|
|
|
final _audioPlayer = ap.AudioPlayer()..setReleaseMode(ReleaseMode.stop);
|
|
late StreamSubscription<void> _playerStateChangedSubscription;
|
|
late StreamSubscription<Duration?> _durationChangedSubscription;
|
|
late StreamSubscription<Duration> _positionChangedSubscription;
|
|
Duration? _position;
|
|
Duration? _duration;
|
|
|
|
@override
|
|
void initState() {
|
|
_playerStateChangedSubscription =
|
|
_audioPlayer.onPlayerComplete.listen((state) async {
|
|
await stop();
|
|
});
|
|
_positionChangedSubscription = _audioPlayer.onPositionChanged.listen(
|
|
(position) => setState(() {
|
|
_position = position;
|
|
}),
|
|
);
|
|
_durationChangedSubscription = _audioPlayer.onDurationChanged.listen(
|
|
(duration) => setState(() {
|
|
_duration = duration;
|
|
}),
|
|
);
|
|
|
|
_audioPlayer.setSource(_source);
|
|
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_playerStateChangedSubscription.cancel();
|
|
_positionChangedSubscription.cancel();
|
|
_durationChangedSubscription.cancel();
|
|
_audioPlayer.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
mainAxisSize: MainAxisSize.max,
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: <Widget>[
|
|
_buildControl(),
|
|
_buildSlider(constraints.maxWidth),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete,
|
|
color: Color(0xFF73748D), size: _deleteBtnSize),
|
|
onPressed: () {
|
|
if (_audioPlayer.state == ap.PlayerState.playing) {
|
|
stop().then((value) => widget.onDelete());
|
|
} else {
|
|
widget.onDelete();
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
Text('${_duration ?? 0.0}'),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildControl() {
|
|
Icon icon;
|
|
Color color;
|
|
|
|
if (_audioPlayer.state == ap.PlayerState.playing) {
|
|
icon = const Icon(Icons.pause, color: Colors.red, size: 30);
|
|
color = Colors.red.withOpacity(0.1);
|
|
} else {
|
|
final theme = Theme.of(context);
|
|
icon = Icon(Icons.play_arrow, color: theme.primaryColor, size: 30);
|
|
color = theme.primaryColor.withOpacity(0.1);
|
|
}
|
|
|
|
return ClipOval(
|
|
child: Material(
|
|
color: color,
|
|
child: InkWell(
|
|
child:
|
|
SizedBox(width: _controlSize, height: _controlSize, child: icon),
|
|
onTap: () {
|
|
if (_audioPlayer.state == ap.PlayerState.playing) {
|
|
pause();
|
|
} else {
|
|
play();
|
|
}
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSlider(double widgetWidth) {
|
|
bool canSetValue = false;
|
|
final duration = _duration;
|
|
final position = _position;
|
|
|
|
if (duration != null && position != null) {
|
|
canSetValue = position.inMilliseconds > 0;
|
|
canSetValue &= position.inMilliseconds < duration.inMilliseconds;
|
|
}
|
|
|
|
double width = widgetWidth - _controlSize - _deleteBtnSize;
|
|
width -= _deleteBtnSize;
|
|
|
|
return SizedBox(
|
|
width: width,
|
|
child: Slider(
|
|
activeColor: Theme.of(context).primaryColor,
|
|
inactiveColor: Theme.of(context).colorScheme.secondary,
|
|
onChanged: (v) {
|
|
if (duration != null) {
|
|
final position = v * duration.inMilliseconds;
|
|
_audioPlayer.seek(Duration(milliseconds: position.round()));
|
|
}
|
|
},
|
|
value: canSetValue && duration != null && position != null
|
|
? position.inMilliseconds / duration.inMilliseconds
|
|
: 0.0,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> play() => _audioPlayer.play(_source);
|
|
|
|
Future<void> pause() async {
|
|
await _audioPlayer.pause();
|
|
setState(() {});
|
|
}
|
|
|
|
Future<void> stop() async {
|
|
await _audioPlayer.stop();
|
|
setState(() {});
|
|
}
|
|
|
|
Source get _source =>
|
|
kIsWeb ? ap.UrlSource(widget.source) : ap.DeviceFileSource(widget.source);
|
|
} |