PageSourceSearch

https://fasosportstv.com/wp-content/plugins/wpstream/public/js/start_streaming.js?ver=202608130912

js fasosportstv.com collected 2026-10-02 00:49:30 UTC 57,833 bytes, 1,640 lines download raw bytes

1/*global $, jQuery, wpstream_start_streaming_vars*/
2/*
3 * start_streaming.js
4 *
5 * Front-end controller for the WpStream "start streaming" dashboard, where a
6 * broadcaster manages their FTV/PPV live channels and VODs. It binds the
7 * Start/Stop buttons, polls channel status over AJAX, reveals the OBS/RTMP
8 * credentials and Larix mobile QR links when a channel goes live, drives the
9 * per-event settings modals (low-latency / adaptive-bitrate / encrypt), the
10 * webcaster (browser broadcaster) launcher, copy-to-clipboard helpers, and the
11 * onboarding bubble hints.
12 *
13 * Relies on the localized `wpstream_start_streaming_vars` object (admin URL,
14 * i18n strings, feature flags) and reports onboarding telemetry via the
15 * optional global wpstream_track_onboarding_step().
16 */
17// Registry of active setInterval poll timers, keyed by show id (and "stop"+id).
18var counters={};
19
20// post_type can be FTV or PPV channel or VOD
21var post_type = null;
22
23/**
24 * Document-ready bootstrap: resolve the current post type and wire up every
25 * interactive behavior on the start-streaming dashboard.
26 */
27jQuery(document).ready(function ($) {
28    "use strict";
29
30	// Resolve which channel/VOD type this page manages from the URL.
31	wpstream_set_current_post_type();
32    // Begin polling any channels already flagged as pending/live.
33    wpstream_check_live_connections();
34    // Wire the settings/broadcast modal open/close behavior.
35    wpestate_start_modal_actions();
36    // Wire the error modal's dismiss button.
37    wpestate_start_modal_error_actions();
38    // Enable hover tooltips.
39    wpstream_tooltip();
40    // Enable copy-to-clipboard buttons.
41    wpstream_copy_to_clipboard();
42    // Commenting the following line because we are using the new webcaster
43    // Bind the webcaster (browser broadcaster) launcher.
44    wpstream_webcaster_actions();
45    // Persist per-event streaming option toggles on change.
46    wpstream_save_options_actions();
47	// Persist the per-event "use global settings" toggle.
48	wpstream_save_use_global_event_options();
49    // Bind all Start/Stop channel buttons.
50    wpstream_bind_start_and_stop();
51
52    // Guard stats/webcaster icons while they are inactive.
53    wpstream_bind_stats_link();
54    // Wire the general (global) settings toggles.
55    wpstream_adjust_settings_general();
56
57});
58
59/**
60 * Fire an onboarding telemetry event, but only if the tracking helper exists.
61 *
62 * @param {string} action       Event/action name.
63 * @param {string} step         Current onboarding step identifier.
64 * @param {string} element_type Element kind that triggered it (default 'button').
65 * @param {string} element_name Optional element name/detail.
66 */
67function wpstream_safe_track_onboarding(action, step, element_type = 'button', element_name = '') {
68	// Guard: the onboarding tracker is only present during onboarding.
69	if (typeof wpstream_track_onboarding_step === 'function') {
70		// Forward the event to the tracker.
71		wpstream_track_onboarding_step(action, step, element_type, element_name);
72	}
73}
74
75/**
76 * Determine the post type managed by this page from the ?branch= query param
77 * and store it in the module-level `post_type`.
78 */
79function wpstream_set_current_post_type() {
80	// Read the query string of the current URL.
81	let params = (new URL(document.location)).searchParams;
82	// Extract the numeric ?branch= selector.
83	let branch= params.get( 'branch' );
84	branch = parseInt( branch );
85
86	// A non-numeric branch means no specific post type.
87	if( isNaN( branch ) ) {
88		post_type = null;
89	}
90
91	// Map the branch number to a WpStream post type.
92	switch ( branch ) {
93		// Branch 1 -> free live channel.
94		case 1:
95			post_type = 'ftv_channel';
96			break;
97		// Branch 2 -> pay-per-view live channel.
98		case 2:
99			post_type = 'ppv_channel';
100			break;
101		// Branch 3 -> free VOD.
102		case 3:
103			post_type = 'ftv_vod';
104			break;
105		// Branch 4 -> pay-per-view VOD.
106		case 4:
107			post_type = 'ppv_vod';
108			break;
109		// Anything else -> no type.
110		default:
111			post_type = null;
112	}
113}
114
115/*
116*
117* genereal settings - low latency vs bitrate vs encrypt
118*
119*/
120
121
122/**
123 * Wire the global (theme-options) streaming toggles so that low-latency /
124 * adaptive-bitrate and encryption remain mutually exclusive.
125 */
126function wpstream_adjust_settings_general(){
127   
128    // On any global option slider click:
129    jQuery('.theme_options_tab_wpstream .wpstream_slider').on('click',function(event){
130        // Keep the click from bubbling to parent handlers.
131        event.stopPropagation();
132        // Locate the toggle's row and its checkbox input.
133        var parent = jQuery(this).parent();
134        var input_element = parent.find('.wpstream_event_option_item');
135        // The ajax name identifies which setting was toggled.
136        var attr_value = input_element.attr('data-attr-ajaxname');
137      
138
139        // Turning on low-latency or adaptive-bitrate disables encryption.
140        if( attr_value === 'adaptive_bitrate' || attr_value==='low_latency'){
141            
142            // Defer briefly so the checkbox state settles first.
143            var timer = setTimeout(function() {
144               
145                // If either speed option is now checked, clear the encrypt option.
146                if( jQuery('.wpstream-setting-adaptive_bitrate .wpstream_event_option_item').is(':checked') || 
147                    jQuery('.wpstream-setting-low_latency .wpstream_event_option_item').is(':checked') ){
148                    jQuery('.wpstream-setting-encrypt .wpstream_event_option_item') .removeAttr('checked'); 
149                }   
150            }, 300);
151
152        }
153
154        // Turning on encryption disables the speed options.
155        if( attr_value === 'encrypt'){
156            // Defer briefly so the checkbox state settles first.
157            var timer = setTimeout(function() {
158               
159                // If encrypt is now checked, clear the speed options.
160                if( jQuery('.wpstream-setting-encrypt .wpstream_event_option_item').is(':checked') ){
161                    jQuery('.wpstream-setting-low_latency .wpstream_event_option_item') .removeAttr('checked'); 
162                    jQuery('.wpstream-setting-adaptive_bitrate .wpstream_event_option_item') .removeAttr('checked'); 
163                }
164            }, 300);
165
166        }
167
168    });
169}
170
171
172
173
174
175
176
177
178
179
180/*
181*
182* Bind block stats link
183*
184*/
185
186/**
187 * Prevent clicks on statistics/webcaster icons while they are marked inactive.
188 */
189function wpstream_bind_stats_link(){
190 
191    // For each stats/webcaster icon:
192    jQuery('.wpstream_statistics_channel,.start_webcaster').each(function(event){
193        // Cache the icon element.
194        var selected_icon = jQuery(this);
195            // Intercept its clicks.
196            selected_icon.on('click',function(event){
197                // When inactive, swallow the click entirely.
198                if(selected_icon.hasClass('wpstream_inactive_icon')){
199                    event.preventDefault();
200                    event.stopPropagation();
201                }
202                
203            });
204        
205    })
206
207
208}
209
210
211
212
213/*
214*
215* Bind Start and Stop channel
216*
217*/
218    
219
220/**
221 * Bind click handlers to every Start button and every Stop button on the page.
222 */
223function wpstream_bind_start_and_stop(){
224    
225    // Bind each Start button.
226    jQuery('.start_event.wpstream_button').each(function(element){
227        // Cache this button.
228        var start_button=jQuery(this);    
229        // Attach the start-channel behavior.
230        wpstream_bind_start_event(start_button);
231    });
232
233
234    // Bind each Stop button.
235    jQuery('.wpstream_stop_event').each(function(element){
236        // Cache this button.
237        var stop_button=jQuery(this);
238        // Attach the stop-channel behavior.
239        wpstream_bind_stop_event(stop_button);
240    });
241
242}
243
244
245/*
246*
247* Bind start  channel action
248*
249*/
250// Onboarding flag forwarded to the start-channel AJAX call (set elsewhere).
251var start_onboarding='';
252/**
253 * Bind the "start channel" click behavior to a Start button: warn for basic
254 * plans, request live credentials over AJAX, and begin polling channel status.
255 *
256 * @param {jQuery} button The Start button element.
257 */
258function wpstream_bind_start_event(button){
259   
260    // On click:
261    button.click(function(event){
262        // Telemetry: a stream start was attempted.
263        wpstream_safe_track_onboarding('stream_attempted', 'wpstream_' + post_type);
264		wpstream_safe_track_onboarding('start_stream_clicked', 'wpstream_' + post_type);
265        // Basic-plan users get a usage warning before starting.
266        if (wpstream_start_streaming_vars.is_basic_streaming) {
267	        // Warn about streaming-hours consumption...
268	        if (wpstream_start_streaming_vars.use_streaming_hours) {
269		        // ...and abort if they decline.
270		        if (!confirm(wpstream_start_streaming_vars.basic_streaming_warning)) {
271			        return false;
272		        }
273	        } else {
274		        // Otherwise warn about traffic consumption and abort if they decline.
275		        if (!confirm(wpstream_start_streaming_vars.basic_streaming_warning_traffic)) {
276			        return false;
277		        }
278	        }
279        }
280        // Stop the default button/link action.
281        event.preventDefault();
282        // Prevent double-submits by unbinding the handler.
283        button.unbind('click');
284        // admin-ajax.php endpoint.
285        var ajaxurl             =   wpstream_start_streaming_vars.admin_url + 'admin-ajax.php';
286        // Cache the clicked button.
287        var acesta              =   jQuery(this);
288        // Notification area for this event row.
289        var notification_area   =   jQuery(this).parent().find('.event_list_unit_notification');
290        // Server-notification container.
291        var curent_content      =   jQuery(this).parent().find('.server_notification');
292        // Whether to record this broadcast.
293        var is_record           =   false;
294        // Whether to encrypt this broadcast.
295        var is_encrypt          =   false;
296        // The channel/show id.
297        var show_id             =   parseFloat( jQuery(this).attr('data-show-id') );
298        // CSRF nonce for the AJAX call.
299        var nonce               =   jQuery('#wpstream_start_event_nonce').val();
300        // The button's parent container.
301        var parent              =   jQuery(this).parent();
302        
303        // Read the "record" toggle.
304        if( jQuery(this).parent().find('.record_event').is(":checked") ){
305            is_record   =   true;
306        }
307        
308        // Read the "encrypt" toggle.
309        if( jQuery(this).parent().find('.encrypt_event').is(":checked") ){
310            is_encrypt   =   true;
311        }
312        // Disable the settings icon while the channel is starting.
313        parent.find('.wpstream_show_settings').addClass('wpstream_inactive_icon');
314        // Show the "turning on" spinner/label on the button.
315        jQuery(this).addClass('wpstream_turning_on').empty().html(wpstream_start_streaming_vars.start_streaming_action+'<div class="wpstream_loader"></div><div class="wpstream_tooltip">'+wpstream_start_streaming_vars.turning_on_tooltip+'</div>');
316        // Update the status label to "turning on".
317        parent.find('.wpstream_channel_status').text(wpstream_start_streaming_vars.channel_turning_on);
318
319        // Ask the server for live credentials / to start the channel.
320        jQuery.ajax({
321            type: 'POST',
322            url: ajaxurl,
323            dataType: 'json',
324            timeout: 300000,
325
326            data: {
327                'action'            :   'wpstream_give_me_live_uri',
328                'security'          :   nonce,
329                'show_id'           :   show_id,
330                'is_record'         :   is_record,
331                'is_encrypt'        :   is_encrypt,
332                'start_onboarding'  :   start_onboarding,
333               
334                
335            },
336            // On success:
337            success: function (data) {
338                
339              
340       
341                // API reachable.
342                if(data.conected===true){
343             
344                        // err1: generic start failure.
345                        if(data.event_data==='err1'){
346							wpstream_safe_track_onboarding('stream_start_failed', 'wpstream_' + post_type, 'system', 'err1');
347                        
348                            wpstream_show_error_on_start(wpstream_start_streaming_vars.error1,parent)
349                        
350                        // Event creation failed on the backend.
351                        }else if(data.event_data ==='failed event creation'){
352							wpstream_safe_track_onboarding('stream_start_failed', 'wpstream_' + post_type, 'system', 'failed_event_creation');
353                        
354                            wpstream_show_error_on_start(wpstream_start_streaming_vars.failed_event_creation,parent)
355                        
356                        // Success: begin polling this channel's status every 10s.
357                        }else{
358                            // Clear any stale server notification.
359                            curent_content.empty();
360                            // Poll live status; store the timer so it can be cleared later.
361                            var counter =  setInterval( function (){
362								wpstream_check_live_connections_on_start(parent,show_id,data.event_data,data)
363                            },10000);
364                            counters['stop'+show_id]=counter;
365                    
366                        }
367                        
368            
369                // API not connected: show the returned error.
370                }else{
371					wpstream_safe_track_onboarding('stream_start_failed', 'wpstream_' + post_type, 'system', 'api_not_connected');
372                    wpstream_show_error_on_start(data.error,parent)
373                }
374                
375            },
376            // On transport error, report telemetry only.
377            error: function (jqXHR,textStatus,errorThrown) {             
378				wpstream_safe_track_onboarding('stream_start_failed', 'wpstream_' + post_type, 'system', 'ajax_error');
379            }
380        });
381        
382    });
383    
384}
385
386
387/*
388*
389* Bind Stop channel action
390*
391*/
392
393/**
394 * Bind the "stop channel" click behavior to a Stop button: confirm, request the
395 * channel be turned off over AJAX, and poll until it reports stopped.
396 *
397 * @param {jQuery} button The Stop button element.
398 */
399function wpstream_bind_stop_event(button){
400    // On click:
401    button.click(function(event){
402		// Telemetry: stop clicked.
403		wpstream_safe_track_onboarding( 'stop_stream_clicked', 'wpstream_' + post_type );
404
405
406        // Confirm before stopping.
407        if(!confirm(wpstream_start_streaming_vars.turn_off_confirm)){
408            return false;
409        }
410        // Prevent double-submits by unbinding the handler.
411        button.unbind('click');
412
413
414        // admin-ajax.php endpoint.
415        var ajaxurl             =   wpstream_start_streaming_vars.admin_url + 'admin-ajax.php';
416        // The channel/show id.
417        var show_id             =   parseFloat( jQuery(this).attr('data-show-id') );
418        // CSRF nonce for the AJAX call.
419        var nonce               =   jQuery('#wpstream_start_event_nonce').val();
420        // The button's parent container.
421        var parent              =   jQuery(this).parent();
422        // Cache the clicked button.
423        var thisButton          =   jQuery(this);
424      
425        // Flip the button out of its stop state...
426        thisButton.removeClass('wpstream_stop_event');
427        // ...and into a "turning off" spinner/label.
428        thisButton.addClass('wpstream_turning_on').empty().html(wpstream_start_streaming_vars.stop_streaming_action+'<div class="wpstream_loader"></div><div class="wpstream_tooltip">'+wpstream_start_streaming_vars.turning_off_tooltip+'</div>');
429        // Update the status label to "turning off".
430        parent.find('.wpstream_channel_status').text(wpstream_start_streaming_vars.channel_turning_off);
431        // Disable the webcaster/pro/live-data icons while stopping.
432        parent.find('.start_webcaster').addClass('wpstream_inactive_icon');
433        parent.find('.wpstream_stream_pro').addClass('wpstream_inactive_icon');
434        parent.find('.wpstream_live_data').addClass('wpstream_inactive_icon');
435
436
437
438        // Ask the server to turn off the channel.
439        jQuery.ajax({
440            type: 'POST',
441            url: ajaxurl,
442            dataType: 'json',
443            timeout: 300000,
444
445            data: {
446                'action'            :   'wpstream_turn_of_channel',
447                'security'          :   nonce,
448                'show_id'           :   show_id
449               
450                
451            },
452            // On success:
453            success: function (data) {
454
455                // API reachable: poll until the channel reports stopped.
456                if(data.conected===true){
457                    
458                    // Prevent further clicks on the button.
459                    thisButton.unbind('click');
460         
461                    // Poll status every 10s; store the timer.
462                    var counter =  setInterval( function (){ 
463                        wpstream_check_live_connections_on_start(parent,show_id,'',data)},10000);
464                        counters["stop"+show_id]=counter;
465
466                // API not connected: show the error.
467                }else{
468               
469                    wpstream_show_error_on_stop(data.error,parent)
470                }
471                
472            },
473            // Transport error: no-op.
474            error: function (jqXHR,textStatus,errorThrown) {
475             
476            }
477        });
478        
479    
480    
481    });
482    
483}
484
485
486/*
487*
488* Enable copy to clipboard
489*
490*/
491
492/**
493 * Enable copy-to-clipboard for the live URI and stream key within a container.
494 *
495 * @param {jQuery|Element} parent Container holding the copy buttons and text.
496 */
497function wpstream_enable_cliboard(parent){
498    
499    // Copy the live URI when its button is clicked.
500    jQuery(parent).find('.copy_live_uri').click(function(){
501      
502        // Read the URI text.
503        var value_uri = jQuery(parent).find('.wpstream_live_uri_text').text();
504        // Use a throwaway input to run execCommand('copy').
505        var temp = jQuery("<input>");
506        // Attach it to the document.
507        jQuery("body").append(temp);
508        // Select its value.
509        jQuery(temp).val(value_uri).select();
510        // Copy to clipboard.
511        document.execCommand("copy");
512        // Clean up the throwaway input.
513        jQuery(temp).remove();
514        
515    });
516    
517    // Copy the stream key when its button is clicked.
518    jQuery(parent).find('.copy_live_key').click(function(){
519        // Read the key text.
520        var value_uri = jQuery(parent).find('.wpstream_live_key_text').text();
521        // Throwaway input for the copy command.
522        var temp = jQuery("<input>");
523        // Attach it to the document.
524        jQuery("body").append(temp);
525        // Select its value.
526        jQuery(temp).val(value_uri).select();
527        // Copy to clipboard.
528        document.execCommand("copy");
529        // Clean up the throwaway input.
530        jQuery(temp).remove();
531        
532    });
533}
534
535
536/*
537 *
538 * Check live events/channels on starts 
539 * 
540 * 
541 * 
542*/
543
544
545/**
546 * Poll callback used right after Start: when the channel becomes active, reveal
547 * the OBS/RTMP credentials, Larix mobile links and QR codes, switch to slow
548 * (60s) database polling, and handle stopped/error states.
549 *
550 * @param {jQuery} parent    The event row container.
551 * @param {number} show_id   Channel/show id.
552 * @param {string} server_id Backend server id for the event.
553 * @param {Object} data      Original AJAX payload from the start request.
554 */
555function wpstream_check_live_connections_on_start( parent,show_id,server_id,data){
556      
557    // server_url is dns change
558  
559    // Query the channel status; the callback fires with the result.
560    wpstream_check_event_status_in_js(show_id,'wpstream_check_live_connections_on_start',
561        function(server_status){
562      
563            // Channel is live:
564            if(server_status.status==='active' ){
565                // Store the webcaster URL on the launcher.
566                parent.find('.start_webcaster').attr('data-webcaster-url',server_status.webcaster_url);
567                // Show the OBS ingest URI.
568                parent.find('.wpstream_live_uri_text').text(server_status.obs_uri);
569                // Show the OBS stream key.
570                parent.find('.wpstream_live_key_text').text(server_status.obs_stream);
571
572                // Reveal the "live" action buttons.
573                wpstream_event_ready_make_actions_visible(parent);
574
575                // Build the combined RTMP URL for Larix.
576                var larix_rtmp= server_status.obs_uri+server_status.obs_stream;
577                // Display it.
578                parent.find('.wpstream_larix_rtmp').text(larix_rtmp); 
579
580                // Build the Larix deep-link (mobile app config).
581                var   larix_qr ='larix://set/v1?conn[][url]='+encodeURIComponent(larix_rtmp);
582                // Point the mobile-start link at it.
583                parent.find('.wpstream_start_with_larix_mobile').attr('href',larix_qr); 
584
585                // Display the RTMP URL in the info and test fields.
586                parent.find('.wpstream_larix_rtmp').text(larix_rtmp); 
587                parent.find('.larrix_test').text(larix_rtmp); 
588              
589
590                // Generate the QR locally (SEC-05): the RTMP URL + secret stream key
591                // encoded in larix_qr must never be sent to a third-party image service.
592                wpstreamRenderQr( parent.find('.print_qrcode'), larix_qr );
593                
594                // Link the live-data button to the stats URL.
595                parent.parent().find('.wpstream_live_data').attr('href',server_status.live_data_url);         
596                // Stop the fast (10s) start poll.
597                clearInterval( counters["stop"+show_id]);
598
599				// Clear any existing slow poll for this channel.
600				if (counters[show_id]) {
601					clearInterval(counters[show_id]);
602				}
603				// Read the server id from context.
604				var server_id   =   jQuery(this).attr('data-server-id');
605				// Start slow (60s) database status polling.
606				counters[show_id] = setInterval( function (){
607					wpstream_check_live_connections_from_database(parent,show_id,server_id);
608				},60000);
609
610                // Fire optional integration notifications.
611                if (typeof wpstream_integration_notifications === 'function') {
612                    wpstream_integration_notifications(show_id);
613                }
614
615                
616            // Channel stopped:
617            }else if(server_status.status==='stopped' ){    
618             // (debug log)
619             console.log('stopped status from _on_start');
620                // Stop the fast poll.
621                clearInterval( counters["stop"+show_id]);
622                // Restore the stopped-state UI.
623                wpstream_event_stopped_make_actions(parent);
624
625            // Channel errored:
626            }else if(server_status.status==='error' ){
627                // Telemetry: start failed with an error status.
628                wpstream_safe_track_onboarding('stream_start_failed', 'wpstream_' + post_type, 'system', 'status_error');
629               
630                // Stop the fast poll.
631                clearInterval( counters["stop"+show_id]);
632                // Show a failure message in the status area.
633                var curent_content = parent.find('.wpstream_channel_status');
634                curent_content.html('<div class="wpstream_channel_status not_ready_to_stream"><span class="dashicons dashicons-dismiss"></span>'+wpstream_start_streaming_vars.failed_event_creation+'</div>');
635            }
636    });
637
638}
639
640
641
642
643/*
644 * check channell id status
645 * 
646 * @param {type} channel_id
647 * @param {type} callback
648 * @returns {undefined}
649 * 
650 * 
651*/
652
653/**
654 * Query a channel's current status via admin-ajax and route the result to the
655 * supplied callbacks.
656 *
657 * @param {number}   channel_id      Channel id to check.
658 * @param {string}   notes           Caller tag (for server-side logging).
659 * @param {Function} successCallback Receives the status object, or false when
660 *                                   the status is not yet resolved.
661 * @param {Function} errorCallback   Called on AJAX transport error.
662 */
663function wpstream_check_event_status_in_js(channel_id,notes,successCallback, errorCallback){
664
665    // admin-ajax.php endpoint.
666    var ajaxurl = wpstream_start_streaming_vars.admin_url + 'admin-ajax.php';
667	// CSRF nonce for the AJAX call.
668	var nonce = jQuery('#wpstream_start_event_nonce').val();
669
670    // Request the channel status.
671    jQuery.ajax({
672        type: 'POST',
673        dataType: 'json',
674        url: ajaxurl,
675        timeout: 3000000,
676
677        data: {
678            'action'            :   'wpstream_check_event_status',
679            'channel_id'        :   channel_id,
680            'notes'             :   notes,
681	        'nonce'             :   nonce
682        },
683        // On success:
684        success: function (data) {
685
686            // The response object.
687            var obj = data;
688            // Its status field.
689            var channel_status = obj.status;
690
691            // Resolved active/stopped: pass the object through.
692            if(channel_status=='active' || channel_status=='stopped'  ){
693                successCallback(obj);
694            // Error status: also pass it through.
695            }else if(channel_status=='error'){
696                successCallback(obj);
697            // Not yet resolved: signal "not ready" with false.
698            }else{
699                successCallback(false);
700            }
701            
702        // On transport error, invoke the error callback.
703        }, error: function (jqXHR,textStatus,errorThrown) {
704            errorCallback();
705        }
706  });
707}
708
709
710/*
711*
712* Make actions visible on event if ready
713*
714*
715*/
716/**
717 * Switch an event row into its "live" state: turn the Start button into a Stop
718 * button, toggle icon availability, update the status label, and advance the
719 * onboarding bubble.
720 *
721 * @param {jQuery} parent The event row container.
722 */
723function wpstream_event_ready_make_actions_visible(parent){
724
725    // The current Start button.
726    var actionButton = parent.find('.start_event');
727    // Mark the row as started.
728    parent.addClass('wpstream_show_started');
729    // Drop the start handler...
730    actionButton.unbind('click');
731    // ...and bind the stop handler instead.
732    wpstream_bind_stop_event(actionButton);
733    
734    // Repaint the button as a Stop button.
735    actionButton.addClass('wpstream_stop_event').removeClass('start_event').html(wpstream_start_streaming_vars.stop_streaming+'<div class="wpstream_tooltip">'+wpstream_start_streaming_vars.turned_off_tooltip+'</div>');
736    // Enable the row's action icons.
737    parent.find('.wpstream-button-icon').removeClass('wpstream_inactive_icon');
738    // Disable the settings icon while live.
739    parent.find('.wpstream_show_settings').addClass('wpstream_inactive_icon');
740    // Update each status label.
741    const channelStatus = parent.find('.wpstream_channel_status');
742	// For each status element:
743	channelStatus.each(function( index, element ) {
744		// Leave failure labels hidden.
745		if ( jQuery(element).hasClass('not_ready_to_stream') ) {
746			jQuery(element).hide();
747			return;
748		}
749		// Otherwise show the "on" label...
750		jQuery(element).text(wpstream_start_streaming_vars.channel_on);
751		// ...and fade it in if it was hidden.
752		if ( jQuery(element).css('display') === 'none' ) {
753			jQuery(element).fadeIn(200);
754		}
755	});
756	// Reveal the Stop button if it was hidden.
757	const stopEventButton = parent.find('.wpstream_stop_event');
758	if ( stopEventButton.css('display') === 'none' ) {
759		stopEventButton.fadeIn(200);
760	}
761
762    //for onboarding
763    // Onboarding bubble step targets.
764    var check_against='3';
765    var check_against_camera_icon='5';
766    // Steps differ on the WooCommerce product screen.
767    if(jQuery('#woocommerce-product-data').length>0){
768        check_against='5'
769        check_against_camera_icon='7';
770    }       
771   
772    
773    // The current onboarding bubble step.
774    var bubble_Step =jQuery('#wpstream_onboard_bubble').attr('data-bubble-step');
775
776    // At the "channel on" step, update the bubble text.
777    if(bubble_Step===check_against){
778        jQuery('#wpstream_onboard_bubble_tile').text('Channel is now ON');
779        jQuery('#wpstream_onboard_bubble_content').text('You are ready to GO LIVE. Click Next to see how.');
780    }
781    // At the "go live" step, prompt the user to open the broadcaster.
782    if(bubble_Step===check_against_camera_icon){
783        jQuery('#wpstream_onboard_bubble_tile').html('Go LIVE');
784        jQuery('#wpstream_onboard_bubble_content').html('To Go Live now, click the <div class=\"wpstream_sample_icon_settings wpstream_sample_icon_camera\"></div> icon. The broadcast app will open in a new window.');
785        
786        // Once live, make the sample camera icon clickable.
787        if( jQuery('.event_list_unit .wpstream_button').hasClass('wpstream_stop_event') ){
788            // Show a pointer cursor.
789            jQuery('.wpstream_sample_icon_camera').css('cursor','pointer');
790          
791            // Clicking it triggers the real webcaster launch.
792            jQuery('.wpstream_sample_icon_camera').on('click',function(){
793                jQuery('.start_webcaster').trigger('click');
794                jQuery(this).unbind('click');
795
796            })   
797        }
798
799    }
800
801	// console.log('adding pending trigger');
802	// parent.addClass('pending_trigger');
803	// console.log('adding check for status');
804	// // wpstream_check_live_connections();
805	// console.log('parent ' , parent);
806}
807
808/**
809 * Put an event row into an error state: hide the Start button and status label
810 * and show a "failed fetching" notice.
811 *
812 * @param {jQuery} parent The event row container.
813 */
814function wpstream_event_error_make_actions_visible(parent){
815    // The Start button.
816    var actionButton = parent.find('.start_event');
817    // The status label(s).
818    const channelStatus = parent.find('.wpstream_channel_status');
819    // Drop the click handler.
820    actionButton.unbind('click');
821    // Hide the button.
822    actionButton.hide();
823    // Hide the status label.
824    channelStatus.hide();
825
826    // Show a fetch-failure message.
827    parent.find('.server_notification').html('<div class="wpstream_channel_status not_ready_to_stream"><span class="dashicons dashicons-dismiss"></span>'+wpstream_start_streaming_vars.failed_fetching+'</div>');
828}
829
830
831/*
832*
833* Make actions visible on event if stopped
834*
835*
836*/
837/**
838 * Restore an event row to its stopped/off state: turn the button back into a
839 * Start button and reset icon availability and the status label.
840 *
841 * @param {jQuery} parent The event row container.
842 */
843function wpstream_event_stopped_make_actions(parent){
844
845    // The button currently in its "turning on" state.
846    var  actionButton = parent.find('.wpstream_turning_on');
847    // Re-bind the start handler.
848    wpstream_bind_start_event(actionButton);
849    // Clear the started marker.
850    parent.removeClass('wpstream_show_started');
851    // Drop the transitional class...
852    actionButton.removeClass('wpstream_turning_on');
853    // ...and restore the Start class.
854    actionButton.addClass('start_event');
855    // Repaint the Start label/tooltip.
856    actionButton.html( wpstream_start_streaming_vars.start_streaming+'<div class="wpstream_tooltip">'+wpstream_start_streaming_vars.turned_on_tooltip+'</div>');
857
858    // Enable the action icons.
859    parent.find('.wpstream-button-icon').removeClass('wpstream_inactive_icon');
860    // Disable the pro icon.
861    parent.find('.wpstream_stream_pro').addClass('wpstream_inactive_icon');
862    // Disable the webcaster icon.
863    parent.find('.start_webcaster').addClass('wpstream_inactive_icon');
864    // Disable the statistics icon.
865    parent.find('.wpstream_statistics_channel').addClass('wpstream_inactive_icon');
866
867    // Set the status label to "off".
868    parent.find('.wpstream_channel_status').text(wpstream_start_streaming_vars.channel_off);
869}
870
871/*
872*
873* Make actions visible on event if stopped
874*
875*
876*/
877/**
878 * Like wpstream_event_stopped_make_actions, but used after a status poll finds
879 * the channel stopped; also handles the case where the button is still in its
880 * Stop state.
881 *
882 * @param {jQuery} parent The event row container.
883 */
884function wpstream_event_stopped_after_status_check(parent){
885
886	// Prefer the "turning on" button...
887	var actionButton = parent.find('.wpstream_turning_on');
888	// ...else fall back to the Stop button.
889	if (actionButton.length === 0) {
890		actionButton = parent.find('.wpstream_stop_event');
891	}
892	// Drop existing handlers.
893	actionButton.unbind('click');
894	// Re-bind the start handler.
895	wpstream_bind_start_event(actionButton);
896	// Clear the started marker.
897	parent.removeClass('wpstream_show_started');
898	// Drop the transitional/stop classes...
899	actionButton.removeClass('wpstream_turning_on wpstream_stop_event');
900	// ...and restore the Start class.
901	actionButton.addClass('start_event');
902	// Repaint the Start label/tooltip.
903	actionButton.html( wpstream_start_streaming_vars.start_streaming+'<div class="wpstream_tooltip">'+wpstream_start_streaming_vars.turned_on_tooltip+'</div>');
904
905	// Enable the action icons.
906	parent.find('.wpstream-button-icon').removeClass('wpstream_inactive_icon');
907	// Disable the pro icon.
908	parent.find('.wpstream_stream_pro').addClass('wpstream_inactive_icon');
909	// Disable the webcaster icon.
910	parent.find('.start_webcaster').addClass('wpstream_inactive_icon');
911	// Disable the statistics icon.
912	parent.find('.wpstream_statistics_channel').addClass('wpstream_inactive_icon');
913
914	// Set the status label to "off".
915	parent.find('.wpstream_channel_status').text(wpstream_start_streaming_vars.channel_off);
916}
917
918
919
920/**
921 * Show the error modal for a failed start and reset the row to stopped state.
922 *
923 * @param {string} text   Error message to display.
924 * @param {jQuery} parent The event row container.
925 */
926function wpstream_show_error_on_start(text,parent){
927    //You don't have enough data to start a new event!
928    // Set the modal message.
929    jQuery('.wpstream_error_content').text(text);
930    // Show the modal backdrop.
931    jQuery('.wpstream_modal_background').show();
932    // Show the error modal.
933    jQuery('.wpstream_error_modal_notification').show();
934    // Reset the row to stopped state.
935    wpstream_event_stopped_make_actions(parent);
936
937}
938
939/**
940 * Show the error modal for a failed stop and keep the row in its live state.
941 *
942 * @param {string} text   Error message to display.
943 * @param {jQuery} parent The event row container.
944 */
945function wpstream_show_error_on_stop(text,parent){
946    //You don't have enough data to start a new event!
947    // Set the modal message.
948    jQuery('.wpstream_error_content').text(text);
949    // Show the modal backdrop.
950    jQuery('.wpstream_modal_background').show();
951    // Show the error modal.
952    jQuery('.wpstream_error_modal_notification').show();
953    // Keep the row in its live state.
954    wpstream_event_ready_make_actions_visible(parent);
955
956}
957
958
959/*
960*
961* Streaming modal trigger functions
962*  
963*
964*/
965/**
966 * Wire the settings/broadcast modals: close buttons, Escape-to-close, the modal
967 * triggers, and the external-broadcast option switcher.
968 */
969function wpestate_start_modal_actions(){
970
971    // Close button hides the modal and its backdrop.
972    jQuery('.wpstream_close_modal').on('click',function(event){
973        jQuery(this).parent().hide();
974        jQuery('.wpstream_modal_background').hide();
975    });
976
977    // Escape key closes any open modal.
978    document.addEventListener('keydown', function(event) {
979        // 27 = Escape.
980        if(event.keyCode == 27){
981            jQuery('.wpstream_modal_form').hide();
982            jQuery('.wpstream_modal_background').hide();
983        }
984    });
985
986
987    // A modal trigger opens its target modal.
988    jQuery('.wpstream-trigger-modal').on('click',function(event){
989
990        // Ignore triggers that are inactive.
991        if( jQuery(this).hasClass('wpstream_inactive_icon') ) {
992            return;
993        };
994
995        // Show the backdrop.
996        jQuery('.wpstream_modal_background').show();
997        // Which modal to open.
998        var modal_class=jQuery(this).attr('data-modal');
999		// Telemetry: modal opened.
1000		wpstream_safe_track_onboarding( 'open_modal', 'wpstream_' + post_type, 'button', modal_class );
1001        // Find the owning event row.
1002        var parent =jQuery(this).closest('.event_list_unit');
1003        // Show the backdrop.
1004        jQuery('.wpstream_modal_background').show();
1005        // Reveal the target modal as a flex column.
1006        parent.find("."+modal_class).css('display','flex').css('flex-direction','column');
1007    })
1008
1009    // Switch which external-software instructions are shown.
1010    jQuery('.wpstream_external_broadcast_options').change(function(event){
1011        // Selected option value.
1012        var new_option = jQuery(this).val();
1013        // Its container.
1014        var parent = jQuery(this).parent();
1015
1016        // Hide all option panels...
1017        parent.find('.external_software_streaming').hide();
1018        // ...then show the selected one.
1019        parent.find('.'+new_option).show();
1020    });
1021
1022
1023}
1024
1025
1026
1027/*
1028*
1029* Tooltips for buttons
1030*  
1031*
1032*/ 
1033
1034/**
1035 * Show the appropriate tooltip on hover, choosing the disabled-variant tooltip
1036 * for inactive icons.
1037 */
1038function wpstream_tooltip(){
1039
1040    // On hover in:
1041    jQuery( ".wpstream_tooltip_wrapper" ).hover(
1042    function() {
1043
1044        // Inactive: show the "disabled" tooltip.
1045        if(jQuery( this ).hasClass('wpstream_inactive_icon')){
1046            jQuery( this ).find('.wpstream_tooltip_disabled').css('opacity',1);
1047        // Active: show the normal tooltip.
1048        }else{
1049            jQuery( this ).find('.wpstream_tooltip').css('opacity',1);
1050        }
1051
1052
1053    // On hover out: hide both tooltips.
1054    }, function() {
1055        jQuery( this ).find('.wpstream_tooltip').css('opacity',0);
1056        jQuery( this ).find('.wpstream_tooltip_disabled').css('opacity',0);
1057    }
1058);
1059}
1060
1061/*
1062*
1063* Modal errors
1064*  
1065*
1066*/ 
1067
1068
1069/**
1070 * Wire the error modal's OK button to close it and clear its message.
1071 */
1072function wpestate_start_modal_error_actions(){
1073    // On OK:
1074    jQuery('.wpstream_error_ok').on('click',function(event){
1075        // Hide the modal.
1076        jQuery(this).parent().hide();
1077        // Hide the backdrop.
1078        jQuery('.wpstream_modal_background').hide();
1079        // Clear the message.
1080        jQuery(this).parent().find('.wpstream_error_content').text('');
1081    });
1082}
1083
1084
1085
1086
1087/*
1088*
1089* Copy to Clipboard
1090*  
1091*
1092*/ 
1093
1094/**
1095 * Enable copy-to-clipboard for the live URI (with a "Copied" tooltip) and the
1096 * stream key, using each button's sibling text.
1097 */
1098function wpstream_copy_to_clipboard(){
1099       
1100    // Copy the live URI on click.
1101    jQuery('.copy_live_uri').on('click',function(){
1102        // Read the URI text.
1103        var value_uri = jQuery(this).parent().find('.wpstream_live_uri_text').text();
1104        // Throwaway input for execCommand('copy').
1105        var temp = jQuery("<input>");
1106        // Attach it to the document.
1107        jQuery("body").append(temp);
1108        // Select its value.
1109        jQuery(temp).val(value_uri).select();
1110        // Copy to clipboard.
1111        document.execCommand("copy");
1112        // Clean up the throwaway input.
1113        jQuery(temp).remove();
1114
1115	    // Show "Copied" tooltip
1116	    // Build the tooltip element.
1117	    var tooltip = jQuery('<div class="wpstream_copy_tooltip">Copied</div>');
1118	    // Style it.
1119	    tooltip.css({
1120		    position: 'absolute',
1121		    background: '#333',
1122		    color: '#fff',
1123		    padding: '5px 10px',
1124		    borderRadius: '3px',
1125		    fontSize: '12px',
1126		    zIndex: 9999,
1127		    pointerEvents: 'none'
1128	    });
1129
1130	    // Position it above the button, horizontally centered.
1131	    var offset = jQuery(this).offset();
1132	    tooltip.css({
1133		    top: offset.top - 35,
1134		    left: offset.left + (jQuery(this).outerWidth() / 2) - (tooltip.outerWidth() / 2)
1135	    });
1136
1137	    // Insert it into the page.
1138	    jQuery('body').append(tooltip);
1139	    // Fade it in.
1140	    tooltip.fadeIn(200);
1141
1142	    // Auto-dismiss after 1.5s.
1143	    setTimeout(function() {
1144		    tooltip.fadeOut(200, function() {
1145			    tooltip.remove();
1146		    });
1147	    }, 1500);
1148    });
1149    
1150    // Copy the stream key on click.
1151    jQuery('.copy_live_key').on('click',function(){
1152        // Read the key text.
1153        var value_uri = jQuery(this).parent().find('.wpstream_live_key_text').text();
1154        // Throwaway input for the copy command.
1155        var temp = jQuery("<input>");
1156        // Attach it to the document.
1157        jQuery("body").append(temp);
1158        // Select its value.
1159        jQuery(temp).val(value_uri).select();
1160        // Copy to clipboard.
1161        document.execCommand("copy");
1162        // Clean up the throwaway input.
1163        jQuery(temp).remove();
1164    });
1165}
1166
1167/*
1168*
1169* Webcaster button action
1170*  
1171*
1172*/ 
1173
1174/**
1175 * Wire the webcaster (browser broadcaster) launcher: check for a WHIP URL and
1176 * open the new in-browser broadcaster, falling back to the legacy webcaster
1177 * window when unavailable.
1178 */
1179function wpstream_webcaster_actions(){
1180    // On launcher click:
1181    jQuery('.start_webcaster').on('click',function(event){
1182        // Ignore while the launcher is inactive.
1183        if(jQuery(this).hasClass('wpstream_inactive_icon')){
1184            return;
1185        }
1186		// Telemetry: webcaster opened.
1187		wpstream_safe_track_onboarding( 'open_webcaster', 'wpstream_' + post_type );
1188        // Cache the launcher.
1189        var $this = jQuery(this);
1190        // admin-ajax.php endpoint.
1191        var ajaxurl = wpstream_start_streaming_vars.admin_url + 'admin-ajax.php';
1192        // The channel id from the row.
1193        var channelId      = jQuery(this).closest('.event_list_unit').data('show-id');
1194        // Will hold the WHIP publish URL.
1195        var whipUrl = '';
1196		// Open a blank popup now (within the click) to avoid popup blockers.
1197		var pendingPopup = window.open('', '_blank', 'location=yes,scrollbars=yes,status=yes');
1198		// CSRF nonce for the AJAX call.
1199		var nonce = jQuery('#wpstream_start_event_nonce').val();
1200
1201        // Ask the server whether a WHIP URL is available.
1202        jQuery.ajax({
1203            type: 'POST',
1204            dataType: 'json',
1205            url: ajaxurl,
1206            timeout: 3000000,
1207            data: {
1208                'action': 'wpstream_check_whipurl',
1209                'channel_id': channelId,
1210	            'nonce': nonce,
1211            },
1212            // On success:
1213            success: function (data) {
1214                // WHIP available.
1215                if( data.success == true ){
1216                    // The WHIP publish URL.
1217                    whipUrl = data.whip_url;
1218
1219	                // Build the new broadcaster URL for this channel.
1220	                if ( whipUrl !== '' ) {
1221		                // Open the new broadcaster in a new window
1222		                // New broadcaster page + channel id.
1223		                var broadcasterUrl = wpstream_start_streaming_vars.broadcaster_url + channelId;
1224						// During onboarding, pass an onboarding flag...
1225						if ( wpstream_start_streaming_vars.is_onboarding == 'yes' ) {
1226							broadcasterUrl += '/?onboarding=1';
1227                            // ...and hand the popup an onboarding payload via its name.
1228                            if (pendingPopup) {
1229                                // Carry the current session id.
1230                                var popupSessionId = (new URLSearchParams(window.location.search)).get('session_id') || '';
1231                                // Payload the broadcaster reads from window.name.
1232                                var popupPayload = {
1233                                    wpstream_onboarding_popup_payload: {
1234                                        session_id: popupSessionId,
1235                                    }
1236                                };
1237
1238                                // Stash it on the popup (JSON-encoded).
1239                                try {
1240                                    pendingPopup.name = JSON.stringify(popupPayload);
1241                                // On failure, clear the name.
1242                                } catch (e) {
1243                                    pendingPopup.name = '';
1244                                }
1245                            }
1246						}
1247		                // window.open(broadcasterUrl, 'wpstream_broadcaster_' + channelId, 'fullscreen=yes');
1248		                // Navigate the pre-opened popup to the broadcaster.
1249		                if (pendingPopup) {
1250			                pendingPopup.location.href = broadcasterUrl;
1251		                // No popup available (blocked): nothing to navigate.
1252		                } else {
1253							// (defensive) close the popup if present.
1254							if (pendingPopup) {
1255								pendingPopup.close();
1256							}
1257		                }
1258	                }
1259                // No WHIP: fall back to the legacy webcaster window.
1260                } else {
1261	                // Legacy webcaster URL from the launcher.
1262	                var caster_url = $this.attr('data-webcaster-url');
1263	                // Collapse the external-software panel.
1264	                $this.parent().find('.external_software_streaming').slideUp()
1265	                // Open the legacy webcaster.
1266	                window.open(caster_url, '_blank', 'location=yes,scrollbars=yes,status=yes');
1267                }
1268            },
1269            // On AJAX error, fall back to the legacy webcaster.
1270            error: function() {
1271                // fallback to the old broadcaster if the AJAX request fails
1272                // Legacy webcaster URL from the launcher.
1273                var caster_url = $this.attr('data-webcaster-url');
1274                // Collapse the external-software panel.
1275                $this.parent().find('.external_software_streaming').slideUp()
1276                // Open the legacy webcaster.
1277                window.open(caster_url, '_blank', 'location=yes,scrollbars=yes,status=yes');
1278            }
1279        });
1280    })
1281}
1282
1283
1284
1285/*
1286*
1287*  per event - low latency vs bitrate vs encrypt
1288*
1289*/
1290
1291
1292/**
1293 * Per-event variant of the mutual-exclusion logic: toggling low-latency /
1294 * adaptive-bitrate clears encrypt and vice-versa, for a single event's modal.
1295 *
1296 * @param {jQuery} input_element The toggled option input.
1297 */
1298function wpstream_adjust_settings(input_element){
1299
1300   
1301   
1302    // Which setting was toggled.
1303    var attr_value = input_element.attr('data-attr-ajaxname');
1304    // (debug log)
1305    console.log(attr_value);
1306
1307 
1308
1309    // Speed options are mutually exclusive with encryption.
1310    if( attr_value === 'adaptive_bitrate' || attr_value==='low_latency'){
1311        // (debug log)
1312        console.log('perform ');
1313
1314        // Defer briefly so the checkbox state settles first.
1315        var timer = setTimeout(function() {
1316           
1317            // If either speed option is checked, clear encrypt.
1318            if( jQuery('.wpstream-setting-adaptive_bitrate .wpstream_event_option_item').is(':checked') || 
1319                jQuery('.wpstream-setting-low_latency .wpstream_event_option_item').is(':checked') ){
1320                jQuery('.wpstream-setting-encrypt .wpstream_event_option_item') .removeAttr('checked');           
1321            }      
1322        }, 300);
1323
1324    }
1325
1326    // Toggling encrypt clears the speed options.
1327    if( attr_value === 'encrypt'){
1328        // (debug log)
1329        console.log('perform2 ');
1330
1331        // Defer briefly so the checkbox state settles first.
1332        var timer = setTimeout(function() {
1333           
1334            // If encrypt is checked, clear the speed options.
1335            if( jQuery('.wpstream-setting-encrypt .wpstream_event_option_item').is(':checked') ){
1336                   
1337                jQuery('.wpstream-setting-low_latency .wpstream_event_option_item') .removeAttr('checked'); 
1338                jQuery('.wpstream-setting-adaptive_bitrate .wpstream_event_option_item') .removeAttr('checked'); 
1339           
1340            }
1341
1342           
1343        }, 300);
1344
1345    }
1346
1347}
1348
1349/*
1350*
1351* Save options actions
1352*  
1353*
1354*/ 
1355
1356/**
1357 * Persist per-event streaming option toggles: when a modal option changes,
1358 * gather all option states and save them via AJAX.
1359 */
1360function wpstream_save_options_actions(){
1361    // On any per-event option click:
1362    jQuery('.wpestate_settings_modal .wpstream_event_option_item').on('click',function(){
1363
1364        // Apply the mutual-exclusion rules.
1365        wpstream_adjust_settings(jQuery(this));
1366
1367        // The option group container.
1368        var holder  =   jQuery(this).parents('.wpstream_event_streaming_local');
1369        // The event's show id.
1370        var show_id =   jQuery(this).parents('.event_list_unit').find('.start_event').attr('data-show-id');
1371        // Accumulator for option name -> 0/1.
1372        var optionarray ={};
1373        // CSRF nonce for the AJAX call.
1374        var nonce               =   jQuery('#wpstream_start_event_nonce').val();
1375        // Defer so toggled states settle before reading them.
1376        var timer = setTimeout(function() {
1377            // Collect every option's checked state.
1378            holder.find('.wpstream_event_option_item').each(function(){
1379                optionarray[jQuery(this).attr('data-attr-ajaxname')]=jQuery(this).prop("checked") ? 1 : 0 ;
1380            });
1381
1382
1383            // JSON snapshot of the options (the object is what is actually sent below).
1384            var myJSON = JSON.stringify(optionarray);
1385            // Save the option set for this event.
1386            jQuery.ajax({
1387                type: 'POST',
1388                url: ajaxurl,
1389                timeout: 300000,
1390                data: {
1391                    'action'            :   'wpstream_update_local_event_settings',
1392                    'show_id'           :   show_id,
1393                    'option'            :   optionarray,
1394                    'security'          :   nonce
1395                                
1396                },
1397                // Success: no UI change needed.
1398                success: function (data) {
1399                },
1400                // Error: ignored.
1401                error: function (jqXHR,textStatus,errorThrown) {
1402                }
1403                
1404            });
1405        }, 300);
1406        
1407    });
1408    
1409}
1410
1411/**
1412 * Persist the per-event "use global settings" toggle and enable/disable the
1413 * individual option inputs accordingly.
1414 */
1415function wpstream_save_use_global_event_options() {
1416	// On toggle click:
1417	jQuery('.wpestate_settings_modal #local_event_options_enabled').on('click',function(){
1418		// CSRF nonce for the AJAX call.
1419		var nonce   = jQuery('#wpstream_start_event_nonce').val();
1420		// The event's show id.
1421		var show_id = jQuery(this).parents('.event_list_unit').find('.start_event').attr('data-show-id');
1422
1423		// Local checked state as 1/0.
1424		var use_global_settings = jQuery(this).prop("checked") ? 1 : 0 ;
1425		// Save the preference.
1426		jQuery.ajax({
1427			type: 'POST',
1428			url: ajaxurl,
1429			timeout: 300000,
1430			data: {
1431				'action'     : 'wpstream_update_use_global_event_options',
1432				'show_id'    : show_id,
1433				'use_global' : jQuery(this).prop("checked") ? 0 : 1 ,
1434				'security'   : nonce
1435			},
1436			// Enable/disable the option inputs based on the toggle.
1437			success: function (data) {
1438				jQuery('.wpestate_settings_modal .wpstream_event_option_item').each(function(){
1439					jQuery(this).prop("disabled", !jQuery('#local_event_options_enabled').prop("checked"));
1440				});
1441			},
1442			// Log on error.
1443			error: function (jqXHR,textStatus,errorThrown) {
1444				console.log('error');
1445			}
1446		});
1447	});
1448}
1449
1450/*
1451*
1452* Function to check live connections
1453*  
1454*
1455*/ 
1456 
1457/**
1458 * On page load, find event rows flagged as pending and begin polling their
1459 * status from the database on a 60s interval.
1460 */
1461function wpstream_check_live_connections(){
1462    // was .pending_streaming.pending_trigger
1463    // Any rows waiting to go live?
1464    if( jQuery('.event_list_unit.pending_trigger').length>0 ){
1465        // For each pending row:
1466        jQuery('.event_list_unit.pending_trigger').each(function(){
1467            // Cache the row.
1468            var acesta      =   jQuery(this);
1469            // Its show id.
1470            var show_id     =   jQuery(this).attr('data-show-id');
1471            // Its server id.
1472            var server_id   =   jQuery(this).attr('data-server-id');
1473
1474            // Poll immediately once.
1475            wpstream_check_live_connections_from_database(acesta,show_id,server_id);
1476            // Then poll every 60s.
1477            var counter_long     =   '';
1478            counter_long =  setInterval( function (){
1479				wpstream_check_live_connections_from_database(acesta,show_id,server_id)
1480            },60000);
1481            // Remember the timer by show id.
1482            counters[show_id]=counter_long;
1483
1484        });
1485    }
1486 
1487}
1488
1489
1490
1491  
1492/*
1493*
1494* check live connection
1495*
1496*
1497*/
1498
1499    
1500/**
1501 * Slow (database-backed) status poll: when the channel is active, refresh the
1502 * OBS credentials, Larix links and QR code; handle stopped/error states.
1503 *
1504 * @param {jQuery} acesta     The event row container.
1505 * @param {number} channel_id Channel id to check.
1506 * @param {string} server_id  Backend server id.
1507 */
1508function wpstream_check_live_connections_from_database( acesta,channel_id,server_id){
1509    // Query channel status; the callback handles the result.
1510    var server_status = wpstream_check_event_status_in_js(channel_id,'wpstream_check_live_connections_from_database',
1511        function(server_status){
1512
1513            // Channel is live:
1514            if(server_status.status==='active' ){
1515                
1516                // Store the webcaster URL on the launcher.
1517                acesta.find('.wpstream_ready_to_stream .start_webcaster').attr('data-webcaster-url',server_status.webcaster_url);
1518                // Show the OBS ingest URI.
1519                acesta.find('.wpstream_live_uri_text').text(server_status.obs_uri);
1520                // Show the OBS stream key.
1521                acesta.find('.wpstream_live_key_text').text(server_status.obs_stream);
1522            
1523                // Reveal the live action buttons.
1524                wpstream_event_ready_make_actions_visible( acesta );
1525
1526                // Build the combined RTMP URL for Larix.
1527                var larix_rtmp= server_status.obs_uri+server_status.obs_stream;
1528                // Display it.
1529                acesta.find('.wpstream_larix_rtmp').text(larix_rtmp);
1530
1531                // Also show it in the test field.
1532                acesta.find('.larrix_test').text(larix_rtmp);
1533
1534                // Build the Larix mobile deep-link.
1535                var   larix_qr ='larix://set/v1?conn[][url]='+encodeURIComponent(larix_rtmp);
1536                // Point the mobile-start link at it.
1537                acesta.find('.wpstream_start_with_larix_mobile').attr('href',larix_qr); 
1538                
1539             
1540                // Generate the QR locally (SEC-05): the RTMP URL + secret stream key
1541                // encoded in larix_qr must never be sent to a third-party image service.
1542                wpstreamRenderQr( acesta.find('.print_qrcode'), larix_qr );
1543                // Link the live-data button.
1544                acesta.find('.wpstream_live_data').attr('href',server_status.live_data_url);
1545                // Stop any fast start poll.
1546                clearInterval( counters["stop"+channel_id]);
1547         
1548            // Channel stopped:
1549            }else if(server_status.status==='stopped' ){
1550	            // Stop the fast poll.
1551	            clearInterval( counters["stop"+channel_id]);
1552	            // Restore the stopped-state UI.
1553	            wpstream_event_stopped_after_status_check(acesta);
1554            // Channel errored:
1555            }else if(server_status.status==='error' ){
1556                
1557                // Stop the fast poll.
1558                clearInterval( counters["stop"+channel_id]);
1559                // Target the status area...
1560                var curent_content = acesta.find('.wpstream_channel_status ');
1561                
1562                // ...and show a failure message.
1563                curent_content.html('<div class="wpstream_channel_status not_ready_to_stream"><span class="dashicons dashicons-dismiss"></span>'+wpstream_start_streaming_vars.failed_event_creation+'</div>');
1564            }
1565        },
1566        // On AJAX error, put the row into its error state.
1567        function(){
1568            wpstream_event_error_make_actions_visible(acesta);
1569        }
1570        );
1571
1572}
1573
1574
1575
1576/*
1577*
1578* Check event/channel status
1579*
1580*
1581*/
1582
1583    
1584/**
1585 * Ping a URL cross-domain (JSONP) and report reachability via a callback.
1586 *
1587 * @param {string}   url_param The URL to probe.
1588 * @param {Function}
1588 callback  Receives true when reachable (HTTP 200/400).
1589 */
1590function wpstream_check_server_status(url_param,callback) {
1591
1592    // The URL to probe.
1593    var url = url_param ;
1594    // (unused) status placeholder.
1595    var status='';
1596    // Fire a cache-disabled, cross-domain JSONP probe.
1597    jQuery.ajax({
1598        url: url,
1599        type: "get",
1600        cache: false,
1601        dataType: 'jsonp', // it is for supporting crossdomain
1602        crossDomain : true,
1603        asynchronous : false,
1604        timeout : 1500, // set a timeout in milliseconds
1605        callback:'',
1606        // On completion, inspect the HTTP status.
1607        complete : function(xhr) {
1608    
1609            // Treat 200/400 as reachable.
1610            if(xhr.status == "200" || xhr.status == "400") {
1611                callback(true);
1612            }
1613            // Anything else is treated as unreachable.
1614            else {
1615                callback(false);
1616            }
1617        }
1618    });
1619}
1620
1621/**
1622 * Convert megabits to gigabits, rounded to one decimal, clamped at 0.
1623 *
1624 * @param {number} megabits Value in megabits.
1625 * @return {number} Value in gigabits (>= 0).
1626 */
1627function wpstream_convert_mb_to_gb(megabits) {
1628	// Divide by 1000 to get gigabits.
1629	let gigabit = megabits / 1000;
1630	// Round to one decimal place.
1631	gigabit = parseFloat( gigabit.toFixed( 1 ) );
1632
1633	// Never return a negative value.
1634	if ( gigabit < 0 ) {
1635		return 0
1636	}
1637// Return the converted value.
1638
1639	return gigabit;
1640}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.