Add stubbed cast.framework API implementation

This commit is contained in:
hensm
2019-03-19 16:32:09 +00:00
parent 89fc20f6a3
commit 270d661de0
20 changed files with 605 additions and 21 deletions

View File

@@ -124,27 +124,20 @@ const SENDER_SCRIPT_FRAMEWORK_URL =
*/
browser.webRequest.onBeforeRequest.addListener(
async details => {
switch (details.url) {
case SENDER_SCRIPT_URL: {
// Content/Page script bridge
await browser.tabs.executeScript(details.tabId, {
file: "shim/content.js"
, frameId: details.frameId
, runAt: "document_start"
});
return {
redirectUrl: browser.runtime.getURL("shim/bundle.js")
};
}
await browser.tabs.executeScript(details.tabId, {
file: "shim/content.js"
, frameId: details.frameId
, runAt: "document_start"
});
case SENDER_SCRIPT_FRAMEWORK_URL: {
// TODO: implement cast.framework
return {
cancel: true
};
}
}
const redirectUrl = details.url === SENDER_SCRIPT_URL
? browser.runtime.getURL("shim/bundle.js")
: browser.runtime.getURL("shim/bundle.js?loadCastFramework=1");
return {
redirectUrl
};
}
, { urls: [
SENDER_SCRIPT_URL
@@ -185,7 +178,7 @@ async function onBeforeSendHeaders (
header.value = getChromeUserAgent(os, true);
break;
}
header.value = currentUAString;
break;
@@ -209,7 +202,8 @@ async function onOptionsUpdated (alteredOptions?: Array<(keyof Options)>) {
}
/**
* Adds a webRequest listener that intercepts and modifies user agent based on
* Adds a webRequest listener that intercepts and modifies user
* agent.
*/
function register_userAgentWhitelist () {
browser.webRequest.onBeforeSendHeaders.addListener(

View File

@@ -0,0 +1,14 @@
"use strict";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ActiveInputStateEventData extends EventData {
constructor (
public activeInputState: number) {
super(SessionEventType.ACTIVE_INPUT_STATE_CHANGED);
}
}

View File

@@ -0,0 +1,22 @@
"use strict";
import Image from "../../cast/classes/Image";
import Session from "../../cast/classes/Session";
export default class ApplicationMetadata {
public applicationId: string;
public images: Image[];
public name: string;
public namespaces: string[];
constructor (sessionObj: Session) {
this.applicationId = sessionObj.appId;
this.images = sessionObj.appImages;
this.name = sessionObj.displayName;
// Convert [{ name: <ns> }, ...] to [ <ns>, ... ]
this.namespaces = sessionObj.namespaces.map(
namespaceObj => namespaceObj.name);
}
}

View File

@@ -0,0 +1,15 @@
"use strict";
import ApplicationMetadata from "./ApplicationMetadata";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationMetadataEventData extends EventData {
constructor (
public metadata: ApplicationMetadata) {
super(SessionEventType.APPLICATION_METADATA_CHANGED);
}
}

View File

@@ -0,0 +1,14 @@
"use strict";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationStatusEventData extends EventData {
constructor (
public status: string) {
super(SessionEventType.APPLICATION_STATUS_CHANGED);
}
}

View File

@@ -0,0 +1,52 @@
"use strict";
import Image from "../../cast/classes/Image";
import Session from "../../cast/classes/Session";
import CastOptions from "./CastOptions";
import CastSession from "./CastSession";
import CastStateEventData from "./CastStateEventData";
import SessionStateEventData from "./SessionStateEventData";
type EventHandler = (eventData:
CastStateEventData
| SessionStateEventData) => void;
export default class ApplicationMetadata {
public addEventListener (type: string, handler: EventHandler): void {
console.info("STUB :: CastContext#addEventListener");
}
public endCurrentSession (stopCasting: boolean): void {
console.info("STUB :: CastContext#endCurrentSession");
}
// @ts-ignore
public getCastState (): string {
console.info("STUB :: CastContext#getCastState");
}
// @ts-ignore
public getCurrentSession (): CastSession {
console.info("STUB :: CastContext#getCurrentSession");
}
// @ts-ignore
public getSessionState (): string {
console.info("STUB :: CastContext#getSessionState");
}
public removeEventListener (type: string, handler: EventHandler): void {
console.info("STUB :: CastContext#removeEventListener");
}
// @ts-ignore
public requestSession (): Promise<string> {
console.info("STUB :: CastContext#requestSession");
}
public setOptions (options: CastOptions): void {
console.info("STUB :: CastContext#setOptions");
}
}

View File

@@ -0,0 +1,25 @@
"use strict";
import { AutoJoinPolicy } from "../../cast/enums";
export default class CastOptions {
public autoJoinPolicy: string = AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED;
public language: string = null;
public receiverApplicationId: string = null;
public resumeSavedSession: boolean = true;
constructor (options: CastOptions = ({} as CastOptions)) {
if (options.autoJoinPolicy) {
this.autoJoinPolicy = options.autoJoinPolicy;
}
if (options.language) {
this.language = options.language;
}
if (options.receiverApplicationId) {
this.receiverApplicationId = options.receiverApplicationId;
}
if (options.resumeSavedSession) {
this.resumeSavedSession = options.resumeSavedSession;
}
}
}

View File

@@ -0,0 +1,137 @@
"use strict";
import Image from "../../cast/classes/Image";
import Receiver from "../../cast/classes/Receiver";
import Session from "../../cast/classes/Session";
import LoadRequest from "../../cast/media/classes/LoadRequest";
import Media from "../../cast/media/classes/Media";
import ActiveInputStateEventData from "./ActiveInputStateEventData";
import ApplicationMetadata from "./ApplicationMetadata";
import ApplicationMetadataEventData from "./ApplicationMetadataEventData";
import ApplicationStatusEventData from "./ApplicationStatusEventData";
import MediaSessionEventData from "./MediaSessionEventData";
import VolumeEventData from "./VolumeEventData";
type EventHandler = (eventData:
ApplicationStatusEventData
| ApplicationMetadataEventData
| ActiveInputStateEventData
| MediaSessionEventData
| VolumeEventData) => void;
type MessageListener = (namespace: string, message: string) => void;
export default class RemotePlayer {
constructor (sessionObj: Session, state: string) {
console.info("STUB :: CastSession#constructor");
}
public addEventListener (
type: string
, handler: EventHandler): void {
console.info("STUB :: CastSession#addEventListener");
}
public addMessageListener (
namespace: string
, listener: MessageListener): void {
console.info("STUB :: CastSession#addMessageListener");
}
public endSession (stopCasting: boolean): void {
console.info("STUB :: CastSession#endSession");
}
// @ts-ignore
public getActiveInputState (): number {
console.info("STUB :: CastSession#getActiveInputState");
}
// @ts-ignore
public getApplicationMetadata (): ApplicationMetadata {
console.info("STUB :: CastSession#getApplicationMetadata");
}
// @ts-ignore
public getApplicationStatus (): string {
console.info("STUB :: CastSession#getApplicationStatus");
}
// @ts-ignore
public getCastDevice (): Receiver {
console.info("STUB :: CastSession#getCastDevice");
}
// @ts-ignore
public getMediaSession (): Media {
console.info("STUB :: CastSession#getMediaSession");
}
// @ts-ignore
public getSessionId (): string {
console.info("STUB :: CastSession#getSessionId");
}
// @ts-ignore
public getSessionObj (): Session {
console.info("STUB :: CastSession#getSessionObj");
}
// @ts-ignore
public getSessionState (): string {
console.info("STUB :: CastSession#getSessionState");
}
// @ts-ignore
public getVolume (): number {
console.info("STUB :: CastSession#getVolume");
}
// @ts-ignore
public isMute (): boolean {
console.info("STUB :: CastSession#isMute");
}
// @ts-ignore
public loadMedia (loadRequest: LoadRequest): Promise<string> {
console.info("STUB :: CastSession#loadMedia");
}
public removeEventListener (
type: string
, handler: EventHandler): void {
console.info("STUB :: CastSession#removeEventListener");
}
public removeMessageListener (
namespace: string
, listener: MessageListener): void {
console.info("STUB :: CastSession#removeMessageListener");
}
public sendMessage (
namespace: string
// @ts-ignore
, data: any): Promise<string> {
console.info("STUB :: CastSession#sendMessage");
}
// @ts-ignore
public setMute (isMute: boolean): Promise<string> {
console.info("STUB :: CastSession#setMute");
}
// @ts-ignore
public setVolume (volume: number): Promise<string> {
console.info("STUB :: CastSession#setVolume");
}
}

View File

@@ -0,0 +1,14 @@
"use strict";
import EventData from "./EventData";
import { CastContextEventType } from "../enums";
export default class ApplicationStatusEventData extends EventData {
constructor (
public castState: string) {
super(CastContextEventType.CAST_STATE_CHANGED);
}
}

View File

@@ -0,0 +1,6 @@
"use strict";
export default class EventData {
constructor (
public type: string) {}
}

View File

@@ -0,0 +1,16 @@
"use strict";
import Media from "../../cast/media/classes/Media";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationStatusEventData extends EventData {
constructor (
public mediaSession: Media) {
super(SessionEventType.MEDIA_SESSION);
}
}

View File

@@ -0,0 +1,34 @@
"use strict";
import MediaInfo from "../../cast/media/classes/MediaInfo";
import RemotePlayerController from "./RemotePlayerController";
interface SavedPlayerState {
mediaInfo: string;
currentTime: number;
isPaused: boolean;
}
export default class RemotePlayer {
public canControlVolume = false;
public canPause = false;
public canSeek = false;
public controller: RemotePlayerController = null;
public currentTime = 0;
public displayName = "";
public displayStatus = "";
public duration = 0;
public imageUrl: string = null;
public isConnected = false;
public isMediaLoaded = false;
public isMuted = false;
public isPaused = false;
public mediaInfo: MediaInfo = null;
public playerState: string = null;
public savedPlayerState: SavedPlayerState = null;
public statusText = "";
public title = "";
public volumeLevel = 1;
}

View File

@@ -0,0 +1,8 @@
"use strict";
export default class RemotePlayerChangedEvent {
constructor (
public type: string
, public field: string
, public value: any) {}
}

View File

@@ -0,0 +1,59 @@
"use strict";
import RemotePlayer from "./RemotePlayer";
import RemotePlayerChangedEvent from "./RemotePlayerChangedEvent";
type EventHandler = (event: RemotePlayerChangedEvent) => void;
export default class RemotePlayerController {
constructor (player: RemotePlayer) {
console.info("STUB :: RemotePlayerController#constructor");
}
public addEventListener (type: string, handler: EventHandler): void {
console.info("STUB :: RemotePlayerContoller#addEventListener");
}
public getFormattedTime (timeInSec: number): string {
const hours = Math.floor(timeInSec / 3600) % 24;
const minutes = Math.floor(timeInSec / 60) % 60;
const seconds = timeInSec % 60;
return [ hours, minutes, seconds ]
.map(c => c.toString().padStart(2, "0"))
.join(":");
}
public getSeekPosition (currentTime: number, duration: number) {
return (currentTime / duration) * 100;
}
public getSeekTime (currentPosition: number, duration: number) {
return (duration / 100) * currentPosition;
}
public muteOrUnmute (): void {
console.info("STUB :: RemotePlayerController#muteOrUnmute");
}
public playOrPause (): void {
console.info("STUB :: RemotePlayerController#playOrPause");
}
public removeEventListener (type: string, handler: EventHandler): void {
console.info("STUB :: RemotePlayerController#removeEventListener");
}
public seek (): void {
console.info("STUB :: RemotePlayerController#seek");
}
public setVolumeLevel (): void {
console.info("STUB :: RemotePlayerController#setVolumeLevel");
}
public stop (): void {
console.info("STUB :: RemotePlayerController#stop");
}
}

View File

@@ -0,0 +1,19 @@
"use strict";
import { ErrorCode } from "../../cast/enums";
import CastSession from "./CastSession";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationStatusEventData extends EventData {
constructor (
public session: CastSession
, public sessionState: string
, public errorCode: string = null) {
super(SessionEventType.APPLICATION_STATUS_CHANGED);
}
}

View File

@@ -0,0 +1,12 @@
"use strict";
import { SessionEventType } from "../enums";
export default class ApplicationStatusEventData {
public type = SessionEventType.VOLUME_CHANGED;
constructor (
public volume: number
, public isMute: boolean) {}
}

View File

@@ -0,0 +1,64 @@
"use strict";
export const ActiveInputState = {
ACTIVE_INPUT_STATE_UNKNOWN: -1
, ACTIVE_INPUT_STATE_NO: 0
, ACTIVE_INPUT_YES: 1
};
export const CastContextEventType = {
CAST_STATE_CHANGED: "caststatechanged"
, SESSION_STATE_CHANGED: "sessionstatechanged"
};
export const CastState = {
NO_DEVICES_AVAILABLE: "NO_DEVICES_AVAILABLE"
, NOT_CONNECTED: "NOT_CONNECTED"
, CONNECTING: "CONNECTING"
, CONNECTED: "CONNECTED"
};
export const LoggerLevel = {
DEBUG: 0
, INFO: 800
, WARNING: 900
, ERROR: 1000
, NONE: 1500
};
export const RemotePlayerEventType = {
ANY_CHANGE: "anyChanged"
, IS_CONNECTED_CHANGE: "isConnectedChanged"
, IS_MEDIA_LOADED_CHANGED: "isMediaLoadedChanged"
, DURATION_CHANGED: "durationChanged"
, CURRENT_TIME_CHANGED: "currentTimeChanged"
, IS_PAUSED_CHANGED: "isPausedChanged"
, VOLUME_LEVEL_CHANGED: "volumeLevelChanged"
, CAN_CONTROL_VOLUME_CHANGED: "canControlVolumeChanged"
, IS_MUTED_CHANGED: "isMutedChanged"
, CAN_PAUSE_CHANGED: "canPauseChanged"
, CAN_SEEK_CHANGED: "canSeekChanged"
, DISPLAY_NAME_CHANGED: "displayNameChanged"
, STATUS_TEXT_CHANGED: "statusTextChanged"
, MEDIA_INFO_CHANGED: "mediaInfoChanged"
, IMAGE_URL_CHANGED: "imageUrlChanged"
, PLAYER_STATE_CHANGED: "playerStateChanged"
};
export const SessionEventType = {
APPLICATION_STATUS_CHANGED: "applicationstatuschanged"
, APPLICATION_METADATA_CHANGED: "applicationmetadatachanged"
, ACTIVE_INPUT_STATE_CHANGED: "activeinputstatechanged"
, VOLUME_CHANGED: "volumechanged"
, MEDIA_SESSION: "mediasession"
};
export const SessionState = {
NO_SESSION: "NO_SESSION"
, SESSION_STARTING: "SESSION_STARTING"
, SESSION_STARTED: "SESSION_STARTED"
, SESSION_START_FAILED: "SESSION_START_FAILED"
, SESSION_ENDING: "SESSION_ENDING"
, SESSION_ENDED: "SESSION_ENDED"
, SESSION_RESUMED: "SESSION_RESUMED"
};

View File

@@ -0,0 +1,63 @@
"use strict";
import cast from "../cast";
import ActiveInputStateEventData from "./classes/ActiveInputStateEventData";
import ApplicationMetadata from "./classes/ApplicationMetadata";
import ApplicationMetadataEventData from "./classes/ApplicationMetadataEventData";
import ApplicationStatusEventData from "./classes/ApplicationStatusEventData";
import CastContext from "./classes/CastContext";
import CastOptions from "./classes/CastOptions";
import CastSession from "./classes/CastSession";
import CastStateEventData from "./classes/CastStateEventData";
import EventData from "./classes/EventData";
import MediaSessionEventData from "./classes/MediaSessionEventData";
import RemotePlayer from "./classes/RemotePlayer";
import RemotePlayerChangedEvent from "./classes/RemotePlayerChangedEvent";
import RemotePlayerController from "./classes/RemotePlayerController";
import SessionStateEventData from "./classes/SessionStateEventData";
import VolumeEventData from "./classes/VolumeEventData";
import { ActiveInputState
, CastContextEventType
, CastState
, LoggerLevel
, RemotePlayerEventType
, SessionEventType
, SessionState } from "./enums";
import { onMessage } from "../messageBridge";
let castContext: CastContext = null;
export default {
// Enums
ActiveInputState, CastContextEventType, CastState, LoggerLevel
, RemotePlayerEventType, SessionEventType, SessionState
// Classes
, ActiveInputStateEventData, ApplicationMetadata
, ApplicationMetadataEventData, ApplicationStatusEventData, CastOptions
, CastSession, CastStateEventData, EventData, MediaSessionEventData
, RemotePlayer, RemotePlayerChangedEvent, RemotePlayerController
, SessionStateEventData, VolumeEventData
, CastContext: Object.assign(CastContext, {
getInstance () {
if (castContext) {
return castContext;
}
castContext = new CastContext();
return castContext;
}
})
, VERSION: "1.0.07"
, setLoggerLevel (level: number) {
console.info("STUB :: cast.framework.setLoggerLevel");
}
};

View File

@@ -14,6 +14,21 @@ if (!global.chrome) {
global.chrome.cast = cast;
if (document.currentScript) {
const currentScript = (document.currentScript as HTMLScriptElement);
const currentScriptUrl = new URL(currentScript.src);
const currentScriptParams = new URLSearchParams(currentScriptUrl.search);
// Load Framework API if requested
if (currentScriptParams.get("loadCastFramework") === "1") {
import("./framework").then(framework => {
global.cast = {};
global.cast.framework = framework.default;
});
}
}
onMessage(message => {
switch (message.subject) {
case "shim:/initialized": {

View File

@@ -4,6 +4,7 @@
, "esModuleInterop": true
, "jsx": "react"
, "lib": [ "esnext", "dom" ]
, "module": "commonjs"
, "moduleResolution": "node"
, "noImplicitAny": true
, "removeComments": true