أدلة APIمرجع أحداث Socket.IO

مرجع أحداث Realtime STT

أحداث Socket.IO للنسخ الفوري وتمييز المتحدثين.

يصف هذا المرجع أحداث Socket.IO التي تستقبل مقاطع صوت PCM16 عبر audio_stream وdiarization_stream وتعيد نتائج النسخ أو تمييز المتحدثين. أسماء الأحداث والحقول الثنائية تبقى كما هي في البروتوكول.

الاتصال

  • المضيف الإنتاجي: https://api.voice.humain.com
  • المسار: /socket.io
  • النقل: websocket فقط
  • المصادقة: ترويسة x-api-key
  • ترويسة Origin: مطلوبة على الإنتاج؛ اضبطها على https://api.voice.humain.com
import { io } from "socket.io-client";

const socket = io("https://api.voice.humain.com", {
  path: "/socket.io",
  transports: ["websocket"],
  extraHeaders: {
    "x-api-key": process.env.API_KEY!,
    Origin: "https://api.voice.humain.com",
  },
});

اللغات

القيمةالمفتاحالمعنى
0arالعربية
1enالإنجليزية
2codeswitchتبديل عربي-إنجليزي
255autoتلقائي؛ يُحل إلى الافتراضي المضبوط للبيئة

الحدود

تنطبق أربعة حدود على الجلسة الفورية. وكلها شاملة: النجاح عند الحد بالضبط، والفشل عند تجاوزه فقط.

لكل حدث: 16 MiB. يجب ألا يتجاوز حدث audio_stream أو diarization_stream واحد 16777216 بايت، محتسبةً ترويسة 18 بايتًا. وتجاوزه يصدر PAYLOAD_TOO_LARGE مع data.bound بقيمة realtime_asr_frame_bytes أو realtime_diarization_frame_bytes، ثم يغلق الاتصال. وهذا مطابق للسقف الذي تفرضه مسارات HTTP المكافئة أصلًا.

لكل جلسة، إجمالي المحتوى الصوتي: 14400 ثانية (4 ساعات). وهو الصوت المقبول المتراكم، وهو كمية مختلفة عن المدة التي بقيت فيها الجلسة مفتوحة. وتجاوزه يصدر AUDIO_DURATION_EXCEEDED مع data.bound: session_audio_duration ويُنهي الجلسة؛ فابدأ جلسة جديدة.

لكل جلسة، معدل الصوت: أربعة أضعاف الزمن الحقيقي. يمكن أن يصل الصوت بما يبلغ 128000 بايت في الثانية، مع سماح اندفاع 16 MiB — وهو إطار بالحجم الأقصى. والعميل الذي يبث بالزمن الحقيقي فعلًا يستخدم ربع حصته ولا يمكن أن يبلغ هذا الحد؛ أما العميل الذي يعوّض تأخرًا بعد تعطل شبكي فيفرّغ متأخراته بثلاثة أضعاف الزمن الحقيقي. وتجاوزه يصدر SESSION_BYTE_RATE_EXCEEDED مع data.bound: session_audio_rate_burst ومع retry_after_seconds (فليس لهذا النقل ترويسة Retry-After، لذلك يسافر الانتظار داخل الإطار)، ولا يغلق الاتصال: فتنجح الحمولة نفسها بمجرد أن يُعاد ملء الرصيد. ولا يستهلك الرفض أي رصيد ولا أي حصة.

وسعة الدلو تغطي دائمًا أكبر إطار تقبله الواجهة، لذلك لا يُرفض إطار بالحجم الأقصى أبدًا بسبب حد المعدل على جلسة بدأت للتو. أما على Socket.IO فالدلو يخص الاتصال وتتشاركه كل أحداث الصوت عليه، لذلك قد يُحدَّد معدل إطار بالحجم الأقصى أُرسل على اتصال بث صوتًا فعلًا؛ فالتزم بـretry_after_seconds وأعد إرساله دون تغيير.

ولم تتغير حدود الجلسة الخاصة بالبايتات المتراكمة ولا بالمدة بالساعة الحقيقية ولا بالسكون.

أخطاء الرصيد والفوترة

يحجز تدفق ASR الفوري رصيدًا قابلًا للتجديد عند البدء، ثم يجدد الحجز أثناء التشغيل. وتُبلّغ النتيجتان التاليتان بالرموز نفسها عبر كل وسائل النقل وHTTP:

  • CREDITS_EXHAUSTED: نفد رصيد الحساب. يقابله HTTP 402 وretryable: false، لأن الإعادة الفورية لا تستعيد الرصيد.
  • BILLING_AUTHORIZATION_UNAVAILABLE: تعذر الوصول إلى جهة اعتماد الفوترة أو لم تعطِ قرارًا حاسمًا، لذلك يفشل الطلب بأمان. يقابله HTTP 503 وretryable: true؛ أعد المحاولة بعد الانتظار.

