Skill: kuangxinyi/xiaopai-player-control (version 1.1.0)

--- name: xiaopai-player-control version: 1.1.0 license: MIT-0 description: Control XiaoPai media player over LAN via HTTP/TCP. Use when asked to play videos, send remote-control keys, adjust volume, or query playback status on a XiaoPai player. --- # XiaoPai Player Control Control a XiaoPai (小湃) media player on the local network through its TCP/IP protocol. ## Prerequisites - The player and this machine must be on the **same LAN**. - The player's `PlayerSupportApp` service must be running (auto-starts on boot). - You need the player's **IP address**. Use auto-discovery (below) or ask the user. ## Discover Player The player registers an mDNS service (`_xiaopai._tcp`) on the LAN. Use any of these methods to find it without knowing the IP: **Linux/macOS:** ```bash # Browse for players (Bonjour) dns-sd -B _xiaopai._tcp . # Or with Avahi avahi-browse -r _xiaopai._tcp # Resolve a found service to get IP and port dns-sd -L "<serviceName>" _xiaopai._tcp . ``` **Windows:** ```bash # Requires Bonjour SDK or iTunes installed dns-sd -B _xiaopai._tcp . ``` **Python (cross-platform):** ```python python3 -c " from zeroconf import ServiceBrowser, Zeroconf, ServiceStateChange import time def on_change(zc, type_, name, state_change): if state_change is ServiceStateChange.Added: info = zc.get_service_info(type_, name) if info: from ipaddress import ip_address ip = ip_address(info.addresses[0]) print(f'{info.server} -> {ip}:{info.port} tcp_port={info.properties.get(b\"tcp_port\",b\"\").decode()}') zc = Zeroconf() ServiceBrowser(zc, '_xiaopai._tcp.local.', handlers=[on_change]) time.sleep(3) zc.close() " ``` **Using the helper script:** ```bash xiaopai-ctl.sh discover ``` The discovered service includes: - **port** (`9050`): HTTP control port - **TXT `tcp_port`** (`9051`): TCP status port - **TXT `mac`**: Device MAC address ## Available Operations ### 1. Send Remote-Control Key Send any remote-control button press to the player. ``` curl "http://{IP}:9050/xiaopai/sendkey?keycode={KEYCODE}" ``` **Response:** - Success: `{"code":0,"msg":"success"}` - Unknown key: `{"code":1,"msg":"unknown keycode"}` **Supported keycodes** (3-character uppercase): | Category | Keycodes | Description | |----------|----------|-------------| | Power | `POW` `OFF` | Power toggle / Power off | | Numbers | `NU0`-`NU9` | Number keys 0-9 | | Navigation | `NUP` `NDN` `NLT` `NRT` `SEL` | Up / Down / Left / Right / OK | | Menu | `OSD` `RET` `HOM` `SET` `MNU` `TOP` | Menu / Back / Home / Settings / PopMenu / TopMenu | | Volume | `VUP` `VDN` `MUT` | Volume Up / Volume Down / Mute | | Playback | `PLA` `PAU` `PLU` `STP` | Play-Pause toggle / Pause / Play / Stop | | Seek | `FWD` `REV` | Fast Forward / Rewind | | Track | `PRE` `NXT` | Previous / Next | | Info | `INF` `DEL` `DOT` | Info / Delete / Dot | | Audio | `AUD` `SUB` `RPT` | Audio Track / Subtitle / Repeat | | Color | `RED` `GRE` `YEL` `BLU` | Red / Green / Yellow / Blue | | Navigate | `MOV` `MUS` `FIL` | Movie page / Music page / File page | | DAC | `DAC` `DON` `DOF` | DAC switch / DAC on / DAC off | | System | `RBT` `RES` `SCR` | Reboot / Resolution / Screenshot | ### 2. Play Video by File Path or URL ``` curl "http://{IP}:9050/xiaopai/play?videopath={URL_ENCODED_PATH}" ``` - `videopath`: file path or URL of the video, **must be URL-encoded**. - Success: `{"code":0,"msg":"success"}` **Example:** ```bash curl "http://192.168.1.100:9050/xiaopai/play?videopath=http%3A%2F%2Fexample.com%2Fmovie.mp4" ``` ### 3. Play Video by Name (Search Library) ``` curl "http://{IP}:9050/xiaopai/play?videoname={URL_ENCODED_NAME}" ``` - `videoname`: movie/video name (Chinese or English), **must be URL-encoded**. - The player searches its local library; if a match is found, playback starts immediately. - Success: `{"code":0,"msg":"success"}` **Example:** ```bash # Play "战狼" (Wolf Warriors) curl "http://192.168.1.100:9050/xiaopai/play?videoname=%E6%88%98%E7%8B%BC" ``` ### 4. Query Player Status (TCP) Connect to TCP port 9051 to receive real-time player state. ```bash # One-shot status query (timeout after 2 seconds) echo "" | nc -w 2 {IP} 9051 ``` **Response JSON:** ```json { "code": 0, "msg": "success", "deviceName": "XiaoPai Player", "mac": "AA:BB:CC:DD:EE:FF", "ipAddress": "192.168.1.100", "playStatus": 1, "playType": 1 } ``` **Field reference:** | Field | Values | |-------|--------| | `playStatus` | `-1` = Stopped, `1` = Playing, `2` = Paused | | `playType` | `1` = Video, `2` = Music | The TCP connection stays open; the player pushes updated JSON whenever playback state changes. ## Common Workflows ### Play a movie by name ```bash curl "http://{IP}:9050/xiaopai/play?videoname={URL_ENCODED_NAME}" ``` ### Pause / Resume playback ```bash curl "http://{IP}:9050/xiaopai/sendkey?keycode=PLA" ``` ### Adjust volume up ```bash curl "http://{IP}:9050/xiaopai/sendkey?keycode=VUP" ``` ### Navigate home screen ```bash curl "http://{IP}:9050/xiaopai/sendkey?keycode=HOM" ``` ### Check if something is playing, then stop it ```bash STATUS=$(echo "" | nc -w 2 {IP} 9051) # Parse playStatus from JSON; if playing, send stop curl "http://{IP}:9050/xiaopai/sendkey?keycode=STP" ``` ## Guidelines - Always URL-encode Chinese characters and special characters in `videopath` and `videoname` parameters. - Use `PLA` to toggle play/pause. Use `PLU` for explicit play, `PAU` for explicit pause. - `RBT` will reboot the device — confirm with the user before sending. - The player responds to HTTP within milliseconds; no polling or retry is needed. - If the user doesn't know the player IP, use mDNS discovery (`dns-sd -B _xiaopai._tcp .` or the `discover` command) to find it automatically. - For rapid key sequences (e.g., navigating menus), add a short delay (~500ms) between commands.

path: scripts/xiaopai-ctl.sh
content: #!/usr/bin/env bash
# xiaopai-ctl.sh — CLI helper for controlling XiaoPai media player
# Usage:
#   xiaopai-ctl.sh discover                          Discover players on LAN
#   xiaopai-ctl.sh <IP> sendkey <KEYCODE>
#   xiaopai-ctl.sh <IP> play-path <video_path_or_url>
#   xiaopai-ctl.sh <IP> play-name <video_name>
#   xiaopai-ctl.sh <IP> status

set -euo pipefail

usage() {
    echo "Usage:"
    echo "  $0 discover                          Discover players on LAN via mDNS"
    echo "  $0 <IP> sendkey <KEYCODE>          Send remote key (e.g. PLA, VUP, HOM)"
    echo "  $0 <IP> play-path <path_or_url>    Play video by file path or URL"
    echo "  $0 <IP> play-name <name>           Play video by name (search library)"
    echo "  $0 <IP> status                     Query current playback status"
    exit 1
}

[ $# -lt 1 ] && usage

# --- discover subcommand (no IP required) ---
if [ "$1" = "discover" ]; then
    echo "Searching for XiaoPai players on LAN (_xiaopai._tcp) ..."
    if command -v dns-sd >/dev/null 2>&1; then
        echo "(Press Ctrl+C to stop)"
        dns-sd -B _xiaopai._tcp .
    elif command -v avahi-browse >/dev/null 2>&1; then
        avahi-browse -r _xiaopai._tcp --terminate
    elif command -v python3 >/dev/null 2>&1; then
        python3 -c "
from zeroconf import ServiceBrowser, Zeroconf, ServiceStateChange
import time, sys

found = []

def on_change(zc, type_, name, state_change):
    if state_change is ServiceStateChange.Added:
        info = zc.get_service_info(type_, name)
        if info:
            from ipaddress import ip_address
            ip = ip_address(info.addresses[0])
            tcp_port = info.properties.get(b'tcp_port', b'').decode()
            mac = info.properties.get(b'mac', b'').decode()
            print(f'  {info.server}  IP={ip}  HTTP={info.port}  TCP={tcp_port}  MAC={mac}')
            found.append(True)

zc = Zeroconf()
ServiceBrowser(zc, '_xiaopai._tcp.local.', handlers=[on_change])
time.sleep(5)
zc.close()
if not found:
    print('  No players found.')
" 2>/dev/null || echo "Error: install zeroconf (pip3 install zeroconf) or use dns-sd / avahi-browse."
    else
        echo "Error: no mDNS tool found. Install one of:"
        echo "  - dns-sd  (macOS built-in, or Bonjour SDK on Windows)"
        echo "  - avahi-browse  (apt install avahi-utils)"
        echo "  - python3 + zeroconf  (pip3 install zeroconf)"
        exit 1
    fi
    exit 0
fi

[ $# -lt 2 ] && usage

IP="$1"
CMD="$2"
BASE="http://${IP}:9050"

url_encode() {
    python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$1" 2>/dev/null \
    || python -c "import urllib,sys; print(urllib.quote(sys.argv[1]))" "$1" 2>/dev/null \
    || printf '%s' "$1"
}

case "$CMD" in
    sendkey)
        [ $# -lt 3 ] && { echo "Error: missing KEYCODE"; usage; }
        KEYCODE=$(echo "$3" | tr '[:lower:]' '[:upper:]')
        curl -s "${BASE}/xiaopai/sendkey?keycode=${KEYCODE}"
        echo
        ;;
    play-path)
        [ $# -lt 3 ] && { echo "Error: missing video path"; usage; }
        ENCODED=$(url_encode "$3")
        curl -s "${BASE}/xiaopai/play?videopath=${ENCODED}"
        echo
        ;;
    play-name)
        [ $# -lt 3 ] && { echo "Error: missing video name"; usage; }
        ENCODED=$(url_encode "$3")
        curl -s "${BASE}/xiaopai/play?videoname=${ENCODED}"
        echo
        ;;
    status)
        echo "" | nc -w 2 "$IP" 9051 2>/dev/null || echo '{"error":"connection failed"}'
        ;;
    *)
        echo "Unknown command: $CMD"
        usage
        ;;
esac


path: references/protocol.md
content: # XiaoPai Player TCP/IP Protocol Reference

## Overview

The XiaoPai (小湃) media player exposes two network services for external control:

- **HTTP Server** on port **9050** — accepts commands and playback requests
- **TCP Server** on port **9051** — pushes real-time playback state to connected clients

Both services are provided by the `PlayerSupportApp` Android system service, which starts automatically on device boot.

## HTTP Server (Port 9050)

Base URL: `http://{PLAYER_IP}:9050`

All responses are `application/json; charset=utf-8` with HTTP 200 status.

### GET /xiaopai/sendkey

Send a remote-control key command.

| Parameter | Type   | Required | Description |
|-----------|--------|----------|-------------|
| keycode   | string | Yes      | 3-character uppercase command code |

**Response:**
```json
{"code":0,"msg":"success"}
```

**Error (unknown keycode):**
```json
{"code":1,"msg":"unknown keycode"}
```

### GET /xiaopai/play

Play a video by file path/URL or by name search.

| Parameter | Type   | Required | Description |
|-----------|--------|----------|-------------|
| videopath | string | No*      | Video file path or URL (URL-encoded) |
| videoname | string | No*      | Video name for library search (URL-encoded) |

*At least one of `videopath` or `videoname` must be provided. If both are present, `videoname` takes priority.

**Response:**
```json
{"code":0,"msg":"success"}
```

**Error:**
```json
{"code":1,"msg":"play failed"}
```

## TCP Server (Port 9051)

Plain TCP, line-delimited JSON.

### Connection Flow

1. Client connects via TCP to port 9051
2. Server immediately sends current state as a JSON line
3. Connection stays open
4. Whenever playback state changes, server pushes updated JSON to all clients
5. Connection closes when client disconnects

### State JSON Format

```json
{
  "code": 0,
  "msg": "success",
  "deviceName": "Model Name",
  "mac": "AA:BB:CC:DD:EE:FF",
  "ipAddress": "192.168.1.100",
  "playStatus": 1,
  "playType": 1
}
```

