Skip to content

Commit 5a1bc01

Browse files
committed
Chomecast support
Any admin using browser2 can push a button and have player2 running on their TV Issues: [ ] There seems to be some bug where chromecast hardware video display glitches off when combined with hyperapp - right now this is bad enough to make casting practically unusable :( [ ] CSS animations are super slow, the default theme has elements sliding in at ~4fps [ ] No indication when casting is active (Although that's not super-relevant here since we cast-and-forget; commands like "play" are sent via MQTT and don't care if we're casting or not) [ ] Requires an internet connetcion [ ] Uses the non-recommended chromecast API. The v2 API supports any random webpage while the recommended v3 API is specifically for use in casting videos and makes anything else a huge pain.
1 parent 1bd83e7 commit 5a1bc01

File tree

11 files changed

+107
-1
lines changed

11 files changed

+107
-1
lines changed

browser2/src/browser.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type State = {
8989
widescreen: boolean,
9090
scroll_stack: Array<number>,
9191
now: number,
92+
casting: boolean,
9293

9394
// login
9495
session_id: string | null,

browser2/src/browser.ts

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Root } from "./screens/root";
1111
import { BeLoggedIn, getMQTTListener, ResizeListener } from "./subs";
1212
import { ApiRequest } from "./effects";
13+
import { CastSender } from "./cc_sender";
1314

1415
// If we're running stand-alone, then use the main karakara.uk
1516
// server; else we're probably running as part of the full-stack,
@@ -37,6 +38,7 @@ let state: State = {
3738
widescreen: isWidescreen(),
3839
scroll_stack: [],
3940
now: Date.now()/1000,
41+
casting: false,
4042

4143
// login
4244
session_id: null,
@@ -149,6 +151,7 @@ const subscriptions = (state: State): Array<Subscription|boolean> => [
149151
return { ...state, now: timestamp_ms/1000 };
150152
},
151153
}),
154+
state.casting && CastSender(state.room_name, state.room_password),
152155
];
153156

154157
app({

browser2/src/cc_sender.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
function _castSender(dispatch, props) {
2+
var applicationID = '05C10F57';
3+
var session = null;
4+
5+
function bail(e) {
6+
console.log("cast error", e);
7+
dispatch((state) => ({...state, casting: false}));
8+
}
9+
10+
var apiConfig = new chrome.cast.ApiConfig(
11+
new chrome.cast.SessionRequest(applicationID),
12+
function (s) { console.log("session", s); }, // session listener
13+
function (r) { console.log("receiver", r); }, // receiver listener
14+
);
15+
chrome.cast.initialize(
16+
apiConfig,
17+
function () { }, // ok
18+
function (e) { bail(e); }, // err
19+
);
20+
chrome.cast.requestSession(
21+
function (s) {
22+
session = s;
23+
session.setReceiverMuted(false);
24+
session.setReceiverVolumeLevel(1.00);
25+
session.sendMessage('urn:x-cast:uk.karakara', {
26+
"command": "join",
27+
"room_name": props.room_name,
28+
"room_password": props.room_password,
29+
});
30+
},
31+
function (e) {bail(e);}
32+
);
33+
34+
return function () {
35+
if (session) {
36+
session.stop(
37+
function () { session = null; },
38+
function (e) { console.log("session stop err", e); },
39+
);
40+
}
41+
}
42+
}
43+
44+
export function CastSender(name, pass): Subscription {
45+
return [_castSender, { room_name: name, room_password: pass }];
46+
}

browser2/src/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
<link href="static/style.scss" rel="stylesheet" type="text/css" />
99
</head>
1010
<body><noscript>This page requires JavaScript</noscript></body>
11+
<script type="text/javascript" src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
1112
<script type="module" src="browser.ts"></script>
1213
</html>

browser2/src/screens/control.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,20 @@ const ControlButtons = (): VNode => (
232232
</footer>
233233
);
234234

235+
export const CastButton = ({ state }: { state: State }): VNode => (
236+
(window.chrome && chrome.cast && chrome.cast.isAvailable) &&
237+
<a onclick={(state) => ({...state, casting: !state.casting})}>
238+
<i class={"fab fa-2x fa-chromecast"} />
239+
</a>
240+
);
241+
235242
export const Control = ({ state }: { state: State }): VNode => (
236243
<Screen
237244
state={state}
238245
className={"queue"}
239246
navLeft={!state.widescreen && <BackToExplore />}
240247
title={"Remote Control"}
241-
// navRight={}
248+
navRight={<CastButton state={state} />}
242249
footer={<ControlButtons />}
243250
>
244251
{current_and_future(state.now, state.queue).length == 0 ? (

browser2/src/static/_brands.css

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*!
2+
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
3+
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4+
*/
5+
@font-face {
6+
font-family: 'Font Awesome 5 Brands';
7+
font-style: normal;
8+
font-weight: 400;
9+
font-display: block;
10+
src: url("fa-brands-400.woff2") format("woff2"); }
11+
12+
.fab {
13+
font-family: 'Font Awesome 5 Brands';
14+
font-weight: 400; }
74.9 KB
Binary file not shown.

browser2/src/static/style.scss

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import 'fontawesome';
22
@import 'solid';
3+
@import 'brands';
34

45
$FG: #000;
56
$BORDER: #DDD;

player2/src/cc_receiver.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function _castReceiver(dispatch, props) {
2+
let crm = cast.receiver.CastReceiverManager.getInstance();
3+
crm.onReady = function (event) {
4+
crm.setApplicationState("Application ready");
5+
};
6+
let bus = crm.getCastMessageBus('urn:x-cast:uk.karakara');
7+
bus.onMessage = function (event) {
8+
var msg = JSON.parse(event.data);
9+
console.groupCollapsed("cast_onmessage(", event.target ,")")
10+
console.log(msg);
11+
console.groupEnd();
12+
dispatch(function (state) {
13+
return {
14+
...state,
15+
room_name: msg.room_name,
16+
room_password: msg.room_password
17+
};
18+
});
19+
//bus.send(event.senderId, event.data);
20+
}
21+
crm.start({ statusText: "Application starting" });
22+
23+
return function () {
24+
crm.stop();
25+
}
26+
}
27+
28+
export function CastReceiver(): Subscription {
29+
return [_castReceiver, {}];
30+
}

player2/src/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
<link href="static/style.scss" rel="stylesheet" type="text/css" />
99
</head>
1010
<body><noscript>This page requires JavaScript</noscript></body>
11+
<script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
1112
<script type="module" src="player.ts"></script>
1213
</html>

player2/src/player.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "./subs";
1616
import { ApiRequest } from "./effects";
1717
import { current_and_future } from "./utils";
18+
import { CastReceiver } from "./cc_receiver";
1819

1920
// If we're running stand-alone, then use the main karakara.uk
2021
// server; else we're probably running as part of the full-stack,
@@ -134,6 +135,7 @@ const subscriptions = (state: State): Array<Subscription|boolean> => [
134135
return { ...state, now: timestamp_ms/1000 };
135136
},
136137
}),
138+
window.location.search.includes("chromecast=true") && CastReceiver(),
137139
];
138140

139141
app({

0 commit comments

Comments
 (0)