avatar_kit 1.0.0-beta.6
avatar_kit: ^1.0.0-beta.6 copied to clipboard
A Flutter plugin for AvatarKit which provides avatar rendering with audio driving support.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart' hide ConnectionState;
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:avatar_kit/avatar_kit_plugin.dart';
final String avatarID = 'e41f7ee0-3807-4956-b169-1becf8497ebc';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AvatarSDK.initialize(
appID: 'yourAppID',
configuration: Configuration(environment: Environment.test, drivingServiceMode: DrivingServiceMode.sdk, logLevel: LogLevel.all)
);
runApp(const AvatarKitApp());
}
class AvatarKitApp extends StatefulWidget {
const AvatarKitApp({super.key});
@override
State<AvatarKitApp> createState() => _AvatarKitAppState();
}
class _AvatarKitAppState extends State<AvatarKitApp> {
String version = '';
bool supportsCurrentDevice = false;
bool isInitializing = false;
@override
void initState() {
super.initState();
_initialize();
_preloadAvatar();
}
void _initialize() async {
setState(() {
isInitializing = true;
});
final version = await AvatarSDK.version();
final supportsCurrentDevice = await AvatarSDK.supportsCurrentDevice();
setState(() {
this.version = version;
this.supportsCurrentDevice = supportsCurrentDevice;
isInitializing = false;
});
}
void _preloadAvatar() async {
try {
// Supports ongoing load tasks.
final avatar = await AvatarManager.shared.retrieve(id: avatarID);
if (avatar == null) {
debugPrint('preload avatar started');
await AvatarManager.shared.load(id: avatarID, onProgress: (progress) {
debugPrint('preload avatar progress: $progress');
});
}
debugPrint('preload avatar completed');
} on AvatarError catch (e) {
debugPrint('preload avatar error: ${e.name}');
} catch (e) {
debugPrint('preload avatar failed: $e');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(
version: version,
supportsCurrentDevice: supportsCurrentDevice,
isInitializing: isInitializing,
),
);
}
}
class HomePage extends StatelessWidget {
final String version;
final bool supportsCurrentDevice;
final bool isInitializing;
const HomePage({
super.key,
required this.version,
required this.supportsCurrentDevice,
required this.isInitializing,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: SafeArea(
child: Center(
child: isInitializing ? const CircularProgressIndicator() :
Column(
spacing: 6,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Text('Select Driving Service Mode'),
ElevatedButton(onPressed: () {
_onSelectDrivingServiceMode(context, DrivingServiceMode.sdk);
}, child: const Text('SDK')),
ElevatedButton(onPressed: () {
_onSelectDrivingServiceMode(context, DrivingServiceMode.host);
}, child: const Text('Host')),
Spacer(),
Text('AvatarKit Version $version', style: TextStyle(color: Colors.grey)),
Text('Supports Current Device ${supportsCurrentDevice ? '✅' : '❌'}', style: TextStyle(color: Colors.grey)),
],
),
),
),
);
}
void _onSelectDrivingServiceMode(BuildContext context, DrivingServiceMode drivingServiceMode) async {
if (drivingServiceMode == DrivingServiceMode.sdk) {
await AvatarSDK.setSessionToken('');
}
await AvatarSDK.setUserID('');
if (context.mounted) {
if (drivingServiceMode == DrivingServiceMode.sdk) {
Navigator.push(context, MaterialPageRoute(builder: (_) => SDKDrivingServiceModePage()));
} else {
Navigator.push(context, MaterialPageRoute(builder: (_) => HostDrivingServiceModePage()));
}
}
}
}
class SDKDrivingServiceModePage extends StatefulWidget {
const SDKDrivingServiceModePage({super.key});
@override
State<SDKDrivingServiceModePage> createState() => _SDKDrivingServiceModePageState();
}
class _SDKDrivingServiceModePageState extends State<SDKDrivingServiceModePage> {
bool _isLoading = false;
ConnectionState _connectionState = ConnectionState.disconnected;
ConversationState _conversationState = ConversationState.idle;
Avatar? _avatar;
AvatarController? _avatarController;
void _loadAvatar() async {
try {
setState(() {
_isLoading = true;
});
Avatar? avatar = await AvatarManager.shared.retrieve(id: avatarID);
if (avatar != null) {
debugPrint('retrieve cached avatar.');
} else {
avatar = await AvatarManager.shared.load(id: avatarID, onProgress: (progress) {
debugPrint('load avatar progress: $progress');
});
debugPrint('load avatar succeeded');
}
setState(() {
_avatar = avatar;
});
} on AvatarError catch (e) {
debugPrint('load avatar error: ${e.name}');
} catch (e) {
debugPrint('load avatar failed: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _onPlatformViewCreated(AvatarController controller) async {
controller.onConnectionState = (state, errorMessage) {
debugPrint('onConnectionState: ${state.name}. ${errorMessage != null ? 'error: $errorMessage' : ''}');
setState(() {
_connectionState = state;
});
};
controller.onConversationState = (state) {
debugPrint('onConversationState: ${state.name}');
setState(() {
_conversationState = state;
});
};
controller.onError = (error) {
debugPrint('onError: ${error.name}');
};
final backgroundImage = await rootBundle.load('assets/background.jpeg').then((value) => value.buffer.asUint8List());
await controller.setBackgroundImage(backgroundImage);
await controller.setVolume(1.0);
await controller.setOpaque(false);
setState(() {
_avatarController = controller;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('SDK Driving Service Mode'),
),
body: SafeArea(
child: Center(
child: _isLoading
? const CircularProgressIndicator()
: _avatar == null
? ElevatedButton(
onPressed: _loadAvatar,
child: const Text('Load Avatar'),
)
: _buildAvatar(),
),
),
),
);
}
Widget _buildAvatar() {
if (_avatar == null) {
return const CircularProgressIndicator();
}
return Column(
spacing: 6,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: SizedBox(width: 260, height: 260, child: AvatarWidget(
avatar: _avatar!,
onPlatformViewCreated: _onPlatformViewCreated,
)),
),
ElevatedButton(onPressed: () {
_avatarController?.start();
}, child: const Text('Start')),
ElevatedButton(onPressed: () async {
final audioData = await rootBundle.load('assets/audio_01.pcm').then((value) => value.buffer.asUint8List());
final conversationID = await _avatarController?.send(audioData, end: false);
debugPrint('send audio 01, conversationID: $conversationID');
}, child: const Text('Send Audio 01')),
ElevatedButton(onPressed: () async {
final audioData = await rootBundle.load('assets/audio_02.pcm').then((value) => value.buffer.asUint8List());
final conversationID = await _avatarController?.send(audioData, end: false);
debugPrint('send audio 02, conversationID: $conversationID');
}, child: const Text('Send Audio 02')),
ElevatedButton(onPressed: () async {
final audioData = await rootBundle.load('assets/audio_03.pcm').then((value) => value.buffer.asUint8List());
final conversationID = await _avatarController?.send(audioData, end: true);
debugPrint('send audio 03, conversationID: $conversationID');
}, child: const Text('Send Audio 03')),
ElevatedButton(onPressed: () {
_avatarController?.close();
}, child: const Text('Close')),
ElevatedButton(onPressed: () {
_avatarController?.interrupt();
}, child: const Text('Interrupt')),
Spacer(),
Text('Connection State: ${_connectionState.name}'),
Text('Conversation State: ${_conversationState.name}'),
],
);
}
}
class HostDrivingServiceModePage extends StatefulWidget {
const HostDrivingServiceModePage({super.key});
@override
State<HostDrivingServiceModePage> createState() => _HostDrivingServiceModePageState();
}
class _HostDrivingServiceModePageState extends State<HostDrivingServiceModePage> {
bool _isLoading = false;
ConversationState _conversationState = ConversationState.idle;
Avatar? _avatar;
AvatarController? _avatarController;
_MockData? _mockData;
void _loadAvatar() async {
try {
setState(() {
_isLoading = true;
});
Avatar? avatar = await AvatarManager.shared.retrieve(id: avatarID);
if (avatar != null) {
debugPrint('retrieve cached avatar.');
} else {
avatar = await AvatarManager.shared.load(id: avatarID, onProgress: (progress) {
debugPrint('load avatar progress: $progress');
});
debugPrint('load avatar succeeded');
}
setState(() {
_avatar = avatar;
});
} on AvatarError catch (e) {
debugPrint('load avatar error: ${e.name}');
} catch (e) {
debugPrint('load avatar failed: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _onPlatformViewCreated(AvatarController controller) async {
controller.onConversationState = (state) {
debugPrint('onConversationState: ${state.name}');
setState(() {
_conversationState = state;
});
};
controller.onError = (error) {
debugPrint('onError: ${error.name}');
};
final backgroundImage = await rootBundle.load('assets/background.jpeg').then((value) => value.buffer.asUint8List());
await controller.setBackgroundImage(backgroundImage);
await controller.setVolume(1.0);
await controller.setOpaque(false);
setState(() {
_avatarController = controller;
});
}
Future<void> _loadMockData() async {
try {
final response = await http.get(
Uri.parse('https://avatar-sdk-go-test.spatialwalk.cn/media'),
);
if (response.statusCode == 200) {
final data = _MockData.fromJson(jsonDecode(response.body));
setState(() {
_mockData = data;
});
}
} catch (e) {
debugPrint('Fetching data failed: $e');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Host Driving Service Mode')),
body: SafeArea(
child: Center(
child: _isLoading
? const CircularProgressIndicator()
: _avatar == null
? ElevatedButton(onPressed: _loadAvatar, child: const Text('Load Avatar'))
: _buildAvatar(),
),
),
),
);
}
Widget _buildAvatar() {
if (_avatar == null) {
return const CircularProgressIndicator();
}
return Column(
spacing: 6,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: SizedBox(width: 260, height: 260, child: AvatarWidget(
avatar: _avatar!,
onPlatformViewCreated: _onPlatformViewCreated,
)),
),
Spacer(),
ElevatedButton(onPressed: () {
_loadMockData();
}, child: const Text('Load Mock Data')),
ElevatedButton(onPressed: () async {
if (_mockData == null || _avatarController == null) {
return;
}
final audioData = _mockData!.audio;
final animations = _mockData!.animations;
// Simulates the stream playing of the audio and animations.
final conversationID = await _avatarController!.yieldAudioData(audioData, end: true);
for (var i = 0; i < animations.length; i++) {
final animation = animations[i];
Future.delayed(Duration(seconds: i + 1), () {
_avatarController?.yieldAnimations([animation], conversationID: conversationID);
});
}
}, child: const Text('Play Stream')),
ElevatedButton(onPressed: () async {
if (_mockData == null || _avatarController == null) {
return;
}
final audioData = _mockData!.audio;
final animations = _mockData!.animations;
// Simulates the playback of the audio and animations.
final conversationID = await _avatarController!.yieldAudioData(audioData, end: true);
_avatarController!.yieldAnimations(animations, conversationID: conversationID);
}, child: const Text('Playback')),
ElevatedButton(onPressed: () {
_avatarController?.interrupt();
}, child: const Text('Interrupt')),
Spacer(),
Text('Mock Data Loaded: ${_mockData != null ? '✅' : '❌'}'),
Text('Conversation State: ${_conversationState.name}'),
],
);
}
}
class _MockData {
final Uint8List audio;
final List<Uint8List> animations;
_MockData({required this.audio, required this.animations});
factory _MockData.fromJson(Map<String, dynamic> json) {
return _MockData(
audio: base64Decode(json['audio']),
animations: (json['animations'] as List<dynamic>)
.map<Uint8List>((animation) => base64Decode(animation as String))
.toList(),
);
}
}