"use strict"; import { v4 as uuid } from "uuid"; import logger from "../../../lib/logger"; import _Error from "./Error"; import Image from "./Image"; import Receiver from "./Receiver"; import SenderApplication from "./SenderApplication"; import Volume from "./Volume"; import LoadRequest from "../media/classes/LoadRequest"; import Media from "../media/classes/Media"; import QueueLoadRequest from "../media/classes/QueueLoadRequest"; import { ErrorCode , SessionStatus } from "../enums"; import { RepeatMode } from "../media/enums"; import { ListenerObject , onMessage , sendMessageResponse } from "../../eventMessageChannel"; import { Callbacks , ErrorCallback , LoadSuccessCallback , MediaListener , MessageListener , SuccessCallback , UpdateListener } from "../../types"; type SessionSuccessCallback = (session: Session) => void; export default class Session { #id = uuid(); #successCallback?: SessionSuccessCallback; #messageListeners = new Map>(); #updateListeners = new Set(); #leaveCallbacks = new Map(); #sendMessageCallbacks = new Map(); #setReceiverMutedCallbacks = new Map(); #setReceiverVolumeLevelCallbacks = new Map(); #stopCallbacks = new Map(); #listener = onMessage(message => { // Filter other session messages if ((message as any).data._id !== this.#id) { return; } switch (message.subject) { case "shim:session/stopped": { // Disconnect from extension messages this.#listener.disconnect(); this.status = SessionStatus.STOPPED; for (const listener of this.#updateListeners) { listener(false); } break; } case "shim:session/connected": { this.status = SessionStatus.CONNECTED; this.sessionId = message.data.sessionId; this.transportId = message.data.sessionId; this.namespaces = message.data.namespaces; this.displayName = message.data.displayName; this.statusText = message.data.statusText; if (this.#successCallback) { this.#successCallback(this); } break; } case "shim:session/updateStatus": { const volume: Volume = message.data.volume; if (volume) { if (!this.receiver.volume) { const receiverVolume = new Volume( volume.level , volume.muted); receiverVolume.controlType = volume.controlType; receiverVolume.stepInterval = volume.stepInterval; this.receiver.volume = receiverVolume; } else { this.receiver.volume.level = volume.level; this.receiver.volume.muted = volume.muted; } } for (const listener of this.#updateListeners) { listener(true); } break; } case "shim:session/impl_addMessageListener": { const { namespace, data } = message.data; const messageListeners = this.#messageListeners.get(namespace); if (messageListeners) { for (const listener of messageListeners) { listener(namespace, data); } } break; } case "shim:session/impl_sendMessage": { const { messageId, error } = message.data; const [ successCallback, errorCallback ] = this.#sendMessageCallbacks.get(messageId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } else if (successCallback) { successCallback(); } this.#sendMessageCallbacks.delete(messageId); break; } case "shim:session/impl_setReceiverMuted": { const { volumeId, error } = message.data; const [ successCallback, errorCallback ] = this.#setReceiverMutedCallbacks.get(volumeId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } else if (successCallback) { successCallback(); } this.#setReceiverMutedCallbacks.delete(volumeId); break; } case "shim:session/impl_setReceiverVolumeLevel": { const { volumeId, error } = message.data; const [ successCallback, errorCallback ] = this.#setReceiverVolumeLevelCallbacks .get(volumeId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } else if (successCallback) { successCallback(); } this.#setReceiverVolumeLevelCallbacks.delete(volumeId); break; } case "shim:session/impl_stop": { const { stopId, error } = message.data; const [ successCallback, errorCallback ] = this.#stopCallbacks.get(stopId) ?? []; // Disconnect from extension messages this.#listener.disconnect(); if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } else { this.status = SessionStatus.STOPPED; for (const listener of this.#updateListeners) { listener(false); } if (successCallback) { successCallback(); } } this.#stopCallbacks.delete(stopId); break; } } }) public media: Media[]; public namespaces: Array<{ name: string }>; public senderApps: SenderApplication[]; public status: SessionStatus; public statusText: string | null; public transportId: string; constructor( public sessionId: string , public appId: string , public displayName: string , public appImages: Image[] , public receiver: Receiver , _successCallback: SessionSuccessCallback) { this.#successCallback = _successCallback; this.media = []; this.namespaces = []; this.senderApps = []; this.status = SessionStatus.CONNECTED; this.statusText = null; this.transportId = sessionId || ""; if (receiver) { sendMessageResponse({ subject: "bridge:session/initialize" , data: { address: (receiver as any)._address , port: (receiver as any)._port , appId , sessionId , _id: this.#id } }); } } public addMediaListener(_mediaListener: MediaListener) { logger.info("STUB :: Session#addMediaListener"); } public addMessageListener( namespace: string , listener: MessageListener) { if (!this.#messageListeners.has(namespace)) { this.#messageListeners.set(namespace, new Set()); } this.#messageListeners.get(namespace)?.add(listener); sendMessageResponse({ subject: "bridge:session/impl_addMessageListener" , data: { namespace , _id: this.#id } }); } public addUpdateListener(listener: UpdateListener) { this.#updateListeners.add(listener); } public leave( successCallback?: SuccessCallback , errorCallback?: ErrorCallback): void { const id = uuid(); sendMessageResponse({ subject: "bridge:session/impl_leave" , data: { id , _id: this.#id } }); this.#leaveCallbacks.set(id, [ successCallback , errorCallback ]); } public loadMedia( loadRequest: LoadRequest , successCallback?: LoadSuccessCallback , errorCallback?: ErrorCallback): void { this._sendMediaMessage({ type: "LOAD" , requestId: 0 , media: loadRequest.media , activeTrackIds: loadRequest.activeTrackIds || [] , autoplay: loadRequest.autoplay || false , currentTime: loadRequest.currentTime || 0 , customData: loadRequest.customData || {} , repeatMode: RepeatMode.OFF }); let hasResponded = false; this.addMessageListener( "urn:x-cast:com.google.cast.media" , (_namespace, data) => { if (hasResponded) { return; } const message = JSON.parse(data); if (message.status && message.status.length > 0) { const sessionId = this.#id; if (!sessionId) { return; } hasResponded = true; const media = new Media( this.sessionId , message.status[0].mediaSessionId , sessionId); media.media = loadRequest.media; this.media = [ media ]; media.play(); if (successCallback) { successCallback(media); } } else { if (errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } } }); } public queueLoad( _queueLoadRequest: QueueLoadRequest , _successCallback?: LoadSuccessCallback , _errorCallback?: ErrorCallback): void { logger.info("STUB :: Session#queueLoad"); } public removeMediaListener(_mediaListener: MediaListener): void { logger.info("STUB :: Session#removeMediaListener"); } public removeMessageListener( namespace: string , listener: MessageListener): void { this.#messageListeners.get(namespace)?.delete(listener); } public removeUpdateListener( _namespace: string , listener: UpdateListener): void { this.#updateListeners.delete(listener); } public sendMessage( namespace: string , message: {} | string , successCallback?: SuccessCallback , errorCallback?: ErrorCallback): void { const messageId = uuid(); sendMessageResponse({ subject: "bridge:session/impl_sendMessage" , data: { namespace , message , messageId , _id: this.#id } }); this.#sendMessageCallbacks.set(messageId, [ successCallback , errorCallback ]); } public setReceiverMuted( muted: boolean , successCallback?: SuccessCallback , errorCallback?: ErrorCallback) { const volumeId = uuid(); sendMessageResponse({ subject: "bridge:session/impl_setReceiverMuted" , data: { muted , volumeId , _id: this.#id } }); this.#setReceiverMutedCallbacks.set(volumeId, [ successCallback , errorCallback ]); } public setReceiverVolumeLevel( newLevel: number , successCallback?: SuccessCallback , errorCallback?: ErrorCallback): void { const volumeId = uuid(); sendMessageResponse({ subject: "bridge:session/impl_setReceiverVolumeLevel" , data: { newLevel , volumeId , _id: this.#id } }); this.#setReceiverVolumeLevelCallbacks.set(volumeId, [ successCallback , errorCallback ]); } public stop( successCallback?: SuccessCallback , errorCallback?: ErrorCallback): void { const stopId = uuid(); sendMessageResponse({ subject: "bridge:session/impl_stop" , data: { stopId , _id: this.#id } }); this.#stopCallbacks.set(stopId, [ successCallback , errorCallback ]); } private _sendMediaMessage(message: string | {}) { this.sendMessage("urn:x-cast:com.google.cast.media", message); } }