1// setUpSpinner 2var opts = { 3 lines: 13 // The number of lines to draw 4 , length: 28 // The length of each line 5 , width: 14 // The line thickness 6 , radius: 42 // The radius of the inner circle 7 , scale: 0.25 // Scales overall size of the spinner 8 , corners: 1 // Corner roundness (0..1) 9 , color: '#fff' // #rgb or #rrggbb or array of colors 10 , opacity: 0.25 // Opacity of the lines 11 , rotate: 0 // The rotation offset 12 , direction: 1 // 1: clockwise, -1: counterclockwise 13 , speed: 1 // Rounds per second 14 , trail: 60 // Afterglow percentage 15 , fps: 20 // Frames per second when using setTimeout() as a fallback for CSS 16 , zIndex: 2e9 // The z-index (defaults to 2000000000) 17 , className: 'spinner' // The CSS class to assign to the spinner 18 , top: '50%' // Top position relative to parent 19 , left: '50%' // Left position relative to parent 20 , shadow: false // Whether to render a shadow 21 , hwaccel: false // Whether to use hardware acceleration 22 , position: 'absolute' // Element positioning 23} 24 25function showOnlyAbout(pounddiv) { 26 if (/^\d+$/.test(pounddiv)) { 27 // if it's a number, do the group-with-N thing 28 $('div.editable-section').hide(); 29 $('div.editable-section.group-with-' + pounddiv).show(); 30 } else { 31 $('body #divAboutInfo > div').hide(); 32 $(pounddiv).show(); 33 } 34} 35 36function showLoading() { $('div#divVideoLoading').addClass('loading'); $("div.jp-controls-holder ul #liPlaybackRate a").text('1.0x'); } 37function hideLoading() { $('div#divVideoLoading').removeClass('loading'); } 38 39function jumpPlayer(secs) { 40 secs = (typeof secs !== 'undefined') ? secs : 15; 41 if (doingHTML5Video) { 42 video.currentTime += secs; 43 if (video.currentTime > video.duration) { 44 video.currentTime = video.duration; 45 } 46 } else { 47 var jp = $("#jquery_jplayer_1"); 48 var ph = jp.data("jPlayer").status.currentTime; 49 jp.jPlayer("play", ph + secs); 50 } 51} 52 53function formatTime(seconds) { 54 const hours = Math.floor(seconds / 3600); 55 const minutes = Math.floor((seconds % 3600) / 60); 56 const secs = Math.floor(seconds % 60); 57 58 return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; 59 // if (hours > 0) { 60 // return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; 61 // } else { 62 // return `${minutes}:${secs.toString().padStart(2, "0")}`; 63 // } 64} 65 66function clockStringToSeconds(str) { 67 var hours = 0, minutes = 0, seconds = 0; 68 if (/(\d+)\:(\d+)\:(\d+)/.test(str)) { 69 hours = RegExp.$1; 70 minutes = RegExp.$2; 71 seconds = RegExp.$3; 72 } else if (/(\d+)\:(\d+)/.test(str)) { 73 minutes = RegExp.$1; 74 seconds = RegExp.$2; 75 } else if (/^\s*(\d+)\s*$/.test(str)) { 76 seconds = RegExp.$1; 77 } 78 return hours * 3600 + minutes * 60 + seconds * 1; 79} 80 81function respectStartTime() { 82 var url = $('#inputCopyPermalink').val().replace(/\&t\=\d+/, ''); 83 if ($('#checkboxStartAt').is(':checked')) { 84 var seconds = clockStringToSeconds($('#inputStartTime').val()); 85 url += '&t=' + seconds; 86 } 87 var url = $('#inputCopyPermalink').val(url); 88} 89 90function copyInputCopyPermalink() { 91 var copyText = document.querySelector("#inputCopyPermalink"); 92 copyText.select(); 93 document.execCommand("copy"); 94 $("#inputCopyPermalink").closest('p').find('.copy-status').show().fadeOut(2000); 95} 96 97function stripeTable(ele) { 98 $(ele).find('tr').removeClass('stripeshaded'); 99 $(ele).find('tr:visible:even').addClass('stripeshaded'); 100} 101 102function updateProgress() { 103 if (doingHTML5Video) { 104 $('#divTimingInfo div.jp-current-time').text(formatTime(video.currentTime)); 105 $('#divTimingInfo div.jp-duration').text(formatTime(video.duration)); 106 if (progressBar) { 107 progressBar.max = video.duration || 0; 108 progressBar.value = video.currentTime; 109 const percent = (video.currentTime / video.duration) * 100; 110 progressBar.style.setProperty("--progress", `${percent}%`); 111 if (video.currentTime >= video.duration) { 112 $("#liPlayPause button").removeClass("jp-pause").addClass("jp-play").find(".sr-only").text("play"); 113 } 114 } 115 } 116} 117 118function updateVolumeBar() { 119 if (doingHTML5Video) { 120 if (video.muted) { 121 volumeBar.value = 0; 122 volumeBar.style.setProperty("--volume-level", '0%'); 123 volumeBar.setAttribute('aria-valuetext', '0%'); 124 } else { 125 volumeBar.value = video.volume; 126 const percent = Math.round(video.volume * 100); 127 volumeBar.style.setProperty("--volume-level", `${percent}%`); 128 volumeBar.setAttribute('aria-valuetext', percent + '%'); 129 } 130 } 131} 132 133function toggleFullscreen() { 134 if (document.fullscreenElement) { 135 document.exitFullscreen(); 136 } else { 137 if (videoContainer.requestFullscreen) { 138 videoContainer.requestFullscreen(); 139 } 140 } 141} 142 143function resetControlsTimer() { 144 videoControls.classList.remove("hidden"); 145 146 clearTimeout(hideControlsTimeout); 147 hideControlsTimeout = setTimeout(() => { 148 videoControls.classList.add("hidden"); // Hide controls after inactivity 149 }, 1500); // Hide after 3 seconds of no movement 150} 151 152function respectPresenseOfCaptions() { 153 if (ccButton && video) { 154 const tracks = video.textTracks; // Get all text tracks 155 if (tracks.length > 0) { 156 ccButton.classList.remove('disabled'); 157 ccButton.title = 'closed captions'; 158 } else { 159 ccButton.classList.add('disabled'); 160 ccButton.title = 'closed captions not available for this video'; 161 } 162 } 163} 164 165function html5VideoLoadAndPlay(ele) { 166 if (doingHTML5Video && video) { 167 while (video.firstChild) { 168 video.removeChild(video.firstChild); 169 } 170 const source = document.createElement("source"); 171 source.src = videobaseurl + "/" + ele.dataset.m4v; 172 source.type = "video/mp4"; // Adjust type as needed 173 video.appendChild(source); 174 if (ele.dataset.vtt) { 175 const track = document.createElement("track"); 176 track.src = videobaseurl + "/" + ele.dataset.vtt; 177 track.default = true; 178 track.kind="captions"; 179 video.appendChild(track); 180 } 181 video.load(); 182 respectPresenseOfCaptions(); 183 html5VideoPlay(); 184 } 185} 186 187function html5VideoPlay() { 188 if (doingHTML5Video && video) { 189 video.play(); 190 jQuery('.jp-video-play').hide(); 191 jQuery("#liPlayPause button").removeClass("jp-play").addClass("jp-pause").find(".sr-only").text("pause"); 192 jQuery('body.html5video .html5-player').removeClass('stopped'); 193 } 194} 195 196var doingHTML5Video = false;
197var video = false; 198var videoContainer = false; 199var videoControls = false; 200var progressBar = false; 201var volumeBar = false; 202var isSeeking = false; // Track if the user is dragging the progress bar 203var ccButton = false; 204 205var ratebtn = false; 206var ratepanel = false; 207var ratelabel = false; 208 209function rateElementsPresent() { 210 if (!ratebtn || !ratepanel || !video) return false; 211 return true; 212} 213 214function isRatepanelOpen() { 215 if (! rateElementsPresent()) { return false; } 216 return ratebtn.getAttribute('aria-expanded') === 'true'; 217} 218 219function getCheckedRateRadio() { 220 if (! rateElementsPresent()) { return false; } 221 return ratepanel.querySelector('input[name="playbackRate"]:checked') 222 || ratepanel.querySelector('input[name="playbackRate"]'); 223} 224 225function openRatepanel() { 226 if (! rateElementsPresent()) { return false; } 227 // Make visible 228 ratepanel.hidden = false; 229 ratebtn.setAttribute('aria-expanded', 'true'); 230 231 // Focus the currently selected rate 232 const r = getCheckedRateRadio(); 233 if (r) r.focus(); 234} 235 236function closeRatepanel({ focusButton = true } = {}) { 237 if (! rateElementsPresent()) { return false; } 238 ratepanel.hidden = true; 239 ratebtn.setAttribute('aria-expanded', 'false'); 240 if (focusButton) ratebtn.focus(); 241} 242 243function toggleRatepanel() { 244 if (! rateElementsPresent()) { return false; } 245 if (isRatepanelOpen()) closeRatepanel(); 246 else openRatepanel(); 247} 248 249let hideControlsTimeout; 250 251$(document).ready(function() { 252 doingHTML5Video = jQuery('body').hasClass('html5video'); 253 video = document.getElementById('videoMain'); 254 if (video) { 255 video.muted = false; 256 video.addEventListener("timeupdate", updateProgress); 257 video.addEventListener("loadedmetadata", updateProgress); // Ensure display updates when video loads 258 259 videoContainer = document.getElementById('jp_container_1'); 260 videoContainer.addEventListener("mousemove", resetControlsTimer); 261 262 videoControls = videoContainer.querySelector('.jp-interface'); 263 progressBar = document.getElementById('progressBar'); 264 // Seek on user input (dragging the thumb) 265 progressBar.addEventListener("input", () => { 266 isSeeking = true; // Prevent automatic updates while seeking 267 video.currentTime = progressBar.value; 268 }); 269 270 // Resume automatic updates when the user releases the thumb 271 progressBar.addEventListener("change", () => { 272 isSeeking = false; 273 }); 274 275 // Update progress bar when video plays, but NOT if user is seeking 276 video.addEventListener("timeupdate", () => { 277 if (!isSeeking) { 278 progressBar.value = video.currentTime; 279 } 280 }); 281 volumeBar = document.getElementById("volumeBar"); 282 volumeBar.addEventListener("input", () => { 283 video.muted = false; 284 video.volume = volumeBar.value; 285 updateVolumeBar(); 286 $("ul.jp-controls li .jp-mute, ul.jp-controls li .jp-unmute").removeClass("jp-unmute").addClass("jp-mute").find(".sr-only").text("mute"); 287 }); 288 289 video.addEventListener("volumechange", () => { 290 updateVolumeBar(); 291 }); 292 updateVolumeBar(); 293 294 ccButton = jQuery('.jp-cc').get(0); 295 ccButton.addEventListener("click", (e) => { 296 e.preventDefault(); 297 e.stopPropagation(); 298 if (! e.target.classList.contains('disabled')) { 299 const tracks = video.textTracks; // Get all text tracks 300 if (tracks.length > 0) { 301 const track = tracks[0]; // Assuming the first track is the default CC 302 track.mode = (track.mode === "showing") ? "hidden" : "showing"; 303 } 304 } 305 }); 306 respectPresenseOfCaptions(); 307 308 document.getElementById('liFullScreen').querySelector('button').addEventListener("click", (e) => { 309 e.preventDefault(); 310 e.stopPropagation(); 311 toggleFullscreen(); 312 }); 313 document.addEventListener("fullscreenchange", () => { 314 resetControlsTimer(); 315 if (document.fullscreenElement) { 316 videoContainer.classList.add("fullscreen"); 317 jQuery('#liFullScreen button .sr-only').text("restore screen"); 318 } else { 319 videoContainer.classList.remove("fullscreen"); 320 videoControls.classList.remove("hidden"); 321 jQuery('#liFullScreen button .sr-only').text("full screen"); 322 } 323 }); 324 video.addEventListener("play", resetControlsTimer); 325 video.addEventListener("waiting", function() { 326 $('.jp-video-play').hide(); 327 showLoading(); 328 }); 329 330 video.addEventListener("playing", function() { 331 $('.jp-video-play').hide(); 332 hideLoading(); 333 }); 334 } else { 335 doingHTML5Video = false;
336 } 337 338 $("div.jp-controls-holder ul li.jump .jp-jumpbackward").click(function() { jumpPlayer(-15); return false; }); 339 $("div.jp-controls-holder ul li.jump .jp-jumpforward").click(function() { jumpPlayer(15); return false; }); 340 341 if (doingHTML5Video) { 342 video.addEventListener("loadedmetadata", () => { 343 if ((typeof starttime !== 'undefined') && starttime > 0 && starttime < video.duration) { 344 video.currentTime = starttime; 345 } 346 }); 347 $("#play-button-overlay").click(function() { 348 $("#liPlayPause button").click(); 349 return false; 350 }); 351 $("#liPlayPause button").click(function() { 352 const btn = $(this); 353 if (btn.hasClass("jp-play")) { 354 video.play(); 355 btn.removeClass("jp-play").addClass("jp-pause").find(".sr-only").text("pause"); 356 $('body.html5video .html5-player').removeClass('stopped'); 357 } else { 358 video.pause(); 359 btn.removeClass("jp-pause").addClass("jp-play").find(".sr-only").text("play"); 360 } 361 return false; 362 }); 363 $("ul.jp-controls li .jp-stop").click(function() { 364 video.pause(); 365 video.currentTime = 0; 366 367 // show the poster after a stop, instead of the first frame of the video. 368 video.load(); 369 370 $("#liPlayPause button").removeClass("jp-pause").addClass("jp-play").find(".sr-only").text("play"); 371 $('body.html5video .html5-player').addClass('stopped'); 372 return false; 373 }); 374 $("ul.jp-controls li .jp-mute").click(function() { 375 const btn = $(this); 376 if (btn.hasClass("jp-mute")) { 377 video.muted = true; 378 btn.removeClass("jp-mute").addClass("jp-unmute").find(".sr-only").text("unmute"); 379 } else { 380 video.muted = false; 381 btn.removeClass("jp-unmute").addClass("jp-mute").find(".sr-only").text("mute"); 382 } 383 updateVolumeBar(); 384 return false; 385 }); 386 $("ul.jp-controls li .jp-volume-max").click(function() { 387 video.muted = false; 388 $("ul.jp-controls li .jp-mute, ul.jp-controls li .jp-unmute").removeClass("jp-unmute").addClass("jp-mute").find(".sr-only").text("mute"); 389 video.volume = 1; 390 updateVolumeBar(); 391 return false; 392 }); 393 } 394 // $("div.jp-controls-holder ul #liPlaybackRate button").click(function() { $('div.jp-playback-rate').toggle(); return false; }); 395 // $("div.jp-playback-rate ul li button").click(function() { 396 // var newval = $(this).text(); 397 // newval = newval.replace(/[^\d\.]/g, ''); 398 // if (doingHTML5Video) { 399 // video.playbackRate = newval; 400 // } else { 401 // $("#jquery_jplayer_1").jPlayer({playbackRate: newval}); 402 // } 403 // $('div.jp-playback-rate').hide(); 404 // $("div.jp-controls-holder ul #liPlaybackRate button").text(newval + 'x'); 405 // return false; 406 // }); 407 408 ratebtn = document.getElementById('btnPlaybackRate'); 409 ratepanel = document.getElementById('playbackRatePanel'); 410 ratelabel = document.getElementById('playbackRateLabel'); // optional 411 412 if (rateElementsPresent()) { 413 // A bunch of listeners ralated to the playback rate popup, especially for keyboard usage 414 // Apply rate when changed 415 ratepanel.addEventListener('change', (e) => { 416 const t = e.target; 417 if (!(t instanceof HTMLInputElement)) return; 418 if (t.name !== 'playbackRate') return; 419 420 const rate = parseFloat(t.value); 421 if (!Number.isFinite(rate)) return; 422 423 video.playbackRate = rate; 424 if (ratelabel) ratelabel.textContent = `${rate}x`; 425 }); 426 427 ratepanel.addEventListener('keydown', (e) => { 428 if (ratepanel.hidden) return; 429 430 if (e.key === 'Escape') { 431 e.preventDefault(); 432 closeRatepanel(); 433 return; 434 } 435 436 // Space/Enter: commit + close 437 if (e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') { 438 // Prevent scrolling on Space 439 e.preventDefault(); 440 441 // Ensure focused radio is checked (usually already is, but safe) 442 const active = document.activeElement; 443 if (active instanceof HTMLInputElement && active.name === 'playbackRate') { 444 active.checked = true; 445 const rate = parseFloat(active.value); 446 if (Number.isFinite(rate)) { 447 video.playbackRate = rate; 448 if (ratelabel) ratelabel.textContent = `${rate}x`; 449 } 450 } 451 452 closeRatepanel(); 453 } 454 }); 455 // Toggle on click 456 ratebtn.addEventListener('click', (e) => { 457 e.preventDefault(); 458 toggleRatepanel(); 459 }); 460 } 461 462 // Escape closes (when open) 463 document.addEventListener('keydown', (e) => { 464 if (!isRatepanelOpen()) return; 465 if (e.key === 'Escape') { 466 e.preventDefault(); 467 closeRatepanel(); 468 } 469 }); 470 471 // Close when clicking outside (when open) 472 document.addEventListener('mousedown', (e) => { 473 if (!isRatepanelOpen()) return; 474 const target = e.target; 475 if (ratepanel.contains(target) || ratebtn.contains(target)) return;
476 closeRatepanel({ focusButton: false }); 477 }); 478 479 if (video) { 480 // if video playbackRate changes elsewhere, keep radios in sync 481 video.addEventListener('ratechange', () => { 482 const rate = video.playbackRate; 483 const radio = ratepanel.querySelector(`input[name="playbackRate"][value="${rate}"]`); 484 if (radio) radio.checked = true; 485 if (ratelabel) ratelabel.textContent = `${rate}x`; 486 }); 487 } 488 489 $('body.about #divChannelSelector a, body.services #divChannelSelector a').click(function() { 490 if ($(this).data('groupnum') == undefined) { 491 showOnlyAbout($(this).attr('href')); 492 } else { 493 showOnlyAbout($(this).data('groupnum')); 494 } 495 $('#divChannelSelector li').removeClass('selected'); 496 $(this).closest('li').addClass('selected'); 497 return false; 498 }); 499 500 $('body.channels #divWeekSchedule').tabs(); 501 var dnow = new Date(); 502 var isodate = dnow.getFullYear() * 10000 + (dnow.getMonth() + 1) * 100 + dnow.getDate(); 503 $('#aDate' + isodate).click(); 504 505 $('#divWeatherAlertHeadline a').click(function () { 506 $('#divWeatherAlertDetails').dialog({title:"Weather Alert", width:900}); 507 return false; 508 }); 509 510 // TODO: there should be HTML5Video events analogous to each of these 511 $("#jquery_jplayer_1").on($.jPlayer.event.play, function(event) { showLoading(); } ); 512 $("#jquery_jplayer_1").on($.jPlayer.event.playing, function(event) { hideLoading(); } ); 513 // according to http://jplayer.org/latest/developer-guide/#jPlayer-events the playing event might not play for videos when using flash 514 $("#jquery_jplayer_1").on($.jPlayer.event.loadedmetadata, function(event) { hideLoading(); } ); 515 516 $('#checkboxStartAt').on('click', function() { 517 if ($(this).is(':checked')) { 518 $('#inputStartTime').prop('disabled', false); 519 if (! /\d/.test($('#inputStartTime').val())) { 520 $('#inputStartTime').val($('#divTimingInfo div.jp-current-time').text()); 521 } 522 } else { 523 $('#inputStartTime').val('').prop('disabled', true); 524 } 525 respectStartTime(); 526 }); 527 $('#inputStartTime').on('keyup', respectStartTime); 528 $('#buttonCopyPermalink').on('click', copyInputCopyPermalink); 529 530 $('#inputFilterName').on('keyup', function() { 531 var needle = $(this).val().toLowerCase(); 532 $('#tableMeeters').find('tr').each(function() { 533 var orgname = $(this).find('td.sf-name').find('.thevalue').text().toLowerCase(); 534 $(this).toggle(orgname.includes(needle)); 535 }); 536 stripeTable('#tableMeeters'); 537 }); 538 stripeTable('#tableMeeters'); 539 $('#tableMeeters').on('click', 'a.copy-text', function() { 540 var cellToCopy = $(this).closest('tr').find('td:nth-child(2)'); 541 var textToCopy = cellToCopy.text(); 542 543 // Use the navigator clipboard API to copy the text 544 navigator.clipboard.writeText(textToCopy).then(function() { 545 console.log('Text copied to clipboard:', textToCopy); 546 547 // Add and remove a class for visual feedback 548 cellToCopy.addClass('copied'); 549 setTimeout(function() { 550 cellToCopy.removeClass('copied'); 551 }, 600); // 1000 milliseconds = 1 second (adjust as needed) 552 553 }, function(err) { 554 console.error('Error copying text to clipboard:', err); 555 }); 556 557 // Prevent default link behavior 558 return false; 559 }); 560}); 561 562 563// Can't use this because of the same origin policy 564// function DisplayTweets(count) { 565// if (! count) { 566// count = 3; 567// } 568 569// var screennames = ['davidreedernst', 'nprscottsimon']; 570// var tweets = []; 571// $.each(screennames, function(index, screenname) { 572// $.getJSON('https://api.twitter.com/1/statuses/user_timeline.json?trim_user=true&count=3&screen_name=' + screenname, 573// function(data) { 574// tweets.push[data.text]; 575// } 576// ); 577// }); 578// }
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.