tccc_user_call_sdk 0.0.3 copy "tccc_user_call_sdk: ^0.0.3" to clipboard
tccc_user_call_sdk: ^0.0.3 copied to clipboard

Tencent Cloud Contact Center (TCCC) Flutter SDK for user-side audio outbound calls, with optional in-app call UI and DTMF.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:tccc_user_call_sdk/tccc_user_call_sdk.dart';
import 'package:tccc_user_call_sdk/ui.dart';

import 'example_auth.dart';

/// Published example. Git checkout internal run:
/// `cd example && flutter run -t lib/dev/internal_main.dart`.
void main() {
  bootstrapExample(publishedExampleAuth);
}

void bootstrapExample(ExampleAuth auth) {
  WidgetsFlutterBinding.ensureInitialized();
  TcccConfig.update(const TcccConfig(locale: Locale('en')));
  final navigatorKey = GlobalKey<NavigatorState>();
  TcccCallUI.install(navigatorKey: navigatorKey);
  runApp(ExampleApp(navigatorKey: navigatorKey, auth: auth));
}

class ExampleApp extends StatelessWidget {
  ExampleApp({
    super.key,
    required this.navigatorKey,
    this.auth = publishedExampleAuth,
  });

  final GlobalKey<NavigatorState> navigatorKey;
  final ExampleAuth auth;

  /// Public SDK facade. Use [TcccUserCall.instance].
  final TcccUserCall userCallInstance = TcccUserCall.instance;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,
      title: 'tccc_user_call_sdk example',
      home: OutboundPage(userCallInstance: userCallInstance, auth: auth),
    );
  }
}

class OutboundPage extends StatefulWidget {
  const OutboundPage({
    super.key,
    required this.userCallInstance,
    required this.auth,
  });

  final TcccUserCall userCallInstance;
  final ExampleAuth auth;

  @override
  State<OutboundPage> createState() => _OutboundPageState();
}

class _OutboundPageState extends State<OutboundPage> {
  late final TextEditingController _sdkAppId;
  late final TextEditingController _userId;
  late final TextEditingController _userSig;
  late final TextEditingController _channelIdController;
  late String _channelId;
  String _status =
      'Tap createUser, then init and wait for Ready, then startAudioCall. Uninit after all calls.';
  bool _busy = false;
  bool _userCreated = false;
  bool _sdkReady = false;
  bool _releaseEnvReady = false;
  StreamSubscription<TcccEvent>? _events;
  StreamSubscription<TcccEvent>? _sessionEvents;

  TcccUserCall get userCallInstance => widget.userCallInstance;

  ExampleAuth get auth => widget.auth;

  bool get _idle => !_busy && userCallInstance.currentCall == null;

  bool get _canCreateUser => _idle && !_userCreated;

  bool get _canInit => _idle && _userCreated;

  bool get _canStart =>
      !_busy && _sdkReady && userCallInstance.currentCall == null;

  bool get _canUninit => _idle && (_userCreated || _sdkReady);

  @override
  void initState() {
    super.initState();
    _sdkAppId = TextEditingController(text: auth.sdkAppId);
    _userId = TextEditingController(text: auth.userId);
    _userSig = TextEditingController();
    _channelId = auth.channelIds.isEmpty ? '' : auth.channelIds.first;
    _channelIdController = TextEditingController(text: _channelId);
    unawaited(userCallInstance.setConsoleEnabled(true));
    // 先订阅 ready,再 init
    _events = userCallInstance.events.listen((event) {
      switch (event.type) {
        case 'ready':
          debugPrint('[tccc_user_call_sdk example] init ready');
          if (!mounted) {
            return;
          }
          setState(() {
            _sdkReady = true;
            _status = 'init ready';
          });
          break;
        case 'error':
          final e = event as TcccErrorEvent;
          debugPrint(
            '[tccc_user_call_sdk example] error ${e.serverType} ${e.failure.code.toInt()} ${e.failure.message}',
          );
          break;
        case 'connecting':
          final e = event as TcccConnectingEvent;
          debugPrint(
            '[tccc_user_call_sdk example] connecting ${e.serverType} attempts=${e.attempts}',
          );
          break;
        case 'connected':
          final e = event as TcccConnectedEvent;
          debugPrint('[tccc_user_call_sdk example] connected ${e.serverType}');
          break;
        case 'disconnected':
          final e = event as TcccDisconnectedEvent;
          debugPrint(
            '[tccc_user_call_sdk example] disconnected ${e.serverType} error=${e.error} code=${e.code} reason=${e.reason}',
          );
          break;
        default:
          debugPrint('[tccc_user_call_sdk example] ${event.type}');
          break;
      }
    });
  }

