Direct Stream Caster

Dynamically detect and cast HLS streams via Chromecast on any site (e.g., StreamEast)

  1. // ==UserScript==
  2. // @name Direct Stream Caster
  3. // @namespace https://tampermonkey.net
  4. // @version 3.2
  5. // @description Dynamically detect and cast HLS streams via Chromecast on any site (e.g., StreamEast)
  6. // @author sharmanhall
  7. // @match *://*/*
  8. // @grant none
  9. // @run-at document-start
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. let detectedStreamUrl = null;
  17. const REFERER = "https://googlapisapi.com";
  18.  
  19. // Hook fetch and XHR to sniff for .m3u8 playlist requests
  20. const originalFetch = window.fetch;
  21. window.fetch = async (...args) => {
  22. if (args[0] && typeof args[0] === 'string' && args[0].includes('.m3u8')) {
  23. console.log('[CastDetect] Found .m3u8 via fetch:', args[0]);
  24. detectedStreamUrl = args[0];
  25. }
  26. return originalFetch(...args);
  27. };
  28.  
  29. const originalXHROpen = XMLHttpRequest.prototype.open;
  30. XMLHttpRequest.prototype.open = function (method, url) {
  31. if (url.includes('.m3u8')) {
  32. console.log('[CastDetect] Found .m3u8 via XHR:', url);
  33. detectedStreamUrl = url;
  34. }
  35. return originalXHROpen.apply(this, arguments);
  36. };
  37.  
  38. // Inject Cast SDK
  39. const sdkScript = document.createElement('script');
  40. sdkScript.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
  41. document.head.appendChild(sdkScript);
  42.  
  43. // Wait for Cast API and then inject cast button
  44. window.__onGCastApiAvailable = function (isAvailable) {
  45. if (!isAvailable) return;
  46.  
  47. cast.framework.CastContext.getInstance().setOptions({
  48. receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
  49. autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
  50. });
  51.  
  52. addCastButton();
  53. };
  54.  
  55. function addCastButton() {
  56. if (document.getElementById('castToChromecast')) return;
  57.  
  58. const btn = document.createElement('button');
  59. btn.id = 'castToChromecast';
  60. btn.textContent = '📺 Cast Stream';
  61. Object.assign(btn.style, {
  62. position: 'fixed',
  63. bottom: '20px',
  64. left: '20px',
  65. zIndex: 9999,
  66. padding: '12px 20px',
  67. backgroundColor: '#1e88e5',
  68. color: 'white',
  69. border: 'none',
  70. borderRadius: '6px',
  71. fontSize: '16px',
  72. cursor: 'pointer',
  73. fontWeight: 'bold',
  74. boxShadow: '0 0 10px rgba(0,0,0,0.5)'
  75. });
  76.  
  77. btn.onclick = () => {
  78. const context = cast.framework.CastContext.getInstance();
  79. const session = context.getCurrentSession();
  80.  
  81. if (!session) {
  82. alert("No Chromecast session. Click the Cast icon in your Chrome toolbar first.");
  83. return;
  84. }
  85.  
  86. if (!detectedStreamUrl) {
  87. alert("No stream detected yet. Wait for the stream to load or refresh.");
  88. return;
  89. }
  90.  
  91. const mediaInfo = new chrome.cast.media.MediaInfo(detectedStreamUrl, 'application/x-mpegurl');
  92. mediaInfo.customData = {
  93. headers: {
  94. Referer: REFERER
  95. }
  96. };
  97.  
  98. const request = new chrome.cast.media.LoadRequest(mediaInfo);
  99. session.loadMedia(request).then(() => {
  100. console.log("Casting stream:", detectedStreamUrl);
  101. }).catch(console.error);
  102. };
  103.  
  104. document.body.appendChild(btn);
  105. }
  106. })();