avatar_kit 1.0.0-beta.8 copy "avatar_kit: ^1.0.0-beta.8" to clipboard
avatar_kit: ^1.0.0-beta.8 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';

// see: https://docs.spatialreal.ai/overview/test-avatars
final String avatarID = '';

void main() {  
  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();
  }

  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;
    });
  }

  @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 {
    await AvatarSDK.initialize(
      appID: 'yourAppID', 
      configuration: Configuration(environment: Environment.intl, drivingServiceMode: drivingServiceMode, logLevel: LogLevel.all)
    );
    
    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://server-sdk-mock-demo.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(),
    );
  }
}
0
likes
0
points
759
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for AvatarKit which provides avatar rendering with audio driving support.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on avatar_kit

Packages that implement avatar_kit