  @override
  void dispose() {
    _sessionEvents?.cancel();
    _events?.cancel();
    unawaited(userCallInstance.unInit());
    _sdkAppId.dispose();
    _userId.dispose();
    _userSig.dispose();
    _channelIdController.dispose();
    super.dispose();
  }

  /// Android demo uses the production visitor API, not `setDebugMode`.
  /// Clear a leftover debug override from a previous hot-reload session.
  Future<void> _ensureReleaseEnv() async {
    if (_releaseEnvReady) {
      return;
    }
    final result =
        await userCallInstance.callExperimentalAPI('setReleaseMode', '{}');
    if (!result.success) {
      throw StateError(
        'setReleaseMode failed ${result.code.toInt()} ${result.message}',
      );
    }
    _releaseEnvReady = true;
  }

  /// Binds identity only. Does not connect; tap init next.
  Future<void> _createUser() async {
    setState(() => _busy = true);
    try {
      await _ensureReleaseEnv();
      final sdkAppId = int.tryParse(_sdkAppId.text.trim()) ?? 0;
      final userId = _userId.text.trim();
      if (sdkAppId <= 0) {
        if (mounted) {
          setState(() => _status = 'createUser failed: sdkAppId is empty');
        }
        return;
      }
      if (userId.isEmpty) {
        if (mounted) {
          setState(() => _status = 'createUser failed: userId is empty');
        }
        return;
      }
      final userSig = auth.fetchesUserSig
          ? await auth.fetchUserSig!(userId)
          : _userSig.text.trim();
      if (userSig.isEmpty) {
        if (mounted) {
          setState(() => _status = 'createUser failed: userSig is empty');
        }
        return;
      }
      final created = userCallInstance.createUser(
        sdkAppId: sdkAppId,
        userId: userId,
        userSig: userSig,
        userClientData: auth.clientData,
      );
      if (!mounted) {
        return;
      }
      if (!created.success) {
        if (created.code == ErrorCode.User.instanceExists) {
          setState(() {
            _userCreated = true;
            _status =
                'createUser ${created.code.toInt()}: identity already bound. Tap init, or unInit to change user.';
          });
          return;
        }
        setState(() {
          _userCreated = false;
          _status =
              'createUser failed ${created.code.toInt()} ${created.message}';
        });
        return;
      }
      setState(() {
        _userCreated = true;
        _status = 'createUser ok; tap init';
      });
    } catch (error) {
      if (mounted) {
        setState(() => _status = 'createUser failed $error');
      }
    } finally {
      if (mounted) {
        setState(() => _busy = false);
      }
    }
  }

  Future<void> _init() async {
    setState(() => _busy = true);
    try {
      await _ensureReleaseEnv();
      final status = await userCallInstance.init();
      if (!mounted) {
        return;
      }
      if (!status.success) {
        setState(() {
          _sdkReady = false;
          _status = 'init failed ${status.code.toInt()} ${status.message}';
        });
        return;
      }
      setState(() {
        _sdkReady = true;
        if (!_status.startsWith('init ready')) {
          _status = 'init succeeded';
        }
      });
    } catch (error) {
      if (mounted) {
        setState(() => _status = 'init failed $error');
      }
    } finally {
      if (mounted) {
        setState(() => _busy = false);
      }
    }
  }

