Skip to main content

Example: live events

Say your plugin has events, a post type event, and keeps the people going to each in its attendees meta, a list of user IDs. This plugin gives each event a live stream:

  • Made with the event, once it is published, and named after it. It goes when the event is deleted.
  • Hosted by its organiser, the author of the event, who goes live on the page of the event. No access to the WordPress admin is needed.
  • Watched by the people going, and nobody else but the administrators.
  • On the page of the event, under its text: the player with the chat beside it, and the studio over them for the organiser.
  • A Live badge beside the event in the list of events, the moment its stream goes live and gone when it ends.
<?php
/**
* Plugin Name: Live events
* Description: A live stream for each event, hosted by its organiser and watched by the people going.
*/

defined( 'ABSPATH' ) || exit;

// Each event gets a stream once it is published, named after it.
add_action( 'save_post_event', function ( $event_id, $event ) {
if ( ! function_exists( 'wordplus_streams' ) || 'publish' !== $event->post_status ) {
return;
}

$stream = wordplus_streams()->get_object_stream( 'live_event', $event_id );

if ( $stream ) {
wordplus_streams()->update( $stream['id'], array( 'title' => $event->post_title ) );
return;
}

wordplus_streams()->create( array(
'title' => $event->post_title,
'object_type' => 'live_event',
'object_id' => $event_id,
'settings' => array( 'access' => 'logged_in', 'chat_with' => 'light' ),
) );
}, 10, 2 );

// The stream goes with its event.
add_action( 'before_delete_post', function ( $event_id ) {
if ( function_exists( 'wordplus_streams' ) && 'event' === get_post_type( $event_id ) ) {
$stream = wordplus_streams()->get_object_stream( 'live_event', $event_id );

if ( $stream ) {
wordplus_streams()->delete( $stream['id'] );
}
}
} );

// The organiser of an event, its author, hosts its stream.
add_filter( 'wordplus_stream_can_host', function ( $can, $stream_id, $user_id ) {
$event = wordplus_streams()->get_object( $stream_id );

if ( $event && 'live_event' === $event['type'] ) {
return (int) get_post_field( 'post_author', $event['id'] ) === $user_id;
}

return $can;
}, 10, 3 );

// Only the people going to an event watch its stream.
add_filter( 'wordplus_stream_can_watch', function ( $can, $rules, $user_id, $stream_id ) {
$event = wordplus_streams()->get_object( $stream_id );

if ( $event && 'live_event' === $event['type'] ) {
$going = array_filter( array_map( 'intval', (array) get_post_meta( $event['id'], 'attendees', true ) ) );

return $can && in_array( $user_id, $going, true );
}

return $can;
}, 10, 4 );

// The stream under the text of the event, and the studio over it for the organiser.
add_filter( 'the_content', function ( $content ) {
if ( ! function_exists( 'wordplus_streams' ) || ! is_singular( 'event' ) || get_the_ID() !== get_queried_object_id() ) {
return $content;
}

$stream = wordplus_streams()->get_object_stream( 'live_event', get_the_ID() );

if ( ! $stream ) {
return $content;
}

if ( wordplus_streams()->user_can( $stream['id'], 'host' ) ) {
$content .= wordplus_streams()->render( $stream['id'], array( 'part' => 'studio' ) );
}

return $content . wordplus_streams()->render( $stream['id'], array( 'layout' => 'side' ) );
} );

// In the list of events, a Live badge beside each event whose stream is live.
add_filter( 'the_title', function ( $title, $post_id = 0 ) {
if ( is_admin() || ! is_post_type_archive( 'event' ) || 'event' !== get_post_type( $post_id ) || ! function_exists( 'wordplus_streams' ) ) {
return $title;
}

$stream = wordplus_streams()->get_object_stream( 'live_event', (int) $post_id );

return $stream ? $title . ' <span class="live-event-badge" data-stream-id="' . (int) $stream['id'] . '" hidden>' . esc_html__( 'Live', 'live-events' ) . '</span>' : $title;
}, 10, 2 );

add_action( 'wp_enqueue_scripts', function () {
if ( ! function_exists( 'wordplus_streams' ) || ! is_post_type_archive( 'event' ) || ! wordplus_streams()->enqueue_live() ) {
return;
}

wp_add_inline_script( 'wordplus-streams-live', 'wordplusStreams.live( function ( live ) {
var on = live.map( function ( stream ) { return stream.id; } );

document.querySelectorAll( ".live-event-badge" ).forEach( function ( badge ) {
badge.hidden = on.indexOf( Number( badge.getAttribute( "data-stream-id" ) ) ) === -1;
} );
} );' );
} );

How it works​

  • The stream of each event is made for it (object_type and object_id), so wordplus_streams()->get_object_stream() finds it again, and the hooks read what it was made for with wordplus_streams()->get_object(). Streams made for other things, and those the administrators make, keep their own rules.
  • Its settings keep visitors out (logged_in), and give it the light chat. Everything else is as in a new stream, and administrators can change it in Live Stream → Studio → Streams, where the stream shows like any other.
  • The organiser gets the studio on the page of the event, since wordplus_streams()->user_can() says they host the stream. They go live there with the camera, a screen or OBS, and nobody else sees the studio.
  • Someone not going sees You do not have access to this stream in the player.
  • The badge hears the streams of the site going live and ending (wordplusStreams.live), on one connection for every tab of the browser, without joining any.

Going further​

  • A screen your script draws: show the stream with wordplusStreams.show(), and hear when it goes live and how many watch from the stream it answers.
  • A tab of its own: put the parts where you like with wordplus_streams()->render(): the player in the main column and the chat in a sidebar, say.
  • A chat of your own: set chat to false and show yours beside the player.
  • When it starts: set scheduled from the date of the event, and the player counts down to it, with Add to calendar.