Implement initial media overlay

This commit is contained in:
hensm
2020-01-12 12:08:29 +00:00
parent 270d62e6f8
commit d65c607479
18 changed files with 552 additions and 11 deletions

View File

@@ -85,6 +85,18 @@
}
, "mediaOverlayTitle": {
"message": "Playing on $receiverName$"
, "description": "Main title for overlay displayed on media elements whilst casting."
, "placeholders": {
"receiverName": {
"content": "$1"
, "example": "Living Room TV"
}
}
}
, "optionsBridgeLoading": {
"message": "Loading bridge info..."
, "description": "Loading placeholder text for bridge section on options page."
@@ -193,6 +205,18 @@
"message": "Enable media casting"
, "description": "Media casting enabled checkbox label."
}
, "optionsMediaOverlayEnabled": {
"message": "Enable media element overlay"
, "description": "Media element overlay checkbox label."
}
, "optionsMediaOverlayEnabledTemp": {
"message": "Enable media element overlay (experimental)"
, "description": "Experimental-labelled version of above."
}
, "optionsMediaOverlayEnabledDescrption": {
"message": "Overlay on media elements displaying information about the current session if connected."
, "description": "Media element overlay option description."
}
, "optionsMediaSyncElement": {
"message": "Sync receiver state with media element"
, "description": "Media casting sync checkbox label."

View File

@@ -183,7 +183,7 @@ async function initMenus () {
});
await browser.tabs.executeScript(tab.id, {
file: "senders/mediaCast.js"
file: "senders/media/bundle.js"
, frameId: info.frameId
});
} else {

View File

@@ -7,6 +7,7 @@ import { Options } from "./lib/options";
export default {
bridgeApplicationName: APPLICATION_NAME
, mediaEnabled: true
, mediaOverlayEnabled: false
, mediaSyncElement: false
, mediaStopOnUnload: false
, localMediaEnabled: true

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 125 125" fill="white">
<path d="M43.5 84.1l1.3-1.5c.3-.3.3-.8 0-1.1-10.5-9.7-11.2-26.2-1.4-36.7s26.2-11.2 36.7-1.4 11.2 26.2 1.4 36.7c-.5.5-.9 1-1.4 1.4-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.3 1.1.1 12-11.1 12.7-29.7 1.7-41.7-11.1-12-29.7-12.7-41.7-1.7s-12.7 29.7-1.7 41.7c.5.6 1.1 1.1 1.7 1.7.3.2.7.2 1-.1z"/>
<path d="M44.8 62.5c0-9.7 7.9-17.6 17.6-17.6S80 52.9 80 62.6c0 4.8-2 9.5-5.5 12.8-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.4 1.1.1 8.5-8 8.9-21.3 1-29.8s-21.3-8.9-29.8-1-9 21.2-1.1 29.7c.3.3.6.7 1 1 .3.3.8.3 1.1 0l1.3-1.5c.3-.3.3-.8 0-1.1-3.5-3.3-5.6-8-5.6-12.9z"/>
<path d="M53.2 62.5c0-5.1 4.1-9.2 9.2-9.2s9.2 4.1 9.2 9.2c0 2.5-1 4.8-2.8 6.6-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.3 1.1 0 5-4.9 5.2-12.9.3-18s-12.9-5.2-18-.3-5.2 12.9-.3 18l.3.3c.3.3.8.3 1.1 0l1.3-1.5c.3-.3.3-.8 0-1.1-1.7-1.7-2.7-4.1-2.7-6.6z"/>
<path d="M80.9 89.1L63.5 69.3c-.5-.6-1.3-.6-1.9-.1l-.1.1-17.6 19.8c-.4.5-.4 1.2.1 1.7.2.2.5.3.7.3H80c.6 0 1.2-.5 1.2-1.2 0-.3-.1-.6-.3-.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 125 125" fill="white">
<path d="M81 88.8c.4.5.4 1.3-.1 1.7-.2.2-.5.3-.8.3H44.9c-.7 0-1.2-.5-1.2-1.2 0-.3.1-.6.3-.8l17.5-20.1c.5-.6 1.3-.6 1.9-.1l.1.1L81 88.8zm-4.1-11.1l-2.8-3.3h10.5c.9.1 1.7-.1 2.5-.4.5-.3 1-.7 1.2-1.2.4-.8.5-1.7.4-2.5V45.8c.1-.9-.1-1.7-.4-2.5-.3-.5-.7-1-1.2-1.2-.8-.4-1.7-.5-2.5-.4h-44c-.9-.1-1.7.1-2.5.4-.5.3-1 .7-1.2 1.2-.4.8-.5 1.7-.4 2.5v24.4c-.1.9.1 1.7.4 2.5.3.5.7 1 1.2 1.2.8.4 1.7.5 2.5.4h10.5l-2.8 3.3h-6.7c-3 0-4-.3-5-.9-1.1-.6-1.9-1.4-2.5-2.5-.6-1.1-.9-2.1-.9-5V46.7c0-3 .3-4 .9-5.1.6-1.1 1.4-1.9 2.5-2.5 1.1-.6 2.1-.9 5-.9h42.2c3 0 4 .3 5.1.9 1.1.6 1.9 1.4 2.5 2.5.6 1.1.9 2.1.9 5.1v22.5c0 3-.3 4-.9 5-.6 1.1-1.4 1.9-2.5 2.5-1.1.6-2.1.9-5.1.9l-6.9.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -34,7 +34,7 @@ export default async function loadSender (opts: LoadSenderOptions) {
});
await browser.tabs.executeScript(opts.tabId, {
file: "senders/mirroringCast.js"
file: "senders/mirroring.js"
, frameId: opts.frameId
});
@@ -43,7 +43,7 @@ export default async function loadSender (opts: LoadSenderOptions) {
case ReceiverSelectorMediaType.File: {
const fileUrl = new URL(`file://${opts.selection.filePath}`);
const { init } = await import("../senders/mediaCast");
const { init } = await import("../senders/media");
init({
mediaUrl: fileUrl.href

View File

@@ -30,7 +30,10 @@
, "content_scripts": [
{
"all_frames": true
, "js": [ "shim/content.js" ]
, "js": [
"shim/content.js"
, "senders/media/overlay/overlayContentLoader.js"
]
, "matches": [ "<all_urls>" ]
, "run_at": "document_start"
}
@@ -58,5 +61,8 @@
, "web_accessible_resources": [
"shim/bundle.js"
, "vendor/webcomponents-lite.js"
, "senders/media/overlay/overlayContent.js"
, "senders/media/overlay/AirPlay_Audio.svg"
, "senders/media/overlay/AirPlay_Video.svg"
]
}

View File

@@ -1,9 +1,9 @@
"use strict";
import options from "../lib/options";
import cast, { ensureInit } from "../shim/export";
import options from "../../lib/options";
import cast, { ensureInit } from "../../shim/export";
import { Message, Receiver } from "../types";
import { Message, Receiver } from "../../types";
function getLocalAddress () {
@@ -360,6 +360,10 @@ export async function init (opts: InitOptions) {
if (opts.targetElementId) {
registerMediaElementListeners();
if (options.get("mediaOverlayEnabled")) {
// TODO: Un-hide overlay here
}
window.addEventListener("beforeunload", async () => {
backgroundPort.postMessage({
subject: "bridge:/mediaServer/stop"

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 125 125" fill="white">
<path d="M43.5 84.1l1.3-1.5c.3-.3.3-.8 0-1.1-10.5-9.7-11.2-26.2-1.4-36.7s26.2-11.2 36.7-1.4 11.2 26.2 1.4 36.7c-.5.5-.9 1-1.4 1.4-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.3 1.1.1 12-11.1 12.7-29.7 1.7-41.7-11.1-12-29.7-12.7-41.7-1.7s-12.7 29.7-1.7 41.7c.5.6 1.1 1.1 1.7 1.7.3.2.7.2 1-.1z"/>
<path d="M44.8 62.5c0-9.7 7.9-17.6 17.6-17.6S80 52.9 80 62.6c0 4.8-2 9.5-5.5 12.8-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.4 1.1.1 8.5-8 8.9-21.3 1-29.8s-21.3-8.9-29.8-1-9 21.2-1.1 29.7c.3.3.6.7 1 1 .3.3.8.3 1.1 0l1.3-1.5c.3-.3.3-.8 0-1.1-3.5-3.3-5.6-8-5.6-12.9z"/>
<path d="M53.2 62.5c0-5.1 4.1-9.2 9.2-9.2s9.2 4.1 9.2 9.2c0 2.5-1 4.8-2.8 6.6-.3.3-.3.8 0 1.1l1.3 1.5c.3.3.8.3 1.1 0 5-4.9 5.2-12.9.3-18s-12.9-5.2-18-.3-5.2 12.9-.3 18l.3.3c.3.3.8.3 1.1 0l1.3-1.5c.3-.3.3-.8 0-1.1-1.7-1.7-2.7-4.1-2.7-6.6z"/>
<path d="M80.9 89.1L63.5 69.3c-.5-.6-1.3-.6-1.9-.1l-.1.1-17.6 19.8c-.4.5-.4 1.2.1 1.7.2.2.5.3.7.3H80c.6 0 1.2-.5 1.2-1.2 0-.3-.1-.6-.3-.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 125 125" fill="white">
<path d="M81 88.8c.4.5.4 1.3-.1 1.7-.2.2-.5.3-.8.3H44.9c-.7 0-1.2-.5-1.2-1.2 0-.3.1-.6.3-.8l17.5-20.1c.5-.6 1.3-.6 1.9-.1l.1.1L81 88.8zm-4.1-11.1l-2.8-3.3h10.5c.9.1 1.7-.1 2.5-.4.5-.3 1-.7 1.2-1.2.4-.8.5-1.7.4-2.5V45.8c.1-.9-.1-1.7-.4-2.5-.3-.5-.7-1-1.2-1.2-.8-.4-1.7-.5-2.5-.4h-44c-.9-.1-1.7.1-2.5.4-.5.3-1 .7-1.2 1.2-.4.8-.5 1.7-.4 2.5v24.4c-.1.9.1 1.7.4 2.5.3.5.7 1 1.2 1.2.8.4 1.7.5 2.5.4h10.5l-2.8 3.3h-6.7c-3 0-4-.3-5-.9-1.1-.6-1.9-1.4-2.5-2.5-.6-1.1-.9-2.1-.9-5V46.7c0-3 .3-4 .9-5.1.6-1.1 1.4-1.9 2.5-2.5 1.1-.6 2.1-.9 5-.9h42.2c3 0 4 .3 5.1.9 1.1.6 1.9 1.4 2.5 2.5.6 1.1.9 2.1.9 5.1v22.5c0 3-.3 4-.9 5-.6 1.1-1.4 1.9-2.5 2.5-1.1.6-2.1.9-5.1.9l-6.9.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -0,0 +1,75 @@
"use strict";
/**
* Walk up the prototype chain until the specified property
* descriptor is found, otherwise return undefined.
*/
export function getPropertyDescriptor (
target: any, prop: string | number | symbol)
: PropertyDescriptor | undefined {
let desc: PropertyDescriptor | undefined;
while (!desc && target !== null) {
desc = Object.getOwnPropertyDescriptor(target, prop);
if (!desc) {
target = Object.getPrototypeOf(target);
}
}
return desc;
}
/**
* Bind either the getter/setter functions or the value function
* to a target object.
*/
export function bindPropertyDescriptor (
desc: PropertyDescriptor, target: any)
: PropertyDescriptor {
if (typeof desc.value === "function") {
desc.value = desc.value.bind(target);
} else {
if (desc.get) { desc.get = desc.get.bind(target); }
if (desc.set) { desc.set = desc.set.bind(target); }
}
return desc;
}
/**
* For each attribute handler, fetch the property descriptor (which may
* be further up in the prototype chain), re-bind it to the target
* element and collect them into a property descriptor map.
*/
export function clonePropsDescriptor<T> (
target: T, props: Array<any/*keyof typeof T*/>)
: PropertyDescriptorMap {
return props.reduce<PropertyDescriptorMap>((descriptorMap, prop) => {
const desc = getPropertyDescriptor(target, prop);
if (desc) {
bindPropertyDescriptor(desc, target);
descriptorMap[prop as any] = desc;
}
return descriptorMap;
}, {});
}
export function makeGetterDescriptor (val: any): PropertyDescriptor {
return {
enumerable: true
, configurable: true
, get () { return val; }
};
}
export function makeValueDescriptor (val: any): PropertyDescriptor {
return {
enumerable: true
, configurable: true
, writable: true
, value: val
};
}

View File

@@ -0,0 +1,366 @@
"use strict";
import { bindPropertyDescriptor
, clonePropsDescriptor
, getPropertyDescriptor
, makeGetterDescriptor
, makeValueDescriptor } from "./descriptorUtils";
// Injected by content loader
declare const iconAirPlayAudio: string;
declare const iconAirPlayVideo: string;
declare const mediaOverlayTitle: string;
/**
* Intercept and store references to shadow root nodes created by
* calls to `attachShadow`. Used to reference shadow roots, even when
* created in closed mode without exposing them to other page scripts.
*/
const internalShadowRoots = new WeakMap<Element, ShadowRoot>();
const _attachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function (init) {
const shadowRoot = _attachShadow.call(this, init);
internalShadowRoots.set(this, shadowRoot);
return shadowRoot;
};
function getShadowRootFromNode (node: Node): ShadowRoot | undefined {
// Don't touch our custom element
if (node instanceof PlayerElement) {
return;
}
return internalShadowRoots.get(node as Element);
}
const DQS_XPATH_EXPRESSION = `//*[contains(name(), "-")]`;
/**
* Return the first matching querySelector result on any ShadowRoot
* nodes present in the document.
*/
function deepQuerySelector (selector: string): Element | null {
const result = document.evaluate(
DQS_XPATH_EXPRESSION, document, null
, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
let node: Node | null;
while (node = result.iterateNext()) {
const shadowRoot = getShadowRootFromNode(node);
if (!shadowRoot) {
continue;
}
const queryResult = shadowRoot.querySelector(selector);
if (queryResult) {
return queryResult;
}
}
return null;
}
/**
* Collect and return the results of querySelectorAll on any
* ShadowRoot nodes present in the document.
*/
function deepQuerySelectorAll (selector: string): Node[] {
const result = document.evaluate(
DQS_XPATH_EXPRESSION, document, null
, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
const nodes: Node[] = [];
let node: Node | null;
while (node = result.iterateNext()) {
const shadowRoot = getShadowRootFromNode(node);
if (shadowRoot) {
nodes.push(...shadowRoot.querySelectorAll(selector));
}
}
return nodes;
}
const mediaElementTypes = [
HTMLMediaElement
, HTMLVideoElement
, HTMLAudioElement
];
const mediaElementEvents = [
"abort", "canplay", "canplaythrough", "durationchange", "emptied"
, "encrypted", "ended", "error", "interruptbegin", "interruptend"
, "loadeddata", "loadedmetadata", "loadstart", "mozaudioavailable", "pause"
, "play", "playing", "progress", "ratechange", "seeked", "seeking", "stalled"
, "suspend", "timeupdate", "volumechange", "waiting"
];
const mediaElementAttributes = mediaElementTypes
.flatMap(type => Object.getOwnPropertyNames(type.prototype))
.concat(mediaElementEvents.map(ev => `on${ev}`));
/**
* Opaque wrapper around the media element to provide an overlay without
* author interference. Relevant properties, attributes, events and
* functions are proxied to the internal media element.
*/
class PlayerElement extends HTMLElement {
constructor () {
super();
const shadowRoot = this.attachShadow({ mode: "closed" });
const { host } = shadowRoot;
let iconUrl;
switch (this.constructor) {
// URL variables injected ahead of current script
case AudioPlayerElement: { iconUrl = iconAirPlayAudio; break; }
case VideoPlayerElement: { iconUrl = iconAirPlayVideo; break; }
}
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
font: menu;
position: relative;
}
:host[hidden],
.overlay[hidden] {
display: none;
}
video {
width: 100%;
}
.overlay {
align-items: center;
background-color: rgba(0, 0, 0, 0.85);
color: white;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
left: 0;
pointer-events: none;
position: absolute;
top: 0;
width: 100%;
}
.overlay__icon {
background-image: url("${iconUrl}");
height: 125px;
width: 125px;
}
.overlay__text {
font-size: 18px;
}
</style>
<div class="overlay" hidden>
<div class="overlay__icon"></div>
<div class="overlay__text">
${mediaOverlayTitle}
</div>
</div>
`;
const videoElement = _createElement.call(document, "video");
for (const attr of mediaElementAttributes) {
if (host.hasOwnProperty(attr)) {
// @ts-ignore
videoElement[attr] = host[attr];
}
}
/**
* Page scripts need to be able to read/write attributes, event
* listeners, etc... on the media element, but since it's hidden
* within the shadow DOM, these properties must be proxied.
*/
Object.defineProperties(host, clonePropsDescriptor(videoElement, [
"attributes", "setAttribute", "removeAttribute", "setAttribute"
, "addEventListener", "removeEventListener", "hasEventListener"
, ...mediaElementAttributes as any ]));
shadowRoot.prepend(videoElement);
}
}
class AudioPlayerElement extends PlayerElement {}
class VideoPlayerElement extends PlayerElement {
set overlayHidden (val: boolean) {
const shadowRoot = internalShadowRoots.get(this);
(shadowRoot.querySelector(".overlay") as HTMLDivElement).hidden = val;
}
get overlayHidden () {
const shadowRoot = internalShadowRoots.get(this);
return (shadowRoot.querySelector(".overlay") as HTMLDivElement).hidden;
}
}
try {
customElements.define("audio-player-element", AudioPlayerElement);
customElements.define("video-player-element", VideoPlayerElement);
} catch (err) {
if (err instanceof DOMException
&& err.code === DOMException.NOT_SUPPORTED_ERR) {
// Script already injected
}
}
// Original functions
const _createElement = document.createElement;
const _createElementNS = document.createElementNS;
/**
* Intercepts `<audio>`/`<video>` element creation and returns a wrapped
* custom element version that imitates the original. Otherwise, returns
* the result of the original.
*/
function createElement (
tagName: string
, options?: ElementCreationOptions) {
// Normalize formatting
const lowerTagName = tagName.toLowerCase();
const upperTagName = tagName.toUpperCase();
if (lowerTagName === "audio" || lowerTagName === "video") {
const fakeElement = _createElement.call(document
, `${lowerTagName}-player-element`) as HTMLMediaElement;
// Ensure all references to the element name match tagName
Object.defineProperties(fakeElement, {
tagName: makeGetterDescriptor(upperTagName)
, nodeName: makeGetterDescriptor(upperTagName)
, localName: makeGetterDescriptor(lowerTagName)
});
return fakeElement;
}
return _createElement.call(document, tagName, options);
}
/**
* If the namespace matches the current document, redirect to the
* patched `createElement` function, otherwise return the result of the
* original.
*/
function createElementNS (
namespaceURI: string
, qualifiedName: string
, options?: ElementCreationOptions) {
if (namespaceURI === document.namespaceURI) {
return createElement(qualifiedName, options);
}
return _createElementNS.call(document
, namespaceURI, qualifiedName, options);
}
/**
* Attempt to hide function source from page scripts by returning the
* toString/toSource values of the native function.
*/
Object.defineProperties(createElement, clonePropsDescriptor(
_createElement, ["toString", "toSource"]));
Object.defineProperties(createElement, clonePropsDescriptor(
_createElementNS, ["toString", "toSource"]));
// Re-define element creation functions
Object.defineProperties(document, {
createElement: makeValueDescriptor(createElement)
, createElementNS: makeValueDescriptor(createElementNS)
});
/**
* Takes a media element, creates a `PlayerElement` via the patched
* `createElement` function, fetches the shadow root and copies any
* attributes before swapping with the original element in-place.
*/
function wrapMediaElement (mediaElement: HTMLMediaElement) {
const wrappedMedia = document.createElement(mediaElement.tagName);
const shadowRoot = internalShadowRoots.get(wrappedMedia);
if (!shadowRoot) {
console.error("err: Failed to fetch shadow root!");
return;
}
/**
* Copy attributes, any non-media specific attributes are set to the
* wrapper element for identification (id, class, etc...) or styling.
*/
for (const attr of mediaElement.attributes) {
if (mediaElementAttributes.includes(attr.name)) {
wrappedMedia.setAttribute(attr.name, attr.value);
} else {
/**
* Since the wrapped element has a patched `setAttribute`
* method, need to call the original from the `HTMLElement`
* prototype, otherwise attributes will be set on the
* internal media element instead.
*/
HTMLElement.prototype.setAttribute.call(
wrappedMedia, attr.name, attr.value);
}
}
/**
* Clone and append any HTMLSourceElement children to the
* internal media element within the wrapped media shadow root.
*/
for (const source of mediaElement.getElementsByTagName("source")) {
const internalMedia = shadowRoot.querySelector("audio,video");
if (!internalMedia) {
console.error("err: Failed to fetch internal video element!");
return;
}
internalMedia.appendChild(source.cloneNode());
}
// Replace media element on page with wrapped media
mediaElement.replaceWith(wrappedMedia);
}
/*function* joinIterables (...iterables: Array<Iterable<any>>) {
for (const iterable of iterables) {
for (const item of iterable) {
yield item;
}
}
}*/
/**
* Find all media elements (both in the main DOM and any shadow DOMs)
* and wrap them.
*/
document.addEventListener("DOMContentLoaded", () => {
const mediaSelector = "audio,video";
setTimeout(() => {
const mediaElements = document.querySelectorAll(mediaSelector);
const deepMediaElements = deepQuerySelectorAll(mediaSelector);
for (const mediaElement of [...Array.from(mediaElements), ...deepMediaElements]) {
wrapMediaElement(mediaElement as HTMLMediaElement);
}
});
});

View File

@@ -0,0 +1,31 @@
"use strict";
const _ = browser.i18n.getMessage;
/**
* Make synchronous request for another script to keep other page
* scripts from loading before its execution.
*/
const req = new XMLHttpRequest();
req.open("GET", browser.runtime.getURL(
"senders/media/overlay/overlayContent.js"), false);
req.send();
if (req.status === 200) {
// TODO: Replace with cast icons until AirPlay support is ready
const iconAirPlayAudio = browser.runtime.getURL(
"senders/media/overlay/AirPlay_Audio.svg");
const iconAirPlayVideo = browser.runtime.getURL(
"senders/media/overlay/AirPlay_Audio.svg");
const scriptElement = document.createElement("script");
scriptElement.textContent = `(function(){
const iconAirPlayAudio = "${iconAirPlayAudio}";
const iconAirPlayVideo = "${iconAirPlayVideo}";
const mediaOverlayTitle = "${_("mediaOverlayTitle", "X")}";
${req.responseText}
})();`;
// <head> probably doesn't exist yet
(document.head || document.documentElement).append(scriptElement);
}

View File

@@ -198,6 +198,21 @@ class OptionsApp extends Component<{}, OptionsAppState> {
</div>
</label>
<label className="option option--inline">
<div className="option__control">
<input name="mediaOverlayEnabled"
type="checkbox"
checked={ this.state.options.mediaOverlayEnabled }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsMediaOverlayEnabledTemp") }
</div>
<div className="option__description">
{ _("optionsMediaOverlayEnabledDescrption") }
</div>
</label>
<hr />
<label className="option option--inline">

View File

@@ -2,7 +2,7 @@
"extends": "../tsconfig"
, "compilerOptions": {
"jsx": "react"
, "lib": [ "esnext", "dom" ]
, "lib": [ "esnext", "dom", "dom.iterable" ]
, "moduleResolution": "node"
, "sourceMap": true
}

View File

@@ -15,8 +15,10 @@ module.exports = (env) => ({
"background": `${env.includePath}/background/background.ts`
// Sender apps
, "senders/mediaCast": `${env.includePath}/senders/mediaCast.ts`
, "senders/mirroringCast": `${env.includePath}/senders/mirroringCast.ts`
, "senders/media/bundle": `${env.includePath}/senders/media/index.ts`
, "senders/media/overlay/overlayContent": `${env.includePath}/senders/media/overlay/overlayContent.ts`
, "senders/media/overlay/overlayContentLoader": `${env.includePath}/senders/media/overlay/overlayContentLoader.ts`
, "senders/mirroring": `${env.includePath}/senders/mirroring.ts`
// Shim entries
, "shim/bundle": `${env.includePath}/shim/index.ts`

View File

@@ -3,7 +3,6 @@
"esModuleInterop": true
, "module": "commonjs"
, "noImplicitAny": true
, "noUnusedLocals": true
, "noUnusedParameters": true
, "removeComments": true
, "resolveJsonModule": true