Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion examples/flyer_chat/lib/local.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flutter_link_previewer/flutter_link_previewer.dart';
import 'package:flyer_chat_audio_message/flyer_chat_audio_message.dart';
import 'package:flyer_chat_file_message/flyer_chat_file_message.dart';
import 'package:flyer_chat_image_message/flyer_chat_image_message.dart';
import 'package:flyer_chat_system_message/flyer_chat_system_message.dart';
Expand All @@ -28,8 +29,12 @@ class Local extends StatefulWidget {
LocalState createState() => LocalState();
}

class LocalChatController extends InMemoryChatController with AudioMessageBinding {

}

class LocalState extends State<Local> {
final _chatController = InMemoryChatController();
final _chatController = LocalChatController();
final _uuid = const Uuid();

final _currentUser = const User(
Expand All @@ -46,6 +51,11 @@ class LocalState extends State<Local> {

bool _isTyping = false;

@override
void initState() {
super.initState();
}

@override
void dispose() {
_chatController.dispose();
Expand Down Expand Up @@ -125,6 +135,42 @@ class LocalState extends State<Local> {
onPressed: () => _chatController.setMessages([]),
destructive: true,
),
ComposerActionButton(
icon: Icons.spatial_audio_off,
title: 'Send audio from current',
onPressed: () {
const source = 'https://cdn.pixabay.com/download/audio/2025/05/20/audio_baf0cfbdf7.mp3?filename=news-intro-344332.mp3';
final audioMessage = AudioMessage(
// Better to use UUID or similar for the ID - IDs must be unique
id: '${Random().nextInt(1000) + 1}',
authorId: _currentUser.id,
createdAt: DateTime.now().toUtc(),
source: source,
size: 510,
duration: Duration(seconds: 8),
);
_chatController.insertMessage(audioMessage);
},
destructive: true,
),
ComposerActionButton(
icon: Icons.spatial_audio_off,
title: 'Send audio from other',
onPressed: () {
const source = 'https://cdn.pixabay.com/download/audio/2025/05/20/audio_baf0cfbdf7.mp3?filename=news-intro-344332.mp3';
final audioMessage = AudioMessage(
// Better to use UUID or similar for the ID - IDs must be unique
id: '${Random().nextInt(1000) + 1}',
authorId: 'user-1',
createdAt: DateTime.now().toUtc(),
source: source,
size: 510,
duration: Duration(seconds: 8),
);
_chatController.insertMessage(audioMessage);
},
destructive: true,
),
],
),
),
Expand Down Expand Up @@ -155,6 +201,14 @@ class LocalState extends State<Local> {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatTextMessage(message: message, index: index),
audioMessageBuilder:
(
context,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatAudioMessage(message: message, waveColor: isSentByMe ? Colors.white : Colors.black,),
fileMessageBuilder:
(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

#include "generated_plugin_registrant.h"

#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
Expand Down
1 change: 1 addition & 0 deletions examples/flyer_chat/linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
file_selector_linux
url_launcher_linux
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import FlutterMacOS
import Foundation

import audioplayers_darwin
import file_picker
import file_selector_macos
import path_provider_foundation
import url_launcher_macos

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
Expand Down
2 changes: 2 additions & 0 deletions examples/flyer_chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ dependencies:
flyer_chat_system_message: ^2.2.0
flyer_chat_text_message: ^2.6.0
flyer_chat_text_stream_message: ^2.3.0
flyer_chat_audio_message:
path: ../../packages/flyer_chat_audio_message
google_generative_ai: ^0.4.7
hive_ce: ^2.15.1
hive_ce_flutter: ^2.3.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

#include "generated_plugin_registrant.h"

#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <url_launcher_windows/url_launcher_windows.h>

void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
UrlLauncherWindowsRegisterWithRegistrar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
file_selector_windows
url_launcher_windows
)
Expand Down
68 changes: 68 additions & 0 deletions packages/flyer_chat_audio_message/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
```dart
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flyer_chat_audio_message/flyer_chat_audio_message.dart';

/// Make your [ChatController] to with [AudioMessageBinding]
class LocalChatController extends InMemoryChatController with AudioMessageBinding {

}

class Basic extends StatefulWidget {
const Basic({super.key});

@override
BasicState createState() => BasicState();
}

class BasicState extends State<Basic> {

final _chatController = LocalChatController();

@override
void dispose() {
_chatController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Chat(
builders: Builders(
audioMessageBuilder: (
context,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatAudioMessage(message: message, waveColor: isSentByMe ? Colors.white : Colors.black,),
),
chatController: _chatController,
currentUserId: 'user1',
onAttachmentTap: () {
const source = 'https://cdn.pixabay.com/download/audio/2025/05/20/audio_baf0cfbdf7.mp3?filename=news-intro-344332.mp3';
final audioMessage = AudioMessage(
// Better to use UUID or similar for the ID - IDs must be unique
id: '${Random().nextInt(1000) + 1}',
authorId: 'user-2',
createdAt: DateTime.now().toUtc(),
source: source,
size: 510,
duration: Duration(seconds: 8),
);
_chatController.insertMessage(audioMessage);
},
onMessageSend: (text) {},
resolveUser: (UserID id) async {
return User(id: id, name: 'John Doe');
},
),
);
}
}

```
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
library;

export 'src/audio_message_binding.dart';
export 'src/flyer_chat_audio_message.dart';
115 changes: 115 additions & 0 deletions packages/flyer_chat_audio_message/lib/src/audio_message_binding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'utils_repo.dart';

Source _getAudioSource(String audioUri) => isNetworkSource(audioUri) ? UrlSource(audioUri) : DeviceFileSource(audioUri);

/// The audio source model to related [AudioMessage] with messageId
class AudioMessageSourceModel {

/// The audio related msg's id
final String messageId;

/// Audio source
final Source audioSource;

AudioMessageSourceModel({required this.messageId, required this.audioSource});

factory AudioMessageSourceModel.fromMsg(AudioMessage msg) {
final audioSource = _getAudioSource(msg.source);
return AudioMessageSourceModel(messageId: msg.id, audioSource: audioSource);
}

}

/// AudioMessage's event baseclass
/// * All event will relate with message through [messageId]
sealed class AudioMessageEvent {

final String messageId;

AudioMessageEvent({required this.messageId});
}

/// Audio player state event.
class AudioPlayerStateEvent extends AudioMessageEvent {

/// The state of [AudioPlayer]
final PlayerState state;

AudioPlayerStateEvent({required super.messageId, required this.state});

}

/// A binding-class that help handle [AudioMessage]
mixin AudioMessageBinding on ChatController {

/// Dispatch the [AudioMessageBinding]'s event
/// * also see [AudioMessageEvent]
final _msgAudioEventStreamController = StreamController<AudioMessageEvent>.broadcast();

/// Listen [AudioMessageBinding]'s event by this stream.
/// * also see [AudioMessageEvent]
Stream<AudioMessageEvent> get msgAudioEventStream => _msgAudioEventStreamController.stream;

/// For play audio from [AudioMessage]
late final AudioPlayer _audioPlayer = AudioPlayer()..onPlayerStateChanged.listen((state) {
final msgId = _currentPlayingSourceModel?.messageId ?? '';
if(msgId.isNotEmpty) {
_msgAudioEventStreamController.add(AudioPlayerStateEvent(messageId: msgId, state: state));
}
if(state == PlayerState.completed || state == PlayerState.stopped) {
_currentPlayingSourceModel = null;
}
});

bool get isPlaying => _audioPlayer.state == PlayerState.playing;

/// The message that current handling.
AudioMessageSourceModel? _currentPlayingSourceModel;
AudioMessageSourceModel? get currentPlayingSourceModel => _currentPlayingSourceModel;

bool isHandleCurrentMsg(AudioMessage msg) => currentPlayingSourceModel?.messageId == msg.id;

/// Play a audio message
/// * Only play one audio message in one time. It will stop play first when some other message are playing.
/// * Other params see [AudioPlayer]
Future<void> playAudioMsg(AudioMessage msg, {
double? volume,
double? balance,
AudioContext? ctx,
Duration? position,
PlayerMode? mode,
}) async {
if(_currentPlayingSourceModel != null) {
await stopAudioMsg();
}
_currentPlayingSourceModel = AudioMessageSourceModel.fromMsg(msg);
await _audioPlayer.play(_currentPlayingSourceModel!.audioSource,
volume: volume, balance: balance, ctx: ctx, position: position, mode: mode);
}

/// Stop current played message
Future<void> stopAudioMsg() async {
await _audioPlayer.stop();
_currentPlayingSourceModel = null;
}

@override
Future<void> dispose() async {
await _audioPlayer.dispose();
await _msgAudioEventStreamController.close();
super.dispose();
}

}







Loading