cast info
This commit is contained in:
18
cast-receiver/README.md
Normal file
18
cast-receiver/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Radio Player - Custom Cast Receiver
|
||||||
|
|
||||||
|
This folder contains a minimal Google Cast Web Receiver that displays a purple gradient background, station artwork, title and subtitle. It accepts `customData` hints sent from the sender (your app) for `backgroundImage`, `backgroundGradient` and `appName`.
|
||||||
|
|
||||||
|
Hosting requirements
|
||||||
|
- The receiver must be served over HTTPS and be publicly accessible.
|
||||||
|
- Recommended: host under GitHub Pages (`gh-pages` branch or `/docs` folder) or any static host (Netlify, Vercel, S3 + CloudFront).
|
||||||
|
|
||||||
|
Registering with Google Cast Console
|
||||||
|
1. Go to the Cast SDK Developer Console and create a new Application.
|
||||||
|
2. Choose "Custom Receiver" and provide the public HTTPS URL to `index.html` (e.g. `https://example.com/cast-receiver/index.html`).
|
||||||
|
3. Note the generated Application ID.
|
||||||
|
|
||||||
|
Sender changes
|
||||||
|
- After obtaining the Application ID, update your sender (sidecar) to launch that app ID instead of the DefaultMediaReceiver. The sidecar already supports passing `metadata.appId` when launching.
|
||||||
|
|
||||||
|
Testing locally
|
||||||
|
- You can serve this folder locally during development, but Chromecast devices require public HTTPS endpoints to use a registered app.
|
||||||
23
cast-receiver/index.html
Normal file
23
cast-receiver/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Radio Player Receiver</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="bg" class="bg"></div>
|
||||||
|
<div id="app" class="app">
|
||||||
|
<div class="artwork"><img id="art" alt="Artwork"></div>
|
||||||
|
<div class="meta">
|
||||||
|
<div id="appName" class="app-name">Radio Player</div>
|
||||||
|
<h1 id="title">Radio Player</h1>
|
||||||
|
<h2 id="subtitle"></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
|
||||||
|
<script src="receiver.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
cast-receiver/receiver.js
Normal file
50
cast-receiver/receiver.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Minimal CAF receiver that applies customData theming and shows media metadata.
|
||||||
|
const context = cast.framework.CastReceiverContext.getInstance();
|
||||||
|
const playerManager = context.getPlayerManager();
|
||||||
|
|
||||||
|
function applyBranding(customData, metadata) {
|
||||||
|
try {
|
||||||
|
const bgEl = document.getElementById('bg');
|
||||||
|
const art = document.getElementById('art');
|
||||||
|
const title = document.getElementById('title');
|
||||||
|
const subtitle = document.getElementById('subtitle');
|
||||||
|
const appName = document.getElementById('appName');
|
||||||
|
|
||||||
|
if (customData) {
|
||||||
|
if (customData.backgroundImage) {
|
||||||
|
bgEl.style.backgroundImage = `url(${customData.backgroundImage})`;
|
||||||
|
bgEl.style.backgroundSize = 'cover';
|
||||||
|
bgEl.style.backgroundPosition = 'center';
|
||||||
|
} else if (customData.backgroundGradient) {
|
||||||
|
bgEl.style.background = customData.backgroundGradient;
|
||||||
|
}
|
||||||
|
if (customData.appName) appName.textContent = customData.appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
if (metadata.title) title.textContent = metadata.title;
|
||||||
|
const sub = metadata.subtitle || metadata.artist || '';
|
||||||
|
subtitle.textContent = sub;
|
||||||
|
if (metadata.images && metadata.images.length) {
|
||||||
|
art.src = metadata.images[0].url || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// swallow UI errors
|
||||||
|
console.warn('Branding apply failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, (request) => {
|
||||||
|
const media = request.media || {};
|
||||||
|
const customData = media.customData || {};
|
||||||
|
applyBranding(customData, media.metadata || {});
|
||||||
|
return request;
|
||||||
|
});
|
||||||
|
|
||||||
|
playerManager.addEventListener(cast.framework.events.EventType.MEDIA_STATUS, () => {
|
||||||
|
const media = playerManager.getMediaInformation();
|
||||||
|
if (media) applyBranding(media.customData || {}, media.metadata || {});
|
||||||
|
});
|
||||||
|
|
||||||
|
context.start();
|
||||||
11
cast-receiver/styles.css
Normal file
11
cast-receiver/styles.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
:root{--primary:#6a0dad;--accent:#b36cf3}
|
||||||
|
html,body{height:100%;margin:0;font-family:Inter,system-ui,Arial,Helvetica,sans-serif}
|
||||||
|
body{background:linear-gradient(135deg,var(--primary),var(--accent));color:#fff}
|
||||||
|
.bg{position:fixed;inset:0;background-size:cover;background-position:center;filter:blur(10px) saturate(120%);opacity:0.9}
|
||||||
|
.app{position:relative;z-index:2;display:flex;align-items:center;gap:24px;padding:48px}
|
||||||
|
.artwork{width:320px;height:320px;flex:0 0 320px;background:rgba(255,255,255,0.06);display:flex;align-items:center;justify-content:center;border-radius:8px;overflow:hidden}
|
||||||
|
.artwork img{width:100%;height:100%;object-fit:cover}
|
||||||
|
.meta{display:flex;flex-direction:column}
|
||||||
|
.app-name{font-weight:600;opacity:0.9}
|
||||||
|
h1{margin:6px 0 0 0;font-size:28px}
|
||||||
|
h2{margin:6px 0 0 0;font-size:18px;opacity:0.9}
|
||||||
@@ -140,7 +140,8 @@ function play(ip, url, metadata) {
|
|||||||
function launchPlayer(url, metadata, didStopFirst) {
|
function launchPlayer(url, metadata, didStopFirst) {
|
||||||
if (!activeClient) return;
|
if (!activeClient) return;
|
||||||
|
|
||||||
activeClient.launch(DefaultMediaReceiver, (err, player) => {
|
const launchApp = (metadata && metadata.appId) ? metadata.appId : DefaultMediaReceiver;
|
||||||
|
activeClient.launch(launchApp, (err, player) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
const details = `Launch error: ${err && err.message ? err.message : String(err)}${err && err.code ? ` (code: ${err.code})` : ''}`;
|
const details = `Launch error: ${err && err.message ? err.message : String(err)}${err && err.code ? ` (code: ${err.code})` : ''}`;
|
||||||
// If launch fails with NOT_ALLOWED, the device may be busy with another app/session.
|
// If launch fails with NOT_ALLOWED, the device may be busy with another app/session.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.1.1",
|
|
||||||
"debug": false,
|
|
||||||
"builtAt": "2026-01-13T12:34:31.351Z"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user