') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - ZapperDJ/rtv: A pseudo-live TV implementation to save bandwidth but still function normally. · GitHub
Skip to content

Repository files navigation

rtv

A pseudo-live TV implementation to save bandwidth but still function normally.

See it in action, pseudo-live!

CHANGES

This is a fork of RTV with some changes inspired by YTCH, FreeTVz and CouchTube

  • Hid and disabled YouTube UI / controls
  • Made menubar floating
  • Added power, volume, channel change, mute, subtitles, and open on YouTube buttons
  • Removed resync button as power button replaces it
  • Updated FontAwesome
  • Changed autohide sidebar button to hide controls button

This changes were tested against youtube, most of them will not work on dailymotion / direct links


What does that even mean?

When you tune in to RTV it obtains a playlist, which has a defined starting point in history (yesterday, 10 years ago, whatever). It proceeds to determine, had that same playlist been repeating since it started, what is currently on right now.

Won't this use a ton of bandwidth?

No! Only if you let it. You only watch what is currently playing and the provider of the video is only active when there is at least one viewer so both sides benefit from only transferring what they must but maintaining sync while "offline".

Why not use something like a website that syncs YouTube videos, or a YouTube playlist?

This is unmanaged and "continually broadcasts", even when there's nobody left to watch. It does not require someone to make a room, load up some videos, or even interact with it to maintain a sync each time somebody feels like watching. The stream is always available where it's expected to be.

Why are all these answers mostly the same?

Because it's that simple. There are only a few instances of smoke and mirrors to maintain the sync, most of the experience comes from the variety of channels and the intricate guide. It's a direct competitor to YouTube syncing sites, but also itself is a unique feature over them. It also works locally/self-hosted if you want to roll your own.


RTV provides a ton of channels, probably not as diverse as all would like. To alleviate that, Custom Channels are supported.

How you can help RTV

Supported video providers

  • Direct links (site.com/video.mp4)
  • YouTube
  • Dailymotion (same as YouTube)

Planned Features

  • Search
  • Playlist switchingDone, the channel select window!
  • Playlist viewingDone, the guide!
    • Maybe browser desktop notification support
      • Service workers (background task)
      • Receive run alerts. "first time only", "every time"?
  • Playlist shufflingDone, seedrandom and Fisher Yates shuffle!
  • Custom playlist URLsDone (not URLs), the channel select window!
  • Picture in Picture
    • Getting there, jQuery UI dialogs help a ton.
  • Quality selector
  • Audio track selector
    • The technology is simultaneously there and not there. Send help.
  • Manual sync adjustment for closer viewing with others
  • Automatic resync (on button click)Done!
  • ChatDone!
    • IRC. Powered, but not endorsed, by Rizon.

Unplanned Features

About

A pseudo-live TV implementation to save bandwidth but still function normally.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages