Media Pull (Input Online Media Stream)
Cloud player lifecycle, playback state derivation, region paths, update/destroy, quota and callbacks.
Media Pull pulls external media streams into a channel ("input online media stream"): you give it a URL (RTMP/FLV/HLS/RTSP) and it appears in the channel as a "player user", which other participants subscribe to exactly like a normal user.
Concepts
| Concept | Meaning |
|---|---|
| player | A pull instance: one source stream → one uid in the channel |
| uid / account | The player's identity inside the channel (a numeric uid and an optional account name) |
| playbackUrl | An address that can be played directly once the pull is established (generated by us, for easier debugging) |
| idleTimeout | Seconds after which the player is reclaimed when nobody is watching |
| repeatTime | Number of loops (0 means play forever) |
| seekPosition | Start position (seconds), used to "start playing from second N" |
Creating a player
curl -s -X POST "$NIMEET_API_BASE/cn-shanghai/v1/projects/$APP_ID/cloud-player/players?streamIp=$STREAM_IP" \
-H "Authorization: Basic $NIMEET_CUSTOMER_BASIC" \
-H 'Content-Type: application/json' \
-d '{
"player": {
"streamUrl": "https://example.com/media/weekly-intro.m3u8",
"channelName": "weekly-sync",
"uid": 30001,
"name": "intro-video",
"idleTimeout": 120
}
}'player (required) fields:
| Field | Type | Description | |
|---|---|---|---|
streamUrl | string | Source address; the supported protocols are listed under "Supported sources" below | |
channelName | string | Target channel | |
uid | integer | The uid the player uses inside the channel (do not collide with a real user) | |
account | string \ | null | Optional account name |
name | string \ | null | Business name |
audioOptions / videoOptions | object | Volume, aspect-ratio scaling / cropping, and so on | |
idleTimeout | integer | Idle reclamation seconds | |
playTs / seekPosition | integer | Start timestamp / start position | |
repeatTime | integer | Number of loops |
Lifecycle
| Operation | Endpoint |
|---|---|
| Create | POST /{region}/v1/projects/{appId}/cloud-player/players |
| Query one | GET /{region}/v1/projects/{appId}/cloud-player/players/{id} |
| List | GET /v1/projects/{appId}/cloud-player/players (pageSize/pageToken/filter) |
| Update | PATCH /{region}/v1/projects/{appId}/cloud-player/players/{id} (can set isPause, seekPosition) |
| Destroy | DELETE /{region}/v1/projects/{appId}/cloud-player/players/{id} |
The status is derived from whether the player participant actually exists: running / paused / stopped / failed. It is not an in-memory field that merely "looks like it is playing" — reconcile corrects records that disagree with the media plane.
Supported sources
| Protocol | Description |
|---|---|
| RTMP / RTMPS | Live sources; the lowest latency |
| HTTP-FLV | A common low-latency live distribution format |
| HLS (m3u8) | Works for both on-demand and live; seekPosition is supported for on-demand |
| RTSP | Cameras and security devices; sources that require authentication must be converted by you first |
streamUrl must be an address the server can reach: internal addresses, loopback addresses, and addresses that require an interactive login cannot be pulled. If you need a test source, create a push stream with the 14-streaming-lifecycle.mjs example first, then feed its playback address to the player.
Controlling playback
# pause / resume
curl -s -X PATCH "$NIMEET_API_BASE/cn-shanghai/v1/projects/$APP_ID/cloud-player/players/$PLAYER_ID?sequence=1" \
-H "Authorization: Basic $NIMEET_CUSTOMER_BASIC" \
-H 'Content-Type: application/json' \
-d '{"player":{"isPause":true}}'
# seek to second 60
curl -s -X PATCH "$NIMEET_API_BASE/cn-shanghai/v1/projects/$APP_ID/cloud-player/players/$PLAYER_ID" \
-H "Authorization: Basic $NIMEET_CUSTOMER_BASIC" \
-H 'Content-Type: application/json' \
-d '{"player":{"seekPosition":60}}'sequence is optimistic concurrency control: on a concurrent update the stale sequence gets 409; GET the resource again and resubmit.
Relationship with other participants in the channel
- A player is a participant that only publishes and never subscribes;
- Real users can subscribe to it normally (the
user-publishedevent fires as usual, withmediaTypebeingaudio/video); - Recording (composite) mixes it into the layout like any other video — which is exactly how you get "intro video + real people" in one stream;
- Destroying a player is equivalent to that user leaving the channel, and triggers
user-left.
Quotas and errors
| Scenario | Response |
|---|---|
| Service not enabled | 403 service_not_enabled |
| Concurrent players exceeded (500) | 429 streaming_quota_exceeded |
| Unreachable source address | Creation succeeds, but status turns into a failure state on query and the error field explains why |
| uid duplicates an existing player | Not rejected (players with the same uid may coexist in practice). Two participants sharing a uid make subscribers unable to tell them apart, so guarantee uniqueness yourself |
End-to-end example
node run-all.mjs --only=1919-media-pull-lifecycle.mjs: create → query → pause → resume → destroy (writes, so it needs --all).