akool-streaming-avatar


Nameakool-streaming-avatar JSON
Version 0.1.4 PyPI version JSON
download
home_pageNone
SummaryPython SDK for Akool Streaming Avatar WebSocket API
upload_time2025-07-24 03:36:59
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords akool streaming avatar websocket audio real-time
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 1.42943s