a desperate attempt at chromecasting

This commit is contained in:
Jelle van Snik 2023-01-25 21:44:15 +01:00
parent 8c9d905a91
commit 6edc0d3959
9 changed files with 268 additions and 49 deletions

View File

@ -41,7 +41,6 @@
<script src="config.js"></script>
<script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@d63f572f6f873bda166c2d7d3772c51d14e1c319/out.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<title>movie-web</title>
</head>

View File

@ -50,6 +50,7 @@
},
"devDependencies": {
"@tailwindcss/line-clamp": "^0.4.2",
"@types/chromecast-caf-sender": "^1.0.5",
"@types/crypto-js": "^4.1.1",
"@types/fscreen": "^1.0.1",
"@types/lodash.throttle": "^4.1.7",

View File

@ -117,7 +117,6 @@ export function VideoPlayer(props: VideoPlayerProps) {
return (
<VideoPlayerContextProvider player={playerRef} wrapper={playerWrapperRef}>
<div
// Fite me 1v1
className="is-video-player relative h-full w-full select-none overflow-hidden bg-black [border-left:env(safe-area-inset-left)_solid_transparent] [border-right:env(safe-area-inset-right)_solid_transparent]"
ref={playerWrapperRef}
>

View File

@ -0,0 +1,110 @@
/// <reference types="chromecast-caf-sender"/>
import { isChromecastAvailable } from "@/setup/chromecast";
import { useEffect, useRef, useState } from "react";
export function useChromecastAvailable() {
const [available, setAvailable] = useState<boolean | null>(null);
useEffect(() => {
isChromecastAvailable((bool) => setAvailable(bool));
}, []);
return available;
}
export function useChromecast() {
const available = useChromecastAvailable();
const instance = useRef<cast.framework.CastContext | null>(null);
const remotePlayerController =
useRef<cast.framework.RemotePlayerController | null>(null);
function startCast() {
const movieMeta = new chrome.cast.media.MovieMediaMetadata();
movieMeta.title = "Big Buck Bunny";
const mediaInfo = new chrome.cast.media.MediaInfo("hello", "video/mp4");
(mediaInfo as any).contentUrl =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED;
mediaInfo.metadata = movieMeta;
const request = new chrome.cast.media.LoadRequest(mediaInfo);
request.autoplay = true;
const session = instance.current?.getCurrentSession();
console.log("testing", session);
if (!session) return;
session
.loadMedia(request)
.then(() => {
console.log("Media is loaded");
})
.catch((e: any) => {
console.error(e);
});
}
function stopCast() {
const session = instance.current?.getCurrentSession();
if (!session) return;
const controller = remotePlayerController.current;
if (!controller) return;
controller.stop();
}
useEffect(() => {
if (!available) return;
// setup instance if not already
if (!instance.current) {
const ins = cast.framework.CastContext.getInstance();
ins.setOptions({
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
});
instance.current = ins;
}
// setup player if not already
if (!remotePlayerController.current) {
const player = new cast.framework.RemotePlayer();
const controller = new cast.framework.RemotePlayerController(player);
remotePlayerController.current = controller;
}
// setup event listener
function listenToEvents(e: cast.framework.RemotePlayerChangedEvent) {
console.log("chromecast event", e);
}
function connectionChanged(e: cast.framework.RemotePlayerChangedEvent) {
console.log("chromecast event connection changed", e);
}
remotePlayerController.current.addEventListener(
cast.framework.RemotePlayerEventType.PLAYER_STATE_CHANGED,
listenToEvents
);
remotePlayerController.current.addEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged
);
return () => {
remotePlayerController.current?.removeEventListener(
cast.framework.RemotePlayerEventType.PLAYER_STATE_CHANGED,
listenToEvents
);
remotePlayerController.current?.removeEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged
);
};
}, [available]);
return {
startCast,
stopCast,
};
}

View File

@ -8,6 +8,7 @@ import App from "@/setup/App";
import "@/setup/i18n";
import "@/setup/index.css";
import "@/backend";
import { initializeChromecast } from "./setup/chromecast";
// initialize
const key =
@ -15,6 +16,7 @@ const key =
if (key) {
(window as any).initMW(conf().BASE_PROXY_URL, key);
}
initializeChromecast();
// TODO video todos:
// - captions

View File

@ -6,7 +6,9 @@ import { NotFoundPage } from "@/views/notfound/NotFoundView";
import { MediaView } from "@/views/media/MediaView";
import { SearchView } from "@/views/search/SearchView";
import { MWMediaType } from "@/backend/metadata/types";
import { TestView } from "@/views/TestView";
// TODO remove test view
function App() {
return (
<WatchedContextProvider>
@ -16,6 +18,7 @@ function App() {
<Redirect to={`/search/${MWMediaType.MOVIE}`} />
</Route>
<Route exact path="/media/:media" component={MediaView} />
<Route exact path="/test" component={TestView} />
<Route
exact
path="/media/:media/:season/:episode"

30
src/setup/chromecast.ts Normal file
View File

@ -0,0 +1,30 @@
const CHROMECAST_SENDER_SDK =
"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
const callbacks: ((available: boolean) => void)[] = [];
let _available: boolean | null = null;
function init(available: boolean) {
_available = available;
callbacks.forEach((cb) => cb(available));
}
export function isChromecastAvailable(cb: (available: boolean) => void) {
if (_available !== null) return cb(_available);
callbacks.push(cb);
}
export function initializeChromecast() {
window.__onGCastApiAvailable = (isAvailable) => {
init(isAvailable);
};
// add script if doesnt exist yet
const exists = !!document.getElementById("chromecast-script");
if (!exists) {
const script = document.createElement("script");
script.setAttribute("src", CHROMECAST_SENDER_SDK);
script.setAttribute("id", "chromecast-script");
document.body.appendChild(script);
}
}

35
src/views/TestView.tsx Normal file
View File

@ -0,0 +1,35 @@
import {
useChromecast,
useChromecastAvailable,
} from "@/hooks/useChromecastAvailable";
import { useEffect, useRef } from "react";
function ChromeCastButton() {
const ref = useRef<HTMLDivElement>(null);
const available = useChromecastAvailable();
useEffect(() => {
if (!available) return;
const tag = document.createElement("google-cast-launcher");
tag.setAttribute("id", "castbutton");
ref.current?.appendChild(tag);
}, [available]);
return <div ref={ref} />;
}
export function TestView() {
const { startCast, stopCast } = useChromecast();
return (
<div>
<ChromeCastButton />
<button type="button" onClick={startCast}>
Start casting
</button>
<button type="button" onClick={stopCast}>
StopCasting
</button>
</div>
);
}

134
yarn.lock
View File

@ -20,9 +20,9 @@
"@colors/colors@1.5.0":
"version" "1.5.0"
"@esbuild/darwin-arm64@0.16.5":
"integrity" "sha512-4HlbUMy50cRaHGVriBjShs46WRPshtnVOqkxEGhEuDuJhgZ3regpWzaQxXOcDXFvVwue8RiqDAAcOi/QlVLE6Q=="
"resolved" "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.5.tgz"
"@esbuild/linux-x64@0.16.5":
"integrity" "sha512-vsOwzKN+4NenUTyuoWLmg5dAuO8JKuLD9MXSeENA385XucuOZbblmOMwwgPlHsgVRtSjz38riqPJU2ALI/CWYQ=="
"resolved" "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.5.tgz"
"version" "0.16.5"
"@eslint/eslintrc@^1.3.3":
@ -98,8 +98,8 @@
"@nodelib/fs.scandir" "2.1.5"
"fastq" "^1.6.0"
"@npmcli/arborist@^6.1.6":
"version" "6.1.6"
"@npmcli/arborist@^6.1.5":
"version" "6.1.5"
dependencies:
"@isaacs/string-locale-compare" "^1.1.0"
"@npmcli/fs" "^3.1.0"
@ -135,8 +135,8 @@
"treeverse" "^3.0.0"
"walk-up-path" "^1.0.0"
"@npmcli/config@^6.1.1":
"version" "6.1.1"
"@npmcli/config@^6.1.0":
"version" "6.1.0"
dependencies:
"@npmcli/map-workspaces" "^3.0.0"
"ini" "^3.0.0"
@ -233,9 +233,14 @@
"read-package-json-fast" "^3.0.0"
"which" "^3.0.0"
"@swc/core-darwin-arm64@1.3.22":
"integrity" "sha512-MMhtPsuXp8gpUgr9bs+RZQ2IyFGiUNDG93usCDAFgAF+6VVp+YaAVjET/3/Bx5Lk2WAt0RxT62C9KTEw1YMo3w=="
"resolved" "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.22.tgz"
"@swc/core-linux-x64-gnu@1.3.22":
"integrity" "sha512-FLkbiqsdXsVIFZi6iedx4rSBGX8x0vo/5aDlklSxJAAYOcQpO0QADKP5Yr65iMT1d6ABCt2d+/StpGLF7GWOcA=="
"resolved" "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.22.tgz"
"version" "1.3.22"
"@swc/core-linux-x64-musl@1.3.22":
"integrity" "sha512-giBuw+Z0Bq6fpZ0Y5TcfpcQwf9p/cE1fOQyO/K1XSTn/haQOqFi7421Jq/dFThSARZiXw1u9Om9VFbwxr8VI+A=="
"resolved" "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.22.tgz"
"version" "1.3.22"
"@swc/core@^1.3.21":
@ -262,16 +267,48 @@
"@tootallnate/once@2":
"version" "2.0.0"
"@types/chrome@*":
"integrity" "sha512-VSjQu1k6a/rAfuqR1Gi/oxHZj4+t6+LG+GobNI3ZWI6DQ+fmphNSF6TrLHG6BYK2bXc9Gb4c1uXFKRRVLaGl5Q=="
"resolved" "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.210.tgz"
"version" "0.0.210"
dependencies:
"@types/filesystem" "*"
"@types/har-format" "*"
"@types/chromecast-caf-sender@^1.0.5":
"integrity" "sha512-8d6RRCOYYiKzDyFJKAYKOp7Eo0kUfj9imnLQj0uuh/QGSz8euL9OOeKmh8XizqTcKW5tXva6li0mRYtnvzVIcA=="
"resolved" "https://registry.npmjs.org/@types/chromecast-caf-sender/-/chromecast-caf-sender-1.0.5.tgz"
"version" "1.0.5"
dependencies:
"@types/chrome" "*"
"@types/crypto-js@^4.1.1":
"integrity" "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA=="
"resolved" "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz"
"version" "4.1.1"
"@types/filesystem@*":
"integrity" "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ=="
"resolved" "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz"
"version" "0.0.32"
dependencies:
"@types/filewriter" "*"
"@types/filewriter@*":
"integrity" "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="
"resolved" "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz"
"version" "0.0.29"
"@types/fscreen@^1.0.1":
"integrity" "sha512-hV2d0BreihMGtrg+EdAFOIl/O2EL5vhAheHJUztGE/lPFZIN8ZCpGFL8hCbtyi1CfhKjDRCf47sHjP+FwJ4q0Q=="
"resolved" "https://registry.npmjs.org/@types/fscreen/-/fscreen-1.0.1.tgz"
"version" "1.0.1"
"@types/har-format@*":
"integrity" "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg=="
"resolved" "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz"
"version" "1.2.10"
"@types/history@^4.7.11":
"integrity" "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
"resolved" "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz"
@ -886,9 +923,9 @@
"version" "3.26.1"
"core-js@^3.6.5":
"integrity" "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
"resolved" "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz"
"version" "3.27.2"
"integrity" "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww=="
"resolved" "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz"
"version" "3.27.1"
"cross-fetch@3.1.5":
"integrity" "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw=="
@ -1050,6 +1087,13 @@
"resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
"version" "9.2.2"
"encoding@^0.1.0":
"integrity" "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="
"resolved" "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz"
"version" "0.1.13"
dependencies:
"iconv-lite" "^0.6.2"
"encoding@^0.1.13":
"version" "0.1.13"
dependencies:
@ -1489,11 +1533,6 @@
"resolved" "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz"
"version" "1.2.0"
"fsevents@~2.3.2":
"integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="
"resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
"version" "2.3.2"
"function-bind@^1.1.1":
"integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@ -2015,16 +2054,16 @@
"version" "1.1.4"
"json5@^1.0.1":
"integrity" "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow=="
"resolved" "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
"version" "1.0.1"
"integrity" "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="
"resolved" "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz"
"version" "1.0.2"
dependencies:
"minimist" "^1.2.0"
"json5@^2.2.0":
"integrity" "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
"resolved" "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz"
"version" "2.2.1"
"integrity" "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
"resolved" "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
"version" "2.2.3"
"jsonparse@^1.3.1":
"version" "1.3.1"
@ -2069,10 +2108,10 @@
"npm-package-arg" "^10.1.0"
"npm-registry-fetch" "^14.0.3"
"libnpmdiff@^5.0.7":
"version" "5.0.7"
"libnpmdiff@^5.0.6":
"version" "5.0.6"
dependencies:
"@npmcli/arborist" "^6.1.6"
"@npmcli/arborist" "^6.1.5"
"@npmcli/disparity-colors" "^3.0.0"
"@npmcli/installed-package-contents" "^2.0.0"
"binary-extensions" "^2.2.0"
@ -2082,10 +2121,10 @@
"pacote" "^15.0.7"
"tar" "^6.1.13"
"libnpmexec@^5.0.7":
"version" "5.0.7"
"libnpmexec@^5.0.6":
"version" "5.0.6"
dependencies:
"@npmcli/arborist" "^6.1.6"
"@npmcli/arborist" "^6.1.5"
"@npmcli/run-script" "^6.0.0"
"chalk" "^4.1.0"
"ci-info" "^3.7.0"
@ -2098,10 +2137,10 @@
"semver" "^7.3.7"
"walk-up-path" "^1.0.0"
"libnpmfund@^4.0.7":
"version" "4.0.7"
"libnpmfund@^4.0.6":
"version" "4.0.6"
dependencies:
"@npmcli/arborist" "^6.1.6"
"@npmcli/arborist" "^6.1.5"
"libnpmhook@^9.0.1":
"version" "9.0.1"
@ -2115,10 +2154,10 @@
"aproba" "^2.0.0"
"npm-registry-fetch" "^14.0.3"
"libnpmpack@^5.0.7":
"version" "5.0.7"
"libnpmpack@^5.0.6":
"version" "5.0.6"
dependencies:
"@npmcli/arborist" "^6.1.6"
"@npmcli/arborist" "^6.1.5"
"@npmcli/run-script" "^6.0.0"
"npm-package-arg" "^10.1.0"
"pacote" "^15.0.7"
@ -2281,9 +2320,9 @@
"encoding" "^0.1.13"
"minipass-fetch@^3.0.0":
"version" "3.0.1"
"version" "3.0.0"
dependencies:
"minipass" "^4.0.0"
"minipass" "^3.1.6"
"minipass-sized" "^1.0.3"
"minizlib" "^2.1.2"
optionalDependencies:
@ -2492,13 +2531,13 @@
"version" "1.0.1"
"npm@^9.2.0":
"integrity" "sha512-ydRVmnWEVXmc3DCM+F9BjiNj3IHkZ3Mwz5VbJYS2BpY/6d4PcKxNW+Xb0vzGeE6PkVhLcPxwhoIi+RFV2fSfEA=="
"resolved" "https://registry.npmjs.org/npm/-/npm-9.3.1.tgz"
"version" "9.3.1"
"integrity" "sha512-oypVdaWGHDuV79RXLvp+B9gh6gDyAmoHKrQ0/JBYTWWx5D8/+AAxFdZC84fSIiyDdyW4qfrSyYGKhekxDOaMXQ=="
"resolved" "https://registry.npmjs.org/npm/-/npm-9.2.0.tgz"
"version" "9.2.0"
dependencies:
"@isaacs/string-locale-compare" "^1.1.0"
"@npmcli/arborist" "^6.1.6"
"@npmcli/config" "^6.1.1"
"@npmcli/arborist" "^6.1.5"
"@npmcli/config" "^6.1.0"
"@npmcli/map-workspaces" "^3.0.0"
"@npmcli/package-json" "^3.0.0"
"@npmcli/run-script" "^6.0.0"
@ -2520,12 +2559,12 @@
"is-cidr" "^4.0.2"
"json-parse-even-better-errors" "^3.0.0"
"libnpmaccess" "^7.0.1"
"libnpmdiff" "^5.0.7"
"libnpmexec" "^5.0.7"
"libnpmfund" "^4.0.7"
"libnpmdiff" "^5.0.6"
"libnpmexec" "^5.0.6"
"libnpmfund" "^4.0.6"
"libnpmhook" "^9.0.1"
"libnpmorg" "^5.0.1"
"libnpmpack" "^5.0.7"
"libnpmpack" "^5.0.6"
"libnpmpublish" "^7.0.6"
"libnpmsearch" "^6.0.1"
"libnpmteam" "^5.0.1"
@ -2554,6 +2593,7 @@
"read" "~1.0.7"
"read-package-json" "^6.0.0"
"read-package-json-fast" "^3.0.1"
"rimraf" "^3.0.2"
"semver" "^7.3.8"
"ssri" "^10.0.1"
"tar" "^6.1.13"