PageSourceSearch

https://catstv.net/js/cats.js?v=20260327

js catstv.net collected 2026-09-26 10:13:02 UTC 19,297 bytes, 578 lines download raw bytes

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.