JavaScript
The player puts window.wordplusStreams on the page. Your script shows streams with it, and hears what happens in them.
On the page
Put the player on the page with wordplus_streams()->enqueue(), and make your script depend on its handle, wordplus-streams:
add_action( 'wp_enqueue_scripts', function () {
if ( function_exists( 'wordplus_streams' ) && wordplus_streams()->enqueue() ) {
wp_enqueue_script( 'my-plugin-live', plugins_url( 'live.js', __FILE__ ), array( 'wordplus-streams' ), '1.0.0', true );
}
} );
A page where wordplus_streams()->render() drew a stream has it already.
Showing a stream
wordplusStreams.show()
wordplusStreams.show( element, id, options ): Promise<Stream>
Shows a stream, or a part of it, in the element, asking the site for its config for whoever looks (REST). options are part and its choices, as wordplus_streams()->render() takes them. It rejects with an error whose code says why: not_found, or not_set_up on a page without wordplus_streams()->enqueue().
const stream = await wordplusStreams.show( element, 12, { part: 'player', shape: '9:16' } );
wordplusStreams.mount()
wordplusStreams.mount( element, config ): Stream
The same, from a config your PHP gave the page with wordplus_streams()->config(), with no request to the site:
wp_add_inline_script( 'my-plugin-live', 'window.myLive = ' . wp_json_encode( wordplus_streams()->config( $stream_id, array( 'part' => 'chat' ) ) ) . ';', 'before' );
wordplusStreams.mount( document.querySelector( '#my-chat' ), window.myLive );
wordplusStreams.unmount()
wordplusStreams.unmount( element )
Takes the stream out of the element. With its last part gone from the page, the page leaves the stream. Call it before your screen removes the element.
wordplusStreams.mountAll()
wordplusStreams.mountAll( scope )
Shows the streams in the HTML of wordplus_streams()->render() that your script added to the page after it loaded, within scope (the whole page when not given). The player shows those on the page as it loads by itself.
Streams of your users
A Go live button anywhere: a user makes a stream of their own, then goes live in its studio, right there. Your PHP decides who may, with wordplus_stream_can_create, and what they may choose, with wordplus_stream_user_settings.
wordplusStreams.create()
wordplusStreams.create( { title, settings, object_type, object_id } ): Promise<stream>
Makes a stream the user hosts, and answers it as wordplus_streams()->get() gives it. It rejects with an error whose code says why: wordplus_stream_cannot_create where your PHP does not let them, wordplus_stream_no_title, not_set_up.
button.addEventListener( 'click', async () => {
const made = await wordplusStreams.create( { title: 'Live with ' + myName } );
await wordplusStreams.show( studio, made.id, { part: 'studio' } );
await wordplusStreams.show( player, made.id, { part: 'player' } );
} );
wordplusStreams.update() and wordplusStreams.delete()
wordplusStreams.update( id, { title, settings } ): Promise<stream>
wordplusStreams.delete( id ): Promise<{ deleted, id }>
Changes or deletes a stream of the user, or any stream for an administrator (wordplus_stream_can_edit). Settings not sent stay as they are.
A stream on the page
wordplusStreams.show() and wordplusStreams.mount() answer the stream, one for all its parts on the page. wordplusStreams.stream( 12 ) finds it by its ID, and wordplusStreams.stream( element ) by an element that shows it. Both give null while no part shows it. Its names are those of the stream in the WordPlus apps SDK.
id | Its ID on the site, 12. |
stream | Its ID on the cloud, s12. |
title | Its title. |
state | idle before it starts, live, or ended. |
viewers | How many watch. |
startedAt | When it went live, in milliseconds, or null. |
role | host or viewer for this person, once joined, or null before. |
Events
stream.on( 'state', ( state ) => {
badge.hidden = state !== 'live';
} );
stream.off( 'state' ); // Every listener of the event, or one with stream.off( 'state', fn ).
| Event | Its value |
|---|---|
state | idle, live or ended, as it changes. A stream live as the page joins it tells live. |
viewers | How many watch, as it changes. |
message | Each new message of its light chat: { id, from: { name, avatar, role }, text, at }. |
error | { code }, when it cannot be joined: forbidden (the person may not watch it), not_found, not_licensed, not_included, banned, expired. |
change | Any of these, with the stream. |
Each listener gets the value and the stream. on and off answer the stream, so they chain.
wordplusStreams.on()
wordplusStreams.on( event, listener )
wordplusStreams.off( event, listener )
Hears every stream on the page, those wordplus_streams()->render() drew included, with the same events:
wordplusStreams.on( 'state', ( state, stream ) => {
if ( state === 'live' ) {
myNotify( stream.title + ' is live' );
}
} );
What is live
wordplusStreams.live()
wordplusStreams.live( listener ): () => void
Tells your listener the streams of the site live now, and again each time one goes live or ends, without joining any: [ { stream: 's12', id: 12, viewers: 31, startedAt } ], the latest first. It answers a function that stops it. For a Live badge in a list of your groups, say:
wordplusStreams.live( ( live ) => {
const on = new Set( live.map( ( s ) => s.id ) );
document.querySelectorAll( '[data-stream-id]' ).forEach( ( el ) => {
el.classList.toggle( 'is-live', on.has( Number( el.dataset.streamId ) ) );
} );
} );
It is the way the Live now list hears it: one connection for every tab of the browser, shared with the player and the other WordPlus plugins on the page. Put it on the page with wordplus_streams()->enqueue_live(), and make your script depend on wordplus-streams-live. On a site whose licence does not include live streaming, it tells nothing.
REST
GET /wp-json/wordplus/v1/streams/12/config
The config of a stream for whoever asks, as wordplusStreams.mount() takes it: what wordplusStreams.show() asks for. Logged-in users send the X-WP-Nonce header with a wp_rest nonce. The query takes part and its choices, as wordplus_streams()->render(), and url, the address of the page.
GET /wp-json/wordplus/v1/streams/12/config?part=player&shape=16:9
It answers for anyone, as the page of the stream would: the grant inside it is that of the person asking, and a person who may not watch gets none, so the player says so. 404 for no such stream.
The streams of a user
For logged-in users, with the nonce:
| Route | |
|---|---|
GET /wp-json/wordplus/v1/streams | Their own streams, the newest first: page and per_page. |
POST /wp-json/wordplus/v1/streams | Makes one: title, settings, object_type, object_id. 201 with the stream, 403 where wordplus_stream_can_create does not let them. |
PUT /wp-json/wordplus/v1/streams/12 | Changes it: title, settings. |
DELETE /wp-json/wordplus/v1/streams/12 | Deletes it. |
From an automation
A tool such as Make, Zapier or n8n, or a script of your own, plans the times of a stream with PUT /wp-json/wordplus/v1/streams/12, signed in with a WordPress application password of an administrator, made in Users → Profile → Application Passwords. An application password needs no nonce. The number of the stream is the one in its shortcode, in Streams.
settings.sessions replaces the times planned (sessions): each at, a timestamp or a date and time in the time zone of the site, and its own title. One stream keeps its page, its OBS key and its chat, so an automation that plans a week never makes a stream a day.
curl -X PUT https://example.com/wp-json/wordplus/v1/streams/12 \
--user 'admin:abcd efgh ijkl mnop qrst uvwx' \
--header 'Content-Type: application/json' \
--data '{"settings":{"sessions":[
{"at":"2026-10-26T08:15","title":"Trading room, No. 1121"},
{"at":"2026-10-27T08:15","title":"Trading room, No. 1122"},
{"at":"2026-10-28T08:15","title":"Trading room, No. 1123"}
]}}'
It answers with the stream, its times as timestamps, the soonest first, and scheduled the next one. A time gone by more than 12 hours is left out. From PHP, the same is wordplus_streams()->update( 12, array( 'settings' => array( 'sessions' => $times ) ) ).