I have a chat bubble widget which has a audio player support with a Slider
widget.
The slider's value is changed according to the AudioPlayer's progress which seems to work fine.
When the first audio is played completely (meaning the slider's value is now 100%), & now the second chat bubble is added to the AnimatedList
then the newest Slider has the value of 100 & the previous has the value of 0.
Here's an example to understand better:
Message 1 added to list: Audio Played completed => Slider value is 100.
Message 2 added to list: Slider value is 100 (should be 0) & the slider from message 1 has value of 0.
Here's the widget:
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
class MessageBubbleAudioPlayer extends StatefulWidget {
final Color color;
final String audioUrl;
const MessageBubbleAudioPlayer({
@required this.audioUrl,
@required this.color,
});
@override
_MessageBubbleAudioPlayerState createState() =>
_MessageBubbleAudioPlayerState();
}
class _MessageBubbleAudioPlayerState extends State<MessageBubbleAudioPlayer> {
bool loading = false;
bool isPlaying = false;
double audioSeekValue = 0;
final AudioPlayer audioPlayer = AudioPlayer();
Duration totalDuration = Duration(milliseconds: 0);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
audioPlayer.onPlayerStateChanged.listen((event) {
if (mounted) setState(() => isPlaying = event == PlayerState.PLAYING);
});
audioPlayer.onAudioPositionChanged.listen((event) {
final percent =
((event.inMilliseconds * 100) / totalDuration.inMilliseconds) ?? 0;
if (mounted) setState(() => audioSeekValue = percent);
});
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
loading
? Container(
height: 30,
width: 30,
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator(
color: widget.color,
strokeWidth: 1.8,
),
)
: Container(
width: 30,
child: IconButton(
icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow,
color: widget.color),
onPressed: () async {
if (audioPlayer.state == PlayerState.PAUSED) {
audioPlayer.resume();
return;
}
if (!isPlaying) {
setState(() => loading = true);
await audioPlayer.play(widget.audioUrl);
audioPlayer.getDuration().then((value) {
totalDuration = Duration(milliseconds: value);
setState(() => loading = false);
});
} else
await audioPlayer.pause();
},
splashRadius: 25,
),
),
SliderTheme(
data: SliderThemeData(
trackHeight: 1.4,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7)),
child: Slider(
label: "Audio",
activeColor: widget.color,
inactiveColor: widget.color.withAlpha(100),
// this (value) should be 0 for a newly added widget
// but is 100 for the newer one & 0 for the previous one,
// which infact should be opposite
value: audioSeekValue,
min: 0,
max: 100,
onChanged: (_) {},
),
)
],
);
}
}
This widget is in turn used in another widget which handles the type of message & shows appropriate ui.
Here it is:
class MessageBubble extends StatelessWidget {
final bool isSender, isAudio;
final String message;
const MessageBubble(this.message, this.isSender, this.isAudio, Key key)
: super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 3),
child: Align(
alignment: isSender ? Alignment.centerRight : Alignment.centerLeft,
child: message.contains(Constants.emojiRegex, 0) &&
!message.contains(Constants.alphaNumericRegex)
? Padding(
padding: EdgeInsets.only(
top: 6,
bottom: 6,
left: isSender ? 16 : 0,
right: isSender ? 0 : 32),
child: Text(message,
style: TextStyle(fontSize: 45, color: Colors.white)),
)
: Material(
borderRadius: BorderRadius.circular(30),
elevation: 4,
color: isSender
? Colors.deepPurpleAccent.shade100.darken()
: Colors.white,
child: isAudio
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 6),
child: MessageBubbleAudioPlayer(
key: ValueKey(message.hashCode.toString()),
audioUrl: message,
color: isSender
? Colors.white
: Colors.deepPurpleAccent,
),
)
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
child: Linkify(
onOpen: (link) async {
if ((await canLaunch(link.url)))
await launch(link.url);
},
options: LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: isSender
? Colors.white
: Colors.deepPurpleAccent),
text: message,
style: TextStyle(
fontSize: 17,
color: isSender ? Colors.white : Colors.black),
),
),
)),
);
}
}
And here's the AnimatedList
:
class ChatAnimatedList extends StatefulWidget {
final bool isInfoShown, isSender;
const ChatAnimatedList(
{@required Key key, @required this.isInfoShown, this.isSender})
: super(key: key);
@override
ChatAnimatedListState createState() => ChatAnimatedListState();
}
class ChatAnimatedListState extends State<ChatAnimatedList> {
final _messageList = <MessageBubble>[];
final _animatedListState = GlobalKey<AnimatedListState>();
get messageLength => _messageList.length;
insertMessageBubble(MessageBubble messageBubble) =>
_messageList.insert(0, messageBubble);
insertViaState() {
if (_animatedListState.currentState != null)
_animatedListState.currentState.insertItem(0);
}
@override
Widget build(BuildContext context) {
return widget.isInfoShown
? InfoPlaceholder(isSender: widget.isSender)
: Expanded(
child: AnimatedList(
reverse: true,
key: _animatedListState,
initialItemCount: _messageList.length,
itemBuilder: (_, index, animation) {
return index == 0
? Padding(
padding: const EdgeInsets.only(bottom: 6),
child: _messageList[index])
: index == _messageList.length - 1
? Padding(
padding: const EdgeInsets.only(top: 30),
child: _messageList[index])
: _messageList[index];
}),
);
}
}
I've also tried using AutomaticKeepAliveClientMixin
but still no use.
Any thoughts on this would be appreciated.
When your audio play is completed make audioSeekValue = 0 . This will start from the begening.
If you want to keep track : Song 1 played = 70% Song 2 played = 50%
In this case, you have to either keep your index song played valued in a list or get the song played value dynamically from the backend.
Please let me know if it helps.