WebExtension as alternative to Chromecast
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

358 lines
11 KiB

6 years ago
  1. console.log("muffcast client v0.1");
  2. var currentStatus;
  3. var syncIntervals = [];
  4. var syncIntervalTime = 30000;
  5. var seekIntervals = [];
  6. var muffcastUrl = "http://localhost:8128";
  7. browser.storage.local.get("muffcast").then(function(result) {
  8. muffcastUrl = result.muffcast && result.muffcast.url || muffcastUrl;
  9. syncIntervalTime = result.muffcast && result.muffcast.syncInterval && parseInt(result.muffcast.syncInterval) || syncIntervalTime;
  10. })
  11. var getStatus = function() {
  12. return new Promise(function(resolve, reject) {
  13. var xhttp = new XMLHttpRequest();
  14. xhttp.open("GET", muffcastUrl, true);
  15. xhttp.onreadystatechange = function() {
  16. if (this.readyState == 4) {
  17. if (this.status == 200) {
  18. var response = this.responseText ? JSON.parse(this.responseText) : false;
  19. resolve(response);
  20. } else {
  21. reject({
  22. status: this.status,
  23. error: this.statusText,
  24. body: this.responseText,
  25. });
  26. }
  27. }
  28. }
  29. xhttp.setRequestHeader("Content-type", "application/json");
  30. xhttp.send();
  31. })
  32. }
  33. var getPlayer = function(type, index, sleep) {
  34. return new Promise(function(resolve, reject) {
  35. setTimeout(function() {
  36. var player = document.getElementsByTagName(type)[index];
  37. if (player) {
  38. resolve(player);
  39. } else if (sleep < 3000) {
  40. return getPlayer(type, index, sleep + 500);
  41. } else {
  42. reject(player);
  43. }
  44. }, sleep);
  45. })
  46. }
  47. var getTimeString = function(seconds) {
  48. var hours = parseInt(seconds / 3600);
  49. var minutes = parseInt((seconds % 3600) / 60);
  50. var seconds = parseInt(seconds % 60);
  51. return (hours > 0 ? hours + ":" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
  52. }
  53. var addCastLinks = function(type) {
  54. // add cast links
  55. var elements = document.getElementsByTagName(type);
  56. for (var i = 0; i < elements.length; i++) {
  57. var element = elements[i];
  58. var position = element.getBoundingClientRect();
  59. var castLink = document.createElement("a");
  60. castLink.id = "muffcast-cast-link_" + i;
  61. castLink.index = i;
  62. castLink.classList.add("muffcast-loader");
  63. castLink.style["top"] = position.top + "px";
  64. castLink.style["left"] = position.left + "px";
  65. castLink.innerHTML = '<i class="fa fa-fw fa-television"></i>';
  66. document.body.appendChild(castLink);
  67. castLink.addEventListener("click", function(event) {
  68. var index = event.target.parentNode.index;
  69. var player = document.getElementsByTagName(type)[index];
  70. player.pause();
  71. browser.runtime.sendMessage({
  72. "command": "load",
  73. "url": encodeURIComponent(window.location.href),
  74. "type": type,
  75. "index": index,
  76. "seek": player.currentTime,
  77. "volume": player.volume,
  78. "muted": player.muted
  79. });
  80. })
  81. }
  82. }
  83. var setStatus = function() {
  84. for (let seekInterval of seekIntervals) {
  85. clearInterval(seekInterval);
  86. }
  87. for (let syncInterval of syncIntervals) {
  88. clearInterval(syncInterval);
  89. }
  90. var overlay = document.getElementById("muffcast-overlay");
  91. if (overlay) {
  92. overlay.parentNode.removeChild(overlay);
  93. }
  94. // remove all cast links
  95. for (let castLink of document.getElementsByClassName("muffcast-loader")) {
  96. document.body.removeChild(castLink);
  97. }
  98. addCastLinks("video");
  99. addCastLinks("audio");
  100. getStatus().then(function(status) {
  101. currentStatus = status;
  102. if (currentStatus.url && currentStatus.url == encodeURIComponent(window.location.href)) {
  103. getPlayer(currentStatus.type, currentStatus.index, 0).then(function(player) {
  104. player.muted = currentStatus.muted;
  105. player.currentTime = currentStatus.currentTime;
  106. player.addEventListener("canplaythrough", function() {
  107. if (currentStatus.playing && currentStatus.url == encodeURIComponent(window.location.href)) {
  108. player.pause();
  109. }
  110. })
  111. overlay = document.createElement("div");
  112. overlay.id = "muffcast-overlay";
  113. document.body.appendChild(overlay);
  114. var play = document.createElement("a");
  115. play.id = "muffcast-play";
  116. play.innerHTML = currentStatus.playing ? '<i class="fa fa-fw fa-pause"></i>' : '<i class="fa fa-fw fa-play"></i>';;
  117. play.addEventListener("click", function(event) {
  118. if (currentStatus.playing) {
  119. browser.runtime.sendMessage({
  120. "command": "pause",
  121. "seek": player.currentTime
  122. });
  123. } else {
  124. browser.runtime.sendMessage({
  125. "command": "play",
  126. "seek": player.currentTime
  127. });
  128. player.pause();
  129. }
  130. currentStatus.playing = !currentStatus.playing;
  131. play.innerHTML = currentStatus.playing ? '<i class="fa fa-fw fa-pause"></i>' : '<i class="fa fa-fw fa-play"></i>';
  132. })
  133. var stop = document.createElement("a");
  134. stop.id = "muffcast-stop";
  135. stop.innerHTML = '<i class="fa fa-fw fa-stop"></i>';
  136. stop.addEventListener("click", function(event) {
  137. browser.runtime.sendMessage({
  138. "command": "stop"
  139. });
  140. setStatus();
  141. })
  142. var duration = document.createElement("span");
  143. duration.id = "muffcast-duration";
  144. duration.classList.add("time");
  145. duration.textContent = getTimeString(player.duration);
  146. var currentTime = document.createElement("span");
  147. currentTime.id = "muffcast-currenttime";
  148. currentTime.classList.add("time");
  149. currentTime.textContent = getTimeString(player.currentTime);
  150. var seek = document.createElement("input");
  151. seek.id = "muffcast-seek";
  152. seek.setAttribute("type", "range");
  153. seek.setAttribute("min", 0);
  154. seek.setAttribute("max", currentStatus.duration);
  155. seek.setAttribute("value", currentStatus.currentTime);
  156. seek.addEventListener("change", function(event) {
  157. browser.runtime.sendMessage({
  158. "command": "seek",
  159. "seek": seek.value
  160. });
  161. player.currentTime = seek.value;
  162. currentTime.textContent = getTimeString(player.currentTime);
  163. });
  164. seekIntervals.push(setInterval(function() {
  165. if (seek.value < currentStatus.duration) {
  166. if (!player.paused || currentStatus.playing) {
  167. seek.value++;
  168. currentTime.textContent = getTimeString(seek.value);
  169. }
  170. } else {
  171. seek.value = 0;
  172. clearInterval(seekInterval);
  173. }
  174. }, 1000));
  175. var audio = document.createElement("div");
  176. audio.id = "muffcast-audio";
  177. var volume = document.createElement("input");
  178. volume.id = "muffcast-volume";
  179. volume.setAttribute("type", "range");
  180. volume.setAttribute("min", 0);
  181. volume.setAttribute("max", 1);
  182. volume.setAttribute("step", 0.01);
  183. volume.setAttribute("value", status.muted ? 0 : status.volume);
  184. volume.addEventListener("change", function(event) {
  185. browser.runtime.sendMessage({
  186. "command": "volume",
  187. "volume": volume.value
  188. });
  189. player.volume = volume.value;
  190. player.muted = volume.value == 0;
  191. });
  192. var mute = document.createElement("a");
  193. mute.id = "muffcast-mute";
  194. mute.innerHTML = status.muted ? '<i class="fa fa-fw fa-volume-off"></i>' : (player.volume < 0.5 ? '<i class="fa fa-fw fa-volume-down"></i>' : '<i class="fa fa-fw fa-volume-up"></i>');
  195. mute.addEventListener("click", function(event) {
  196. browser.runtime.sendMessage({
  197. "command": "mute",
  198. "muted": !player.muted
  199. });
  200. player.muted = !player.muted;
  201. volume.value = player.muted ? 0 : player.volume;
  202. mute.innerHTML = player.muted ? '<i class="fa fa-fw fa-volume-off"></i>' : (player.volume < 0.5 ? '<i class="fa fa-fw fa-volume-down"></i>' : '<i class="fa fa-fw fa-volume-up"></i>');
  203. })
  204. var icon = document.createElement("span");
  205. icon.id = "muffcast-icon";
  206. icon.innerHTML = '<i class="fa fa-television"></i>';
  207. overlay.appendChild(icon);
  208. overlay.appendChild(play);
  209. audio.appendChild(mute);
  210. audio.appendChild(volume);
  211. overlay.appendChild(audio);
  212. overlay.appendChild(currentTime);
  213. overlay.appendChild(seek);
  214. overlay.appendChild(duration);
  215. overlay.appendChild(stop);
  216. var castLink = document.getElementById("muffcast-cast-link_" + status.index);
  217. castLink.classList.add("active");
  218. syncIntervals.push(setInterval(function() {
  219. // sync status
  220. if (!player.isSyncInterval) {
  221. getStatus().then(function(status) {
  222. player.isSyncInterval = true;
  223. currentStatus.playing = status.playing;
  224. currentStatus.currentTime = status.currentTime;
  225. currentStatus.volume = status.volume;
  226. currentStatus.muted = status.muted;
  227. currentTime.textContent = getTimeString(currentStatus.currentTime);
  228. seek.value = currentStatus.currentTime
  229. play.innerHTML = currentStatus.playing ? '<i class="fa fa-fw fa-pause"></i>' : '<i class="fa fa-fw fa-play"></i>';
  230. volume.value = currentStatus.muted ? 0 : currentStatus.volume;
  231. mute.innerHTML = currentStatus.muted ? '<i class="fa fa-fw fa-volume-off"></i>' : (currentStatus.volume < 0.5 ? '<i class="fa fa-fw fa-volume-down"></i>' : '<i class="fa fa-fw fa-volume-up"></i>');
  232. player.volume = currentStatus.volume;
  233. player.muted = currentStatus.muted;
  234. player.currentTime = currentStatus.currentTime;
  235. })
  236. }
  237. }, syncIntervalTime));
  238. setTimeout(function() {
  239. player.addEventListener("play", function(event) {
  240. if (status.playing && status.url == encodeURIComponent(window.location.href)) {
  241. browser.runtime.sendMessage({
  242. "command": "pause",
  243. "seek": player.currentTime
  244. });
  245. play.innerHTML = '<i class="fa fa-fw fa-play"></i>';
  246. status.playing = !status.playing;
  247. }
  248. })
  249. player.addEventListener("pause", function(event) {
  250. if (!status.playing && status.url == encodeURIComponent(window.location.href.href)) {
  251. browser.runtime.sendMessage({
  252. "command": "play",
  253. "seek": player.currentTime
  254. });
  255. play.innerHTML = '<i class="fa fa-fw fa-play"></i>';
  256. status.playing = !status.playing;
  257. }
  258. })
  259. player.addEventListener("seeked", function(event) {
  260. if (status.playing && status.url == encodeURIComponent(window.location.href)) {
  261. if (!player.isSyncInterval) {
  262. browser.runtime.sendMessage({
  263. "command": "seek",
  264. "seek": player.currentTime
  265. });
  266. seek.value = player.currentTime;
  267. currentTime.textContent = getTimeString(player.currentTime);
  268. } else {
  269. player.isSyncInterval = false;
  270. };
  271. }
  272. })
  273. player.addEventListener("volumechange", function(event) {
  274. if (status.url == encodeURIComponent(window.location.href)) {
  275. browser.runtime.sendMessage({
  276. "command": "volume",
  277. "volume": player.volume
  278. });
  279. volume.value = player.muted ? 0 : player.volume;
  280. mute.innerHTML = player.muted ? '<i class="fa fa-fw fa-volume-off"></i>' : (player.volume < 0.5 ? '<i class="fa fa-fw fa-volume-down"></i>' : '<i class="fa fa-fw fa-volume-up"></i>');
  281. }
  282. })
  283. }, 1500);
  284. })
  285. }
  286. })
  287. }
  288. browser.runtime.onMessage.addListener(function(message) {
  289. var videos = document.getElementsByTagName("video");
  290. switch (message.command) {
  291. case "update":
  292. setStatus();
  293. break;
  294. case "load":
  295. var player = videos[message.index];
  296. player.pause();
  297. player.style.border = "none";
  298. browser.runtime.sendMessage({
  299. "command": "load",
  300. "url": encodeURIComponent(window.location.href),
  301. "type": "video",
  302. "index": message.index,
  303. "seek": player.currentTime,
  304. "volume": player.volume,
  305. "muted": player.muted
  306. });
  307. break;
  308. case "mark":
  309. var player = videos[message.index];
  310. player.style.border = "5px solid red";
  311. break;
  312. case "unmark":
  313. var player = videos[message.index];
  314. player.style.border = "none";
  315. break;
  316. }
  317. })