Skip to content

Commit

Permalink
Add better support for handling device capabilities and receiver objects
Browse files Browse the repository at this point in the history
  • Loading branch information
hensm committed Apr 17, 2022
1 parent 1da709e commit b672b8d
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 77 deletions.
16 changes: 9 additions & 7 deletions app/src/bridge/components/cast/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ export default class Remote extends CastClient {

constructor(private host: string, private options?: CastRemoteOptions) {
super();
super.connect(host, {
onReceiverMessage: message => {
this.onReceiverMessage(message);
}
}).then(() => {
this.sendReceiverMessage({ type: "GET_STATUS" });
});
super
.connect(host, {
onReceiverMessage: message => {
this.onReceiverMessage(message);
}
})
.then(() => {
this.sendReceiverMessage({ type: "GET_STATUS" });
});
}

disconnect() {
Expand Down
13 changes: 8 additions & 5 deletions app/src/bridge/components/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ interface CastRecord {
md: string;
// Friendly name (user-visible)
fn: string;
// Capabilities
ca: string;
// Version (?)
ve: string;
// Icon path (?)
ic: string;

cd: string;
rm: string;
ca: string;
st: string;
bs: string;
nf: string;
Expand Down Expand Up @@ -71,16 +72,18 @@ browser.on("serviceUp", service => {

const record = service.txtRecord as CastRecord;
const device: ReceiverDevice = {
id: record.id,
friendlyName: record.fn,
modelName: record.md,
capabilities: parseInt(record.ca),
host: service.addresses[0],
port: service.port,
id: service.name,
friendlyName: record.fn
port: service.port
};

sendMessage({
subject: "main:receiverDeviceUp",
data: {
deviceId: service.name,
deviceId: device.id,
deviceInfo: device
}
});
Expand Down
15 changes: 13 additions & 2 deletions app/src/bridge/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ import {
Volume
} from "./components/cast/types";

export enum ReceiverDeviceCapabilities {
NONE = 0,
VIDEO_OUT = 1,
VIDEO_IN = 2,
AUDIO_OUT = 4,
AUDIO_IN = 8,
MULTIZONE_GROUP = 32
}

export interface ReceiverDevice {
host: string;
friendlyName: string;
id: string;
friendlyName: string;
modelName: string;
capabilities: ReceiverDeviceCapabilities;
host: string;
port: number;
status?: ReceiverStatus;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/src/background/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ browser.menus.onClicked.addListener(async (info, tab) => {
if (selection.mediaType === ReceiverSelectorMediaType.App) {
await browser.tabs.executeScript(tab.id, {
code: stringify`
window.receiver = ${selection.receiver};
window.receiver = ${selection.receiverDevice};
window.mediaUrl = ${info.srcUrl};
window.targetElementId = ${info.targetElementId};
`,
Expand Down
14 changes: 11 additions & 3 deletions ext/src/background/receiverDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import logger from "../lib/logger";
import { TypedEventTarget } from "../lib/TypedEventTarget";

import { Message, Port } from "../messaging";
import { ReceiverDevice } from "../types";
import { ReceiverDevice, ReceiverDeviceCapabilities } from "../types";
import { ReceiverStatus } from "../cast/api/types";

interface EventMap {
Expand Down Expand Up @@ -84,6 +84,16 @@ export default new (class extends TypedEventTarget<EventMap> {
case "main:receiverDeviceUp": {
const { deviceId, deviceInfo } = message.data;

// TODO: Add proper support for Chromecast Audio devices
if (
!(
deviceInfo.capabilities &
ReceiverDeviceCapabilities.VIDEO_OUT
)
) {
break;
}

this.receiverDevices.set(deviceId, deviceInfo);
this.dispatchEvent(
new CustomEvent("receiverDeviceUp", {
Expand Down Expand Up @@ -112,9 +122,7 @@ export default new (class extends TypedEventTarget<EventMap> {
case "main:receiverDeviceStatusUpdated": {
const { deviceId, status } = message.data;
const receiverDevice = this.receiverDevices.get(deviceId);

if (!receiverDevice) {
logger.error(`Receiver ID \`${deviceId}\` not found!`);
break;
}

Expand Down
18 changes: 9 additions & 9 deletions ext/src/background/receiverSelector/ReceiverSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
private messagePort?: Port;
private messagePortDisconnected?: boolean;

private receivers?: ReceiverDevice[];
private receiverDevices?: ReceiverDevice[];
private defaultMediaType?: ReceiverSelectorMediaType;
private availableMediaTypes?: ReceiverSelectorMediaType;

Expand Down Expand Up @@ -69,7 +69,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
}

public async open(
receivers: ReceiverDevice[],
receiverDevices: ReceiverDevice[],
defaultMediaType: ReceiverSelectorMediaType,
availableMediaTypes: ReceiverSelectorMediaType,
appId?: string,
Expand All @@ -83,7 +83,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
await browser.windows.remove(this.windowId);
}

this.receivers = receivers;
this.receiverDevices = receiverDevices;
this.defaultMediaType = defaultMediaType;
this.availableMediaTypes = availableMediaTypes;

Expand Down Expand Up @@ -135,12 +135,12 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
}
}

public update(receivers: ReceiverDevice[]) {
this.receivers = receivers;
public update(receiverDevices: ReceiverDevice[]) {
this.receiverDevices = receiverDevices;
this.messagePort?.postMessage({
subject: "popup:update",
data: {
receivers: this.receivers
receiverDevices: this.receiverDevices
}
});
}
Expand Down Expand Up @@ -176,7 +176,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
});

if (
this.receivers === undefined ||
this.receiverDevices === undefined ||
this.defaultMediaType === undefined ||
this.availableMediaTypes === undefined
) {
Expand All @@ -191,7 +191,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
this.messagePort.postMessage({
subject: "popup:update",
data: {
receivers: this.receivers,
receiverDevices: this.receiverDevices,
defaultMediaType: this.defaultMediaType,
availableMediaTypes: this.availableMediaTypes
}
Expand Down Expand Up @@ -250,7 +250,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
// Cleanup
this.windowId = undefined;
this.messagePort = undefined;
this.receivers = undefined;
this.receiverDevices = undefined;
this.defaultMediaType = undefined;
this.availableMediaTypes = undefined;
this.wasReceiverSelected = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async function getSelection(
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast,
receiver: ev.detail.receiver,
receiverDevice: ev.detail.receiverDevice,
mediaType: ev.detail.mediaType,
filePath: ev.detail.filePath
});
Expand Down Expand Up @@ -203,11 +203,11 @@ async function getSelection(
"stop",
storeListener("stop", async ev => {
logger.info("Stopping receiver app...", ev.detail);
receiverDevices.stopReceiverApp(ev.detail.receiver.id);
receiverDevices.stopReceiverApp(ev.detail.receiverDevice.id);

resolve({
actionType: ReceiverSelectionActionType.Stop,
receiver: ev.detail.receiver
receiverDevice: ev.detail.receiverDevice
});
removeListeners();
})
Expand Down
4 changes: 2 additions & 2 deletions ext/src/background/receiverSelector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export enum ReceiverSelectionActionType {

export interface ReceiverSelectionCast {
actionType: ReceiverSelectionActionType.Cast;
receiver: ReceiverDevice;
receiverDevice: ReceiverDevice;
mediaType: ReceiverSelectorMediaType;
filePath?: string;
}
export interface ReceiverSelectionStop {
actionType: ReceiverSelectionActionType.Stop;
receiver: ReceiverDevice;
receiverDevice: ReceiverDevice;
}

export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;
75 changes: 59 additions & 16 deletions ext/src/cast/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import logger from "../../lib/logger";

import { ReceiverDevice } from "../../types";
import {
ReceiverDevice,
ReceiverDeviceCapabilities as ReceiverDeviceCapabilities
} from "../../types";
import { ErrorCallback, SuccessCallback } from "../types";

import { onMessage, sendMessageResponse } from "../eventMessageChannel";
Expand Down Expand Up @@ -92,17 +95,40 @@ export const timeout = new Timeout();
// chrome.cast.media namespace
export * as media from "./media";

/**
* Create `chrome.cast.Receiver` object from receiver device info.
*/
function createReceiver(device: ReceiverDevice) {
// Convert capabilities bitflag to string array
const capabilities: Capability[] = [];
if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_OUT) {
capabilities.push(Capability.VIDEO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_IN) {
capabilities.push(Capability.VIDEO_IN);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_OUT) {
capabilities.push(Capability.AUDIO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_IN) {
capabilities.push(Capability.AUDIO_IN);
} else if (
device.capabilities & ReceiverDeviceCapabilities.MULTIZONE_GROUP
) {
capabilities.push(Capability.MULTIZONE_GROUP);
}

const receiver = new Receiver(device.id, device.friendlyName, capabilities);

// Currently only supports CAST receivers
receiver.receiverType = ReceiverType.CAST;

return receiver;
}

function sendSessionRequest(
sessionRequest: SessionRequest,
receiverDevice: ReceiverDevice
) {
for (const listener of receiverActionListeners) {
const receiver = new Receiver(
receiverDevice.id,
receiverDevice.friendlyName
);

listener(receiver, ReceiverAction.CAST);
listener(createReceiver(receiverDevice), ReceiverAction.CAST);
}

sendMessageResponse({
Expand Down Expand Up @@ -258,13 +284,28 @@ onMessage(message => {
const status = message.data;

// TODO: Implement persistent per-origin receiver IDs
const receiver = new Receiver(
const receiver1 = new Receiver(
status.receiverId, // label
status.receiverFriendlyName, // friendlyName
[Capability.VIDEO_OUT, Capability.AUDIO_OUT], // capabilities
status.volume // volume
);

const receiverDevice = receiverDevices.get(status.receiverId);
if (!receiverDevice) {
logger.error(
`Could not find receiver device "${status.receiverFriendlyName}" (${status.receiverId})`
);
break;
}

const receiver = createReceiver(receiverDevice);
receiver.volume = status.volume;
receiver.displayStatus = new ReceiverDisplayStatus(
status.statusText,
status.appImages
);

const session = new Session(
status.sessionId, // sessionId
status.appId, // appId
Expand Down Expand Up @@ -401,28 +442,27 @@ onMessage(message => {
logger.info("Selected receiver");

if (sessionRequest) {
sendSessionRequest(sessionRequest, message.data.receiver);
sendSessionRequest(sessionRequest, message.data.receiverDevice);
sessionRequest = null;
}

break;
}

case "cast:selectReceiver/stopped": {
const { receiver } = message.data;
const { receiverDevice } = message.data;

logger.info("Stopped receiver");

if (sessionRequest) {
sessionRequest = null;

for (const listener of receiverActionListeners) {
const castReceiver = new Receiver(
receiver.id,
receiver.friendlyName
listener(
// TODO: Use existing receiver object?
createReceiver(receiverDevice),
ReceiverAction.STOP
);

listener(castReceiver, ReceiverAction.STOP);
}
}

Expand Down Expand Up @@ -451,7 +491,10 @@ onMessage(message => {
break;
}

sendSessionRequest(apiConfig.sessionRequest, message.data.receiver);
sendSessionRequest(
apiConfig.sessionRequest,
message.data.receiverDevice
);

break;
}
Expand Down

0 comments on commit b672b8d

Please sign in to comment.