diff --git a/index.html b/index.html index 5e3d67e5..7580549c 100644 --- a/index.html +++ b/index.html @@ -41,7 +41,6 @@ - movie-web diff --git a/package.json b/package.json index 8dd02062..65898799 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/video/VideoPlayer.tsx b/src/components/video/VideoPlayer.tsx index 8f33e11e..3e064aa1 100644 --- a/src/components/video/VideoPlayer.tsx +++ b/src/components/video/VideoPlayer.tsx @@ -117,7 +117,6 @@ export function VideoPlayer(props: VideoPlayerProps) { return (
diff --git a/src/hooks/useChromecastAvailable.ts b/src/hooks/useChromecastAvailable.ts new file mode 100644 index 00000000..6190a288 --- /dev/null +++ b/src/hooks/useChromecastAvailable.ts @@ -0,0 +1,110 @@ +/// + +import { isChromecastAvailable } from "@/setup/chromecast"; +import { useEffect, useRef, useState } from "react"; + +export function useChromecastAvailable() { + const [available, setAvailable] = useState(null); + + useEffect(() => { + isChromecastAvailable((bool) => setAvailable(bool)); + }, []); + + return available; +} + +export function useChromecast() { + const available = useChromecastAvailable(); + const instance = useRef(null); + const remotePlayerController = + useRef(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, + }; +} diff --git a/src/index.tsx b/src/index.tsx index bd0fcf88..1a7a7aa0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 6ab1374c..5f1b2b11 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -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 ( @@ -16,6 +18,7 @@ function App() { + 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); + } +} diff --git a/src/views/TestView.tsx b/src/views/TestView.tsx new file mode 100644 index 00000000..f9cd6502 --- /dev/null +++ b/src/views/TestView.tsx @@ -0,0 +1,35 @@ +import { + useChromecast, + useChromecastAvailable, +} from "@/hooks/useChromecastAvailable"; +import { useEffect, useRef } from "react"; + +function ChromeCastButton() { + const ref = useRef(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
; +} + +export function TestView() { + const { startCast, stopCast } = useChromecast(); + + return ( +
+ + + +
+ ); +} diff --git a/yarn.lock b/yarn.lock index 2b105f10..1335083b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"