mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-12 02:29:59 +00:00
Cast API overhaul (#173)
Re-write the shim<->bridge messaging interface and session creation/update handling for better accuracy.
This commit is contained in:
260
app/src/bridge/components/cast/Session.ts
Normal file
260
app/src/bridge/components/cast/Session.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
"use strict";
|
||||
|
||||
import { Channel, Client } from "castv2";
|
||||
|
||||
import { sendMessage } from "../../lib/nativeMessaging";
|
||||
|
||||
import { ReceiverDevice } from "../../types";
|
||||
import { ReceiverApplication, ReceiverMessage, SenderMessage } from "./types";
|
||||
|
||||
|
||||
export const NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection";
|
||||
export const NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat";
|
||||
export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver";
|
||||
|
||||
const HEARTBEAT_INTERVAL = 5000;
|
||||
|
||||
|
||||
class CastClient {
|
||||
protected client = new Client();
|
||||
|
||||
protected connectionChannel?: Channel;
|
||||
protected heartbeatChannel?: Channel;
|
||||
protected heartbeatIntervalId?: NodeJS.Timeout;
|
||||
|
||||
constructor(protected sourceId = "sender-0"
|
||||
, protected destinationId = "receiver-0") {}
|
||||
|
||||
/**
|
||||
* Create a channel on the client connection with a given
|
||||
* namespace.
|
||||
*/
|
||||
createChannel(namespace: string
|
||||
, sourceId = this.sourceId
|
||||
, destinationId = this.destinationId) {
|
||||
|
||||
return this.client.createChannel(sourceId, destinationId, namespace, "JSON");
|
||||
}
|
||||
|
||||
connect(host: string, port: number, onHeartbeat?: () => void) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Handle errors
|
||||
this.client.on("error", reject);
|
||||
this.client.on("close", () => {
|
||||
if (this.heartbeatChannel && this.heartbeatIntervalId) {
|
||||
clearInterval(this.heartbeatIntervalId);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.connect({ host, port }, () => {
|
||||
this.connectionChannel = this.createChannel(NS_CONNECTION);
|
||||
this.heartbeatChannel = this.createChannel(NS_HEARTBEAT);
|
||||
|
||||
this.connectionChannel.send({ type: "CONNECT" });
|
||||
this.heartbeatChannel.send({ type: "PING" });
|
||||
|
||||
this.heartbeatIntervalId = setInterval(() => {
|
||||
this.heartbeatChannel?.send({ type: "PING" });
|
||||
if (onHeartbeat) {
|
||||
onHeartbeat();
|
||||
}
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type OnSessionCreatedCallback = (sessionId: string) => void;
|
||||
|
||||
export default class Session extends CastClient {
|
||||
// Assigned by the receiver once the session is established
|
||||
public sessionId?: string;
|
||||
|
||||
// Platform messaging
|
||||
private receiverChannel?: Channel;
|
||||
private receiverRequestId = 0;
|
||||
|
||||
// Receiver app messaging
|
||||
private transportId?: string;
|
||||
private transportConnection?: Channel;
|
||||
private transportHeartbeat?: Channel;
|
||||
|
||||
// Channels created by `sendCastSessionMessage` messages
|
||||
private namespaceChannelMap = new Map<string, Channel>();
|
||||
|
||||
/**
|
||||
* Request ID used to correlate the launch request with the
|
||||
* RECEIVER_STATUS message associated with session creation.
|
||||
*/
|
||||
private launchRequestId?: number;
|
||||
|
||||
private onSessionCreated?: OnSessionCreatedCallback;
|
||||
|
||||
|
||||
private establishAppConnection(transportId: string) {
|
||||
this.transportConnection = this.createChannel(
|
||||
NS_CONNECTION, this.sourceId, transportId);
|
||||
this.transportHeartbeat = this.createChannel(
|
||||
NS_HEARTBEAT, this.sourceId, transportId);
|
||||
|
||||
this.transportConnection.send({ type: "CONNECT" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming receiver messages.
|
||||
*/
|
||||
private onReceiverMessage = (message: ReceiverMessage) => {
|
||||
switch (message.type) {
|
||||
case "RECEIVER_STATUS": {
|
||||
const { status } = message;
|
||||
const application = status.applications?.find(
|
||||
app => app.appId === this.appId);
|
||||
|
||||
/**
|
||||
* If application isn't set, still waiting on the launch
|
||||
* request response.
|
||||
*/
|
||||
if (!this.sessionId) {
|
||||
// Launch message response only
|
||||
if (message.requestId !== this.launchRequestId) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (application) {
|
||||
this.sessionId = application.sessionId;
|
||||
this.transportId = application.transportId;
|
||||
|
||||
this.establishAppConnection(this.transportId);
|
||||
this.onSessionCreated?.(this.sessionId);
|
||||
|
||||
const { friendlyName } = this.receiverDevice;
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:castSessionCreated"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, statusText: application.statusText
|
||||
, namespaces: application.namespaces
|
||||
, volume: status.volume
|
||||
, appId: application.appId
|
||||
, displayName: application.displayName
|
||||
, receiverFriendlyName: friendlyName
|
||||
, transportId: this.sessionId
|
||||
|
||||
// TODO: Fix this
|
||||
, senderApps: []
|
||||
, appImages: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle session stop
|
||||
if (!application) {
|
||||
this.client.close();
|
||||
break;
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:castSessionUpdated"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, statusText: application.statusText
|
||||
, namespaces: application.namespaces
|
||||
, volume: message.status.volume
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "LAUNCH_ERROR": {
|
||||
console.error(`err: LAUNCH_ERROR, ${message.reason}`);
|
||||
this.client.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(namespace: string, message: unknown) {
|
||||
let channel = this.namespaceChannelMap.get(namespace);
|
||||
if (!channel) {
|
||||
channel = this.createChannel(
|
||||
namespace, this.sourceId, this.transportId);
|
||||
|
||||
channel.on("message", messageData => {
|
||||
if (!this.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageData = JSON.stringify(messageData);
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:receivedCastSessionMessage"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, namespace
|
||||
, messageData
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.namespaceChannelMap.set(namespace, channel);
|
||||
}
|
||||
|
||||
channel.send(message);
|
||||
}
|
||||
|
||||
sendReceiverMessage(message: DistributiveOmit<SenderMessage, "requestId">) {
|
||||
if (!this.receiverChannel) {
|
||||
this.receiverChannel = this.createChannel(NS_RECEIVER);
|
||||
this.receiverChannel.on("message", this.onReceiverMessage);
|
||||
}
|
||||
|
||||
const requestId = this.receiverRequestId++;
|
||||
this.receiverChannel?.send({ ...message, requestId });
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
constructor(public appId: string
|
||||
, public receiverDevice: ReceiverDevice) {
|
||||
|
||||
super();
|
||||
|
||||
this.client.on("close", () => {
|
||||
if (this.sessionId) {
|
||||
sendMessage({
|
||||
subject: "shim:castSessionStopped"
|
||||
, data: { sessionId: this.sessionId }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async connect(host: string
|
||||
, port: number
|
||||
, onSessionCreated?: OnSessionCreatedCallback) {
|
||||
|
||||
if (onSessionCreated) {
|
||||
this.onSessionCreated = onSessionCreated;
|
||||
}
|
||||
|
||||
await super.connect(host, port, () => {
|
||||
// Include transport heartbeat with platform heartbeat
|
||||
if (this.transportHeartbeat) {
|
||||
this.transportHeartbeat.send({ type: "PING" });
|
||||
}
|
||||
});
|
||||
|
||||
this.launchRequestId = this.sendReceiverMessage({
|
||||
type: "LAUNCH"
|
||||
, appId: this.appId
|
||||
});
|
||||
}
|
||||
}
|
||||
114
app/src/bridge/components/cast/index.ts
Normal file
114
app/src/bridge/components/cast/index.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
|
||||
import { sendMessage } from "../../lib/nativeMessaging";
|
||||
import { Message } from "../../messaging";
|
||||
|
||||
import Session from "./Session";
|
||||
|
||||
|
||||
const sessions = new Map<string, Session>();
|
||||
|
||||
export function handleCastMessage(message: Message) {
|
||||
switch (message.subject) {
|
||||
case "bridge:createCastSession": {
|
||||
const { appId, receiverDevice } = message.data;
|
||||
|
||||
// Connect and store with returned ID
|
||||
const session = new Session(appId, receiverDevice);
|
||||
session.connect(
|
||||
receiverDevice.host, receiverDevice.port, sessionId => {
|
||||
sessions.set(sessionId, session);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "bridge:sendCastReceiverMessage": {
|
||||
const { sessionId, messageData, messageId } = message.data;
|
||||
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: "Session does not exist"
|
||||
, sessionId, messageId
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
session.sendReceiverMessage(messageData);
|
||||
} catch (err) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: `Failed to send message (${err})`
|
||||
, sessionId, messageId
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Success
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: { sessionId, messageId }
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "bridge:sendCastSessionMessage": {
|
||||
const { namespace, sessionId, messageId } = message.data;
|
||||
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: "Session does not exist"
|
||||
, sessionId, messageId
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle string messages
|
||||
let { messageData } = message.data;
|
||||
if (typeof messageData === "string") {
|
||||
messageData = JSON.parse(messageData);
|
||||
}
|
||||
|
||||
session.sendMessage(namespace, messageData);
|
||||
} catch (err) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: `Failed to send message (${err})`
|
||||
, sessionId, messageId
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Success
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: { sessionId, messageId }
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "bridge:stopCastApp": {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
437
app/src/bridge/components/cast/types.ts
Normal file
437
app/src/bridge/components/cast/types.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
"use strict";
|
||||
|
||||
export interface Image {
|
||||
url: string;
|
||||
height: Nullable<number>;
|
||||
width: Nullable<number>;
|
||||
}
|
||||
|
||||
enum Capability {
|
||||
VIDEO_OUT = "video_out"
|
||||
, AUDIO_OUT = "audio_out"
|
||||
, VIDEO_IN = "video_in"
|
||||
, AUDIO_IN = "audio_in"
|
||||
, MULTIZONE_GROUP = "multizone_group"
|
||||
}
|
||||
|
||||
enum ReceiverType {
|
||||
CAST = "cast"
|
||||
, DIAL = "dial"
|
||||
, HANGOUT = "hangout"
|
||||
, CUSTOM = "custom"
|
||||
}
|
||||
|
||||
export interface SenderApplication {
|
||||
packageId: Nullable<string>;
|
||||
platform: string;
|
||||
url: Nullable<string>;
|
||||
}
|
||||
|
||||
enum VolumeControlType {
|
||||
ATTENUATION = "attenuation"
|
||||
, FIXED = "fixed"
|
||||
, MASTER = "master"
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
controlType?: VolumeControlType;
|
||||
stepInterval?: number;
|
||||
level: Nullable<number>;
|
||||
muted: Nullable<boolean>;
|
||||
}
|
||||
|
||||
// Media
|
||||
|
||||
enum IdleReason {
|
||||
CANCELLED = "CANCELLED"
|
||||
, INTERRUPTED = "INTERRUPTED"
|
||||
, FINISHED = "FINISHED"
|
||||
, ERROR = "ERROR"
|
||||
}
|
||||
|
||||
enum HlsSegmentFormat {
|
||||
AAC = "aac"
|
||||
, AC3 = "ac3"
|
||||
, MP3 = "mp3"
|
||||
, TS = "ts"
|
||||
, TS_AAC = "ts_aac"
|
||||
, E_AC3 = "e_ac3"
|
||||
, FMP4 = "fmp4"
|
||||
}
|
||||
|
||||
export enum HlsVideoSegmentFormat {
|
||||
MPEG2_TS = "mpeg2_ts"
|
||||
, FMP4 = "fmp4"
|
||||
}
|
||||
|
||||
enum MetadataType {
|
||||
GENERIC
|
||||
, MOVIE
|
||||
, TV_SHOW
|
||||
, MUSIC_TRACK
|
||||
, PHOTO
|
||||
, AUDIOBOOK_CHAPTER
|
||||
}
|
||||
|
||||
enum PlayerState {
|
||||
IDLE = "IDLE"
|
||||
, PLAYING = "PLAYING"
|
||||
, PAUSED = "PAUSED"
|
||||
, BUFFERING = "BUFFERING"
|
||||
}
|
||||
|
||||
enum RepeatMode {
|
||||
OFF = "REPEAT_OFF"
|
||||
, ALL = "REPEAT_ALL"
|
||||
, SINGLE = "REPEAT_SINGLE"
|
||||
, ALL_AND_SHUFFLE = "REPEAT_ALL_AND_SHUFFLE"
|
||||
}
|
||||
|
||||
enum ResumeState {
|
||||
PLAYBACK_START = "PLAYBACK_START"
|
||||
, PLAYBACK_PAUSE = "PLAYBACK_PAUSE"
|
||||
}
|
||||
|
||||
enum StreamType {
|
||||
BUFFERED = "BUFFERED"
|
||||
, LIVE = "LIVE"
|
||||
, OTHER = "OTHER"
|
||||
}
|
||||
|
||||
enum TrackType {
|
||||
TEXT = "TEXT"
|
||||
, AUDIO = "AUDIO"
|
||||
, VIDEO = "VIDEO"
|
||||
}
|
||||
|
||||
export enum UserAction {
|
||||
LIKE = "LIKE"
|
||||
, DISLIKE = "DISLIKE"
|
||||
, FOLLOW = "FOLLOW"
|
||||
, UNFOLLOW = "UNFOLLOW"
|
||||
}
|
||||
|
||||
|
||||
interface Break {
|
||||
breakClipIds: string[];
|
||||
duration?: number;
|
||||
id: string;
|
||||
isEmbedded?: boolean;
|
||||
isWatched: boolean;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface BreakClip {
|
||||
clickThroughUrl?: string;
|
||||
contentId?: string;
|
||||
contentType?: string;
|
||||
contentUrl?: string;
|
||||
customData?: {};
|
||||
duration?: number;
|
||||
id: string;
|
||||
hlsSegmentFormat?: HlsSegmentFormat;
|
||||
posterUrl?: string;
|
||||
title?: string;
|
||||
vastAdsRequest?: VastAdsRequest;
|
||||
whenSkippable?: number;
|
||||
}
|
||||
|
||||
interface TextTrackStyle {
|
||||
backgroundColor: Nullable<string>;
|
||||
customData: any;
|
||||
edgeColor: Nullable<string>;
|
||||
edgeType: Nullable<string>;
|
||||
fontFamily: Nullable<string>;
|
||||
fontGenericFamily: Nullable<string>;
|
||||
fontScale: Nullable<number>;
|
||||
fontStyle: Nullable<string>;
|
||||
foregroundColor: Nullable<string>;
|
||||
windowColor: Nullable<string>;
|
||||
windowRoundedCornerRadius: Nullable<number>;
|
||||
windowType: Nullable<string>;
|
||||
}
|
||||
|
||||
interface Track {
|
||||
customData: any;
|
||||
language: Nullable<string>;
|
||||
name: Nullable<string>;
|
||||
subtype: Nullable<string>;
|
||||
trackContentId: Nullable<string>;
|
||||
trackContentType: Nullable<string>;
|
||||
trackId: string;
|
||||
type: TrackType;
|
||||
}
|
||||
|
||||
interface UserActionState {
|
||||
customData: any;
|
||||
userAction: UserAction;
|
||||
}
|
||||
|
||||
interface VastAdsRequest {
|
||||
adsResponse?: string;
|
||||
adTagUrl?: string;
|
||||
}
|
||||
|
||||
type Metadata =
|
||||
GenericMediaMetadata
|
||||
| MovieMediaMetadata
|
||||
| MusicTrackMediaMetadata
|
||||
| PhotoMediaMetadata
|
||||
| TvShowMediaMetadata;
|
||||
|
||||
interface MediaInformation {
|
||||
atvEntity?: string;
|
||||
breakClips?: BreakClip[];
|
||||
breaks?: Break[];
|
||||
contentId: string;
|
||||
contentType: string;
|
||||
contentUrl?: string;
|
||||
customData: any;
|
||||
duration: Nullable<number>;
|
||||
entity?: string;
|
||||
hlsSegmentFormat?: HlsSegmentFormat;
|
||||
hlsVideoSegmentFormat?: HlsVideoSegmentFormat;
|
||||
metadata: Nullable<Metadata>;
|
||||
startAbsoluteTime?: number;
|
||||
streamType: StreamType;
|
||||
textTrackStyle: Nullable<TextTrackStyle>;
|
||||
tracks: Nullable<Track[]>;
|
||||
userActionStates?: UserActionState[];
|
||||
vmapAdsRequest?: VastAdsRequest;
|
||||
}
|
||||
|
||||
interface GenericMediaMetadata {
|
||||
images?: Image[];
|
||||
metadataType: number;
|
||||
releaseDate?: string;
|
||||
releaseYear?: number;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
type: MetadataType.GENERIC;
|
||||
}
|
||||
|
||||
interface MovieMediaMetadata {
|
||||
images?: Image[];
|
||||
metadataType: number;
|
||||
releaseDate?: string;
|
||||
releaseYear?: number;
|
||||
studio?: string;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
type: MetadataType.MOVIE;
|
||||
}
|
||||
|
||||
interface TvShowMediaMetadata {
|
||||
episode?: number;
|
||||
episodeNumber?: number;
|
||||
episodeTitle?: string;
|
||||
images?: Image[];
|
||||
metadataType: number;
|
||||
originalAirdate?: string;
|
||||
releaseYear?: number;
|
||||
season?: number;
|
||||
seasonNumber?: number;
|
||||
seriesTitle?: string;
|
||||
title?: string;
|
||||
type: MetadataType.TV_SHOW;
|
||||
}
|
||||
|
||||
interface MusicTrackMediaMetadata {
|
||||
albumArtist?: string;
|
||||
albumName?: string;
|
||||
artist?: string;
|
||||
artistName?: string;
|
||||
composer?: string;
|
||||
discNumber?: number;
|
||||
images?: Image[];
|
||||
metadataType: number;
|
||||
releaseDate?: string;
|
||||
releaseYear?: number;
|
||||
songName?: string;
|
||||
title?: string;
|
||||
trackNumber?: number;
|
||||
type: MetadataType.MUSIC_TRACK;
|
||||
}
|
||||
|
||||
interface PhotoMediaMetadata {
|
||||
artist?: string;
|
||||
creationDateTime?: string;
|
||||
height?: number;
|
||||
images?: Image[];
|
||||
latitude?: number;
|
||||
location?: string;
|
||||
longitude?: number;
|
||||
metadataType: number;
|
||||
title?: string;
|
||||
type: MetadataType.PHOTO;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface QueueItem {
|
||||
activeTrackIds: Nullable<number[]>;
|
||||
autoplay: boolean;
|
||||
customData: any;
|
||||
itemId: Nullable<number>;
|
||||
media: MediaInformation;
|
||||
playbackDuration: Nullable<number>;
|
||||
preloadTime: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
export interface MediaStatus {
|
||||
mediaSessionId: number;
|
||||
media?: MediaInformation;
|
||||
playbackRate: number;
|
||||
playerState: PlayerState;
|
||||
idleReason?: IdleReason;
|
||||
items?: QueueItem[]
|
||||
currentTime: number;
|
||||
supportedMediaCommands: number;
|
||||
repeatMode: RepeatMode;
|
||||
volume: Volume
|
||||
customData: unknown;
|
||||
}
|
||||
|
||||
interface ReceiverDisplayStatus {
|
||||
showStop: Nullable<boolean>;
|
||||
statusText: string;
|
||||
appImages: Image[];
|
||||
}
|
||||
|
||||
export interface Receiver {
|
||||
displayStatus: Nullable<ReceiverDisplayStatus>;
|
||||
isActiveInput: Nullable<boolean>;
|
||||
receiverType: ReceiverType;
|
||||
label: string;
|
||||
friendlyName: string;
|
||||
capabilities: Capability[];
|
||||
volume: Nullable<Volume>;
|
||||
}
|
||||
|
||||
export interface ReceiverApplication {
|
||||
appId: string;
|
||||
appType: string;
|
||||
displayName: string;
|
||||
iconUrl: string;
|
||||
isIdleScreen: boolean;
|
||||
launchedFromCloud: boolean;
|
||||
namespaces: Array<{ name: string }>;
|
||||
sessionId: string;
|
||||
statusText: string;
|
||||
transportId: string;
|
||||
universalAppId: string;
|
||||
}
|
||||
|
||||
export interface ReceiverStatus {
|
||||
applications?: ReceiverApplication[];
|
||||
isActiveInput?: boolean;
|
||||
isStandBy?: boolean;
|
||||
volume: Volume;
|
||||
}
|
||||
|
||||
|
||||
interface ReqBase {
|
||||
requestId: number;
|
||||
}
|
||||
|
||||
// NS: urn:x-cast:com.google.cast.receiver
|
||||
export type SenderMessage =
|
||||
ReqBase & { type: "LAUNCH", appId: string }
|
||||
| ReqBase & { type: "STOP", sessionId: string }
|
||||
| ReqBase & { type: "GET_STATUS" }
|
||||
| ReqBase & { type: "GET_APP_AVAILABILITY", appId: string[] }
|
||||
| ReqBase & { type: "SET_VOLUME", volume: Volume };
|
||||
|
||||
export type ReceiverMessage =
|
||||
ReqBase & { type: "RECEIVER_STATUS", status: ReceiverStatus }
|
||||
| ReqBase & { type: "LAUNCH_ERROR", reason: string }
|
||||
|
||||
|
||||
interface MediaReqBase extends ReqBase {
|
||||
mediaSessionId: number;
|
||||
customData?: unknown;
|
||||
}
|
||||
|
||||
// NS: urn:x-cast:com.google.cast.media
|
||||
export type SenderMediaMessage =
|
||||
| MediaReqBase & { type: "PLAY" }
|
||||
| MediaReqBase & { type: "PAUSE" }
|
||||
| MediaReqBase & { type: "MEDIA_GET_STATUS" }
|
||||
| MediaReqBase & { type: "STOP" }
|
||||
| MediaReqBase & { type: "MEDIA_SET_VOLUME", volume: Volume }
|
||||
| MediaReqBase & { type: "SET_PLAYBACK_RATE" , playbackRate: number }
|
||||
| ReqBase & {
|
||||
type: "LOAD"
|
||||
, activeTrackIds: Nullable<number[]>
|
||||
, atvCredentials?: string
|
||||
, atvCredentialsType?: string
|
||||
, autoplay: Nullable<boolean>
|
||||
, currentTime: Nullable<number>
|
||||
, customData?: unknown
|
||||
, media: MediaInformation
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
| MediaReqBase & {
|
||||
type: "SEEK"
|
||||
, resumeState: Nullable<ResumeState>
|
||||
, currentTime: Nullable<number>
|
||||
}
|
||||
| MediaReqBase & {
|
||||
type: "EDIT_TRACKS_INFO"
|
||||
, activeTrackIds: Nullable<number[]>
|
||||
, textTrackStyle: Nullable<string>
|
||||
}
|
||||
// QueueLoadRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_LOAD"
|
||||
, items: QueueItem[]
|
||||
, startIndex: number
|
||||
, repeatMode: string
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueInsertItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_INSERT"
|
||||
, items: QueueItem[]
|
||||
, insertBefore: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueUpdateItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, items: QueueItem[]
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueJumpRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, jump: Nullable<number>
|
||||
, currentItemId: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueRemoveItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_REMOVE"
|
||||
, itemIds: number[]
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueReorderItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_REORDER"
|
||||
, itemIds: number[]
|
||||
, insertBefore: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueSetPropertiesRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, repeatMode: Nullable<string>
|
||||
, sessionId: Nullable<string>
|
||||
};
|
||||
|
||||
export type ReceiverMediaMessage =
|
||||
MediaReqBase & { type: "MEDIA_STATUS", status: MediaStatus[] }
|
||||
| MediaReqBase & { type: "INVALID_PLAYER_STATE" }
|
||||
| MediaReqBase & { type: "LOAD_FAILED" }
|
||||
| MediaReqBase & { type: "LOAD_CANCELLED" }
|
||||
| MediaReqBase & { type: "INVALID_REQUEST" };
|
||||
Reference in New Issue
Block a user