mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-09 09:09:58 +00:00
Allow mediaCast sender to run in background context
This commit is contained in:
@@ -276,11 +276,9 @@ function handleMediaServerMessage (message: Message) {
|
||||
sendMessage("mediaCast:/mediaServer/started");
|
||||
});
|
||||
mediaServer.on("close", () => {
|
||||
console.error("mediaServer close");
|
||||
sendMessage("mediaCast:/mediaServer/stopped");
|
||||
});
|
||||
mediaServer.on("error", (a) => {
|
||||
console.error("mediaServer error", a);
|
||||
sendMessage("mediaCast:/mediaServer/error");
|
||||
});
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ async function getSelection (
|
||||
ReceiverSelectorMediaType.Tab
|
||||
, availableMediaTypes =
|
||||
ReceiverSelectorMediaType.Tab
|
||||
| ReceiverSelectorMediaType.Screen)
|
||||
// | ReceiverSelectorMediaType.File)
|
||||
| ReceiverSelectorMediaType.Screen
|
||||
| ReceiverSelectorMediaType.File)
|
||||
: Promise<ReceiverSelection> {
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
@@ -13,151 +13,218 @@ import SelectorManager from "./SelectorManager";
|
||||
import StatusManager from "./StatusManager";
|
||||
|
||||
|
||||
type Port = browser.runtime.Port | MessagePort;
|
||||
|
||||
export interface Shim {
|
||||
bridgePort: browser.runtime.Port;
|
||||
contentPort?: browser.runtime.Port;
|
||||
contentPort: Port;
|
||||
contentTabId?: number;
|
||||
contentFrameId?: number;
|
||||
}
|
||||
|
||||
export default async function createShim (
|
||||
port: browser.runtime.Port): Promise<Shim> {
|
||||
|
||||
const contentPort = port;
|
||||
const contentTabId = port.sender.tab.id;
|
||||
const contentFrameId = port.sender.frameId;
|
||||
const activeShims = new Set<Shim>();
|
||||
|
||||
const bridgePort = await bridge.connect();
|
||||
|
||||
|
||||
/**
|
||||
* If either the bridge port or the content port disconnects,
|
||||
* just teardown all communication.
|
||||
*/
|
||||
function onDisconnect () {
|
||||
bridgePort.onMessage.removeListener(onBridgePortMessage);
|
||||
contentPort.onMessage.removeListener(onContentPortMessage);
|
||||
|
||||
// Ensure all ports are disconnected
|
||||
contentPort.disconnect();
|
||||
bridgePort.disconnect();
|
||||
StatusManager.addEventListener("serviceUp", ev => {
|
||||
for (const shim of activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/serviceUp"
|
||||
, data: { id: ev.detail.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
bridgePort.onDisconnect.addListener(onDisconnect);
|
||||
contentPort.onDisconnect.addListener(onDisconnect);
|
||||
|
||||
|
||||
// Add listeners
|
||||
bridgePort.onMessage.addListener(onBridgePortMessage);
|
||||
contentPort.onMessage.addListener(onContentPortMessage);
|
||||
|
||||
function onBridgePortMessage (message: Message) {
|
||||
contentPort.postMessage(message);
|
||||
}
|
||||
|
||||
async function onContentPortMessage (message: Message) {
|
||||
const [ destination ] = message.subject.split(":/");
|
||||
if (destination === "bridge") {
|
||||
bridgePort.postMessage(message);
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "main:/shimInitialized": {
|
||||
for (const receiver of StatusManager.getReceivers()) {
|
||||
contentPort.postMessage({
|
||||
subject: "shim:/serviceUp"
|
||||
, data: { id: receiver.id }
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:/selectReceiverBegin": {
|
||||
const allMediaTypes =
|
||||
ReceiverSelectorMediaType.App
|
||||
| ReceiverSelectorMediaType.Tab
|
||||
| ReceiverSelectorMediaType.Screen
|
||||
| ReceiverSelectorMediaType.File;
|
||||
|
||||
try {
|
||||
const selection = await SelectorManager.getSelection(
|
||||
ReceiverSelectorMediaType.App
|
||||
, allMediaTypes);
|
||||
|
||||
// Handle cancellation
|
||||
if (!selection) {
|
||||
contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the media type returned from the selector has been
|
||||
* changed, we need to cancel the current sender and switch
|
||||
* it out for the right one.
|
||||
*/
|
||||
if (selection.mediaType !== ReceiverSelectorMediaType.App) {
|
||||
contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
|
||||
loadSender({
|
||||
tabId: contentTabId
|
||||
, frameId: contentFrameId
|
||||
, selection
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Pass selection back to shim
|
||||
contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverEnd"
|
||||
, data: selection
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
// TODO: Report errors properly
|
||||
contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: If we're closing a selector, make sure it's the
|
||||
* same one that caused the session creation.
|
||||
*/
|
||||
case "main:/sessionCreated": {
|
||||
const selector = await SelectorManager.getSharedSelector();
|
||||
|
||||
const shouldClose = await options.get(
|
||||
"receiverSelectorWaitForConnection");
|
||||
|
||||
if (selector.isOpen && shouldClose) {
|
||||
selector.close();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
StatusManager.addEventListener("serviceDown", ev => {
|
||||
for (const shim of activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/serviceDown"
|
||||
, data: { id: ev.detail.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
contentPort.postMessage({
|
||||
async function createShim (port: Port): Promise<void> {
|
||||
const shim = await (port instanceof MessagePort
|
||||
? createShimFromBackground(port)
|
||||
: createShimFromContent(port));
|
||||
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/initialized"
|
||||
, data: await bridge.getInfo()
|
||||
});
|
||||
|
||||
return {
|
||||
bridgePort
|
||||
, contentPort
|
||||
, contentTabId
|
||||
, contentFrameId
|
||||
};
|
||||
activeShims.add(shim);
|
||||
}
|
||||
|
||||
|
||||
async function createShimFromBackground (
|
||||
contentPort: MessagePort): Promise<Shim> {
|
||||
|
||||
const shim: Shim = {
|
||||
bridgePort: await bridge.connect()
|
||||
, contentPort
|
||||
};
|
||||
|
||||
shim.bridgePort.onDisconnect.addListener(() => {
|
||||
contentPort.close();
|
||||
activeShims.delete(shim);
|
||||
});
|
||||
|
||||
shim.bridgePort.onMessage.addListener((message: Message) => {
|
||||
contentPort.postMessage(message);
|
||||
});
|
||||
|
||||
contentPort.onmessage = ev => {
|
||||
const message = ev.data as Message;
|
||||
handleContentMessage(shim, message);
|
||||
};
|
||||
|
||||
return shim;
|
||||
}
|
||||
|
||||
|
||||
async function createShimFromContent (
|
||||
contentPort: browser.runtime.Port): Promise<Shim> {
|
||||
|
||||
/**
|
||||
* If there's already an active shim for the sender
|
||||
* tab/frame ID, disconnect it.
|
||||
*/
|
||||
for (const activeShim of activeShims) {
|
||||
if (activeShim.contentTabId === contentPort.sender.tab.id
|
||||
&& activeShim.contentFrameId === contentPort.sender.frameId) {
|
||||
activeShim.bridgePort.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
const shim: Shim = {
|
||||
bridgePort: await bridge.connect()
|
||||
, contentPort
|
||||
, contentTabId: contentPort.sender.tab.id
|
||||
, contentFrameId: contentPort.sender.frameId
|
||||
};
|
||||
|
||||
function onContentPortMessage (message: Message) {
|
||||
handleContentMessage(shim, message);
|
||||
}
|
||||
function onBridgePortMessage (message: Message) {
|
||||
contentPort.postMessage(message);
|
||||
}
|
||||
|
||||
function onDisconnect () {
|
||||
shim.bridgePort.onMessage.removeListener(onBridgePortMessage);
|
||||
contentPort.onMessage.removeListener(onContentPortMessage);
|
||||
|
||||
shim.bridgePort.disconnect();
|
||||
contentPort.disconnect();
|
||||
|
||||
activeShims.delete(shim);
|
||||
}
|
||||
|
||||
|
||||
shim.bridgePort.onDisconnect.addListener(onDisconnect);
|
||||
shim.bridgePort.onMessage.addListener(onBridgePortMessage);
|
||||
|
||||
contentPort.onDisconnect.addListener(onDisconnect);
|
||||
contentPort.onMessage.addListener(onContentPortMessage);
|
||||
|
||||
|
||||
return shim;
|
||||
}
|
||||
|
||||
|
||||
async function handleContentMessage (shim: Shim, message: Message) {
|
||||
const [ destination ] = message.subject.split(":/");
|
||||
if (destination === "bridge") {
|
||||
shim.bridgePort.postMessage(message);
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "main:/shimInitialized": {
|
||||
for (const receiver of StatusManager.getReceivers()) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/serviceUp"
|
||||
, data: { id: receiver.id }
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:/selectReceiverBegin": {
|
||||
const allMediaTypes =
|
||||
ReceiverSelectorMediaType.App
|
||||
| ReceiverSelectorMediaType.Tab
|
||||
| ReceiverSelectorMediaType.Screen
|
||||
| ReceiverSelectorMediaType.File;
|
||||
|
||||
try {
|
||||
const selection = await SelectorManager.getSelection(
|
||||
ReceiverSelectorMediaType.App
|
||||
, allMediaTypes);
|
||||
|
||||
// Handle cancellation
|
||||
if (!selection) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the media type returned from the selector has been
|
||||
* changed, we need to cancel the current sender and switch
|
||||
* it out for the right one.
|
||||
*/
|
||||
if (selection.mediaType !== ReceiverSelectorMediaType.App) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
|
||||
loadSender({
|
||||
tabId: shim.contentTabId
|
||||
, frameId: shim.contentFrameId
|
||||
, selection
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Pass selection back to shim
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverEnd"
|
||||
, data: selection
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
// TODO: Report errors properly
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/selectReceiverCancelled"
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: If we're closing a selector, make sure it's the
|
||||
* same one that caused the session creation.
|
||||
*/
|
||||
case "main:/sessionCreated": {
|
||||
const selector = await SelectorManager.getSharedSelector();
|
||||
|
||||
const shouldClose = await options.get(
|
||||
"receiverSelectorWaitForConnection");
|
||||
|
||||
if (selector.isOpen && shouldClose) {
|
||||
selector.close();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createShim;
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
, mirroringAppId: MIRRORING_APP_ID
|
||||
, receiverSelectorType: ReceiverSelectorType.Popup
|
||||
, receiverSelectorCloseIfFocusLost: true
|
||||
, receiverSelectorWaitForConnection: false
|
||||
, receiverSelectorWaitForConnection: true
|
||||
, userAgentWhitelistEnabled: true
|
||||
, userAgentWhitelist: [
|
||||
"https://www.netflix.com/*"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
import mediaCasting from "./mediaCasting";
|
||||
|
||||
import { stringify } from "./utils";
|
||||
|
||||
import { ReceiverSelection
|
||||
@@ -45,8 +43,12 @@ export default async function loadSender (opts: LoadSenderOptions) {
|
||||
|
||||
case ReceiverSelectorMediaType.File: {
|
||||
const fileUrl = new URL(`file://${opts.selection.filePath}`);
|
||||
const mediaSession = await mediaCasting.loadMediaUrl(
|
||||
fileUrl.href, opts.selection.receiver);
|
||||
const { init } = await import("../senders/mediaCast");
|
||||
|
||||
init({
|
||||
mediaUrl: fileUrl.href
|
||||
, receiver: opts.selection.receiver
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import cast, { ensureInit } from "../shim/export";
|
||||
import options from "./options";
|
||||
|
||||
import { Receiver } from "../types";
|
||||
|
||||
|
||||
function getMediaSession (
|
||||
receiver?: Receiver): Promise<cast.Session> {
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await ensureInit();
|
||||
|
||||
/**
|
||||
* If a receiver is available, call requestSession. If a
|
||||
* specific receiver was specified, bypass receiver selector
|
||||
* and create session directly.
|
||||
*/
|
||||
function receiverListener (availability: string) {
|
||||
if (availability === cast.ReceiverAvailability.AVAILABLE) {
|
||||
if (receiver) {
|
||||
cast._requestSession(receiver, resolve, reject);
|
||||
} else {
|
||||
cast.requestSession(resolve, reject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sessionRequest = new cast.SessionRequest(
|
||||
cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);
|
||||
|
||||
const apiConfig = new cast.ApiConfig(
|
||||
sessionRequest
|
||||
, null // sessionListener
|
||||
, receiverListener); // receiverListener
|
||||
|
||||
cast.initialize(apiConfig);
|
||||
});
|
||||
}
|
||||
|
||||
function loadMediaUrl (
|
||||
mediaUrl: string
|
||||
, receiver: Receiver): Promise<cast.Session> {
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const isLocalMedia = mediaUrl.startsWith("file://");
|
||||
const isLocalMediaEnabled = await options.get("localMediaEnabled");
|
||||
|
||||
if (isLocalMedia && !isLocalMediaEnabled) {
|
||||
console.error("fx_cast (Debug): Local media casting not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const mediaUrlObject = new URL(mediaUrl);
|
||||
const mediaInfo = new cast.media.MediaInfo(mediaUrlObject.href, null);
|
||||
|
||||
mediaInfo.metadata = new cast.media.GenericMediaMetadata();
|
||||
mediaInfo.metadata.metadataType = cast.media.MetadataType.GENERIC;
|
||||
mediaInfo.metadata.title = mediaUrlObject.pathname;
|
||||
|
||||
|
||||
const mediaSession = await getMediaSession(receiver);
|
||||
|
||||
const loadRequest = new cast.media.LoadRequest(mediaInfo);
|
||||
loadRequest.autoplay = false;
|
||||
|
||||
mediaSession.loadMedia(loadRequest
|
||||
, null // successCallback
|
||||
, () => { reject(); }); // errorCallback
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getMediaSession
|
||||
, loadMediaUrl
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import defaultOptions from "./defaultOptions";
|
||||
import bridge from "./lib/bridge";
|
||||
import loadSender from "./lib/loadSender";
|
||||
import mediaCasting from "./lib/mediaCasting";
|
||||
import options, { Options } from "./lib/options";
|
||||
|
||||
import { getChromeUserAgent } from "./lib/userAgents";
|
||||
@@ -49,6 +48,17 @@ browser.runtime.onInstalled.addListener(async details => {
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* When a message port connection with the name "shim" is
|
||||
* established, pass it to createShim to handle the setup
|
||||
* and maintenance.
|
||||
*/
|
||||
browser.runtime.onConnect.addListener(async port => {
|
||||
if (port.name === "shim") {
|
||||
createShim(port);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* When the browser action is clicked, open a receiver
|
||||
* selector and load a sender for the response. The
|
||||
@@ -66,61 +76,6 @@ browser.browserAction.onClicked.addListener(async tab => {
|
||||
});
|
||||
|
||||
|
||||
|
||||
const activeShims = new Set<Shim>();
|
||||
|
||||
browser.runtime.onConnect.addListener(async port => {
|
||||
if (port.name === "shim") {
|
||||
/**
|
||||
* If there's already an active shim for the sender
|
||||
* tab/frame ID, disconnect it.
|
||||
*/
|
||||
for (const activeShim of activeShims) {
|
||||
if (activeShim.contentTabId === port.sender.tab.id
|
||||
&& activeShim.contentFrameId === port.sender.frameId) {
|
||||
|
||||
activeShim.contentPort.disconnect();
|
||||
activeShim.bridgePort.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const shim = await createShim(port);
|
||||
|
||||
shim.bridgePort.onDisconnect.addListener(() => {
|
||||
activeShims.delete(shim);
|
||||
});
|
||||
shim.contentPort.onDisconnect.addListener(() => {
|
||||
activeShims.delete(shim);
|
||||
});
|
||||
|
||||
activeShims.add(shim);
|
||||
}
|
||||
});
|
||||
|
||||
StatusManager.addEventListener("serviceUp", ev => {
|
||||
for (const shim of activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/serviceUp"
|
||||
, data: { id: ev.detail.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
StatusManager.addEventListener("serviceDown", ev => {
|
||||
for (const shim of activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:/serviceDown"
|
||||
, data: { id: ev.detail.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
let mediaCastTabId: number;
|
||||
let mediaCastFrameId: number;
|
||||
|
||||
async function initMenus () {
|
||||
console.info("fx_cast (Debug): init (menus)");
|
||||
|
||||
@@ -135,8 +90,8 @@ async function initMenus () {
|
||||
const allMediaTypes =
|
||||
ReceiverSelectorMediaType.App
|
||||
| ReceiverSelectorMediaType.Tab
|
||||
| ReceiverSelectorMediaType.Screen;
|
||||
// | ReceiverSelectorMediaType.File;
|
||||
| ReceiverSelectorMediaType.Screen
|
||||
| ReceiverSelectorMediaType.File;
|
||||
|
||||
const selection = await SelectorManager.getSelection(
|
||||
ReceiverSelectorMediaType.App
|
||||
@@ -154,8 +109,8 @@ async function initMenus () {
|
||||
if (selection.mediaType === ReceiverSelectorMediaType.App) {
|
||||
await browser.tabs.executeScript(tab.id, {
|
||||
code: stringify`
|
||||
window.selectedReceiver = ${selection.receiver};
|
||||
window.srcUrl = ${info.srcUrl};
|
||||
window.receiver = ${selection.receiver};
|
||||
window.mediaUrl = ${info.srcUrl};
|
||||
window.targetElementId = ${info.targetElementId};
|
||||
`
|
||||
, frameId: info.frameId
|
||||
@@ -165,10 +120,6 @@ async function initMenus () {
|
||||
file: "senders/mediaCast.js"
|
||||
, frameId: info.frameId
|
||||
});
|
||||
|
||||
// Store for later
|
||||
mediaCastTabId = tab.id;
|
||||
mediaCastFrameId = info.frameId;
|
||||
} else {
|
||||
|
||||
// Handle other responses
|
||||
|
||||
@@ -1,44 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
import mediaCasting from "../lib/mediaCasting";
|
||||
import options from "../lib/options";
|
||||
import cast, { ensureInit } from "../shim/export";
|
||||
|
||||
import { Message, Receiver } from "../types";
|
||||
|
||||
|
||||
// Variables passed from background
|
||||
const { selectedReceiver
|
||||
, srcUrl
|
||||
, targetElementId }
|
||||
: { selectedReceiver: Receiver
|
||||
, srcUrl: string
|
||||
, targetElementId: number } = (window as any);
|
||||
|
||||
|
||||
let backgroundPort: browser.runtime.Port;
|
||||
|
||||
let session: cast.Session;
|
||||
let currentMedia: cast.media.Media;
|
||||
|
||||
let ignoreMediaEvents = false;
|
||||
|
||||
|
||||
const isLocalFile = srcUrl.startsWith("file:");
|
||||
|
||||
const mediaElement = browser.menus.getTargetElement(
|
||||
targetElementId) as HTMLMediaElement;
|
||||
|
||||
window.addEventListener("beforeunload", async () => {
|
||||
backgroundPort.postMessage({
|
||||
subject: "bridge:/mediaServer/stop"
|
||||
});
|
||||
|
||||
if (await options.get("mediaStopOnUnload")) {
|
||||
session.stop(null, null);
|
||||
}
|
||||
});
|
||||
|
||||
function getLocalAddress () {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel(null);
|
||||
@@ -56,7 +23,6 @@ function getLocalAddress () {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function startMediaServer (filePath: string, port: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
backgroundPort.postMessage({
|
||||
@@ -67,177 +33,235 @@ function startMediaServer (filePath: string, port: number) {
|
||||
}
|
||||
});
|
||||
|
||||
backgroundPort.onMessage.addListener(
|
||||
function onMessage (message: Message) {
|
||||
backgroundPort.addEventListener("message", function onMessage (ev) {
|
||||
const message = ev.data as Message;
|
||||
|
||||
if (message.subject.startsWith("mediaCast:/mediaServer/")) {
|
||||
backgroundPort.removeEventListener("message", onMessage);
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "mediaCast:/mediaServer/started": {
|
||||
backgroundPort.onMessage.removeListener(onMessage);
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
case "mediaCast:/mediaServer/error": {
|
||||
backgroundPort.onMessage.removeListener(onMessage);
|
||||
reject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
backgroundPort.start();
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMedia () {
|
||||
let mediaUrl = new URL(srcUrl);
|
||||
const mediaTitle = mediaUrl.pathname;
|
||||
|
||||
/**
|
||||
* If the media is a local file, start an HTTP media server
|
||||
* and change the media URL to point to it.
|
||||
*/
|
||||
if (isLocalFile) {
|
||||
const host = await getLocalAddress();
|
||||
const port = await options.get("localMediaServerPort");
|
||||
let backgroundPort: MessagePort;
|
||||
|
||||
try {
|
||||
// Wait until media server is listening
|
||||
await startMediaServer(mediaUrl.pathname, port);
|
||||
} catch (err) {
|
||||
console.error("Failed to start media server");
|
||||
return;
|
||||
let currentSession: cast.Session;
|
||||
let currentMedia: cast.media.Media;
|
||||
|
||||
let mediaElement: HTMLMediaElement;
|
||||
|
||||
|
||||
function getSession (opts: InitOptions): Promise<cast.Session> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
/**
|
||||
* If a receiver is available, call requestSession. If a
|
||||
* specific receiver was specified, bypass receiver selector
|
||||
* and create session directly.
|
||||
*/
|
||||
function receiverListener (availability: string) {
|
||||
if (availability === cast.ReceiverAvailability.AVAILABLE) {
|
||||
if (opts.receiver) {
|
||||
cast._requestSession(
|
||||
opts.receiver
|
||||
, onRequestSessionSuccess
|
||||
, onRequestSessionError);
|
||||
} else {
|
||||
cast.requestSession(
|
||||
onRequestSessionSuccess
|
||||
, onRequestSessionError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mediaUrl = new URL(`http://${host}:${port}/`);
|
||||
}
|
||||
|
||||
|
||||
const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, null);
|
||||
|
||||
// Media metadata (title/poster)
|
||||
mediaInfo.metadata = new cast.media.GenericMediaMetadata();
|
||||
mediaInfo.metadata.metadataType = cast.media.MetadataType.GENERIC;
|
||||
mediaInfo.metadata.title = mediaTitle;
|
||||
|
||||
if (mediaElement instanceof HTMLVideoElement && mediaElement.poster) {
|
||||
mediaInfo.metadata.images = [
|
||||
new cast.Image(mediaElement.poster)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
const activeTrackIds = [];
|
||||
|
||||
if (mediaElement.textTracks.length) {
|
||||
const trackElements = mediaElement.querySelectorAll("track");
|
||||
|
||||
let index = 0;
|
||||
for (const textTrack of Array.from(mediaElement.textTracks)) {
|
||||
const trackElement = trackElements[index];
|
||||
|
||||
// Create Track object
|
||||
const track = new cast.media.Track(
|
||||
index // trackId
|
||||
, cast.media.TrackType.TEXT); // trackType
|
||||
|
||||
// Copy TextTrack properties to Track
|
||||
track.name = textTrack.label;
|
||||
track.language = textTrack.language;
|
||||
track.trackContentId = trackElement.src;
|
||||
track.trackContentType = "text/vtt";
|
||||
|
||||
const { TextTrackType } = cast.media;
|
||||
|
||||
switch (textTrack.kind) {
|
||||
case "subtitles":
|
||||
track.subtype = TextTrackType.SUBTITLES;
|
||||
break;
|
||||
case "captions":
|
||||
track.subtype = TextTrackType.CAPTIONS;
|
||||
break;
|
||||
case "descriptions":
|
||||
track.subtype = TextTrackType.DESCRIPTIONS;
|
||||
break;
|
||||
case "chapters":
|
||||
track.subtype = TextTrackType.CHAPTERS;
|
||||
break;
|
||||
case "metadata":
|
||||
track.subtype = TextTrackType.METADATA;
|
||||
break;
|
||||
|
||||
// Default to subtitles
|
||||
default:
|
||||
track.subtype = TextTrackType.SUBTITLES;
|
||||
}
|
||||
|
||||
// Add track to mediaInfo
|
||||
mediaInfo.tracks.push(track);
|
||||
|
||||
// If enabled, set as active track for load request
|
||||
if (textTrack.mode === "showing" || trackElement.default) {
|
||||
activeTrackIds.push(index);
|
||||
}
|
||||
|
||||
index++;
|
||||
function onRequestSessionSuccess (session: cast.Session) {
|
||||
resolve(session);
|
||||
}
|
||||
function onRequestSessionError (err: cast.Error) {
|
||||
reject(err.description);
|
||||
}
|
||||
}
|
||||
|
||||
const loadRequest = new cast.media.LoadRequest(mediaInfo);
|
||||
loadRequest.autoplay = false;
|
||||
loadRequest.activeTrackIds = activeTrackIds;
|
||||
|
||||
session.loadMedia(loadRequest
|
||||
, onLoadMediaSuccess
|
||||
, onLoadMediaError);
|
||||
const sessionRequest = new cast.SessionRequest(
|
||||
cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);
|
||||
|
||||
const apiConfig = new cast.ApiConfig(
|
||||
sessionRequest
|
||||
, null // sessionListener
|
||||
, receiverListener); // receiverListener
|
||||
|
||||
|
||||
cast.initialize(apiConfig);
|
||||
});
|
||||
}
|
||||
|
||||
function getMedia (opts: InitOptions): Promise<cast.media.Media> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let mediaUrlObject = new URL(opts.mediaUrl);
|
||||
const mediaTitle = mediaUrlObject.pathname;
|
||||
|
||||
/**
|
||||
* If the media is a local file, start an HTTP media server
|
||||
* and change the media URL to point to it.
|
||||
*/
|
||||
if (opts.mediaUrl.startsWith("file://")) {
|
||||
const host = await getLocalAddress();
|
||||
const port = await options.get("localMediaServerPort");
|
||||
|
||||
try {
|
||||
// Wait until media server is listening
|
||||
await startMediaServer(mediaUrlObject.pathname, port);
|
||||
} catch (err) {
|
||||
console.error("Failed to start media server");
|
||||
return;
|
||||
}
|
||||
|
||||
mediaUrlObject = new URL(`http://${host}:${port}/`);
|
||||
}
|
||||
|
||||
|
||||
const activeTrackIds: number[] = [];
|
||||
const mediaInfo = new cast.media.MediaInfo(mediaUrlObject.href, null);
|
||||
|
||||
mediaInfo.metadata = new cast.media.GenericMediaMetadata();
|
||||
mediaInfo.metadata.metadataType = cast.media.MetadataType.GENERIC;
|
||||
mediaInfo.metadata.title = mediaTitle;
|
||||
mediaInfo.tracks = [];
|
||||
|
||||
|
||||
if (mediaElement) {
|
||||
if (mediaElement instanceof HTMLVideoElement) {
|
||||
if (mediaElement.poster) {
|
||||
mediaInfo.metadata.images = [
|
||||
new cast.Image(mediaElement.poster)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaElement.textTracks.length) {
|
||||
const tracks = Array.from(mediaElement.textTracks);
|
||||
const trackElements = mediaElement.querySelectorAll("track");
|
||||
|
||||
tracks.forEach((track, index) => {
|
||||
const trackElement = trackElements[index];
|
||||
|
||||
/**
|
||||
* Create media.Track object with the index as the track ID
|
||||
* and type as TrackType.TEXT.
|
||||
*/
|
||||
const castTrack = new cast.media.Track(
|
||||
index, cast.media.TrackType.TEXT);
|
||||
|
||||
// Copy TextTrack properties
|
||||
castTrack.name = track.label;
|
||||
castTrack.language = track.language;
|
||||
castTrack.trackContentId = trackElement.src;
|
||||
castTrack.trackContentType = "text/vtt";
|
||||
|
||||
switch (track.kind) {
|
||||
case "subtitles":
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.SUBTITLES;
|
||||
break;
|
||||
case "captions":
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.CAPTIONS;
|
||||
break;
|
||||
case "descriptions":
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.DESCRIPTIONS;
|
||||
break;
|
||||
case "chapters":
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.CHAPTERS;
|
||||
break;
|
||||
case "metadata":
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.METADATA;
|
||||
break;
|
||||
|
||||
// Default to subtitles
|
||||
default:
|
||||
castTrack.subtype =
|
||||
cast.media.TextTrackType.SUBTITLES;
|
||||
}
|
||||
|
||||
// Add track to mediaInfo
|
||||
mediaInfo.tracks.push(castTrack);
|
||||
|
||||
// If enabled, mark as active track for load request
|
||||
if (track.mode === "showing" || trackElement.default) {
|
||||
activeTrackIds.push(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const loadRequest = new cast.media.LoadRequest(mediaInfo);
|
||||
loadRequest.autoplay = false;
|
||||
loadRequest.activeTrackIds = activeTrackIds;
|
||||
|
||||
currentSession.loadMedia(loadRequest
|
||||
, (media) => resolve(media)
|
||||
, null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function onLoadMediaSuccess (media: cast.media.Media) {
|
||||
cast.logMessage("onLoadMediaSuccess");
|
||||
|
||||
currentMedia = media;
|
||||
let ignoreMediaEvents = false;
|
||||
|
||||
async function registerMediaElementListeners () {
|
||||
if (await options.get("mediaSyncElement")) {
|
||||
mediaElement.addEventListener("play", () => {
|
||||
|
||||
function checkIgnore (ev: Event) {
|
||||
if (ignoreMediaEvents) {
|
||||
ignoreMediaEvents = false;
|
||||
return;
|
||||
ev.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
currentMedia.play(null
|
||||
, onMediaPlaySuccess
|
||||
, onMediaPlayError);
|
||||
mediaElement.addEventListener("play", checkIgnore, true);
|
||||
mediaElement.addEventListener("pause", checkIgnore, true);
|
||||
mediaElement.addEventListener("suspend", checkIgnore, true);
|
||||
mediaElement.addEventListener("seeking", checkIgnore, true);
|
||||
mediaElement.addEventListener("ratechange", checkIgnore, true);
|
||||
mediaElement.addEventListener("volumechange", checkIgnore, true);
|
||||
|
||||
|
||||
mediaElement.addEventListener("play", () => {
|
||||
currentMedia.play(null, null, null);
|
||||
});
|
||||
|
||||
mediaElement.addEventListener("pause", () => {
|
||||
if (ignoreMediaEvents) {
|
||||
ignoreMediaEvents = false;
|
||||
return;
|
||||
}
|
||||
|
||||
currentMedia.pause(null
|
||||
, onMediaPauseSuccess
|
||||
, onMediaPauseError);
|
||||
currentMedia.pause(null, null, null);
|
||||
});
|
||||
|
||||
mediaElement.addEventListener("suspend", () => {
|
||||
/*currentMedia.stop(null
|
||||
, onMediaStopSuccess
|
||||
, onMediaStopError);*/
|
||||
// currentMedia.stop(null, null, null);
|
||||
});
|
||||
|
||||
mediaElement.addEventListener("seeking", () => {
|
||||
if (ignoreMediaEvents) {
|
||||
ignoreMediaEvents = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mediaElement.addEventListener("seeked", () => {
|
||||
const seekRequest = new cast.media.SeekRequest();
|
||||
seekRequest.currentTime = mediaElement.currentTime;
|
||||
|
||||
currentMedia.seek(seekRequest
|
||||
, onMediaSeekSuccess
|
||||
, onMediaSeekError);
|
||||
currentMedia.seek(seekRequest, null, null);
|
||||
});
|
||||
|
||||
mediaElement.addEventListener("ratechange", () => {
|
||||
(currentMedia as any)._sendMediaMessage({
|
||||
currentMedia._sendMediaMessage({
|
||||
type: "SET_PLAYBACK_RATE"
|
||||
, playbackRate: mediaElement.playbackRate
|
||||
});
|
||||
@@ -248,10 +272,8 @@ async function onLoadMediaSuccess (media: cast.media.Media) {
|
||||
currentMedia.volume.level
|
||||
, currentMedia.volume.muted);
|
||||
|
||||
const volumeRequest =
|
||||
new cast.media.VolumeRequest(newVolume);
|
||||
const volumeRequest = new cast.media.VolumeRequest(newVolume);
|
||||
|
||||
cast.logMessage("Volume change");
|
||||
currentMedia.setVolume(volumeRequest);
|
||||
});
|
||||
|
||||
@@ -261,44 +283,46 @@ async function onLoadMediaSuccess (media: cast.media.Media) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PlayerState
|
||||
const localPlayerState = mediaElement.paused
|
||||
? cast.media.PlayerState.PAUSED
|
||||
: cast.media.PlayerState.PLAYING;
|
||||
|
||||
if (localPlayerState !== currentMedia.playerState) {
|
||||
ignoreMediaEvents = true;
|
||||
|
||||
switch (currentMedia.playerState) {
|
||||
case cast.media.PlayerState.PLAYING:
|
||||
case cast.media.PlayerState.PLAYING: {
|
||||
mediaElement.play();
|
||||
break;
|
||||
|
||||
case cast.media.PlayerState.PAUSED:
|
||||
}
|
||||
case cast.media.PlayerState.PAUSED: {
|
||||
mediaElement.pause();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RepeatMode
|
||||
|
||||
const localRepeatMode = mediaElement.loop
|
||||
? cast.media.RepeatMode.SINGLE
|
||||
: cast.media.RepeatMode.OFF;
|
||||
|
||||
if (localRepeatMode !== currentMedia.repeatMode) {
|
||||
ignoreMediaEvents = true;
|
||||
|
||||
switch (currentMedia.repeatMode) {
|
||||
case cast.media.RepeatMode.SINGLE:
|
||||
case cast.media.RepeatMode.SINGLE: {
|
||||
mediaElement.loop = true;
|
||||
break;
|
||||
|
||||
case cast.media.RepeatMode.OFF:
|
||||
}
|
||||
case cast.media.RepeatMode.OFF: {
|
||||
mediaElement.loop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// currentTime
|
||||
if (currentMedia.currentTime !== mediaElement.currentTime) {
|
||||
ignoreMediaEvents = true;
|
||||
mediaElement.currentTime = currentMedia.currentTime;
|
||||
@@ -307,57 +331,57 @@ async function onLoadMediaSuccess (media: cast.media.Media) {
|
||||
}
|
||||
}
|
||||
|
||||
function onRequestSessionError () {
|
||||
cast.logMessage("onRequestSessionError");
|
||||
}
|
||||
function sessionListener (newSession: cast.Session) {
|
||||
cast.logMessage("sessionListener");
|
||||
}
|
||||
function onInitializeSuccess () {
|
||||
cast.logMessage("onInitializeSuccess");
|
||||
}
|
||||
function onInitializeError () {
|
||||
cast.logMessage("onInitializeError");
|
||||
}
|
||||
function onLoadMediaError () {
|
||||
cast.logMessage("onLoadMediaError");
|
||||
}
|
||||
function onMediaPlaySuccess () {
|
||||
cast.logMessage("onMediaPlaySuccess");
|
||||
}
|
||||
function onMediaPlayError (err: cast.Error) {
|
||||
cast.logMessage("onMediaPlayError");
|
||||
}
|
||||
function onMediaPauseSuccess () {
|
||||
cast.logMessage("onMediaPauseSuccess");
|
||||
}
|
||||
function onMediaPauseError (err: cast.Error) {
|
||||
cast.logMessage("onMediaPauseError");
|
||||
}
|
||||
function onMediaStopSuccess () {
|
||||
cast.logMessage("onMediaStopSuccess");
|
||||
}
|
||||
function onMediaStopError (err: cast.Error) {
|
||||
cast.logMessage("onMediaStopError");
|
||||
}
|
||||
function onMediaSeekSuccess () {
|
||||
cast.logMessage("onMediaSeekSuccess");
|
||||
}
|
||||
function onMediaSeekError (err: cast.Error) {
|
||||
cast.logMessage("onMediaSeekError");
|
||||
|
||||
interface InitOptions {
|
||||
mediaUrl: string;
|
||||
receiver: Receiver;
|
||||
targetElementId?: number;
|
||||
}
|
||||
|
||||
export async function init (opts: InitOptions) {
|
||||
backgroundPort = await ensureInit();
|
||||
|
||||
ensureInit().then(async (port) => {
|
||||
backgroundPort = port;
|
||||
|
||||
const isLocalMedia = opts.mediaUrl.startsWith("file://");
|
||||
const isLocalMediaEnabled = await options.get("localMediaEnabled");
|
||||
if (isLocalFile && !isLocalMediaEnabled) {
|
||||
|
||||
if (isLocalMedia && !isLocalMediaEnabled) {
|
||||
cast.logMessage("Local media casting not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
session = await mediaCasting.getMediaSession(selectedReceiver);
|
||||
if (opts.targetElementId) {
|
||||
mediaElement = browser.menus.getTargetElement(
|
||||
opts.targetElementId) as HTMLMediaElement;
|
||||
}
|
||||
|
||||
loadMedia();
|
||||
});
|
||||
currentSession = await getSession(opts);
|
||||
currentMedia = await getMedia(opts);
|
||||
|
||||
if (opts.targetElementId) {
|
||||
registerMediaElementListeners();
|
||||
|
||||
window.addEventListener("beforeunload", async () => {
|
||||
backgroundPort.postMessage({
|
||||
subject: "bridge:/mediaServer/stop"
|
||||
});
|
||||
|
||||
if (await options.get("mediaStopOnUnload")) {
|
||||
currentSession.stop(null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If loaded as a content script, the init values are
|
||||
* provided on the window object.
|
||||
*/
|
||||
if (window.location.protocol !== "moz-extension:") {
|
||||
const _window = (window as any);
|
||||
|
||||
init({
|
||||
mediaUrl: _window.mediaUrl
|
||||
, receiver: _window.receiver
|
||||
, targetElementId: _window.targetElementId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { ErrorCode
|
||||
, SessionStatus
|
||||
, VolumeControlType } from "../enums";
|
||||
|
||||
import { RepeatMode } from "../media/enums";
|
||||
|
||||
import { ListenerObject
|
||||
, onMessage
|
||||
, sendMessageResponse } from "../../eventMessageChannel";
|
||||
@@ -305,9 +307,10 @@ export default class Session {
|
||||
, autoplay: loadRequest.autoplay || false
|
||||
, currentTime: loadRequest.currentTime || 0
|
||||
, customData: loadRequest.customData || {}
|
||||
, repeatMode: "REPEAT_OFF"
|
||||
, repeatMode: RepeatMode.OFF
|
||||
});
|
||||
|
||||
|
||||
let hasResponded = false;
|
||||
|
||||
this.addMessageListener(
|
||||
@@ -318,23 +321,28 @@ export default class Session {
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaObject = JSON.parse(data);
|
||||
const message = JSON.parse(data);
|
||||
|
||||
if (mediaObject.status && mediaObject.status.length > 0) {
|
||||
if (message.status && message.status.length > 0) {
|
||||
hasResponded = true;
|
||||
|
||||
const media = new Media(
|
||||
this.sessionId
|
||||
, mediaObject.status[0].mediaSessionId
|
||||
, message.status[0].mediaSessionId
|
||||
, _id.get(this));
|
||||
|
||||
media.media = loadRequest.media;
|
||||
this.media = [ media ];
|
||||
|
||||
media.play();
|
||||
successCallback(media);
|
||||
|
||||
if (successCallback) {
|
||||
successCallback(media);
|
||||
}
|
||||
} else {
|
||||
errorCallback(new _Error(ErrorCode.SESSION_ERROR));
|
||||
if (errorCallback) {
|
||||
errorCallback(new _Error(ErrorCode.SESSION_ERROR));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,9 +210,6 @@ export function _requestSession (
|
||||
|
||||
sessionRequestInProgress = true;
|
||||
|
||||
sessionSuccessCallback = successCallback;
|
||||
sessionErrorCallback = errorCallback;
|
||||
|
||||
|
||||
const selectedReceiver = new Receiver_(
|
||||
_receiver.id
|
||||
@@ -235,8 +232,8 @@ export function _requestSession (
|
||||
|
||||
sessionRequestInProgress = false;
|
||||
|
||||
if (sessionSuccessCallback) {
|
||||
sessionSuccessCallback(session);
|
||||
if (successCallback) {
|
||||
successCallback(session);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ export default class Media {
|
||||
}
|
||||
|
||||
|
||||
private _sendMediaMessage (
|
||||
public _sendMediaMessage (
|
||||
message: any
|
||||
, successCallback?: SuccessCallback
|
||||
, errorCallback?: ErrorCallback) {
|
||||
|
||||
@@ -4,11 +4,12 @@ import * as cast from "./cast";
|
||||
|
||||
import { BridgeInfo } from "../lib/bridge";
|
||||
import { Message } from "../types";
|
||||
import { onMessage } from "./eventMessageChannel";
|
||||
|
||||
import { onMessage, onMessageResponse, sendMessage } from "./eventMessageChannel";
|
||||
|
||||
|
||||
let initializedBridgeInfo: BridgeInfo;
|
||||
let initializedBackgroundPort: browser.runtime.Port;
|
||||
let initializedBackgroundPort: MessagePort;
|
||||
|
||||
/**
|
||||
* To support exporting an API from a module, we need to
|
||||
@@ -17,7 +18,7 @@ let initializedBackgroundPort: browser.runtime.Port;
|
||||
* for and emits these messages, and changing that behavior
|
||||
* is too messy.
|
||||
*/
|
||||
export function ensureInit (): Promise<browser.runtime.Port> {
|
||||
export function ensureInit (): Promise<MessagePort> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
// If already initialized, just return existing bridge info
|
||||
@@ -31,6 +32,9 @@ export function ensureInit (): Promise<browser.runtime.Port> {
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = new MessageChannel();
|
||||
initializedBackgroundPort = channel.port1;
|
||||
|
||||
/**
|
||||
* If the module is imported into a background script
|
||||
* context, the location will be the internal extension URL,
|
||||
@@ -38,14 +42,48 @@ export function ensureInit (): Promise<browser.runtime.Port> {
|
||||
* URL.
|
||||
*/
|
||||
if (window.location.protocol === "moz-extension:") {
|
||||
//
|
||||
const { default: createShim } = await import("../createShim");
|
||||
|
||||
// port2 will post bridge messages to port 1
|
||||
await createShim(channel.port2);
|
||||
|
||||
// bridge -> shim
|
||||
channel.port1.onmessage = ev => {
|
||||
const message = ev.data as Message;
|
||||
|
||||
// Send message to shim
|
||||
sendMessage(message);
|
||||
handleIncomingMessageToShim(message);
|
||||
};
|
||||
|
||||
// shim -> bridge
|
||||
onMessageResponse(message => {
|
||||
channel.port1.postMessage(message);
|
||||
});
|
||||
} else {
|
||||
// Trigger message port setup side-effects
|
||||
/**
|
||||
* Import reference to message port created by contentBridge.
|
||||
* Creation of the port triggers side-effects in the
|
||||
* background script.
|
||||
*/
|
||||
const { backgroundPort } = await import("./contentBridge");
|
||||
initializedBackgroundPort = backgroundPort;
|
||||
|
||||
// backgroundPort -> channel.port2
|
||||
backgroundPort.onMessage.addListener((message: Message) => {
|
||||
channel.port2.postMessage(message);
|
||||
});
|
||||
|
||||
// channel.port2 -> backgroundPort
|
||||
channel.port2.onmessage = ev => {
|
||||
const message = ev.data as Message;
|
||||
backgroundPort.postMessage(message);
|
||||
};
|
||||
|
||||
// Handle shim messages
|
||||
onMessage(handleIncomingMessageToShim);
|
||||
}
|
||||
|
||||
onMessage(message => {
|
||||
function handleIncomingMessageToShim (message: Message) {
|
||||
switch (message.subject) {
|
||||
case "shim:/initialized": {
|
||||
initializedBridgeInfo = message.data;
|
||||
@@ -57,7 +95,7 @@ export function ensureInit (): Promise<browser.runtime.Port> {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user