قد يصل الخطأ عند بدء التدفق إذا رُفض الحجز، أو في منتصفه إذا رُفض تجديده أثناء وصول الصوت. والخطأ في منتصف التدفق نهائي لذلك التدفق: يتوقف الخادم عن قبول الصوت المدفوع، ويرسل حدث error النهائي أولًا، ثم يفصل الاتصال. ويُحاسب فقط الصوت المقبول قبل الخطأ. أما رفض الحجز عند البدء فلا يغلق الاتصال، لأن المقبس قد يحمل عمليات أخرى.

في واجهة WebSocket الخام يحمل إطار الإغلاق رمزًا خاصًا يساوي 4000 + HTTP status: الرمز 4402 لنفاد الرصيد و4503 لتعذر اعتماد الفوترة. ولا تحمل واجهة Socket.IO رمز إغلاق على مستوى التطبيق؛ لذا يكون حدث error المنظم هو الإشارة المعتمدة، وليس رمز الإغلاق.

التحقق من الإطار

يُتحقق من كل إطار قبل أي عمل للنموذج. ويُرفض الإطار عندما يكون أقصر من ترويسته البالغة 18 بايتًا، أو عندما يكون UUID تدفقه أصفارًا كلها، أو عندما يضبط بت راية غير معرَّف في تخطيط تلك الواجهة، أو عندما لا يكون بايت لغته أحد 0 أو 1 أو 2 أو 255، أو عندما يكون صوته فارغًا، أو عندما يكون طول صوته فرديًا. ويُجاب على الإطار المقطوع، ولا يُقبل صامتًا أبدًا. وكل هذه أخطاء إدخال قابلة للإصلاح من جهة العميل، ولا يُبلَّغ عن أي منها كخطأ خادم.

الأحداث

الحدثالاتجاهالمعنى
audio_streamالعميل إلى الخادممقطع صوت للنسخ الفوري.
speaker_idالعميل إلى الخادمقديم؛ يعيد الخادم METHOD_NOT_ALLOWED.
transcription_resultالخادم إلى العميلنتيجة نسخ جزئية أو نهائية.
speaker_id_resultالخادم إلى العميلقديم؛ لا يصدر الخادم هذا الحدث.
diarization_streamالعميل إلى الخادممقطع صوت لتمييز المتحدثين.
diarization_resultالخادم إلى العميلمقاطع المتحدثين النهائية أو النشطة.
errorالخادم إلى العميلخطأ منظم.

إطار audio_stream

audio_stream
UUID التدفق
bytes 0..15
معرّف واحد يعاد استخدامه في كل مقاطع التدفق.
الرايات
byte 16
bit 0 is_start، bit 1 is_final، bit 2 diarization_enabled.
اللغة
byte 17
0=ar، 1=en، 2=codeswitch، 255=auto.
الصوت
bytes 18..end
PCM16 little-endian، 16 kHz، أحادي القناة.

أرسل المقطع الأول مع is_start، والمقاطع الوسطية بلا رايات، والمقطع الأخير مع is_final. عندما تكون diarization_enabled مفعّلة، ينسخ الخادم الصوت ويرسل أيضًا أحداث diarization_result.

أرسل 1,600 عينة، أي 100 ms، في المقطع الموصى به. يخزن ASR المقاطع الأقصر حتى 1,600 عينة ويجزئ المقاطع الأطول إلى وحدات بهذا الحجم؛ ولا تنطبق قواعد التخزين هذه على diarization_stream. يمكن لاتصال واحد حمل عدة تدفقات: أعد استخدام UUID نفسه لتدفق واحد، واستخدم UUID جديدًا لكل تدفق جديد.

نتيجة النسخ

تصل transcription_result عادة بالشكل التالي:

{
  "id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
  "seq": 0,
  "transcription": "مرحبا بكم",
  "words": [
    { "start_time": 0, "end_time": 1.2, "word": "مرحبا بكم" }
  ],
  "is_final": false,
  "is_speech_final": true
}

تمثل is_speech_final: true حد نهاية كلام ويمكن أن يستمر التدفق بعده. ولا ينتهي التدفق كله إلا عند is_final: true. عامل seq بوصفه قيمة تشخيصية؛ فلا يضمن العقد العام ترتيبها أو تفرّدها.

إطار diarization_stream

يستخدم diarization_stream تخطيط البايتات نفسه المستخدم في audio_stream، لكن مجموعة الرايات ليست نفسها: إطار تمييز المتحدثين يعرّف bit 0 وbit 1 فقط، أما البتات المحجوزة 2..7 فيجب أن تكون أصفارًا، ويُرفض أي إطار يضبط أيًا منها. ولا تضبط bit 2 هنا؛ فراية diarization_enabled تخص audio_stream وحده. ونموذج تمييز المتحدثين يحل من model_config في جهة الخادم، لذلك يكون حقل اللغة موجودًا للاتساق فقط، وأرسله صفرًا.

