mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Fix SDK initialization timing issues
This commit is contained in:
@@ -43,8 +43,8 @@ For an instance created for a page script SDK:
|
||||
1. The [`contentInitial.ts`](./ext/src/cast/contentInitial.ts) content script is run at document start and handles some compatibility issues that can't be addressed via extension APIs (like SDK scripts directly loaded from `chrome-extension://` URLs).
|
||||
2. The page loads the SDK via the usual Google-hosted `cast_sender.js` loader script.
|
||||
3. The extension intercepts this script load, injects the [`contentBridge.ts`](./ext/src/cast/contentBridge.ts) script that creates a messaging connection to the Cast Manager (via extension messaging) that registers an instance for that context, and waits for a page messaging connection to forward messages through (as described [here](#communication)). The initial request is then transparently redirected to the extension-hosted SDK page script at [`src/cast/content.ts`](./src/cast/content.ts).
|
||||
4. The SDK page script then creates the SDK objects ([`window.chrome.cast`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast)), handles loading the Framework API (if requested) and adds a page messaging listener for `cast:intitialized` events.
|
||||
5. The Cast Manager sends a `cast:initialized` message to the SDK, which then calls the app's initialization handler ([`window.__onGCastApiAvailable`](https://developers.google.com/cast/docs/web_sender/integrate#initialization)).
|
||||
4. The SDK page script then creates the SDK objects ([`window.chrome.cast`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast)), handles loading the Framework API (if requested) and adds a page messaging listener for `cast:instanceCreated` events.
|
||||
5. The Cast Manager sends a `cast:instanceCreated` message to the SDK, which then calls the sender app's entry handler ([`window.__onGCastApiAvailable`](https://developers.google.com/cast/docs/web_sender/integrate#initialization)).
|
||||
|
||||
#### Extension script
|
||||
|
||||
@@ -55,14 +55,15 @@ For an instance created for an extension script:
|
||||
Depending on the extension script context:
|
||||
- If **background**: The Cast Manager is called directly, registering a new cast instance, providing it with a port for a newly-created message channel (since extension messaging is only supported between contexts). Page messaging is hooked up such that messages from the SDK are sent to the Cast Manager through this channel and vice versa.
|
||||
- If **content/extension page**: The `contentBridge.ts` script is imported as a module, with the usual side-effects of creating a messaging connection to the Cast Manager and hooking up page messaging (as described for page script instances).
|
||||
3. Listeners are added for the `cast:initialized` message, so that the `ensureInit` function can resolve its promise and provide a Cast Manager port after initialization.
|
||||
3. Listeners are added for the `cast:instanceCreated` message, so that the `ensureInit` function can resolve its promise and provide a Cast Manager port after initialization.
|
||||
|
||||
#### All contexts
|
||||
|
||||
The process now continues identically for all contexts:
|
||||
|
||||
1. The page's now-active sender app calls the [`chrome.cast.initialize`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast#.initialize) API function, sending a `main:initializeCast` message to the Cast Manager, providing it with the [`chrome.cast.ApiConfig`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.ApiConfig) data and prompting a receiver availability update.
|
||||
2. The page is now free to request a session if receivers are available.
|
||||
1. The page's now-active sender app calls the [`chrome.cast.initialize`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast#.initialize) API function, sending a `main:initializeCastSdk` message to the Cast Manager, providing it with the [`chrome.cast.ApiConfig`](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.ApiConfig) data and prompting a receiver availability update.
|
||||
2. The SDK handles the first `cast:receiverAvailabilityUpdated` message as an response to the `main:initializeCastSdk` message and calls the appropriate app callbacks.
|
||||
3. The page is now free to request a session if receivers are available.
|
||||
|
||||
### Sessions
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ const castManager = new (class {
|
||||
this.activeInstances.add(instance);
|
||||
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:initialized",
|
||||
subject: "cast:instanceCreated",
|
||||
data: { isAvailable: (await bridge.getInfo()).isVersionCompatible }
|
||||
});
|
||||
|
||||
@@ -349,7 +349,7 @@ const castManager = new (class {
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "main:initializeCast":
|
||||
case "main:initializeCastSdk":
|
||||
instance.apiConfig = message.data.apiConfig;
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:receiverAvailabilityUpdated",
|
||||
|
||||
@@ -32,7 +32,7 @@ if (document.currentScript) {
|
||||
|
||||
pageMessenging.page.addListener(async message => {
|
||||
switch (message.subject) {
|
||||
case "cast:initialized": {
|
||||
case "cast:instanceCreated": {
|
||||
// If framework API is loading, wait until completed
|
||||
await frameworkScriptPromise;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export function ensureInit(contextTabId?: number): Promise<CastPort> {
|
||||
// castManager -> cast instance
|
||||
managerPort.addEventListener("message", ev => {
|
||||
const message = ev.data as Message;
|
||||
if (message.subject === "cast:initialized") {
|
||||
if (message.subject === "cast:instanceCreated") {
|
||||
if (message.data.isAvailable) {
|
||||
resolve(existingPort);
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ export function ensureInit(contextTabId?: number): Promise<CastPort> {
|
||||
backgroundPort.onMessage.addListener(function onManagerMessage(
|
||||
message: Message
|
||||
) {
|
||||
if (message.subject === "cast:initialized") {
|
||||
if (message.subject === "cast:instanceCreated") {
|
||||
if (message.data.isAvailable) {
|
||||
resolve(pageMessenging.page.messagePort);
|
||||
} else {
|
||||
|
||||
@@ -54,8 +54,10 @@ export default class {
|
||||
#apiConfig?: ApiConfig;
|
||||
#sessionRequest?: SessionRequest;
|
||||
|
||||
#isInitialized = false;
|
||||
|
||||
/** Current receiver availability. */
|
||||
#receiverAvailability = ReceiverAvailability.UNAVAILABLE;
|
||||
#receiverAvailability?: ReceiverAvailability;
|
||||
|
||||
#initializeSuccessCallback?: () => void;
|
||||
|
||||
@@ -105,10 +107,37 @@ export default class {
|
||||
|
||||
#onMessage(message: Message) {
|
||||
switch (message.subject) {
|
||||
case "cast:initialized":
|
||||
case "cast:instanceCreated":
|
||||
this.isAvailable = true;
|
||||
this.#initializeSuccessCallback?.();
|
||||
this.#apiConfig?.receiverListener(this.#receiverAvailability);
|
||||
break;
|
||||
|
||||
case "cast:receiverAvailabilityUpdated": {
|
||||
/**
|
||||
* The first availability update happens after
|
||||
* initialize is called.
|
||||
*/
|
||||
if (!this.#isInitialized) {
|
||||
this.#isInitialized = true;
|
||||
this.#initializeSuccessCallback?.();
|
||||
}
|
||||
|
||||
const availability = message.data.isAvailable
|
||||
? ReceiverAvailability.AVAILABLE
|
||||
: ReceiverAvailability.UNAVAILABLE;
|
||||
|
||||
// If availability has changed, call receiver listeners
|
||||
if (availability !== this.#receiverAvailability) {
|
||||
this.#receiverAvailability = availability;
|
||||
this.#apiConfig?.receiverListener(availability);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "cast:receiverAction":
|
||||
for (const actionListener of this.#receiverActionListeners) {
|
||||
actionListener(message.data.receiver, message.data.action);
|
||||
}
|
||||
break;
|
||||
|
||||
// Popup closed before session established
|
||||
@@ -127,6 +156,7 @@ export default class {
|
||||
* and data needed to create cast API objects is sent.
|
||||
*/
|
||||
case "cast:sessionCreated": {
|
||||
this.#sessionRequest = undefined;
|
||||
const status = message.data;
|
||||
|
||||
status.receiver.volume = status.volume;
|
||||
@@ -248,26 +278,6 @@ export default class {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "cast:receiverAvailabilityUpdated": {
|
||||
const availability = message.data.isAvailable
|
||||
? ReceiverAvailability.AVAILABLE
|
||||
: ReceiverAvailability.UNAVAILABLE;
|
||||
|
||||
// If availability has changed, call receiver listeners
|
||||
if (availability !== this.#receiverAvailability) {
|
||||
this.#receiverAvailability = availability;
|
||||
this.#apiConfig?.receiverListener(availability);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "cast:receiverAction":
|
||||
for (const actionListener of this.#receiverActionListeners) {
|
||||
actionListener(message.data.receiver, message.data.action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +301,7 @@ export default class {
|
||||
}
|
||||
|
||||
pageMessenging.page.sendMessage({
|
||||
subject: "main:initializeCast",
|
||||
subject: "main:initializeCastSdk",
|
||||
data: { apiConfig: this.#apiConfig }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,10 +66,19 @@ type ExtMessageDefinitions = {
|
||||
* stopped. Used to provide cast API receiver action updates.
|
||||
*/
|
||||
"main:receiverStopped": { deviceId: string };
|
||||
/** Allows the selector popup to send cast NS_RECEIVER messages. */
|
||||
"main:sendReceiverMessage": ReceiverSelectorReceiverMessage;
|
||||
/** Allows the selector popup to send cast NS_MEDIA messages. */
|
||||
"main:sendMediaMessage": ReceiverSelectorMediaMessage;
|
||||
|
||||
/**
|
||||
* Tells the cast manager to provide the cast API instance with
|
||||
* receiver data.
|
||||
*/
|
||||
"main:initializeCastSdk": { apiConfig: ApiConfig };
|
||||
"cast:initialized": { isAvailable: boolean };
|
||||
|
||||
/**
|
||||
* Sent to the cast API when a session is requested or stopped via
|
||||
* the extension UI.
|
||||
*/
|
||||
"cast:receiverAction": { receiver: Receiver; action: ReceiverAction };
|
||||
|
||||
/**
|
||||
* Sent from the cast API to trigger receiver selection on session
|
||||
@@ -81,23 +90,16 @@ type ExtMessageDefinitions = {
|
||||
/** Return message to the cast API when a selection is cancelled. */
|
||||
"cast:sessionRequestCancelled": undefined;
|
||||
|
||||
/**
|
||||
* Sent to the cast API when a session is requested or stopped via
|
||||
* the extension UI.
|
||||
*/
|
||||
"cast:receiverAction": { receiver: Receiver; action: ReceiverAction };
|
||||
|
||||
/**
|
||||
* Tells the cast manager to provide the cast API instance with
|
||||
* receiver data.
|
||||
*/
|
||||
"main:initializeCast": { apiConfig: ApiConfig };
|
||||
"cast:initialized": { isAvailable: boolean };
|
||||
"cast:instanceCreated": { isAvailable: boolean };
|
||||
"cast:receiverAvailabilityUpdated": { isAvailable: boolean };
|
||||
|
||||
"cast:sessionCreated": CastSessionCreatedDetails & { receiver: Receiver };
|
||||
"cast:sessionUpdated": CastSessionUpdatedDetails;
|
||||
|
||||
"cast:receiverAvailabilityUpdated": { isAvailable: boolean };
|
||||
/** Allows the selector popup to send cast NS_RECEIVER messages. */
|
||||
"main:sendReceiverMessage": ReceiverSelectorReceiverMessage;
|
||||
/** Allows the selector popup to send cast NS_MEDIA messages. */
|
||||
"main:sendMediaMessage": ReceiverSelectorMediaMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user