diff --git a/.ci/dockerfile-hash.yaml b/.ci/dockerfile-hash.yaml index c821340b..e09dd2e3 100644 --- a/.ci/dockerfile-hash.yaml +++ b/.ci/dockerfile-hash.yaml @@ -1,2 +1,2 @@ variables: - DOCKERFILE_HASH: c73491a7429660713b481343620d1ea6295bdfba \ No newline at end of file + DOCKERFILE_HASH: 63bfdd73722cb4b4b4b9301905dadc3d22fa3668 \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8c687cdb..8b887d8b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,7 @@ builder: builder-rebuild: stage: build + interruptible: true image: name: curlimages/curl:latest entrypoint: [""] @@ -39,6 +40,7 @@ build: interruptible: true image: "$CI_REGISTRY_IMAGE:builder-$DOCKERFILE_HASH" script: + - rm -rf /usr/x86_64-w64-mingw32/include/d3dcompiler.h /usr/share/mingw-w64/include/d3dcompiler.h - git fetch --unshallow || echo 'already unshallow' - make ci -j$(nproc) artifacts: @@ -92,6 +94,7 @@ build-docker-arm64: build-docker-manifest: stage: build + interruptible: true image: name: curlimages/curl:latest entrypoint: [""] @@ -128,12 +131,18 @@ build-mac: script: - git fetch --unshallow || echo 'already unshallow' - - brew install meson ninja go && go version + - brew install ninja go openssl@3 && go version - sudo gem install --user-install xcpretty - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && bash rustup.sh -y && rm rustup.sh - export PATH="$PATH:$HOME/.cargo/bin:$(find $HOME/.gem/ruby -type d -name bin -maxdepth 2)" - - make ci-macos -j16 - - make test + - > + brew install python@3.11 + && python3.11 -m pip install virtualenv + && python3.11 -m virtualenv ~/venv + && source ~/venv/bin/activate + && pip3 install meson + && make ci-macos -j16 + && make test release: stage: build diff --git a/.vscode/settings.json b/.vscode/settings.json index 07fee667..36b646bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "workbench.colorCustomizations": { - "statusBar.background": "#83447f" + "statusBar.background": "#83447f", + "activityBar.background": "#83447f" }, "files.associations": { "mistserver.h": "c", diff --git a/Makefile b/Makefile index ba1f5c8a..aead0fdb 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,8 @@ app: schema install .PHONY: node node: schema - meson setup build --native=./util/linux-amd64-gnu.ini && meson compile -C build + $(MAKE) meson-setup + meson compile -C build aquareum mv ./build/aquareum ./bin/aquareum .PHONY: schema @@ -44,6 +45,16 @@ schema: test: meson test -C build go-tests +# test to make sure we haven't added any more dynamic dependencies +.PHONY: link-test +link-test: + count=$(shell ldd ./build/aquareum | wc -l) \ + && echo $$count \ + && if [ "$$count" != "6" ]; then echo "ldd reports new libaries linked! want 6 got $$count" \ + && ldd ./bin/aquareum \ + && exit 1; \ + fi + .PHONY: all all: version install check app test node-all-platforms android @@ -58,7 +69,7 @@ ci-android: version install check android ci-upload-android .PHONY: ci-test ci-test: app - meson setup build + meson setup build $(OPTS) meson test -C build go-tests .PHONY: android @@ -98,23 +109,55 @@ ios: app mkdir -p .build \ && curl -L -o ./.build/bundletool.jar https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar +OPTS = -D "gst-plugins-base:audioresample=enabled" \ + -D "gst-plugins-base:playback=enabled" \ + -D "gst-plugins-base:opus=enabled" \ + -D "gst-plugins-base:gio-typefinder=enabled" \ + -D "gst-plugins-base:typefind=enabled" \ + -D "gst-plugins-good:matroska=enabled" \ + -D "gst-plugins-bad:fdkaac=enabled" \ + -D "gstreamer-full:gst-full=enabled" \ + -D "gstreamer-full:gst-full-plugins=libgstaudioresample.a;libgstmatroska.a;libgstfdkaac.a;libgstopus.a;libgstplayback.a;libgsttypefindfunctions.a" \ + -D "gstreamer-full:gst-full-libraries=gstreamer-controller-1.0,gstreamer-plugins-base-1.0,gstreamer-pbutils-1.0" \ + -D "gstreamer-full:gst-full-target-type=static_library" \ + -D "gstreamer-full:gst-full-elements=coreelements:fdsrc,fdsink,queue,queue2,typefind,tee" \ + -D "gstreamer-full:bad=enabled" \ + -D "gstreamer-full:ugly=disabled" \ + -D "gstreamer-full:tls=disabled" \ + -D "gstreamer-full:gpl=enabled" \ + -D "gstreamer-full:gst-full-typefind-functions=" + +.PHONY: meson-setup +meson-setup: + meson setup build $(OPTS) + meson configure build $(OPTS) + .PHONY: node-all-platforms node-all-platforms: app - meson setup build + meson setup build $(OPTS) --buildtype debugoptimized meson compile -C build archive + $(MAKE) link-test + $(MAKE) linux-arm64 + $(MAKE) windows-amd64 + +.PHONY: linux-arm64 +linux-arm64: rustup target add aarch64-unknown-linux-gnu - meson setup --cross-file util/linux-arm64-gnu.ini build-aarch64 + meson setup --cross-file util/linux-arm64-gnu.ini --buildtype debugoptimized build-aarch64 $(OPTS) meson compile -C build-aarch64 archive + +.PHONY: windows-amd64 +windows-amd64: rustup target add x86_64-pc-windows-gnu - meson setup --cross-file util/windows-amd64-gnu.ini build-windows + meson setup --cross-file util/windows-amd64-gnu.ini --buildtype debugoptimized build-windows $(OPTS) meson compile -C build-windows archive 2>&1 | grep -v drectve .PHONY: node-all-platforms-macos node-all-platforms-macos: app - meson setup build + meson setup --buildtype debugoptimized build $(OPTS) meson compile -C build archive rustup target add x86_64-apple-darwin - meson setup --cross-file util/darwin-amd64-apple.ini build-amd64 + meson setup --buildtype debugoptimized --cross-file util/darwin-amd64-apple.ini build-amd64 $(OPTS) meson compile -C build-amd64 archive # link your local version of mist for dev @@ -123,12 +166,24 @@ link-mist: rm -rf subprojects/mistserver ln -s $$(realpath ../mistserver) ./subprojects/mistserver -# link your local version of c2pa-gop for dev +# link your local version of c2pa-go for dev .PHONY: link-c2pa-go link-c2pa-go: rm -rf subprojects/c2pa_go ln -s $$(realpath ../c2pa-go) ./subprojects/c2pa_go +# link your local version of gstreamer +.PHONY: link-gstreamer +link-gstreamer: + rm -rf subprojects/gstreamer-full + ln -s $$(realpath ../gstreamer) ./subprojects/gstreamer-full + +# link your local version of ffmpeg for dev +.PHONY: link-ffmpeg +link-ffmpeg: + rm -rf subprojects/FFmpeg + ln -s $$(realpath ../ffmpeg) ./subprojects/FFmpeg + .PHONY: docker-build docker-build: docker-build-builder docker-build-in-container diff --git a/cmd/libaquareum/aquareum.go b/cmd/libaquareum/aquareum.go index aa54fb2d..e3700400 100644 --- a/cmd/libaquareum/aquareum.go +++ b/cmd/libaquareum/aquareum.go @@ -8,8 +8,11 @@ import ( "aquareum.tv/aquareum/pkg/log" "aquareum.tv/aquareum/pkg/cmd" + // _ "github.com/go-gst/go-glib/glib" + // _ "github.com/go-gst/go-gst/gst" ) +//#cgo LDFLAGS: -lz import "C" //export AquareumMain diff --git a/docker/build.Dockerfile b/docker/build.Dockerfile index c73491a7..63bfdd73 100644 --- a/docker/build.Dockerfile +++ b/docker/build.Dockerfile @@ -12,7 +12,7 @@ ENV NODE_VERSION 22.3.0 RUN apt update \ && apt install -y build-essential curl git openjdk-17-jdk unzip jq g++ python3-pip ninja-build \ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu clang lld qemu-user-static pkg-config \ - wine64 nasm gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools zip \ + wine64 nasm gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools zip bison flex \ && pip install meson tomli \ && curl -L --fail https://go.dev/dl/go$GO_VERSION.linux-$TARGETARCH.tar.gz -o go.tar.gz \ && tar -C /usr/local -xf go.tar.gz \ diff --git a/go.mod b/go.mod index dd2242d9..9a4c20c3 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module aquareum.tv/aquareum go 1.22.2 +replace github.com/livepeer/lpms => github.com/aquareum-tv/lpms v0.0.0-20240828210246-5ac9b407751e + require ( firebase.google.com/go/v4 v4.14.1 git.aquareum.tv/aquareum-tv/c2pa-go v0.0.0-20240827194057-dc4fd1d2c425 @@ -10,6 +12,8 @@ require ( github.com/dunglas/httpsfv v1.0.2 github.com/ethereum/go-ethereum v1.14.7 github.com/go-git/go-git/v5 v5.12.0 + github.com/go-gst/go-glib v1.2.1 + github.com/go-gst/go-gst v1.2.1 github.com/golang/glog v1.2.0 github.com/google/uuid v1.6.0 github.com/julienschmidt/httprouter v1.3.0 @@ -90,6 +94,7 @@ require ( github.com/jstemmer/go-junit-report v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/livepeer/m3u8 v0.11.1 // indirect + github.com/mattn/go-pointer v0.0.1 // indirect github.com/mitchellh/gox v1.0.1 // indirect github.com/mitchellh/iochan v1.0.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect diff --git a/go.sum b/go.sum index cd8ab6dc..93de65e7 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,6 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= -git.aquareum.tv/aquareum-tv/c2pa-go v0.0.0-20240821182412-c7339a12f026 h1:6GprIUXv9uXc16zD0c0IY72o+xroaTZP1isC3Pbhf2c= -git.aquareum.tv/aquareum-tv/c2pa-go v0.0.0-20240821182412-c7339a12f026/go.mod h1:lp4UzqUyNZ4gieOSCdjsGoSUudZsDrBpsBgnRoNLlSw= git.aquareum.tv/aquareum-tv/c2pa-go v0.0.0-20240827194057-dc4fd1d2c425 h1:4xgRxVpXdHwBGyf5BAyWfWp0WrwjD1gP0EU5K6r8sw4= git.aquareum.tv/aquareum-tv/c2pa-go v0.0.0-20240827194057-dc4fd1d2c425/go.mod h1:lp4UzqUyNZ4gieOSCdjsGoSUudZsDrBpsBgnRoNLlSw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -43,6 +41,8 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjC github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/aquareum-tv/lpms v0.0.0-20240828210246-5ac9b407751e h1:YMbqxUpsM7cjfPdO4y2FVH+VMBMC+ZG7DRbF7J5NMs0= +github.com/aquareum-tv/lpms v0.0.0-20240828210246-5ac9b407751e/go.mod h1:z5ROP1l5OzAKSoqVRLc34MjUdueil6wHSecQYV7llIw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -135,6 +135,10 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-gst/go-glib v1.2.1 h1:ibAr5N1NmuHmZ5RaCFjFjeUy0Rk3t3LgvGutmwBeR9E= +github.com/go-gst/go-glib v1.2.1/go.mod h1:JybIYeoHNwCkHGaBf1fHNIaM4sQTrJPkPLsi7dmPNOU= +github.com/go-gst/go-gst v1.2.1 h1:FqUFGFllbuC8LkQoqULgAui2ZS0VU1WEBCNekIMcBEE= +github.com/go-gst/go-gst v1.2.1/go.mod h1:OGPRsJdvNYCKjt3e4H4i8J6KVd2Wk5S2lzsEQ8mO1+g= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -228,14 +232,14 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/livepeer/joy4 v0.1.2-0.20191121080656-b2fea45cbded/go.mod h1:xkDdm+akniYxVT9KW1Y2Y7Hso6aW+rZObz3nrA9yTHw= -github.com/livepeer/lpms v0.0.0-20240812093642-b5181eb92cb2 h1:2Cjgt/Bg6bVSPHwUUeSf3n55JuCQGGfD7HXk2Qcg0I8= -github.com/livepeer/lpms v0.0.0-20240812093642-b5181eb92cb2/go.mod h1:z5ROP1l5OzAKSoqVRLc34MjUdueil6wHSecQYV7llIw= github.com/livepeer/m3u8 v0.11.1 h1:VkUJzfNTyjy9mqsgp5JPvouwna8wGZMvd/gAfT5FinU= github.com/livepeer/m3u8 v0.11.1/go.mod h1:IUqAtwWPAG2CblfQa4SVzTQoDcEMPyfNOaBSxqHMS04= github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= diff --git a/hack/push.mjs b/hack/push.mjs index c80f7036..9e91d77e 100644 --- a/hack/push.mjs +++ b/hack/push.mjs @@ -52,7 +52,7 @@ const blast = async (body) => { console.log(JSON.stringify(notification.notification, null, 2)); // console.log("launching in 5 sec") // await delay(5000) - const res = await admin.messaging().sendMulticast(notification); + const res = await admin.messaging().sendEachForMulticast(notification); console.log(JSON.stringify(res)); }; export default blast; diff --git a/js/app/.env.development b/js/app/.env.development index 55d1f761..2c3011b0 100644 --- a/js/app/.env.development +++ b/js/app/.env.development @@ -1,2 +1,3 @@ EXPO_PUBLIC_AQUAREUM_URL=http://localhost:8080 +EXPO_PUBLIC_WEB_TRY_LOCAL=false EXPO_USE_METRO_WORKSPACE_ROOT=1 \ No newline at end of file diff --git a/js/app/.env.production b/js/app/.env.production index ca52c778..bc7fb1de 100644 --- a/js/app/.env.production +++ b/js/app/.env.production @@ -1,2 +1,3 @@ EXPO_PUBLIC_AQUAREUM_URL=https://aquareum.tv +EXPO_PUBLIC_WEB_TRY_LOCAL=true EXPO_USE_METRO_WORKSPACE_ROOT=0 \ No newline at end of file diff --git a/js/app/app/Provider.tsx b/js/app/app/Provider.tsx deleted file mode 100644 index 0582ff89..00000000 --- a/js/app/app/Provider.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useColorScheme } from "hooks/useColorScheme"; -import { TamaguiProvider, type TamaguiProviderProps } from "tamagui"; -import { ToastProvider, ToastViewport } from "@tamagui/toast"; -import { CurrentToast } from "./CurrentToast"; -import { config } from "../tamagui.config"; -import NativeProvider from "components/providers"; - -export function Provider({ - children, - ...rest -}: Omit) { - const colorScheme = useColorScheme(); - - return ( - - - {children} - - - - - ); -} diff --git a/js/app/app/_layout.tsx b/js/app/app/_layout.tsx index d1ef233d..0ab1846f 100644 --- a/js/app/app/_layout.tsx +++ b/js/app/app/_layout.tsx @@ -21,8 +21,7 @@ import { } from "@react-navigation/native"; import { useFonts } from "expo-font"; import { SplashScreen, Stack } from "expo-router"; -import { Provider } from "./Provider"; -import "./updates"; +import { Provider } from "components"; import { Helmet } from "react-native-helmet-async"; import { Settings } from "@tamagui/lucide-icons"; @@ -85,11 +84,8 @@ function RootLayoutNav() { title: "", headerShown: true, headerRight: () => { - if (isWeb) { - return ; - } return ( - + ); @@ -104,9 +100,15 @@ function RootLayoutNav() { }} /> + ; + } + return ; +} diff --git a/js/app/app/layout.tsx b/js/app/app/layout.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/js/app/app/options.tsx b/js/app/app/options.tsx deleted file mode 100644 index 0a16b35a..00000000 --- a/js/app/app/options.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import UpdatesDemo from "components/updates"; -import { isWeb, Text, View } from "tamagui"; - -export default function OptionsScreen() { - if (isWeb) { - return ; - } - return ( - - - - ); -} diff --git a/js/app/app/settings.tsx b/js/app/app/settings.tsx new file mode 100644 index 00000000..bdd75a7c --- /dev/null +++ b/js/app/app/settings.tsx @@ -0,0 +1,10 @@ +import { Settings } from "components"; +import { View } from "tamagui"; + +export default function OptionsScreen() { + return ( + + + + ); +} diff --git a/js/app/app/stream/[stream].tsx b/js/app/app/stream/[stream].tsx new file mode 100644 index 00000000..c8927f98 --- /dev/null +++ b/js/app/app/stream/[stream].tsx @@ -0,0 +1,11 @@ +import { Player } from "components"; +import { useLocalSearchParams } from "expo-router"; +import { View } from "tamagui"; + +export default function StreamPage() { + const params = useLocalSearchParams(); + if (typeof params.stream !== "string") { + return ; + } + return ; +} diff --git a/js/app/app/updates.tsx b/js/app/app/updates.tsx deleted file mode 100644 index 67824760..00000000 --- a/js/app/app/updates.tsx +++ /dev/null @@ -1 +0,0 @@ -import * as Updates from "expo-updates"; diff --git a/js/app/components/index.tsx b/js/app/components/index.tsx index 88acb177..cc819c5c 100644 --- a/js/app/components/index.tsx +++ b/js/app/components/index.tsx @@ -1 +1,4 @@ export { Countdown } from "./countdown"; +export { Player } from "./player/player"; +export { Settings } from "./settings/settings"; +export { default as Provider } from "./provider/provider"; diff --git a/js/app/components/player/player.native.tsx b/js/app/components/player/player.native.tsx new file mode 100644 index 00000000..4140719c --- /dev/null +++ b/js/app/components/player/player.native.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { View, Text } from "tamagui"; + +export function Player() { + return ( + + WIP + + ); +} diff --git a/js/app/components/player/player.tsx b/js/app/components/player/player.tsx new file mode 100644 index 00000000..1df342c4 --- /dev/null +++ b/js/app/components/player/player.tsx @@ -0,0 +1,126 @@ +import React, { + ForwardedRef, + forwardRef, + useCallback, + useEffect, + useRef, + useState, + useTransition, +} from "react"; +import { Button, Text, View, XStack } from "tamagui"; +import WHEPClient from "./webrtc"; +import Hls from "hls.js"; +import { Circle, CheckCircle } from "@tamagui/lucide-icons"; +import useAquareumNode from "hooks/useAquareumNode"; + +export function Player(props: { src: string }) { + const [proto, setProto] = useState("hls"); + let p; + if (proto === "webrtc") { + p = ; + } else if (proto === "hls") { + p = ; + } + return ( + + {p} + + + + + + ); +} + +const PickerButton = (props: { + name: string; + picked: string; + title: string; + setProto: (string) => void; +}) => { + const on = props.picked === props.name; + return <>; + return ( + + ); +}; + +export function HLSPlayer(props: { src: string }) { + const videoRef = useRef(null); + const { url } = useAquareumNode(); + useEffect(() => { + if (!videoRef.current) { + return; + } + const index = `${url}/api/hls/${props.src}/index.m3u8`; + if (Hls.isSupported()) { + var hls = new Hls(); + hls.loadSource(index); + hls.attachMedia(videoRef.current); + hls.on(Hls.Events.MANIFEST_PARSED, () => { + if (!videoRef.current) { + return; + } + videoRef.current.play(); + }); + return () => { + hls.stopLoad(); + }; + } else if (videoRef.current.canPlayType("application/vnd.apple.mpegurl")) { + videoRef.current.src = index; + videoRef.current.addEventListener("canplay", () => { + if (!videoRef.current) { + return; + } + videoRef.current.play(); + }); + } + }, [videoRef.current]); + return ; +} + +export function WebRTCPlayer(props: { src: string }) { + const videoRef = useRef(null); + const { url } = useAquareumNode(); + useEffect(() => { + if (!videoRef.current) { + return; + } + const client = new WHEPClient( + `${url}/api/webrtc/${props.src}`, + videoRef.current, + ); + return () => { + client.close(); + }; + }, [videoRef.current]); + return ; +} + +const VideoElement = forwardRef( + (props, ref: ForwardedRef) => { + return ( + + + ); + }, +); diff --git a/js/app/components/player/webrtc.tsx b/js/app/components/player/webrtc.tsx new file mode 100644 index 00000000..a38b243b --- /dev/null +++ b/js/app/components/player/webrtc.tsx @@ -0,0 +1,178 @@ +/** + * Example implementation of a client that uses WHEP to playback video over WebRTC + * + * https://www.ietf.org/id/draft-murillo-whep-00.html + */ +export default class WHEPClient { + endpoint: string; + videoElement: HTMLVideoElement; + peerConnection: RTCPeerConnection; + stream: MediaStream; + constructor(endpoint, videoElement) { + this.endpoint = endpoint; + this.videoElement = videoElement; + this.stream = new MediaStream(); + /** + * Create a new WebRTC connection, using public STUN servers with ICE, + * allowing the client to disover its own IP address. + * https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols#ice + */ + this.peerConnection = new RTCPeerConnection({ + // iceServers: [ + // { + // urls: "stun:stun.cloudflare.com:3478", + // }, + // ], + bundlePolicy: "max-bundle", + }); + /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver */ + this.peerConnection.addTransceiver("video", { + direction: "recvonly", + }); + this.peerConnection.addTransceiver("audio", { + direction: "recvonly", + }); + /** + * When new tracks are received in the connection, store local references, + * so that they can be added to a MediaStream, and to the