نتيجة تمييز المتحدثين

تحتوي diarization_result على مقاطع نهائية ونشطة:

{
  "id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
  "is_final": false,
  "final_segments": [
    { "speaker": "SPEAKER_00", "start_time": 0, "end_time": 1.5 }
  ],
  "active_segments": [
    { "speaker": "SPEAKER_01", "start_time": 1.5, "end_time": 3 }
  ]
}

الأخطاء

حمولة الحدث error كائن JSON منظم، وحقول code وmessage وretryable وtimestamp كلها مطلوبة فيه. فرّع على code المعدود لا على نص message؛ فـmessage يحمل النص الحر نفسه الذي كان يُرسل سابقًا سلسلةً مجردة:

{
  "code": "VALIDATION_INVALID_FORMAT",
  "message": "Invalid data type",
  "retryable": false,
  "timestamp": "2025-05-07T10:00:00.000Z"
}
codeالمعنى
AUTH_FORBIDDENمفتاح API لا يمنح الصلاحية
VALIDATION_INVALID_FORMATتأطير أو نوع بيانات غير صالح
VALIDATION_INVALID_LANGUAGEقيمة لغة غير معروفة
VALIDATION_INVALID_UUIDUUID تدفق غير صالح
VALIDATION_FILE_CORRUPTصوت تالف أو غير قابل لفك الترميز
VALIDATION_REQUIRED_FIELDحقل مطلوب مفقود
PAYLOAD_TOO_LARGEتجاوز الإطار سقف 16 MiB
AUDIO_DURATION_EXCEEDEDتجاوزت الجلسة حصتها الصوتية
SESSION_BYTE_RATE_EXCEEDEDالصوت يصل أسرع مما يسمح به معدل الجلسة
SESSION_BYTES_EXCEEDEDتجاوز بايتات الجلسة المتراكمة
SESSION_DURATION_EXCEEDEDتجاوز مدة الجلسة بالزمن الحقيقي
SESSION_IDLE_TIMEOUTانقضت مهلة سكون الجلسة
SESSION_EXPIREDالجلسة غير حيّة؛ ابدأ جلسة جديدة
SESSION_SLOTS_EXHAUSTEDاستُنفدت خانات الجلسات المتزامنة للحساب
CONCURRENCY_LIMIT_EXCEEDEDبلغ الحساب حد العمليات المتزامنة في خطته
CREDITS_EXHAUSTEDنفد رصيد الحساب؛ لا تفِد الإعادة الفورية
BILLING_AUTHORIZATION_UNAVAILABLEتعذر التحقق من الفوترة؛ أعد المحاولة بعد الانتظار
ASR_TRANSCRIPTION_FAILEDفشل النسخ في الخلفية
ASR_STREAM_EXPIREDانتهى تدفق ASR؛ افتح تدفقًا جديدًا ولا تعد استخدام معرّفه
DIARIZATION_FAILEDفشل تمييز المتحدثين في الخلفية
DIARIZATION_MODEL_NOT_FOUNDنموذج تمييز المتحدثين غير مضبوط
METHOD_NOT_ALLOWEDحدث مهجور، مثل speaker_id

ويحمل رفض الحدود كائن data يسمّي الحد وقيمته المضبوطة والقيمة المرصودة. أما رفض الحدود القابل لإعادة المحاولة فيحمل أيضًا retry_after_seconds:

{
  "id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
  "code": "CONCURRENCY_LIMIT_EXCEEDED",
  "message": "too many concurrent operations for this account",
  "retryable": true,
  "timestamp": "2026-01-15T10:30:00Z",
  "retry_after_seconds": 5,
  "data": {
    "limit": 8,
    "observed": 8,
    "unit": "operations",
    "bound": "account_concurrency_realtime_asr"
  }
}

حد العمليات المتزامنة لكل حساب قابل للفوترة، لذلك تتشارك عدة مفاتيح API تابعة لحساب واحد حصة واحدة. ولا يُغلق الاتصال: فالعمليات الأخرى المقبولة عليه تبقى تعمل.

ويحمل ASR_STREAM_EXPIRED كذلك reason بقيمة audio_inactivity أو backend_sequence_lost، ويحمل retry_scope: "new_stream". افتح تدفقًا جديدًا بمعرّف UUID جديد، ولا تعاود الإرسال على المعرّف المنتهي:

{
  "id": "7f51f2c2-e7bc-41c8-a850-f848df2ddfc8",
  "code": "ASR_STREAM_EXPIRED",
  "message": "realtime ASR stream expired",
  "retryable": true,
  "timestamp": "2026-01-15T10:30:00Z",
  "reason": "audio_inactivity",
  "retry_scope": "new_stream"
}

في هذه الصفحة