mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Minor media session tweaks
This commit is contained in:
@@ -23,8 +23,8 @@ import {
|
||||
import type { LoadRequest, QueueLoadRequest, QueueItem } from "./media/classes";
|
||||
import Media, {
|
||||
createMedia,
|
||||
MediaLastUpdateTimes,
|
||||
MediaUpdateListeners,
|
||||
mediaLastUpdateTimes,
|
||||
mediaUpdateListeners,
|
||||
NS_MEDIA
|
||||
} from "./media/Media";
|
||||
|
||||
@@ -36,10 +36,11 @@ const logger = new Logger("fx_cast [sdk :: cast.Session]");
|
||||
*/
|
||||
export function updateMedia(media: Media, status: MediaStatus) {
|
||||
if (status.currentTime) {
|
||||
MediaLastUpdateTimes.set(media, Date.now());
|
||||
mediaLastUpdateTimes.set(media, Date.now());
|
||||
}
|
||||
|
||||
// Copy basic props
|
||||
if (status.breakStatus) media.breakStatus = status.breakStatus;
|
||||
if (status.currentTime) media.currentTime = status.currentTime;
|
||||
if (status.customData) media.customData = status.customData;
|
||||
if (status.idleReason) media.idleReason = status.idleReason;
|
||||
@@ -87,20 +88,20 @@ export function updateMedia(media: Media, status: MediaStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
export const SessionMessageListeners = new WeakMap<
|
||||
export const sessionMessageListeners = new WeakMap<
|
||||
Session,
|
||||
Map<string, Set<MessageListener>>
|
||||
>();
|
||||
export const SessionUpdateListeners = new WeakMap<
|
||||
export const sessionUpdateListeners = new WeakMap<
|
||||
Session,
|
||||
Set<UpdateListener>
|
||||
>();
|
||||
export const SessionSendMessageCallbacks = new WeakMap<
|
||||
export const sessionSendMessageCallbacks = new WeakMap<
|
||||
Session,
|
||||
Map<string, SendMessageCallback>
|
||||
>();
|
||||
|
||||
export const SessionLeaveSuccessCallback = new WeakMap<
|
||||
export const sessionLeaveSuccessCallback = new WeakMap<
|
||||
Session,
|
||||
Optional<() => void>
|
||||
>();
|
||||
@@ -108,24 +109,58 @@ export const SessionLeaveSuccessCallback = new WeakMap<
|
||||
type SendMediaMessage = (
|
||||
message: DistributiveOmit<SenderMediaMessage, "requestId">
|
||||
) => Promise<void>;
|
||||
export const SessionSendMediaMessage = new WeakMap<Session, SendMediaMessage>();
|
||||
export const sessionSendMediaMessage = new WeakMap<Session, SendMediaMessage>();
|
||||
|
||||
interface MediaRequest {
|
||||
successCallback: () => void;
|
||||
errorCallback: (error: CastError) => void;
|
||||
message: SenderMediaMessage;
|
||||
requestId: number;
|
||||
}
|
||||
|
||||
const sessionMediaRequests = new WeakMap<Session, Map<number, MediaRequest>>();
|
||||
|
||||
/** Creates a Session object and initializes private data. */
|
||||
export function createSession(
|
||||
sessionArgs: ConstructorParameters<typeof Session>
|
||||
) {
|
||||
const session = new Session(...sessionArgs);
|
||||
SessionUpdateListeners.set(session, new Set());
|
||||
SessionSendMessageCallbacks.set(session, new Map());
|
||||
sessionUpdateListeners.set(session, new Set());
|
||||
sessionSendMessageCallbacks.set(session, new Map());
|
||||
|
||||
SessionSendMediaMessage.set(session, message => {
|
||||
// Record of pending media requests
|
||||
// FIXME: Handle request timeouts
|
||||
const mediaRequests = new Map<number, MediaRequest>();
|
||||
sessionMediaRequests.set(session, mediaRequests);
|
||||
|
||||
// Current media request ID
|
||||
let mediaRequestId = 1;
|
||||
|
||||
/**
|
||||
* Stores callbacks for request response, then adds current request
|
||||
* ID to the message and sends it.
|
||||
*/
|
||||
sessionSendMediaMessage.set(session, message => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
session.sendMessage(
|
||||
NS_MEDIA,
|
||||
{ ...message, requestId: 0 },
|
||||
resolve,
|
||||
reject
|
||||
);
|
||||
const requestId = mediaRequestId++;
|
||||
const request: MediaRequest = {
|
||||
successCallback: () => {
|
||||
mediaRequests.delete(requestId);
|
||||
resolve();
|
||||
},
|
||||
errorCallback: () => {
|
||||
mediaRequests.delete(requestId);
|
||||
reject();
|
||||
},
|
||||
message: { ...message, requestId },
|
||||
requestId
|
||||
};
|
||||
|
||||
mediaRequests.set(request.requestId, request);
|
||||
session.sendMessage(NS_MEDIA, request.message, undefined, () => {
|
||||
mediaRequests.delete(requestId);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,36 +177,43 @@ export default class Session {
|
||||
#loadMediaErrorCallback?: (err: CastError) => void;
|
||||
|
||||
get #messageListeners() {
|
||||
const messageListeners = SessionMessageListeners.get(this);
|
||||
const messageListeners = sessionMessageListeners.get(this);
|
||||
if (!messageListeners)
|
||||
throw logger.error("Missing session message listeners!");
|
||||
return messageListeners;
|
||||
}
|
||||
get #updateListeners() {
|
||||
const updateListeners = SessionUpdateListeners.get(this);
|
||||
const updateListeners = sessionUpdateListeners.get(this);
|
||||
if (!updateListeners)
|
||||
throw logger.error("Missing session update listeners!");
|
||||
return updateListeners;
|
||||
}
|
||||
get #sendMessageCallbacks() {
|
||||
const sendMessageCallback = SessionSendMessageCallbacks.get(this);
|
||||
const sendMessageCallback = sessionSendMessageCallbacks.get(this);
|
||||
if (!sendMessageCallback)
|
||||
throw logger.error("Missing session sendMessage callback!");
|
||||
return sendMessageCallback;
|
||||
}
|
||||
|
||||
get #sendMediaMessage() {
|
||||
const sendMediaMessage = SessionSendMediaMessage.get(this);
|
||||
const sendMediaMessage = sessionSendMediaMessage.get(this);
|
||||
if (!sendMediaMessage)
|
||||
throw logger.error("Missing send media message function!");
|
||||
return sendMediaMessage;
|
||||
}
|
||||
|
||||
get #mediaRequests() {
|
||||
const mediaRequests = sessionMediaRequests.get(this);
|
||||
if (!mediaRequests)
|
||||
throw logger.error("Missing session media requests!");
|
||||
return mediaRequests;
|
||||
}
|
||||
|
||||
get #leaveSuccessCallback() {
|
||||
return SessionLeaveSuccessCallback.get(this);
|
||||
return sessionLeaveSuccessCallback.get(this);
|
||||
}
|
||||
set #leaveSuccessCallback(successCallback: Optional<() => void>) {
|
||||
SessionLeaveSuccessCallback.set(this, successCallback);
|
||||
sessionLeaveSuccessCallback.set(this, successCallback);
|
||||
}
|
||||
|
||||
media: Media[] = [];
|
||||
@@ -190,42 +232,48 @@ export default class Session {
|
||||
) {
|
||||
this.transportId = sessionId || "";
|
||||
|
||||
SessionMessageListeners.set(this, new Map());
|
||||
sessionMessageListeners.set(this, new Map());
|
||||
this.addMessageListener(NS_MEDIA, this.#mediaMessageListener);
|
||||
}
|
||||
|
||||
#mediaMessageListener = (namespace: string, messageString: string) => {
|
||||
if (namespace !== NS_MEDIA) return;
|
||||
|
||||
const message: ReceiverMediaMessage = JSON.parse(messageString);
|
||||
switch (message.type) {
|
||||
case "MEDIA_STATUS": {
|
||||
// Update media
|
||||
for (const mediaStatus of message.status) {
|
||||
let media = this.media.find(
|
||||
media =>
|
||||
media.mediaSessionId === mediaStatus.mediaSessionId
|
||||
);
|
||||
if (message.type !== "MEDIA_STATUS") return;
|
||||
|
||||
// Handle Media creation
|
||||
if (!media) {
|
||||
media = createMedia(
|
||||
[this.sessionId, mediaStatus.mediaSessionId],
|
||||
this.#sendMediaMessage
|
||||
);
|
||||
for (const status of message.status) {
|
||||
let media = this.media.find(
|
||||
media => media.mediaSessionId === status.mediaSessionId
|
||||
);
|
||||
|
||||
this.media.push(media);
|
||||
updateMedia(media, mediaStatus);
|
||||
this.#loadMediaSuccessCallback?.(media);
|
||||
} else {
|
||||
updateMedia(media, mediaStatus);
|
||||
const updateListeners = MediaUpdateListeners.get(media);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!media) {
|
||||
media = createMedia(
|
||||
[this.sessionId, status.mediaSessionId],
|
||||
this.#sendMediaMessage
|
||||
);
|
||||
updateMedia(media, status);
|
||||
this.media.push(media);
|
||||
} else {
|
||||
updateMedia(media, status);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle media request responses
|
||||
const mediaRequest = this.#mediaRequests.get(message.requestId);
|
||||
if (mediaRequest) {
|
||||
mediaRequest.successCallback();
|
||||
}
|
||||
|
||||
for (const status of message.status) {
|
||||
const media = this.media.find(
|
||||
media => media.mediaSessionId === status.mediaSessionId
|
||||
);
|
||||
if (!media) continue;
|
||||
|
||||
const updateListeners = mediaUpdateListeners.get(media);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +355,11 @@ export default class Session {
|
||||
this.#loadMediaErrorCallback = errorCallback;
|
||||
|
||||
loadRequest.sessionId = this.sessionId;
|
||||
this.#sendMediaMessage(loadRequest).catch(errorCallback);
|
||||
this.#sendMediaMessage(loadRequest)
|
||||
.then(() => {
|
||||
successCallback?.(this.media[this.media.length - 1]);
|
||||
})
|
||||
.catch(errorCallback);
|
||||
}
|
||||
|
||||
queueLoad(
|
||||
|
||||
@@ -33,11 +33,11 @@ import {
|
||||
|
||||
import Session, {
|
||||
createSession,
|
||||
SessionLeaveSuccessCallback,
|
||||
SessionMessageListeners,
|
||||
SessionSendMediaMessage,
|
||||
SessionSendMessageCallbacks,
|
||||
SessionUpdateListeners,
|
||||
sessionLeaveSuccessCallback,
|
||||
sessionMessageListeners,
|
||||
sessionSendMediaMessage,
|
||||
sessionSendMessageCallbacks,
|
||||
sessionUpdateListeners,
|
||||
updateMedia
|
||||
} from "./Session";
|
||||
|
||||
@@ -186,7 +186,7 @@ export default class {
|
||||
const media = createMedia(
|
||||
[status.sessionId, status.media.mediaSessionId],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
SessionSendMediaMessage.get(session)!
|
||||
sessionSendMediaMessage.get(session)!
|
||||
);
|
||||
updateMedia(media, status.media);
|
||||
session.media = [media];
|
||||
@@ -223,7 +223,7 @@ export default class {
|
||||
session.namespaces = status.namespaces;
|
||||
session.receiver.volume = status.volume;
|
||||
|
||||
const updateListeners = SessionUpdateListeners.get(session);
|
||||
const updateListeners = sessionUpdateListeners.get(session);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(session.status !== SessionStatus.STOPPED);
|
||||
@@ -238,7 +238,7 @@ export default class {
|
||||
if (session?.status === SessionStatus.CONNECTED) {
|
||||
session.status = SessionStatus.STOPPED;
|
||||
|
||||
const updateListeners = SessionUpdateListeners.get(session);
|
||||
const updateListeners = sessionUpdateListeners.get(session);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(false);
|
||||
@@ -254,9 +254,9 @@ export default class {
|
||||
if (session?.status === SessionStatus.CONNECTED) {
|
||||
session.status = SessionStatus.DISCONNECTED;
|
||||
|
||||
SessionLeaveSuccessCallback.get(session)?.();
|
||||
sessionLeaveSuccessCallback.get(session)?.();
|
||||
|
||||
const updateListeners = SessionUpdateListeners.get(session);
|
||||
const updateListeners = sessionUpdateListeners.get(session);
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(true);
|
||||
@@ -271,8 +271,9 @@ export default class {
|
||||
const { sessionId, namespace, messageData } = message.data;
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session) {
|
||||
const listeners =
|
||||
SessionMessageListeners.get(session)?.get(namespace);
|
||||
const listeners = sessionMessageListeners
|
||||
.get(session)
|
||||
?.get(namespace);
|
||||
if (listeners) {
|
||||
for (const listener of listeners) {
|
||||
listener(namespace, messageData);
|
||||
@@ -291,8 +292,9 @@ export default class {
|
||||
break;
|
||||
}
|
||||
|
||||
const sendMessageCallback =
|
||||
SessionSendMessageCallbacks.get(session)?.get(messageId);
|
||||
const sendMessageCallback = sessionSendMessageCallbacks
|
||||
.get(session)
|
||||
?.get(messageId);
|
||||
if (sendMessageCallback) {
|
||||
const [successCallback, errorCallback] =
|
||||
sendMessageCallback;
|
||||
|
||||
@@ -38,9 +38,9 @@ type MediaMessageCallback = (
|
||||
message: DistributiveOmit<SenderMediaMessage, "requestId">
|
||||
) => Promise<void>;
|
||||
|
||||
const MediaMessageCallbacks = new WeakMap<Media, MediaMessageCallback>();
|
||||
export const MediaUpdateListeners = new WeakMap<Media, Set<UpdateListener>>();
|
||||
export const MediaLastUpdateTimes = new WeakMap<Media, number>();
|
||||
const mediaMessageCallbacks = new WeakMap<Media, MediaMessageCallback>();
|
||||
export const mediaUpdateListeners = new WeakMap<Media, Set<UpdateListener>>();
|
||||
export const mediaLastUpdateTimes = new WeakMap<Media, number>();
|
||||
|
||||
/** Creates a Media object and initializes private data. */
|
||||
export function createMedia(
|
||||
@@ -48,9 +48,9 @@ export function createMedia(
|
||||
mediaMessageCallback: MediaMessageCallback
|
||||
) {
|
||||
const media = new Media(...mediaArgs);
|
||||
MediaMessageCallbacks.set(media, mediaMessageCallback);
|
||||
MediaUpdateListeners.set(media, new Set());
|
||||
MediaLastUpdateTimes.set(media, 0);
|
||||
mediaMessageCallbacks.set(media, mediaMessageCallback);
|
||||
mediaUpdateListeners.set(media, new Set());
|
||||
mediaLastUpdateTimes.set(media, 0);
|
||||
|
||||
return media;
|
||||
}
|
||||
@@ -61,18 +61,18 @@ export default class Media {
|
||||
#id = uuid();
|
||||
|
||||
get #updateListeners() {
|
||||
const updateListeners = MediaUpdateListeners.get(this);
|
||||
const updateListeners = mediaUpdateListeners.get(this);
|
||||
if (!updateListeners)
|
||||
throw logger.error("Missing media update listeners!");
|
||||
return updateListeners;
|
||||
}
|
||||
get #mediaMessageCallback() {
|
||||
const callback = MediaMessageCallbacks.get(this);
|
||||
const callback = mediaMessageCallbacks.get(this);
|
||||
if (!callback) throw logger.error("Missing media message callback!");
|
||||
return callback;
|
||||
}
|
||||
get #lastUpdateTime() {
|
||||
const lastUpdateTime = MediaLastUpdateTimes.get(this);
|
||||
const lastUpdateTime = mediaLastUpdateTimes.get(this);
|
||||
if (lastUpdateTime === undefined)
|
||||
throw logger.error("Missing last update time!");
|
||||
return lastUpdateTime;
|
||||
@@ -127,18 +127,15 @@ export default class Media {
|
||||
* information reported by the receiver.
|
||||
*/
|
||||
getEstimatedBreakClipTime() {
|
||||
if (!this.breakStatus?.currentBreakClipTime) return;
|
||||
if (this.breakStatus?.currentBreakClipTime === undefined) return;
|
||||
if (this.playerState === PlayerState.PLAYING) {
|
||||
return getEstimatedTime({
|
||||
currentTime: this.breakStatus.currentBreakClipTime,
|
||||
lastUpdateTime: this.#lastUpdateTime
|
||||
});
|
||||
}
|
||||
|
||||
const currentBreakClip = this.media?.breakClips?.find(
|
||||
breakClip => breakClip.id === this.breakStatus?.breakClipId
|
||||
);
|
||||
if (!currentBreakClip) return;
|
||||
|
||||
return getEstimatedTime({
|
||||
currentTime: this.breakStatus.currentBreakClipTime,
|
||||
lastUpdateTime: this.#lastUpdateTime,
|
||||
duration: currentBreakClip.duration
|
||||
});
|
||||
return this.breakStatus.currentBreakClipTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,18 +143,15 @@ export default class Media {
|
||||
* information reported by the receiver.
|
||||
*/
|
||||
getEstimatedBreakTime() {
|
||||
if (!this.breakStatus?.currentBreakTime) return;
|
||||
if (this.breakStatus?.currentBreakTime === undefined) return;
|
||||
if (this.playerState === PlayerState.PLAYING) {
|
||||
return getEstimatedTime({
|
||||
currentTime: this.breakStatus.currentBreakTime,
|
||||
lastUpdateTime: this.#lastUpdateTime
|
||||
});
|
||||
}
|
||||
|
||||
const currentBreak = this.media?.breaks?.find(
|
||||
break_ => break_.id === this.breakStatus?.breakId
|
||||
);
|
||||
if (!currentBreak) return;
|
||||
|
||||
return getEstimatedTime({
|
||||
currentTime: this.breakStatus.currentBreakTime,
|
||||
lastUpdateTime: this.#lastUpdateTime,
|
||||
duration: currentBreak.duration
|
||||
});
|
||||
return this.breakStatus.currentBreakTime;
|
||||
}
|
||||
|
||||
getEstimatedLiveSeekableRange() {
|
||||
@@ -173,6 +167,7 @@ export default class Media {
|
||||
return getEstimatedTime({
|
||||
currentTime: this.currentTime,
|
||||
lastUpdateTime: this.#lastUpdateTime,
|
||||
playbackRate: this.playbackRate,
|
||||
duration: this.media?.duration
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
*/
|
||||
|
||||
import type { SenderApplication, Volume, Image } from "./classes";
|
||||
import type { MediaInfo, QueueItem } from "./media/classes";
|
||||
import type {
|
||||
BreakStatus,
|
||||
LiveSeekableRange,
|
||||
MediaInfo,
|
||||
QueueItem
|
||||
} from "./media/classes";
|
||||
import type {
|
||||
IdleReason,
|
||||
PlayerState,
|
||||
@@ -14,18 +19,20 @@ import type {
|
||||
|
||||
export interface MediaStatus {
|
||||
activeTrackIds?: number[];
|
||||
breakStatus?: BreakStatus;
|
||||
currentItemId?: number;
|
||||
mediaSessionId: number;
|
||||
media?: MediaInfo;
|
||||
playbackRate: number;
|
||||
playerState: PlayerState;
|
||||
currentTime: Nullable<number>;
|
||||
customData: unknown;
|
||||
idleReason?: IdleReason;
|
||||
items?: QueueItem[];
|
||||
currentTime: Nullable<number>;
|
||||
supportedMediaCommands: number;
|
||||
liveSeekableRange?: LiveSeekableRange;
|
||||
media?: MediaInfo;
|
||||
mediaSessionId: number;
|
||||
playbackRate: number;
|
||||
playerState: PlayerState;
|
||||
repeatMode?: RepeatMode;
|
||||
supportedMediaCommands: number;
|
||||
volume: Volume;
|
||||
customData: unknown;
|
||||
}
|
||||
|
||||
export interface ReceiverApplication {
|
||||
|
||||
@@ -77,11 +77,13 @@ export function convertSupportedMediaCommandsFlags(flags: _MediaCommand) {
|
||||
interface GetEstimatedTimeOpts {
|
||||
currentTime: number;
|
||||
lastUpdateTime: number;
|
||||
playbackRate?: number;
|
||||
duration?: Nullable<number>;
|
||||
}
|
||||
export function getEstimatedTime(opts: GetEstimatedTimeOpts) {
|
||||
let estimatedTime =
|
||||
opts.currentTime + (Date.now() - opts.lastUpdateTime) / 1000;
|
||||
opts.currentTime +
|
||||
(opts.playbackRate ?? 1) * ((Date.now() - opts.lastUpdateTime) / 1000);
|
||||
|
||||
// Enforce valid range
|
||||
if (estimatedTime < 0) {
|
||||
|
||||
Reference in New Issue
Block a user