PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / 4.14.1
WpStream – Live Streaming, Video on Demand, Pay Per View v4.14.1
4.14.1 4.14.0 4.13.2 4.13.1 4.13 4.12.5 4.12.4 4.12.3 4.12.2 4.12.1 4.12 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5 4.5.1 4.5.11 4.5.11.1 4.5.11.2 4.5.11.4 4.5.11.5 4.5.11.6 All 181 releases
wpstream / templates / broadcaster-template.php

broadcaster-template.php in WpStream – Live Streaming, Video on Demand, Pay Per View 4.14.1, at templates/broadcaster-template.php

281 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Template Name: WPStream OvenLiveKit Broadcaster
4 *
5 * Full-page browser broadcaster. Loads OvenLiveKit (WebRTC/WHIP publisher),
6 * captures the user's camera/microphone, and streams to the channel's WHIP URL.
7 * Access is gated to logged-in users who can publish, and requires a valid
8 * `channel_id` query var whose post meta supplies the OBS/WHIP credentials.
9 *
10 * @package Wpstream
11 * @subpackage Wpstream/templates
12 */
13
14 // Security check
15 // Block direct file access outside of WordPress.
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 // Must be logged in to open the broadcaster at all.
21 if (!is_user_logged_in()) {
22 wp_die(__('You do not have sufficient permissions to access this page.', 'wpstream'));
23 }
24
25 // Get channel ID from URL parameter
26 // The channel to broadcast to is passed as a query var; abort if absent.
27 $channel_id = get_query_var('channel_id');
28 if (empty($channel_id)) {
29 wp_die(__('No channel ID specified.', 'wpstream'));
30 }
31
32 // Ownership gate: only the channel's author (or an admin) may open the
33 // broadcaster and receive its RTMP/WHIP publishing credentials. This replaces
34 // the old broad publish_posts check, which both let any author-level user load
35 // another broadcaster's credentials for an arbitrary channel id AND wrongly
36 // blocked legitimate subscriber-streamers who own their own channel.
37 if ( ! wpstream_can_manage_channel( get_current_user_id(), intval( $channel_id ) ) ) {
38 wp_die(__('You do not have sufficient permissions to access this page.', 'wpstream'));
39 }
40
41 // Get stream information from post meta
42 // Pull the publishing credentials stored against the channel post.
43 $obs_uri = get_post_meta($channel_id, 'obs_uri', true); // RTMP/OBS ingest URI
44 $obs_stream = get_post_meta($channel_id, 'obs_stream', true); // OBS stream key
45 $whip_url = get_post_meta($channel_id, 'whipUrl', true); // WebRTC WHIP endpoint
46
47 // Detect whether this page was opened as part of the onboarding wizard flow.
48 $onboarding = isset( $_GET['onboarding'] ) ? sanitize_text_field( wp_unslash( $_GET['onboarding'] ) ) : '';
49 $is_onboarding = ( $onboarding === '1' );
50
51 // A WHIP URL is mandatory for WebRTC publishing; abort if it is missing.
52 if (empty($whip_url)) {
53 wp_die(__('WHIP URL not available for this channel.', 'wpstream'));
54 }
55
56 // Enqueue the broadcaster stylesheet (cache-busted by file mtime).
57 wp_enqueue_style(
58 'wpstream-broadcaster-css',
59 WPSTREAM_PLUGIN_DIR_URL . 'public/css/broadcaster.css',
60 array(),
61 filemtime(WPSTREAM_PLUGIN_PATH . 'public/css/broadcaster.css')
62 );
63
64 // Enqueue the broadcaster JS (loaded in the footer, cache-busted by mtime).
65 wp_enqueue_script(
66 'wpstream-broadcaster-new',
67 WPSTREAM_PLUGIN_DIR_URL . 'public/js/broadcaster.js',
68 array(),
69 filemtime(WPSTREAM_PLUGIN_PATH . 'public/js/broadcaster.js'),
70 true
71 );
72
73 // Pass server-side config and localized strings to the broadcaster script.
74 wp_localize_script(
75 'wpstream-broadcaster-new',
76 'wpstream_broadcaster_vars',
77 array(
78 'ajax_url' => admin_url('admin-ajax.php'), // admin-ajax endpoint
79 'nonce' => wp_create_nonce('wpstream_broadcaster_nonce'), // CSRF token for AJAX calls
80 'plugin_url' => plugin_dir_url(__FILE__), // base URL for asset lookups
81 'obs_uri' => $obs_uri, // RTMP ingest URI
82 'obs_stream' => $obs_stream, // OBS stream key
83 'channel_id' => $channel_id, // channel being broadcast to
84 'whip_url' => get_post_meta($channel_id, 'whipUrl', true), // WebRTC WHIP publish endpoint
85 'no_video_audio_access' => esc_html__('We couldn’t access your camera or microphone. Please allow permissions and reload the page.', 'wpstream'),
86 'no_audio_access' => esc_html__('We couldn’t access your microphone. Please allow permissions and reload the page.', 'wpstream'),
87 'no_video_access' => esc_html__('We couldn’t access your camera. Please allow permissions and reload the page.', 'wpstream'),
88 'channel_off' => esc_html__('Invalid event. Your live event may have expired or its credentials are incorrect.', 'wpstream'),
89 'not_enough_traffic' => sprintf(
90 esc_html__('Not enough streaming traffic to broadcast. Please %supgrade your subscription%s for extra resources.', 'wpstream'),
91 '<a href="https://wpstream.net/pricing/" target="_blank">',
92 '</a>'
93 ),
94 'is_onboarding' => $is_onboarding, // whether we are inside the onboarding wizard
95 // Gates the broadcaster's diagnostic console logging: silent in production,
96 // verbose only when the site opts into SCRIPT_DEBUG.
97 'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
98 )
99 );
100
101 // When launched from onboarding, also load the wizard's page script + telemetry vars.
102 if ( $is_onboarding ) {
103 wp_enqueue_script('wpstream-on-boarding-page-js', plugin_dir_url( __DIR__ ) .'admin/js/wpstream-onboarding-page.js',array(), WPSTREAM_PLUGIN_VERSION, true);
104 wp_localize_script( 'wpstream-on-boarding-page-js', 'wpstream_onboarding_page_vars',
105 array(
106 'admin_url' => get_admin_url(),
107 'request_url' => WPSTREAM_CLICK,
108 'wps_user' => get_option('wpstream_api_username_from_token'),
109 'current_page' => 'broadcaster',
110 'plugin_version' => WPSTREAM_PLUGIN_VERSION,
111 )
112 );
113 }
114
115 ?>
116 <!DOCTYPE html>
117 <html <?php language_attributes(); ?>>
118 <head>
119 <meta charset="<?php bloginfo('charset'); ?>">
120 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
121 <title><?php esc_html_e('WpStream Broadcaster', 'wpstream'); ?></title>
122
123 <?php wp_head(); ?>
124
125 <!-- Load the bundled libraries in correct order (underscore first, then the WHIP publisher) -->
126 <script src="<?php echo esc_url( WPSTREAM_PLUGIN_DIR_URL . 'public/js/vendor/underscore-min.js?ver=1.12.0' ); ?>"></script>
127 <script src="<?php echo esc_url( WPSTREAM_PLUGIN_DIR_URL . 'public/js/vendor/OvenLiveKit.min.js?ver=1.5.8' ); ?>"></script>
128 </head>
129 <body class="wpstream-broadcaster-page">
130 <?php
131 /**
132 * Fires at the top of the broadcaster page body, before the header.
133 *
134 * @since 4.14.0
135 *
136 * @param int $channel_id Channel being broadcast to.
137 * @param bool $is_onboarding Whether the page was opened from the onboarding wizard.
138 */
139 do_action( 'wpstream_before_broadcaster_page', intval( $channel_id ), $is_onboarding );
140 ?>
141 <!-- Top bar: WpStream logo (links home) and a static section label. -->
142 <header class="broadcaster-header">
143 <div class="header-container">
144 <div class="header-logo">
145 <a href="<?php echo esc_url(home_url('/')); ?>">
146 <img src="<?php echo esc_url(WPSTREAM_PLUGIN_DIR_URL . 'img/wpstream-logo.svg'); ?>" alt="WpStream Logo">
147 </a>
148 </div>
149 <nav class="header-nav">
150 <span class="nav-item">Browser Broadcaster</span>
151 </nav>
152 </div>
153 </header>
154
155 <!-- JS injects status/error banners into this container. -->
156 <div id="messageContainer"></div>
157
158 <!-- Main layout: live preview on the left, capture settings panel on the right. -->
159 <div class="broadcaster-container">
160 <div class="wrapper">
161 <!-- Local camera preview, expand toggle, and LIVE/Connecting indicators. -->
162 <div class="video-container">
163 <button id="videoExpandToggle" class="video-expand-toggle" type="button" aria-label="<?php esc_attr_e('Toggle Full View', 'wpstream'); ?>" title="<?php esc_attr_e('Toggle Full View', 'wpstream'); ?>">
164 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>
165 </button>
166 <div id="videoLiveIndicator" class="video-live-indicator">
167 <span id="videoLiveIndicatorLive" class="badge badge-pill badge-danger" style="display:none;"><?php esc_html_e('LIVE', 'wpstream'); ?></span>
168 <span id="videoLiveIndicatorError" class="badge badge-pill badge-warning" style="display:none;"><?php esc_html_e('Connecting...', 'wpstream'); ?></span>
169 </div>
170 <!-- Muted, autoplaying local video element the capture stream is attached to. -->
171 <div class="video-wrapper">
172 <div id="wpstream-pre-load-spinner" class="wpstream-pre-load-spinner"></div>
173 <video id="localVideo" autoplay muted playsinline></video>
174 </div>
175 </div>
176
177 <!-- Settings panel: start/stop controls, connection status, and device pickers. -->
178 <div class="settings-panel" id="settingsPanel">
179 <?php
180 /**
181 * Fires at the top of the broadcaster settings panel, before the start/stop controls.
182 *
183 * @since 4.14.0
184 *
185 * @param int $channel_id Channel being broadcast to.
186 */
187 do_action( 'wpstream_broadcaster_settings_panel_before', intval( $channel_id ) );
188 ?>
189 <div>
190 <?php
191 // Nonce consumed by the AJAX call that starts the live event.
192 $ajax_nonce = wp_create_nonce( "wpstream_start_event_nonce" );
193 print '<input type="hidden" id="wpstream_start_event_nonce" value="'.$ajax_nonce.'">';
194 ?>
195 <!-- Start begins publishing; Stop (hidden until live) ends the broadcast. -->
196 <div class="controls-container">
197 <button id="startBroadcast" class="button start-broadcast" disabled><?php esc_html_e('Start Broadcast', 'wpstream'); ?></button>
198 <button id="stopBroadcast" class="button stop-broadcast hidden"><?php esc_html_e('Stop Broadcast', 'wpstream'); ?></button>
199 </div>
200 <!-- Text + colored dot reflecting the current connection state. -->
201 <div class="status-container">
202 <div>
203 <span class="status-indicator" id="statusIndicator"></span>
204 <span id="statusText"><?php esc_html_e('Not connected', 'wpstream'); ?></span>
205 </div>
206 </div>
207 </div>
208
209 <!-- Device selection row: camera and microphone pickers with on/off toggles. -->
210 <div class="settings-row media-row">
211 <!-- Video source picker; JS fills the <select> with enumerated cameras. -->
212 <div class="settings-group">
213 <label for="videoDevice"><?php esc_html_e('Video Source', 'wpstream'); ?></label>
214 <div class="controls-group">
215 <select id="videoDevice">
216 <option selected></option>
217 </select>
218 <button id="videoToggle" class="control-button">
219 <img alt="" class="noll" id="video-off" src="<?php echo esc_url(WPSTREAM_PLUGIN_DIR_URL . 'img/videocam-32px.svg'); ?>">
220 <img alt="" class="noll" id="video-on" src="<?php echo esc_url(WPSTREAM_PLUGIN_DIR_URL . 'img/videocam-off-32px.svg'); ?>" style="display:none;">
221 </button>
222 </div>
223 </div>
224
225 <!-- Audio source picker; JS fills the <select> with enumerated microphones. -->
226 <div class="settings-group">
227 <label for="audioDevice"><?php esc_html_e('Audio Source', 'wpstream'); ?></label>
228 <div class="controls-group">
229 <select id="audioDevice">
230 <option selected></option>
231 </select>
232 <button id="audioToggle" class="control-button">
233 <img alt="" class="noll" id="audio-off" src="<?php echo esc_url( WPSTREAM_PLUGIN_DIR_URL . 'img/mic-32px.svg' ); ?>">
234 <img alt="" class="noll" id="audio-on" src="<?php echo esc_url( WPSTREAM_PLUGIN_DIR_URL . 'img/mic-off-32px.svg' ); ?>" style="display:none;">
235 </button>
236 </div>
237 </div>
238 </div>
239
240 <!-- Resolution row: preset capture sizes passed to getUserMedia constraints. -->
241 <div class="settings-row">
242 <div class="settings-group">
243 <label for="videoQuality"><?php esc_html_e('Video Resolution', 'wpstream'); ?></label>
244 <select id="videoQuality">
245 <option selected value="default">Default</option>
246 <option value="fhd"><?php esc_html_e('1920x1080', 'wpstream'); ?></option>
247 <option value="hd"><?php esc_html_e('1280x720', 'wpstream'); ?></option>
248 <option value="square"><?php esc_html_e('800x600', 'wpstream'); ?></option>
249 <option value="vga"><?php esc_html_e('640x360', 'wpstream'); ?></option>
250 </select>
251 </div>
252 </div>
253 <?php
254 /**
255 * Fires at the bottom of the broadcaster settings panel, after the device pickers.
256 *
257 * @since 4.14.0
258 *
259 * @param int $channel_id Channel being broadcast to.
260 */
261 do_action( 'wpstream_broadcaster_settings_panel_after', intval( $channel_id ) );
262 ?>
263 </div>
264 </div>
265 </div>
266
267 <?php
268 /**
269 * Fires at the end of the broadcaster page body, before the footer scripts.
270 *
271 * @since 4.14.0
272 *
273 * @param int $channel_id Channel being broadcast to.
274 * @param bool $is_onboarding Whether the page was opened from the onboarding wizard.
275 */
276 do_action( 'wpstream_after_broadcaster_page', intval( $channel_id ), $is_onboarding );
277 ?>
278 <!-- wp_footer() prints footer scripts, including the enqueued broadcaster.js. -->
279 <?php wp_footer(); ?>
280 </body>
281 </html>