"use strict"; import options from "../lib/options"; import cast, { ensureInit } from "../shim/export"; import { ReceiverSelectorMediaType } from "../background/receiverSelector"; import { ReceiverDevice } from "../types"; // Variables passed from background const { selectedMedia, selectedReceiver }: { selectedMedia: ReceiverSelectorMediaType; selectedReceiver: ReceiverDevice; } = window as any; const FX_CAST_RECEIVER_APP_NAMESPACE = "urn:x-cast:fx_cast"; let session: cast.Session; let wasSessionRequested = false; let peerConnection: RTCPeerConnection; /** * Sends a message to the fx_cast app running on the * receiver device. */ function sendAppMessage(subject: string, data: any) { if (!session) { return; } session.sendMessage(FX_CAST_RECEIVER_APP_NAMESPACE, { subject, data }); } window.addEventListener("beforeunload", () => { sendAppMessage("close", null); }); async function onRequestSessionSuccess(newSession: cast.Session) { cast.logMessage("onRequestSessionSuccess"); session = newSession; session.addMessageListener( FX_CAST_RECEIVER_APP_NAMESPACE, async (_namespace, message) => { const { subject, data } = JSON.parse(message); switch (subject) { case "peerConnectionAnswer": { peerConnection.setRemoteDescription(data); break; } case "iceCandidate": { peerConnection.addIceCandidate(data); break; } } } ); peerConnection = new RTCPeerConnection(); peerConnection.addEventListener("icecandidate", ev => { sendAppMessage("iceCandidate", ev.candidate); }); switch (selectedMedia) { case ReceiverSelectorMediaType.Tab: { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Shouldn't be possible if (!ctx) { break; } // Set initial size canvas.width = window.innerWidth * window.devicePixelRatio; canvas.height = window.innerHeight * window.devicePixelRatio; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); // Resize canvas whenever the window resizes window.addEventListener("resize", () => { canvas.width = window.innerWidth * window.devicePixelRatio; canvas.height = window.innerHeight * window.devicePixelRatio; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.scale(window.devicePixelRatio, window.devicePixelRatio); }); // TODO: Test performance const drawFlags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES | ctx.DRAWWINDOW_USE_WIDGET_LAYERS; let lastFrame: DOMHighResTimeStamp; window.requestAnimationFrame(function draw( now: DOMHighResTimeStamp ) { if (!lastFrame) { lastFrame = now; } if (now - lastFrame > 1000 / 30) { ctx.drawWindow( window, // window 0, 0, // x, y canvas.width, // w canvas.height, // h "white", // bgColor drawFlags ); // flags lastFrame = now; } window.requestAnimationFrame(draw); }); /** * Capture video stream from canvas and feed into the RTC * connection. */ peerConnection.addStream(canvas.captureStream()); break; } case ReceiverSelectorMediaType.Screen: { const stream = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: "motion" }, audio: false }); peerConnection.addStream(stream); break; } } // Create SDP offer and set locally const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); // Send local offer to receiver app sendAppMessage("peerConnectionOffer", offer); } function receiverListener(availability: string) { cast.logMessage("receiverListener"); if (wasSessionRequested) { return; } if (availability === cast.ReceiverAvailability.AVAILABLE) { wasSessionRequested = true; cast.requestSession( onRequestSessionSuccess, onRequestSessionError, undefined, selectedReceiver ); } } function onRequestSessionError() { cast.logMessage("onRequestSessionError"); } function sessionListener() { cast.logMessage("sessionListener"); } function onInitializeSuccess() { cast.logMessage("onInitializeSuccess"); } function onInitializeError() { cast.logMessage("onInitializeError"); } ensureInit().then(async () => { const mirroringAppId = await options.get("mirroringAppId"); const sessionRequest = new cast.SessionRequest(mirroringAppId); const apiConfig = new cast.ApiConfig( sessionRequest, sessionListener, receiverListener, undefined, undefined ); cast.initialize(apiConfig, onInitializeSuccess, onInitializeError); });