  Future<void> _startAudioCall() async {
    setState(() => _busy = true);
    try {
      final channelId =
          auth.fetchesUserSig ? _channelId : _channelIdController.text.trim();
      if (channelId.isEmpty) {
        if (mounted) {
          setState(() => _status = 'start failed: channelId is empty');
        }
        return;
      }
      final started = await userCallInstance.startAudioCall(
        channelId,
        showUI: true,
      );
      if (!started.success) {
        if (mounted) {
          setState(() {
            _status = 'start failed ${started.code.toInt()} ${started.message}';
          });
        }
        return;
      }
      final session = started.data!;
      await _sessionEvents?.cancel();
      _sessionEvents = session.events.listen((event) {
        if (event.type == 'ended') {
          debugPrint('[tccc_user_call_sdk example] session ended');
        }
      });
      if (mounted) {
        setState(() => _status = 'calling ${session.callId}');
      }
      final result = await session.ended;
      if (mounted) {
        final failure = result.failure;
        setState(() {
          _status = failure == null
              ? 'ended ${result.cause.name}'
              : 'ended ${result.cause.name} ${failure.code.toInt()} ${failure.message}';
        });
      }
    } catch (error) {
      if (mounted) {
        setState(() => _status = 'call failed $error');
      }
    } finally {
      if (mounted) {
        setState(() => _busy = false);
      }
    }
  }

  Future<void> _unInit() async {
    setState(() => _busy = true);
    try {
      final status = await userCallInstance.unInit();
      if (!mounted) {
        return;
      }
      if (!status.success) {
        setState(() {
          _status = 'unInit failed ${status.code.toInt()} ${status.message}';
        });
        return;
      }
      setState(() {
        _userCreated = false;
        _sdkReady = false;
        _status = 'unInited';
      });
    } catch (error) {
      if (mounted) {
        setState(() => _status = 'unInit failed $error');
      }
    } finally {
      if (mounted) {
        setState(() => _busy = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('TCCC outbound')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          TextField(
            controller: _sdkAppId,
            enabled: _canCreateUser,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(labelText: 'sdkAppId'),
          ),
          TextField(
            controller: _userId,
            enabled: _canCreateUser,
            decoration: const InputDecoration(labelText: 'userId'),
          ),
          if (!auth.fetchesUserSig)
            TextField(
              controller: _userSig,
              enabled: _canCreateUser,
              decoration: const InputDecoration(labelText: 'userSig'),
            ),
          if (auth.fetchesUserSig)
            DropdownButtonFormField<String>(
              // Flutter 3.7 exposes `value`; `initialValue` exists only from 3.33.
              // ignore: deprecated_member_use
              value: _channelId,
              isExpanded: true,
              decoration: const InputDecoration(labelText: 'channelId'),
              items: [
                for (var i = 0; i < auth.channelIds.length; i++)
                  DropdownMenuItem(
                    value: auth.channelIds[i],
                    child: Text('Audio ${i + 1}  ${auth.channelIds[i]}'),
                  ),
              ],
              onChanged: _busy
                  ? null
                  : (value) {
                      if (value != null) {
                        setState(() => _channelId = value);
                      }
                    },
            )
          else
            TextField(
              controller: _channelIdController,
              decoration: const InputDecoration(labelText: 'channelId'),
            ),
          const SizedBox(height: 12),
          Text(_status),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: _canCreateUser ? _createUser : null,
            child: const Text('createUser'),
          ),
          const SizedBox(height: 8),
          FilledButton(
            onPressed: _canInit ? _init : null,
            child: const Text('init'),
          ),
          const SizedBox(height: 8),
          FilledButton(
            onPressed: _canStart ? _startAudioCall : null,
            child: const Text('startAudioCall'),
          ),
          const SizedBox(height: 8),
          OutlinedButton(
            onPressed: _canUninit ? _unInit : null,
            child: const Text('unInit'),
          ),
        ],
      ),
    );
  }
}
1
likes
115
points
194
downloads

Documentation

Documentation
API reference

Publisher

verified publishertrtc.io

Weekly Downloads

Tencent Cloud Contact Center (TCCC) Flutter SDK for user-side audio outbound calls, with optional in-app call UI and DTMF.

Homepage
View/report issues

Topics

#audio #voip #sip #tencent-cloud

License

unknown (license)

Dependencies

connectivity_plus, dio, flutter, flutter_localizations, intl, logger, path, path_provider, tencent_rtc_sdk

More

Packages that depend on tccc_user_call_sdk

Packages that implement tccc_user_call_sdk