Skip to main content

agenticerp_voice 0.1.0 copy "agenticerp_voice: ^0.1.0" to clipboard
agenticerp_voice: ^0.1.0 copied to clipboard

Voice SDK for the AgenticERP call centre: place phone calls to customers from your Flutter app through your company hotline, with the call audio in the app.

agenticerp_voice #

SDK Flutter để gọi điện cho khách hàng qua tổng đài AgenticERP ngay trong ứng dụng của bạn: nhân viên bấm "Gọi" → khách thấy số hotline của doanh nghiệp → âm thanh đi ngay trong app → cuộc gọi được ghi âm và lưu vào lịch sử tổng đài như mọi cuộc gọi khác.

Thành phần Việc
AerpClient Kết nối tổng đài bằng token: connect(token) · updateToken · sự kiện kết nối
AerpCall Một cuộc gọi: makeCall(AerpCallParams) · hangup · mute · hold · getRecording
AerpAudioManager Loa ngoài / chọn thiết bị âm thanh
AerpResult Kết quả mọi thao tác: status, code, message

English version below.


1. Chuẩn bị (phía tổng đài) #

Quản trị viên tổng đài vào Tổng đài → Cấu hình → SDK gọi điện → Tạo API key và nhận:

  • API Key SID (dạng SK…)
  • API Key Secret — chỉ hiện một lần, lưu vào cấu hình máy chủ của bạn
  • Địa chỉ máy chủ cho SDK (ví dụ https://erp.example.com)

Mỗi nhân viên gọi qua SDK phải là một người dùng tổng đài có quyền gọi ra và ít nhất một đầu số gọi ra. userId trong token được khớp với người dùng theo cách quản trị chọn khi tạo key: email (mặc định), số máy lẻ, hoặc ID người dùng.

2. Máy chủ của bạn ký token #

Token là JWT HS256, ký bằng API Key Secret, do máy chủ của bạn phát cho app. Không bao giờ đưa Secret vào app di động.

Claim Bắt buộc Ý nghĩa
iss ✔ API Key SID
jti ✔ Mã duy nhất của token
exp ✔ Thời điểm hết hạn (giây Unix). Không được dài hơn "Hạn token tối đa" của key (mặc định 24 giờ)
userId ✔ Email / số máy lẻ / ID người dùng của nhân viên (theo cách khớp của key)

Node.js (jsonwebtoken)

const jwt = require('jsonwebtoken');
const crypto = require('crypto');

function voiceToken(userEmail) {
  return jwt.sign(
    { jti: crypto.randomUUID(), userId: userEmail },
    process.env.API_KEY_SECRET,
    { algorithm: 'HS256', issuer: process.env.API_KEY_SID, expiresIn: '1h' },
  );
}

PHP (firebase/php-jwt)

use Firebase\JWT\JWT;

$token = JWT::encode([
    'iss'    => getenv('API_KEY_SID'),
    'jti'    => bin2hex(random_bytes(16)),
    'exp'    => time() + 3600,
    'userId' => $userEmail,
], getenv('API_KEY_SECRET'), 'HS256');

C# (System.IdentityModel.Tokens.Jwt)

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(apiKeySecret));
var token = new JwtSecurityToken(
    issuer: apiKeySid,
    claims: [new Claim("jti", Guid.NewGuid().ToString()), new Claim("userId", userEmail)],
    expires: DateTime.UtcNow.AddHours(1),
    signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
var jwt = new JwtSecurityTokenHandler().WriteToken(token);

Python (PyJWT)

import time, uuid, jwt

token = jwt.encode(
    {"iss": API_KEY_SID, "jti": str(uuid.uuid4()), "exp": int(time.time()) + 3600, "userId": user_email},
    API_KEY_SECRET, algorithm="HS256")

Gợi ý: cấp token hạn 1 giờ, chỉ cho nhân viên đã đăng nhập vào hệ thống của bạn.

3. Cài đặt #

flutter pub add agenticerp_voice

Yêu cầu: Flutter ≥ 3.27 (Dart ≥ 3.6). Android minSdk 21+, iOS 13+.

Android — android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

iOS — ios/Runner/Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Cần micro để nói chuyện với khách hàng qua tổng đài.</string>
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

UIBackgroundModes/audio giữ cuộc gọi khi nhân viên chuyển sang app khác. Trong ios/Podfile đặt platform :ios, '13.0'.

Quyền micro: SDK không tự hỏi quyền — app xin quyền trước khi gọi (ví dụ dùng permission_handler, xem example/).

4. Sử dụng #

Kết nối #

import 'package:agenticerp_voice/agenticerp_voice.dart';

final client = AerpClient(baseUrl: 'https://erp.example.com');

client.events.listen((event) async {
  switch (event) {
    case AerpClientConnected(:final user):
      print('Đã kết nối: ${user.displayName}, máy lẻ ${user.extension}');
    case AerpTokenWillExpire():          // ~60 giây trước khi hết hạn
    case AerpRequestAccessToken():       // phiên đã hết hạn / bị thu hồi
      await client.updateToken(await myServer.fetchVoiceToken());
    case AerpClientDisconnected(:final reason):
      print('Mất kết nối: $reason');
    case AerpClientError(:final code, :final message):
      print('Lỗi $code: $message');
  }
});

final result = await client.connect(await myServer.fetchVoiceToken());
if (!result.status) print('connect thất bại: ${result.code}');

// Các đầu số nhân viên được dùng để gọi ra (khách sẽ thấy số này):
for (final n in client.fromNumbers) print('${n.number} ${n.isDefault ? "(mặc định)" : ""}');

Phiên sống đúng bằng hạn token. Nếu phiên hết hạn giữa cuộc gọi, âm thanh vẫn tiếp tục; chỉ cần updateToken(...) là SDK nối lại việc theo dõi cuộc gọi (và gửi lại lệnh gác máy nếu nhân viên đã gác trong lúc chờ).

Gọi điện #

final call = AerpCall(client);

call.events.listen((event) {
  switch (event) {
    case AerpSignalingStateChanged(:final state, :final reason):
      // calling → ringing → answered → ended (hoặc busy)
      print('Trạng thái: ${state.name} ${reason ?? ""}');
    case AerpMediaStateChanged(:final state):
      print('Âm thanh: ${state.name}');
    case AerpCallInfoUpdated(:final info):
      print('Thời lượng: ${call.talkDuration}');
    case AerpCallError(:final code, :final message):
      print('Lỗi cuộc gọi $code: $message');
  }
});

final result = await call.makeCall(const AerpCallParams(
  to: '0912345678',             // số khách hàng
  from: '02812345678',          // tuỳ chọn — bỏ trống = đầu số mặc định của nhân viên
  customData: {'booking_id': 'BK-1024'},   // mã của bạn, đọc lại ở call.info.customData
));
if (!result.status) print('Không gọi được: ${result.code} — ${result.message}');

await call.mute(true);                  // tắt tiếng
await call.setSpeakerphoneOn(true);     // loa ngoài
await call.hold(true);                  // giữ máy (khách nghe nhạc chờ), chỉ khi đang nói chuyện
await call.hangup();                    // gác máy
await call.destroy();                   // giải phóng khi đóng màn hình

Mỗi AerpCall dùng cho một cuộc gọi; mỗi AerpClient có tối đa một cuộc gọi đang diễn ra. Trong lúc đổ chuông SDK tự phát nhạc chờ (tắt bằng AerpCallOptions(playRingback: false)).

Ghi âm #

final rec = await call.getRecording();   // sau khi cuộc gọi kết thúc
if (rec.available) openUrl(rec.playbackUrl!);   // link ký sẵn, hiệu lực ~10 phút

Tệp ghi âm cần vài giây để tải lên sau khi gác máy; nếu available == false hãy thử lại sau.

5. Trạng thái và mã lỗi #

Trạng thái cuộc gọi (AerpSignalingState): calling (đang quay số) → ringing (máy khách đổ chuông) → answered (khách nghe máy) → ended / busy.

Lý do kết thúc (reason / call.info.endReason): local_hangup (nhân viên gác máy), remote_hangup / normal (khách gác máy), no_answer, busy, unreachable (thuê bao không liên lạc được — nhà mạng phát thông báo), cancelled, media_lost, hoặc mã lỗi của tổng đài.

Mã lỗi thường gặp (AerpResult.code):

Mã Ý nghĩa Cách xử lý
sdk_token_expired Token hết hạn Lấy token mới, gọi updateToken
sdk_token_signature_invalid Sai Secret Kiểm tra API_KEY_SECRET trên máy chủ của bạn
sdk_token_ttl_too_long Hạn token dài hơn mức cho phép của key Giảm exp
sdk_key_invalid / sdk_key_revoked SID sai hoặc key đã tắt Liên hệ quản trị tổng đài
sdk_user_not_found userId không khớp nhân viên nào Kiểm tra email / máy lẻ của nhân viên
forbidden Nhân viên chưa có quyền gọi ra Quản trị cấp quyền Tổng đài
invalid_phone Số điện thoại không hợp lệ Kiểm tra số
dnc_blocked Số nằm trong danh sách không gọi —
call_permission_denied Máy lẻ không được gọi loại số này (quốc tế, 1900…) Quản trị nâng quyền gọi
from_number_not_allowed Đầu số from không thuộc quyền của nhân viên Dùng số trong client.fromNumbers
no_from_number Nhân viên chưa được cấp đầu số gọi ra Quản trị cấp đầu số
call_failed Tổng đài không đặt được cuộc gọi Xem message, thử lại
call_in_progress Đang có cuộc gọi khác Gác cuộc gọi trước
not_connected Chưa connect Gọi connect(token)
network_error / timeout Mất mạng Thử lại

6. App ví dụ #

example/ là app hoàn chỉnh: nhập địa chỉ tổng đài + token, chọn đầu số, gọi, tắt tiếng, loa ngoài, giữ máy, gác máy. Example dùng gói agenticerp_voice trên pub.dev, nên có thể chép riêng thư mục này làm khung cho app của bạn. Chạy: cd example && flutter pub get && flutter run.


English #

agenticerp_voice lets your Flutter app place phone calls to customers through the AgenticERP call centre: the customer sees the company hotline, the call audio runs inside your app, and the call is recorded and logged in the call centre like any other call. Main types: AerpClient (connection + token), AerpCall (one call), AerpAudioManager (loudspeaker / devices) and AerpResult (result of every operation).

1. Get an API key. A call-centre admin opens Call Center → Configuration → Calling SDK → Create API key and gets an API Key SID, an API Key Secret (shown once — store it on your server) and the server address. Each staff member calling through the SDK must be a call-centre user with call-out permission and at least one outbound hotline; the token's userId is matched by email (default), extension number or user id, as chosen on the key.

2. Your server signs tokens. HS256 JWT signed with the Secret, claims iss (SID), jti, exp (not longer than the key's maximum lifetime, 24 h by default) and userId. Samples for Node.js, PHP, C# and Python are in section 2 above. Never ship the Secret in the app.

3. Install. flutter pub add agenticerp_voice, then add the Android permissions and the iOS Info.plist keys from section 3. Flutter ≥ 3.27, Android minSdk 21, iOS 13. Ask for the microphone permission in your app before calling.

4. Use. AerpClient(baseUrl:) → connect(token) → listen to events and call updateToken(newToken) on AerpTokenWillExpire / AerpRequestAccessToken. Then AerpCall(client).makeCall(AerpCallParams(to: ..., from: ..., customData: ...)), follow AerpSignalingStateChanged (calling → ringing → answered → ended/busy), and use mute, setSpeakerphoneOn, hold, hangup, getRecording, destroy. One call per AerpCall, one active call per client. A local ringback tone plays while ringing (disable with AerpCallOptions(playRingback: false)).

5. Errors. Every operation returns AerpResult(status, code, message); the codes are listed in the table in section 5 and in AerpErrorCode.

6. Example. example/ is a complete demo app that depends on the pub.dev release, so you can copy the folder as a starting point (cd example && flutter pub get && flutter run).

1
likes
0
points
130
downloads

Publisher

unverified uploader

Weekly Downloads

Voice SDK for the AgenticERP call centre: place phone calls to customers from your Flutter app through your company hotline, with the call audio in the app.

Homepage

License

unknown (license)

Dependencies

audioplayers, clock, flutter, http, livekit_client, meta

More

Packages that depend on agenticerp_voice