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.