# Akool Streaming Avatar SDK
A Python SDK for connecting to Akool's streaming avatar services via WebSocket. This SDK provides easy-to-use interfaces for real-time audio streaming, automatic load balancing, and robust error handling.
## Features
- đ **Token-based Authentication** - Secure API key authentication
- âī¸ **Automatic Load Balancing** - Service discovery with random server selection
- đ **Retry Logic** - Automatic reconnection with configurable retry attempts
- đĩ **Audio Streaming** - Real-time PCM audio transmission
- đ **Built-in Heartbeat** - Automatic connection health monitoring
- đĄī¸ **Error Handling** - Comprehensive exception handling with user-friendly messages
- đ **Event-driven** - Callback-based architecture for real-time responses
## Installation
```bash
pip install akool-streaming-avatar
```
## Quick Start
### Method 1: Manual Connection Management
```python
import asyncio
from akool_streaming_avatar import StreamingAvatarClient
async def main():
# Initialize client
client = StreamingAvatarClient(
api_key="your-api-key",
discovery_url="https://api.akool.com/streamingAvatar/service_status",
avatar_id="your-avatar-id"
)
try:
# Connect to service
await client.connect()
# Set Agora parameters (required)
await client.set_agora_params(
agora_app_id="your-agora-app-id",
agora_channel="your-channel",
agora_token="your-rtc-token",
agora_uid="12345"
)
# Send audio data
audio_data = b"..." # PCM 16-bit, 16kHz, mono
await client.send_audio(audio_data)
finally:
# Always disconnect when done
await client.disconnect()
# Run the client
asyncio.run(main())
```
### Method 2: Using Async Context Manager (Recommended)
```python
import asyncio
from akool_streaming_avatar import AsyncStreamingAvatarClient
async def main():
# Using async context manager for automatic connection handling
async with AsyncStreamingAvatarClient(
api_key="your-api-key",
discovery_url="https://api.akool.com/streamingAvatar/service_status",
avatar_id="your-avatar-id"
) as client:
# Client is automatically connected
# Set Agora parameters (required)
await client.set_agora_params(
agora_app_id="your-agora-app-id",
agora_channel="your-channel",
agora_token="your-rtc-token",
agora_uid="12345"
)
# Send audio data
audio_data = b"..." # PCM 16-bit, 16kHz, mono
await client.send_audio(audio_data)
# Client is automatically disconnected when exiting the context
# Run the client
asyncio.run(main())
```
## API Reference
### StreamingAvatarClient
Main client class for connecting to Akool streaming avatar services.
#### Constructor
```python
StreamingAvatarClient(
api_key: str,
discovery_url: str,
avatar_id: str,
session_id: Optional[str] = None,
max_retry_attempts: int = 3,
heartbeat_interval: int = 30,
connection_timeout: int = 30,
discovery_timeout: int = 10
)
```
**Parameters:**
- `api_key`: Your Akool API key
- `discovery_url`: Service discovery endpoint URL
- `avatar_id`: Target avatar identifier
- `session_id`: Session ID (will be generated if not provided)
- `max_retry_attempts`: Maximum retry attempts (default: 3)
- `heartbeat_interval`: Heartbeat interval in seconds (default: 30)
- `connection_timeout`: Connection timeout in seconds (default: 30)
- `discovery_timeout`: Service discovery timeout in seconds (default: 10)
#### Methods
##### connect()
```python
async def connect() -> None
```
Establish connection to the streaming avatar service with automatic service discovery and retry logic.
**Raises:**
- `AuthenticationError`: Invalid API key
- `ServiceDiscoveryError`: No available services
- `ConnectionError`: Connection failed
- `RetryError`: All retry attempts exhausted
##### disconnect()
```python
async def disconnect() -> None
```
Close the WebSocket connection gracefully.
##### set_agora_params()
```python
async def set_agora_params(
agora_app_id: str,
agora_channel: str,
agora_token: str,
agora_uid: str,
voice_id: Optional[str] = None,
language: str = "en",
background_url: Optional[str] = None
) -> None
```
Configure Agora RTC parameters (required before sending audio).
**Parameters:**
- `agora_app_id`: Agora application ID
- `agora_channel`: Agora channel name
- `agora_token`: Agora RTC token
- `agora_uid`: Agora user ID
- `voice_id`: Voice ID for the avatar (optional)
- `language`: Language code (default: "en")
- `background_url`: Background image/video URL (optional)
**Raises:**
- `ConnectionError`: Not connected to server
- `AudioStreamError`: If setting parameters fails
##### send_audio()
```python
async def send_audio(audio_data: bytes) -> None
```
Send PCM audio data to the avatar.
**Parameters:**
- `audio_data`: PCM audio bytes (16-bit, 16kHz, mono)
**Raises:**
- `ConnectionError`: Not connected to server
- `AudioStreamError`: Audio transmission failed
##### send_audio_stream()
```python
async def send_audio_stream(audio_chunks: List[bytes]) -> None
```
Send multiple audio chunks in sequence.
**Parameters:**
- `audio_chunks`: List of PCM audio data chunks
**Raises:**
- `ConnectionError`: Not connected to server
- `AudioStreamError`: If sending audio fails
##### interrupt()
```python
async def interrupt() -> None
```
Send interrupt command to stop current avatar response.
**Raises:**
- `ConnectionError`: If not connected
##### Event Handlers
The client supports event-driven programming through callback properties:
```python
# Set event handlers
client.on_connected = lambda: print("Connected!")
client.on_disconnected = lambda: print("Disconnected!")
client.on_agora_connected = lambda channel: print(f"Agora connected: {channel}")
client.on_error = lambda error: print(f"Error: {error}")
client.on_message = lambda message: print(f"Message: {message}")
```
**Available event handlers:**
- `on_connected`: Called when WebSocket connection is established
- `on_disconnected`: Called when connection is lost
- `on_agora_connected`: Called when Agora connection is established (receives channel ID)
- `on_error`: Called when errors occur (receives Exception)
- `on_message`: Called when receiving messages from server (receives Dict)
##### get_connection_info()
```python
def get_connection_info() -> Dict[str, Any]
```
Get information about the current connection.
**Returns:**
- Dictionary with connection information including status, session ID, service details
##### Connection Status Properties
The client provides several properties to check connection status:
```python
# Check WebSocket connection status
if client.is_connected:
print("â
WebSocket connected")
# Check Agora connection status
if client.is_agora_connected:
print("â
Agora connected")
# Get detailed connection information
connection_info = client.get_connection_info()
print(f"đ Connection details: {connection_info}")
```
**Connection Status Properties:**
- `client.is_connected`: Boolean indicating if WebSocket is connected
- `client.is_agora_connected`: Boolean indicating if Agora connection is established
- `client.session_id`: Current session ID string
## Monitoring Connection Status
### Real-time Connection Monitoring
```python
import asyncio
from akool_streaming_avatar import StreamingAvatarClient
async def monitor_connection():
client = StreamingAvatarClient(
api_key="your-api-key",
discovery_url="https://api.akool.com/streamingAvatar/service_status",
avatar_id="your-avatar-id"
)
# Set up event handlers for real-time monitoring
def on_connected():
print("đ WebSocket connected!")
def on_agora_connected(channel_id: str):
print(f"đĨ Agora connected to channel: {channel_id}")
def on_message_received(message):
msg_type = message.get("type")
print(f"đ¨ Received message: {msg_type}")
# Handle different message types
if msg_type == "system":
handle_system_message(message)
elif msg_type == "ack":
handle_acknowledgment(message)
elif msg_type == "error":
handle_error_message(message)
def on_error_occurred(error):
print(f"â Error: {error}")
def on_disconnected():
print("đ Connection lost!")
# Register event handlers
client.on_connected = on_connected
client.on_agora_connected = on_agora_connected
client.on_message = on_message_received
client.on_error = on_error_occurred
client.on_disconnected = on_disconnected
try:
# Connect to service
await client.connect()
print(f"â
Initial connection: {client.is_connected}")
# Set Agora parameters and wait for connection
await client.set_agora_params(
agora_app_id="your-agora-app-id",
agora_channel="your-channel",
agora_token="your-rtc-token",
agora_uid="12345"
)
# Check status after Agora setup
print(f"đĨ Agora connected: {client.is_agora_connected}")
# Monitor connection for 30 seconds
for i in range(6):
await asyncio.sleep(5)
# Check connection health
info = client.get_connection_info()
print(f"đ Status check {i+1}: WebSocket={info['connected']}, "
f"Agora={info['agora_connected']}")
finally:
await client.disconnect()
def handle_system_message(message):
"""Handle system messages from the server."""
pld = message.get("pld", {})
status = pld.get("status")
if status == "websocket_connected":
print("â
WebSocket connection confirmed by server")
elif status == "agora_connected":
channel_id = pld.get("channel_id", "")
print(f"â
Agora connection confirmed: {channel_id}")
else:
print(f"âšī¸ System status: {status}")
def handle_acknowledgment(message):
"""Handle acknowledgment messages."""
pld = message.get("pld", {})
original_mid = pld.get("original_mid")
print(f"â
Command acknowledged: {original_mid}")
def handle_error_message(message):
"""Handle error messages from server."""
pld = message.get("pld", {})
error_code = pld.get("error_code")
error_message = pld.get("error_message", "Unknown error")
print(f"â Server error {error_code}: {error_message}")
asyncio.run(monitor_connection())
```
### Polling Connection Status
```python
async def check_connection_status(client):
"""Periodically check connection status."""
while True:
try:
# Get current status
info = client.get_connection_info()
print(f"đ Connection Status:")
print(f" WebSocket: {'â
' if info['connected'] else 'â'}")
print(f" Agora: {'â
' if info['agora_connected'] else 'â'}")
print(f" Session: {info['session_id']}")
print(f" Service: {info['current_service']}")
# Wait before next check
await asyncio.sleep(10)
except Exception as e:
print(f"â Status check failed: {e}")
break
```
## WebSocket Message Types
The SDK receives various message types from the server. Here are the common message types and their meanings:
### System Messages
```json
{
"v": 2,
"type": "system",
"mid": "system-123",
"pld": {
"status": "websocket_connected" | "agora_connected",
"channel_id": "channel-name" // Only for agora_connected
}
}
```
### Acknowledgment Messages
```json
{
"v": 2,
"type": "ack",
"mid": "ack-123",
"pld": {
"original_mid": "cmd-set-params-456",
"status": "success"
}
}
```
### Error Messages
```json
{
"v": 2,
"type": "error",
"mid": "error-123",
"pld": {
"error_code": 4001,
"error_message": "Authentication failed"
}
}
```
### Pong Messages (Heartbeat Response)
```json
{
"v": 2,
"type": "pong",
"mid": "pong-123",
"pld": {}
}
```
## Advanced Usage
### Manual Connection Management
```python
client = StreamingAvatarClient(api_key, discovery_url, avatar_id)
try:
await client.connect()
await client.set_agora_params(
agora_app_id=agora_app_id,
agora_channel=channel,
agora_token=rtc_token,
agora_uid=user_id
)
# Your audio streaming logic here
while streaming:
audio_data = get_audio_data()
await client.send_audio(audio_data)
finally:
await client.disconnect()
```
### Event Callbacks
```python
def on_message_received(message):
print(f"Received: {message}")
def on_error_occurred(error):
print(f"Error: {error}")
def on_connection_lost():
print("Connection lost")
# Set event handlers
client.on_message = on_message_received
client.on_error = on_error_occurred
client.on_disconnected = on_connection_lost
```
### Custom Retry Configuration
```python
client = StreamingAvatarClient(
api_key="your-api-key",
discovery_url="https://api.akool.com/streamingAvatar/service_status",
avatar_id="your-avatar-id",
max_retry_attempts=5, # Try up to 5 times
heartbeat_interval=15 # Ping every 15 seconds
)
```
## Error Handling
The SDK provides specific exception types for different error scenarios:
```python
from akool_streaming_avatar.exceptions import (
AuthenticationError,
ServiceDiscoveryError,
ConnectionError,
AudioStreamError,
RetryError,
ConfigurationError
)
try:
await client.connect()
except AuthenticationError:
print("Invalid API key")
except ServiceDiscoveryError:
print("No available services")
except ConnectionError:
print("Failed to connect")
except RetryError:
print("All retry attempts failed")
```
## Audio Format Requirements
The SDK expects audio data in the following format:
- **Format**: PCM (uncompressed)
- **Sample Rate**: 16,000 Hz
- **Bit Depth**: 16-bit
- **Channels**: Mono (1 channel)
- **Encoding**: Little-endian
### Converting Audio
```python
import wave
import numpy as np
# Load and convert audio file
with wave.open('input.wav', 'rb') as wav_file:
frames = wav_file.readframes(-1)
sound_info = np.frombuffer(frames, dtype=np.int16)
# Convert to mono if stereo
if wav_file.getnchannels() == 2:
sound_info = sound_info.reshape(-1, 2).mean(axis=1).astype(np.int16)
# Resample to 16kHz if needed
# (use librosa or scipy for resampling)
# Send to avatar
await client.send_audio(sound_info.tobytes())
```
## Service Discovery
The SDK automatically discovers available streaming avatar services by calling the service status endpoint. The discovery process:
1. Makes HTTP GET request to `discovery_url`
2. Parses the response to extract available services
3. Randomly selects a service for load balancing
4. Attempts WebSocket connection
5. Falls back to other services if connection fails
### Expected Service Response Format
```json
{
"success": true,
"data": {
"services": [
{
"host": "avatar1.akool.com",
"port": 8080,
"status": "active"
},
{
"host": "avatar2.akool.com",
"port": 8080,
"status": "active"
}
]
}
}
```
## WebSocket Protocol
The SDK uses WebSocket protocol version 2 with the following message format:
### Authentication
```json
{
"type": "auth",
"token": "your-api-key",
"avatar_id": "your-avatar-id"
}
```
### Agora Configuration
```json
{
"type": "agora_params",
"data": {
"channel": "channel-name",
"userId": 12345,
"rtcToken": "agora-rtc-token"
}
}
```
### Audio Data
```json
{
"type": "audio_data",
"data": "base64-encoded-pcm-audio"
}
```
### Heartbeat
```json
{
"type": "ping"
}
```
## Development
### Setting up Development Environment
```bash
git clone https://github.com/akoolteam/akool-streaming-avatar-sdk.git
cd akool-streaming-avatar-sdk
pip install -e ".[dev]"
```
### Running Tests
```bash
pytest tests/
```
### Code Formatting
```bash
black akool_streaming_avatar/
flake8 akool_streaming_avatar/
```
## Examples
See the `examples/` directory for more usage examples:
- `basic_usage.py` - Simple connection and audio streaming
- `error_handling.py` - Comprehensive error handling patterns
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Run the test suite
6. Submit a pull request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Support
For support, please contact [support@akool.com](mailto:support@akool.com) or visit our [documentation](https://docs.akool.com).
## Changelog
### v0.1.0 (2024-01-XX)
- Initial release
- Basic WebSocket connection and authentication
- Service discovery and load balancing
- Audio streaming support
- Retry logic and error handling
- Event callback system
Raw data
{
"_id": null,
"home_page": null,
"name": "akool-streaming-avatar",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "akool, streaming, avatar, websocket, audio, real-time",
"author": null,
"author_email": "Akool Team <support@akool.com>",
"download_url": "https://files.pythonhosted.org/packages/a8/47/91d6d386fbc18e0769be522a54fe1972c8bf97aefff0baa0458d52bd0d84/akool_streaming_avatar-0.1.4.tar.gz",
"platform": null,
"description": "# Akool Streaming Avatar SDK\n\nA Python SDK for connecting to Akool's streaming avatar services via WebSocket. This SDK provides easy-to-use interfaces for real-time audio streaming, automatic load balancing, and robust error handling.\n\n## Features\n\n- \ud83d\udd10 **Token-based Authentication** - Secure API key authentication\n- \u2696\ufe0f **Automatic Load Balancing** - Service discovery with random server selection\n- \ud83d\udd04 **Retry Logic** - Automatic reconnection with configurable retry attempts\n- \ud83c\udfb5 **Audio Streaming** - Real-time PCM audio transmission\n- \ud83d\udc93 **Built-in Heartbeat** - Automatic connection health monitoring\n- \ud83d\udee1\ufe0f **Error Handling** - Comprehensive exception handling with user-friendly messages\n- \ud83d\udcdd **Event-driven** - Callback-based architecture for real-time responses\n\n## Installation\n\n```bash\npip install akool-streaming-avatar\n```\n\n## Quick Start\n\n### Method 1: Manual Connection Management\n\n```python\nimport asyncio\nfrom akool_streaming_avatar import StreamingAvatarClient\n\nasync def main():\n # Initialize client\n client = StreamingAvatarClient(\n api_key=\"your-api-key\",\n discovery_url=\"https://api.akool.com/streamingAvatar/service_status\",\n avatar_id=\"your-avatar-id\"\n )\n \n try:\n # Connect to service\n await client.connect()\n \n # Set Agora parameters (required)\n await client.set_agora_params(\n agora_app_id=\"your-agora-app-id\",\n agora_channel=\"your-channel\",\n agora_token=\"your-rtc-token\",\n agora_uid=\"12345\"\n )\n \n # Send audio data\n audio_data = b\"...\" # PCM 16-bit, 16kHz, mono\n await client.send_audio(audio_data)\n \n finally:\n # Always disconnect when done\n await client.disconnect()\n\n# Run the client\nasyncio.run(main())\n```\n\n### Method 2: Using Async Context Manager (Recommended)\n\n```python\nimport asyncio\nfrom akool_streaming_avatar import AsyncStreamingAvatarClient\n\nasync def main():\n # Using async context manager for automatic connection handling\n async with AsyncStreamingAvatarClient(\n api_key=\"your-api-key\",\n discovery_url=\"https://api.akool.com/streamingAvatar/service_status\",\n avatar_id=\"your-avatar-id\"\n ) as client:\n # Client is automatically connected\n \n # Set Agora parameters (required)\n await client.set_agora_params(\n agora_app_id=\"your-agora-app-id\",\n agora_channel=\"your-channel\",\n agora_token=\"your-rtc-token\",\n agora_uid=\"12345\"\n )\n \n # Send audio data\n audio_data = b\"...\" # PCM 16-bit, 16kHz, mono\n await client.send_audio(audio_data)\n \n # Client is automatically disconnected when exiting the context\n\n# Run the client\nasyncio.run(main())\n```\n\n## API Reference\n\n### StreamingAvatarClient\n\nMain client class for connecting to Akool streaming avatar services.\n\n#### Constructor\n\n```python\nStreamingAvatarClient(\n api_key: str,\n discovery_url: str,\n avatar_id: str,\n session_id: Optional[str] = None,\n max_retry_attempts: int = 3,\n heartbeat_interval: int = 30,\n connection_timeout: int = 30,\n discovery_timeout: int = 10\n)\n```\n\n**Parameters:**\n- `api_key`: Your Akool API key\n- `discovery_url`: Service discovery endpoint URL\n- `avatar_id`: Target avatar identifier\n- `session_id`: Session ID (will be generated if not provided)\n- `max_retry_attempts`: Maximum retry attempts (default: 3)\n- `heartbeat_interval`: Heartbeat interval in seconds (default: 30)\n- `connection_timeout`: Connection timeout in seconds (default: 30)\n- `discovery_timeout`: Service discovery timeout in seconds (default: 10)\n\n#### Methods\n\n##### connect()\n```python\nasync def connect() -> None\n```\nEstablish connection to the streaming avatar service with automatic service discovery and retry logic.\n\n**Raises:**\n- `AuthenticationError`: Invalid API key\n- `ServiceDiscoveryError`: No available services\n- `ConnectionError`: Connection failed\n- `RetryError`: All retry attempts exhausted\n\n##### disconnect()\n```python\nasync def disconnect() -> None\n```\nClose the WebSocket connection gracefully.\n\n##### set_agora_params()\n```python\nasync def set_agora_params(\n agora_app_id: str,\n agora_channel: str,\n agora_token: str,\n agora_uid: str,\n voice_id: Optional[str] = None,\n language: str = \"en\",\n background_url: Optional[str] = None\n) -> None\n```\nConfigure Agora RTC parameters (required before sending audio).\n\n**Parameters:**\n- `agora_app_id`: Agora application ID\n- `agora_channel`: Agora channel name\n- `agora_token`: Agora RTC token\n- `agora_uid`: Agora user ID\n- `voice_id`: Voice ID for the avatar (optional)\n- `language`: Language code (default: \"en\")\n- `background_url`: Background image/video URL (optional)\n\n**Raises:**\n- `ConnectionError`: Not connected to server\n- `AudioStreamError`: If setting parameters fails\n\n##### send_audio()\n```python\nasync def send_audio(audio_data: bytes) -> None\n```\nSend PCM audio data to the avatar.\n\n**Parameters:**\n- `audio_data`: PCM audio bytes (16-bit, 16kHz, mono)\n\n**Raises:**\n- `ConnectionError`: Not connected to server\n- `AudioStreamError`: Audio transmission failed\n\n##### send_audio_stream()\n```python\nasync def send_audio_stream(audio_chunks: List[bytes]) -> None\n```\nSend multiple audio chunks in sequence.\n\n**Parameters:**\n- `audio_chunks`: List of PCM audio data chunks\n\n**Raises:**\n- `ConnectionError`: Not connected to server\n- `AudioStreamError`: If sending audio fails\n\n##### interrupt()\n```python\nasync def interrupt() -> None\n```\nSend interrupt command to stop current avatar response.\n\n**Raises:**\n- `ConnectionError`: If not connected\n\n##### Event Handlers\n\nThe client supports event-driven programming through callback properties:\n\n```python\n# Set event handlers\nclient.on_connected = lambda: print(\"Connected!\")\nclient.on_disconnected = lambda: print(\"Disconnected!\")\nclient.on_agora_connected = lambda channel: print(f\"Agora connected: {channel}\")\nclient.on_error = lambda error: print(f\"Error: {error}\")\nclient.on_message = lambda message: print(f\"Message: {message}\")\n```\n\n**Available event handlers:**\n- `on_connected`: Called when WebSocket connection is established\n- `on_disconnected`: Called when connection is lost\n- `on_agora_connected`: Called when Agora connection is established (receives channel ID)\n- `on_error`: Called when errors occur (receives Exception)\n- `on_message`: Called when receiving messages from server (receives Dict)\n\n##### get_connection_info()\n```python\ndef get_connection_info() -> Dict[str, Any]\n```\nGet information about the current connection.\n\n**Returns:**\n- Dictionary with connection information including status, session ID, service details\n\n##### Connection Status Properties\n\nThe client provides several properties to check connection status:\n\n```python\n# Check WebSocket connection status\nif client.is_connected:\n print(\"\u2705 WebSocket connected\")\n\n# Check Agora connection status \nif client.is_agora_connected:\n print(\"\u2705 Agora connected\")\n\n# Get detailed connection information\nconnection_info = client.get_connection_info()\nprint(f\"\ud83d\udcca Connection details: {connection_info}\")\n```\n\n**Connection Status Properties:**\n- `client.is_connected`: Boolean indicating if WebSocket is connected\n- `client.is_agora_connected`: Boolean indicating if Agora connection is established\n- `client.session_id`: Current session ID string\n\n## Monitoring Connection Status\n\n### Real-time Connection Monitoring\n\n```python\nimport asyncio\nfrom akool_streaming_avatar import StreamingAvatarClient\n\nasync def monitor_connection():\n client = StreamingAvatarClient(\n api_key=\"your-api-key\",\n discovery_url=\"https://api.akool.com/streamingAvatar/service_status\", \n avatar_id=\"your-avatar-id\"\n )\n \n # Set up event handlers for real-time monitoring\n def on_connected():\n print(\"\ud83d\udd17 WebSocket connected!\")\n \n def on_agora_connected(channel_id: str):\n print(f\"\ud83c\udfa5 Agora connected to channel: {channel_id}\")\n \n def on_message_received(message):\n msg_type = message.get(\"type\")\n print(f\"\ud83d\udce8 Received message: {msg_type}\")\n \n # Handle different message types\n if msg_type == \"system\":\n handle_system_message(message)\n elif msg_type == \"ack\":\n handle_acknowledgment(message)\n elif msg_type == \"error\":\n handle_error_message(message)\n \n def on_error_occurred(error):\n print(f\"\u274c Error: {error}\")\n \n def on_disconnected():\n print(\"\ud83d\udd0c Connection lost!\")\n \n # Register event handlers\n client.on_connected = on_connected\n client.on_agora_connected = on_agora_connected\n client.on_message = on_message_received\n client.on_error = on_error_occurred\n client.on_disconnected = on_disconnected\n \n try:\n # Connect to service\n await client.connect()\n print(f\"\u2705 Initial connection: {client.is_connected}\")\n \n # Set Agora parameters and wait for connection\n await client.set_agora_params(\n agora_app_id=\"your-agora-app-id\",\n agora_channel=\"your-channel\", \n agora_token=\"your-rtc-token\",\n agora_uid=\"12345\"\n )\n \n # Check status after Agora setup\n print(f\"\ud83c\udfa5 Agora connected: {client.is_agora_connected}\")\n \n # Monitor connection for 30 seconds\n for i in range(6):\n await asyncio.sleep(5)\n \n # Check connection health\n info = client.get_connection_info()\n print(f\"\ud83d\udcca Status check {i+1}: WebSocket={info['connected']}, \"\n f\"Agora={info['agora_connected']}\")\n \n finally:\n await client.disconnect()\n\ndef handle_system_message(message):\n \"\"\"Handle system messages from the server.\"\"\"\n pld = message.get(\"pld\", {})\n status = pld.get(\"status\")\n \n if status == \"websocket_connected\":\n print(\"\u2705 WebSocket connection confirmed by server\")\n elif status == \"agora_connected\": \n channel_id = pld.get(\"channel_id\", \"\")\n print(f\"\u2705 Agora connection confirmed: {channel_id}\")\n else:\n print(f\"\u2139\ufe0f System status: {status}\")\n\ndef handle_acknowledgment(message):\n \"\"\"Handle acknowledgment messages.\"\"\"\n pld = message.get(\"pld\", {})\n original_mid = pld.get(\"original_mid\")\n print(f\"\u2705 Command acknowledged: {original_mid}\")\n\ndef handle_error_message(message):\n \"\"\"Handle error messages from server.\"\"\"\n pld = message.get(\"pld\", {})\n error_code = pld.get(\"error_code\")\n error_message = pld.get(\"error_message\", \"Unknown error\")\n print(f\"\u274c Server error {error_code}: {error_message}\")\n\nasyncio.run(monitor_connection())\n```\n\n### Polling Connection Status\n\n```python\nasync def check_connection_status(client):\n \"\"\"Periodically check connection status.\"\"\"\n while True:\n try:\n # Get current status\n info = client.get_connection_info()\n \n print(f\"\ud83d\udcca Connection Status:\")\n print(f\" WebSocket: {'\u2705' if info['connected'] else '\u274c'}\")\n print(f\" Agora: {'\u2705' if info['agora_connected'] else '\u274c'}\")\n print(f\" Session: {info['session_id']}\")\n print(f\" Service: {info['current_service']}\")\n \n # Wait before next check\n await asyncio.sleep(10)\n \n except Exception as e:\n print(f\"\u274c Status check failed: {e}\")\n break\n```\n\n## WebSocket Message Types\n\nThe SDK receives various message types from the server. Here are the common message types and their meanings:\n\n### System Messages\n```json\n{\n \"v\": 2,\n \"type\": \"system\", \n \"mid\": \"system-123\",\n \"pld\": {\n \"status\": \"websocket_connected\" | \"agora_connected\",\n \"channel_id\": \"channel-name\" // Only for agora_connected\n }\n}\n```\n\n### Acknowledgment Messages\n```json\n{\n \"v\": 2,\n \"type\": \"ack\",\n \"mid\": \"ack-123\", \n \"pld\": {\n \"original_mid\": \"cmd-set-params-456\",\n \"status\": \"success\"\n }\n}\n```\n\n### Error Messages\n```json\n{\n \"v\": 2,\n \"type\": \"error\",\n \"mid\": \"error-123\",\n \"pld\": {\n \"error_code\": 4001,\n \"error_message\": \"Authentication failed\"\n }\n}\n```\n\n### Pong Messages (Heartbeat Response)\n```json\n{\n \"v\": 2,\n \"type\": \"pong\",\n \"mid\": \"pong-123\",\n \"pld\": {}\n}\n```\n\n## Advanced Usage\n\n### Manual Connection Management\n\n```python\nclient = StreamingAvatarClient(api_key, discovery_url, avatar_id)\n\ntry:\n await client.connect()\n await client.set_agora_params(\n agora_app_id=agora_app_id,\n agora_channel=channel,\n agora_token=rtc_token,\n agora_uid=user_id\n )\n \n # Your audio streaming logic here\n while streaming:\n audio_data = get_audio_data()\n await client.send_audio(audio_data)\n \nfinally:\n await client.disconnect()\n```\n\n### Event Callbacks\n\n```python\ndef on_message_received(message):\n print(f\"Received: {message}\")\n\ndef on_error_occurred(error):\n print(f\"Error: {error}\")\n\ndef on_connection_lost():\n print(\"Connection lost\")\n\n# Set event handlers\nclient.on_message = on_message_received\nclient.on_error = on_error_occurred\nclient.on_disconnected = on_connection_lost\n```\n\n### Custom Retry Configuration\n\n```python\nclient = StreamingAvatarClient(\n api_key=\"your-api-key\",\n discovery_url=\"https://api.akool.com/streamingAvatar/service_status\",\n avatar_id=\"your-avatar-id\",\n max_retry_attempts=5, # Try up to 5 times\n heartbeat_interval=15 # Ping every 15 seconds\n)\n```\n\n## Error Handling\n\nThe SDK provides specific exception types for different error scenarios:\n\n```python\nfrom akool_streaming_avatar.exceptions import (\n AuthenticationError,\n ServiceDiscoveryError,\n ConnectionError,\n AudioStreamError,\n RetryError,\n ConfigurationError\n)\n\ntry:\n await client.connect()\nexcept AuthenticationError:\n print(\"Invalid API key\")\nexcept ServiceDiscoveryError:\n print(\"No available services\")\nexcept ConnectionError:\n print(\"Failed to connect\")\nexcept RetryError:\n print(\"All retry attempts failed\")\n```\n\n## Audio Format Requirements\n\nThe SDK expects audio data in the following format:\n- **Format**: PCM (uncompressed)\n- **Sample Rate**: 16,000 Hz\n- **Bit Depth**: 16-bit\n- **Channels**: Mono (1 channel)\n- **Encoding**: Little-endian\n\n### Converting Audio\n\n```python\nimport wave\nimport numpy as np\n\n# Load and convert audio file\nwith wave.open('input.wav', 'rb') as wav_file:\n frames = wav_file.readframes(-1)\n sound_info = np.frombuffer(frames, dtype=np.int16)\n \n # Convert to mono if stereo\n if wav_file.getnchannels() == 2:\n sound_info = sound_info.reshape(-1, 2).mean(axis=1).astype(np.int16)\n \n # Resample to 16kHz if needed\n # (use librosa or scipy for resampling)\n \n # Send to avatar\n await client.send_audio(sound_info.tobytes())\n```\n\n## Service Discovery\n\nThe SDK automatically discovers available streaming avatar services by calling the service status endpoint. The discovery process:\n\n1. Makes HTTP GET request to `discovery_url`\n2. Parses the response to extract available services\n3. Randomly selects a service for load balancing\n4. Attempts WebSocket connection\n5. Falls back to other services if connection fails\n\n### Expected Service Response Format\n\n```json\n{\n \"success\": true,\n \"data\": {\n \"services\": [\n {\n \"host\": \"avatar1.akool.com\",\n \"port\": 8080,\n \"status\": \"active\"\n },\n {\n \"host\": \"avatar2.akool.com\", \n \"port\": 8080,\n \"status\": \"active\"\n }\n ]\n }\n}\n```\n\n## WebSocket Protocol\n\nThe SDK uses WebSocket protocol version 2 with the following message format:\n\n### Authentication\n```json\n{\n \"type\": \"auth\",\n \"token\": \"your-api-key\",\n \"avatar_id\": \"your-avatar-id\"\n}\n```\n\n### Agora Configuration\n```json\n{\n \"type\": \"agora_params\",\n \"data\": {\n \"channel\": \"channel-name\",\n \"userId\": 12345,\n \"rtcToken\": \"agora-rtc-token\"\n }\n}\n```\n\n### Audio Data\n```json\n{\n \"type\": \"audio_data\",\n \"data\": \"base64-encoded-pcm-audio\"\n}\n```\n\n### Heartbeat\n```json\n{\n \"type\": \"ping\"\n}\n```\n\n## Development\n\n### Setting up Development Environment\n\n```bash\ngit clone https://github.com/akoolteam/akool-streaming-avatar-sdk.git\ncd akool-streaming-avatar-sdk\npip install -e \".[dev]\"\n```\n\n### Running Tests\n\n```bash\npytest tests/\n```\n\n### Code Formatting\n\n```bash\nblack akool_streaming_avatar/\nflake8 akool_streaming_avatar/\n```\n\n## Examples\n\nSee the `examples/` directory for more usage examples:\n\n- `basic_usage.py` - Simple connection and audio streaming\n- `error_handling.py` - Comprehensive error handling patterns\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests\n5. Run the test suite\n6. Submit a pull request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\nFor support, please contact [support@akool.com](mailto:support@akool.com) or visit our [documentation](https://docs.akool.com).\n\n## Changelog\n\n### v0.1.0 (2024-01-XX)\n\n- Initial release\n- Basic WebSocket connection and authentication\n- Service discovery and load balancing\n- Audio streaming support\n- Retry logic and error handling\n- Event callback system \n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python SDK for Akool Streaming Avatar WebSocket API",
"version": "0.1.4",
"project_urls": {
"Changelog": "https://gitlab.akool.com/algorithm/akool_streaming_avatar_sdk/-/releases",
"Documentation": "https://gitlab.akool.com/algorithm/akool_streaming_avatar_sdk#readme",
"Homepage": "https://gitlab.akool.com/algorithm/akool_streaming_avatar_sdk",
"Issues": "https://gitlab.akool.com/algorithm/akool_streaming_avatar_sdk/-/issues",
"Repository": "https://gitlab.akool.com/algorithm/akool_streaming_avatar_sdk.git"
},
"split_keywords": [
"akool",
" streaming",
" avatar",
" websocket",
" audio",
" real-time"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "c483655e4288158a7d562affc018d8a79294c71f9f9cbfb7909762a2b47d349a",
"md5": "bd4e7e67d46c0d64152b00350a25ab5f",
"sha256": "93230f8b084c09ee4d97c49bbfe6b3bef1e660e62e58298d89b9722de14dae43"
},
"downloads": -1,
"filename": "akool_streaming_avatar-0.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bd4e7e67d46c0d64152b00350a25ab5f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 18320,
"upload_time": "2025-07-24T03:36:57",
"upload_time_iso_8601": "2025-07-24T03:36:57.467742Z",
"url": "https://files.pythonhosted.org/packages/c4/83/655e4288158a7d562affc018d8a79294c71f9f9cbfb7909762a2b47d349a/akool_streaming_avatar-0.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a84791d6d386fbc18e0769be522a54fe1972c8bf97aefff0baa0458d52bd0d84",
"md5": "a6b3ab0af70310889edf365ce312fc57",
"sha256": "a3d1f66bb777f1547d7cd9cbf1df08e361ffa1a1d0f69e8a3aa5a4cc1640bca3"
},
"downloads": -1,
"filename": "akool_streaming_avatar-0.1.4.tar.gz",
"has_sig": false,
"md5_digest": "a6b3ab0af70310889edf365ce312fc57",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 25010,
"upload_time": "2025-07-24T03:36:59",
"upload_time_iso_8601": "2025-07-24T03:36:59.066818Z",
"url": "https://files.pythonhosted.org/packages/a8/47/91d6d386fbc18e0769be522a54fe1972c8bf97aefff0baa0458d52bd0d84/akool_streaming_avatar-0.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-24 03:36:59",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "akool-streaming-avatar"
}