From 01546e81dde37cde34779c281798195d22106c40 Mon Sep 17 00:00:00 2001 From: hensm Date: Sat, 13 Apr 2019 04:35:08 +0100 Subject: [PATCH] Add initial status bridge implementation --- app/src/StatusListener.ts | 8 +++--- app/src/castTypes.ts | 54 ++++++++++++++++++++++++++++++++++++++ app/src/main.ts | 44 +++++++++++++++++++++++-------- ext/src/main.ts | 46 ++++++++++++++++++++++++++++---- ext/src/messageTypes.ts | 25 ++++++++++++++++++ ext/src/shim/cast/index.ts | 2 +- ext/src/types.ts | 17 ++++++++++-- ext/src/ui/popup/index.tsx | 37 ++++++++++++-------------- 8 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 app/src/castTypes.ts create mode 100644 ext/src/messageTypes.ts diff --git a/app/src/StatusListener.ts b/app/src/StatusListener.ts index 2e292ae..e00a7a9 100644 --- a/app/src/StatusListener.ts +++ b/app/src/StatusListener.ts @@ -59,9 +59,11 @@ export default class StatusListener extends EventEmitter { } case "RECEIVER_STATUS": { - // Send update message - this.emit("statusUpdate", data.status); - + this.emit("receiverStatus", data.status); + break; + } + case "MEDIA_STATUS": { + this.emit("mediaStatus", data.status); break; } } diff --git a/app/src/castTypes.ts b/app/src/castTypes.ts new file mode 100644 index 0000000..9c3384b --- /dev/null +++ b/app/src/castTypes.ts @@ -0,0 +1,54 @@ +"use strict"; + +export interface ReceiverStatus { + volume: { + muted: boolean; + stepInterval: number; + controlType: string; + level: number; + }; + applications?: { + displayName: string; + statusText: string; + transportId: string; + isIdleScreen: boolean; + sessionId: string; + namespaces: { name: string }[]; + appId: string; + }[]; + userEq?: {}; +} + +export interface MediaStatus { + mediaSessionId: number; + supportedMediaCommands: number; + currentTime: number; + media: { + duration: number; + contentId: string; + streamType: string; + contentType: string; + }; + playbackRate: number; + volume: { + muted: boolean; + level: number; + } + currentItemId: number; + idleReason: string; + playerState: string; + extendedStatus: { + playerState: string; + media: { + contentId: string; + streamType: string; + contentType: string; + metadata: { + images: { url: string }[]; + metadataType: number; + artist: string; + title: string; + }; + } + } +} diff --git a/app/src/main.ts b/app/src/main.ts index cead592..a762d4f 100755 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -11,6 +11,8 @@ import Session from "./Session"; import StatusListener from "./StatusListener"; import * as transforms from "./transforms"; +import { ReceiverStatus, MediaStatus } from "./castTypes"; + import { Message } from "./types"; import { __applicationName @@ -185,29 +187,48 @@ function initialize (options: InitializeOptions) { const statusListeners = new Map(); browser.on("serviceUp", (service: dnssd.Service) => { - const address = service.addresses[0]; + const host = service.addresses[0]; const port = service.port; const id = service.txt.id; if (options.shouldWatchStatus) { - const listener = new StatusListener(address, port); + const listener = new StatusListener(host, port); - listener.on("statusUpdate", (status: any) => { - sendMessage({ - subject: "main:/receiverStatusUpdate" - , data: { id, status } - }); + listener.on("receiverStatus", (status: ReceiverStatus) => { + const receiverStatusMessage: any = { + subject: "receiverStatus" + , data: { + id + , status: { + volume: { + level: status.volume.level + , muted: status.volume.muted + } + } + } + }; + + if ("applications" in status) { + const application = status.applications[0]; + + receiverStatusMessage.data.status.application = { + displayName: application.displayName + , isIdleScreen: application.isIdleScreen + , statusText: application.statusText + }; + } + + sendMessage(receiverStatusMessage); }); statusListeners.set(id, listener); } - transforms.encode.write({ + sendMessage({ subject: "shim:/serviceUp" , data: { - address, port, id + host, port, id , friendlyName: service.txt.fn - , currentApp: service.txt.rs } }); }); @@ -215,11 +236,12 @@ function initialize (options: InitializeOptions) { browser.on("serviceDown", (service: dnssd.Service) => { const id = service.txt.id; + // De-register status listener if (options.shouldWatchStatus && statusListeners.has(id)) { statusListeners.get(id).deregister(); } - transforms.encode.write({ + sendMessage({ subject: "shim:/serviceDown" , data: { id } }); diff --git a/ext/src/main.ts b/ext/src/main.ts index 248759f..9eb49a6 100755 --- a/ext/src/main.ts +++ b/ext/src/main.ts @@ -7,7 +7,11 @@ import messageRouter from "./lib/messageRouter"; import { getChromeUserAgent } from "./lib/userAgents"; import { getWindowCenteredProps } from "./lib/utils"; -import { Message } from "./types"; +import { Message, Receiver } from "./types"; + +import { ReceiverStatusMessage + , ServiceDownMessage + , ServiceUpMessage } from "./messageTypes"; import semver from "semver"; @@ -540,13 +544,45 @@ browser.runtime.onConnect.addListener(port => { const statusBridge = browser.runtime.connectNative(APPLICATION_NAME); -const receiverStatusMap = new Map(); +const statusBridgeReceivers = new Map(); statusBridge.onMessage.addListener((message: Message) => { switch (message.subject) { - case "main:/receiverStatusUpdate": { - const { id, status } = message.data; - receiverStatusMap.set(id, status); + + case "shim:/serviceUp": { + const serviceUpMessage = message as ServiceUpMessage; + const receiver = serviceUpMessage.data; + + statusBridgeReceivers.set(receiver.id, receiver); + + break; + } + + case "shim:/serviceDown": { + const serviceDownMessage = (message as ServiceDownMessage); + const { id } = serviceDownMessage.data; + + if (statusBridgeReceivers.has(id)) { + statusBridgeReceivers.delete(id); + } + + break; + } + + case "receiverStatus": { + const receiverStatusMessage = message as ReceiverStatusMessage; + const { id, status } = receiverStatusMessage.data; + + const receiver = statusBridgeReceivers.get(id); + + // Merge new status with old status + statusBridgeReceivers.set(id, { + ...receiver + , status: { + ...receiver.status + , ...status + } + }); break; } diff --git a/ext/src/messageTypes.ts b/ext/src/messageTypes.ts new file mode 100644 index 0000000..72cefae --- /dev/null +++ b/ext/src/messageTypes.ts @@ -0,0 +1,25 @@ +"use strict"; + +import { Message, Receiver, ReceiverStatus } from "./types"; + + +export interface ReceiverStatusMessage extends Message { + subject: "receiverStatus"; + data: { + id: string; + status: ReceiverStatus; + } +} + +export interface ServiceDownMessage extends Message { + subject: "shim:/serviceDown"; + data: { + id: string; + }; +} + +export interface ServiceUpMessage extends Message { + subject: "shim:/serviceUp"; + data: Receiver; +} + diff --git a/ext/src/shim/cast/index.ts b/ext/src/shim/cast/index.ts index c158607..eef2082 100755 --- a/ext/src/shim/cast/index.ts +++ b/ext/src/shim/cast/index.ts @@ -235,7 +235,7 @@ onMessage(message => { message.data.receiver.id , message.data.receiver.friendlyName); - (selectedReceiver as any)._address = message.data.receiver.address; + (selectedReceiver as any)._address = message.data.receiver.host; (selectedReceiver as any)._port = message.data.receiver.port; function createSession () { diff --git a/ext/src/types.ts b/ext/src/types.ts index 450766e..541d21f 100644 --- a/ext/src/types.ts +++ b/ext/src/types.ts @@ -7,11 +7,24 @@ export interface Message { } export interface Receiver { - address: string; - currentApp: string; + host: string; friendlyName: string; id: string; port: number; + status?: ReceiverStatus; +} + +export interface ReceiverStatus { + application: { + displayName: string; + isIdleScreen: string; + statusText: string; + }; + id: string; + volume: { + level: number; + muted: boolean + }; } export interface DownloadDelta { diff --git a/ext/src/ui/popup/index.tsx b/ext/src/ui/popup/index.tsx index a0748f3..231ce51 100755 --- a/ext/src/ui/popup/index.tsx +++ b/ext/src/ui/popup/index.tsx @@ -5,7 +5,7 @@ import React, { Component } from "react"; import ReactDOM from "react-dom"; import { getNextEllipsis } from "../../lib/utils"; -import * as types from "../../types"; +import { Message, Receiver } from "../../types"; const _ = browser.i18n.getMessage; @@ -29,7 +29,7 @@ let frameWidth: number; interface PopupAppState { - receivers: types.Receiver[]; + receivers: Receiver[]; selectedMedia: string; isLoading: boolean; } @@ -63,7 +63,7 @@ class PopupApp extends Component<{}, PopupAppState> { name: "popup" }); - backgroundPort.onMessage.addListener((message: types.Message) => { + backgroundPort.onMessage.addListener((message: Message) => { if (message.subject === "popup:/assignShim") { this.setPort(message.data.tabId , message.data.frameId); @@ -92,10 +92,10 @@ class PopupApp extends Component<{}, PopupAppState> {
    { this.state.receivers.map((receiver, i) => { return ( - + ); })}
@@ -117,7 +117,7 @@ class PopupApp extends Component<{}, PopupAppState> { subject: "shim:/popupReady" }); - this.port.onMessage.addListener((message: types.Message) => { + this.port.onMessage.addListener((message: Message) => { switch (message.subject) { case "popup:/populateReceiverList": { this.setState({ @@ -145,7 +145,7 @@ class PopupApp extends Component<{}, PopupAppState> { }); } - private onCast (receiver: types.Receiver) { + private onCast (receiver: Receiver) { this.setState({ isLoading: true }); @@ -168,19 +168,19 @@ class PopupApp extends Component<{}, PopupAppState> { } -interface ReceiverProps { - receiver: types.Receiver; +interface ReceiverEntryProps { + receiver: Receiver; isLoading: boolean; - onCast (receiver: types.Receiver): void; + onCast (receiver: Receiver): void; } -interface ReceiverState { +interface ReceiverEntryState { ellipsis: string; isLoading: boolean; } -class Receiver extends Component { - constructor (props: ReceiverProps) { +class ReceiverEntry extends Component { + constructor (props: ReceiverEntryProps) { super(props); this.state = { @@ -198,12 +198,9 @@ class Receiver extends Component { { this.props.receiver.friendlyName }
- { `${this.props.receiver.address}:${this.props.receiver.port}` } -
-
- { this.props.receiver.currentApp && - `- ${this.props.receiver.currentApp}` } + { `${this.props.receiver.host}:${this.props.receiver.port}` }
+