### Field Definitions

| Field       | Type    | Values |
|-------------|---------|--------|
| code        | int     | `0` = success |
| msg         | string  | `"success"` |
| deviceName  | string  | Android Build.MODEL |
| mac         | string  | MAC address (XX:XX:XX:XX:XX:XX) |
| ipAddress   | string  | IPv4 address |
| playStatus  | int     | `-1` = Stopped, `1` = Playing, `2` = Paused |
| playType    | int     | `1` = Video, `2` = Music |

## Complete Keycode Reference

### Power
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| POW  | Power toggle | KEYCODE_POWER |
| OFF  | Power off | KEYCODE_POWER |

### Number Keys
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| NU0  | Number 0 | KEYCODE_0 |
| NU1  | Number 1 | KEYCODE_1 |
| NU2  | Number 2 | KEYCODE_2 |
| NU3  | Number 3 | KEYCODE_3 |
| NU4  | Number 4 | KEYCODE_4 |
| NU5  | Number 5 | KEYCODE_5 |
| NU6  | Number 6 | KEYCODE_6 |
| NU7  | Number 7 | KEYCODE_7 |
| NU8  | Number 8 | KEYCODE_8 |
| NU9  | Number 9 | KEYCODE_9 |
| DOT  | Dot (.) | KEYCODE_NUMPAD_DOT |

### Navigation
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| NUP  | Up | KEYCODE_DPAD_UP |
| NDN  | Down | KEYCODE_DPAD_DOWN |
| NLT  | Left | KEYCODE_DPAD_LEFT |
| NRT  | Right | KEYCODE_DPAD_RIGHT |
| SEL  | OK / Confirm | KEYCODE_DPAD_CENTER |

### Menu
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| OSD  | Menu | KEYCODE_MENU |
| RET  | Back / Return | KEYCODE_BACK |
| HOM  | Home | KEYCODE_HOME |
| SET  | Settings | Launches Settings Activity |
| MNU  | Pop Menu | (reserved) |
| TOP  | Top Menu | KEYCODE_MEDIA_TOP_MENU |

### Volume
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| VUP  | Volume Up | KEYCODE_VOLUME_UP |
| VDN  | Volume Down | KEYCODE_VOLUME_DOWN |
| MUT  | Mute | KEYCODE_VOLUME_MUTE |

### Playback Control
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| PLA  | Play/Pause toggle | KEYCODE_MEDIA_PLAY_PAUSE |
| PAU  | Pause | KEYCODE_MEDIA_PAUSE |
| PLU  | Play | KEYCODE_MEDIA_PLAY |
| STP  | Stop | KEYCODE_MEDIA_STOP |
| FWD  | Fast Forward | KEYCODE_MEDIA_FAST_FORWARD |
| REV  | Rewind | KEYCODE_MEDIA_REWIND |
| PRE  | Previous / Page Up | KEYCODE_PAGE_UP |
| NXT  | Next / Page Down | KEYCODE_PAGE_DOWN |

### Information
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| INF  | Video Info | KEYCODE_INFO |
| DEL  | Delete | KEYCODE_DEL |

### Audio & Subtitle
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| AUD  | Audio Track | KEYCODE_MEDIA_AUDIO_TRACK |
| SUB  | Subtitle | keycode 2019 |
| RPT  | Repeat Mode | (reserved) |

### Color Keys
| Code | Function | Android KeyEvent |
|------|----------|-----------------|
| RED  | Red | KEYCODE_PROG_RED |
| GRE  | Green | KEYCODE_PROG_GREEN |
| YEL  | Yellow | KEYCODE_PROG_YELLOW |
| BLU  | Blue | KEYCODE_PROG_BLUE |

### Content Pages
| Code | Function | Note |
|------|----------|------|
| MOV  | Movie page | (reserved) |
| MUS  | Music page | (reserved) |
| FIL  | File page | (reserved) |

### DAC Control
| Code | Function | Note |
|------|----------|------|
| DAC  | DAC switch | (reserved) |
| DON  | DAC on | (reserved) |
| DOF  | DAC off | (reserved) |

### System
| Code | Function | Note |
|------|----------|------|
| RBT  | Reboot | PowerManager.reboot() — DESTRUCTIVE |
| RES  | Resolution | KEYCODE_TV_ZOOM_MODE |
| SCR  | Screenshot | (reserved) |


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)