Start adapting new streamforward to Android

This commit is contained in:
Dmitrii Okunev
2024-10-15 01:57:03 +01:00
parent 765da7c365
commit 197206c17a
77 changed files with 1448 additions and 1373 deletions

119
Makefile
View File

@@ -29,10 +29,10 @@ WINDOWS_CGO_FLAGS?=-I$(PWD)/3rdparty/amd64/windows/vlc-$(WINDOWS_VLC_VERSION)/sd
WINDOWS_LINKER_FLAGS?=-L$(PWD)/3rdparty/amd64/windows/vlc-$(WINDOWS_VLC_VERSION)/sdk/lib -L$(PWD)/3rdparty/amd64/windows/ffmpeg-n7.0.2-19-g45ecf80f0e-win64-gpl-shared-7.0/lib
WINDOWS_PKG_CONFIG_PATH?=$(PWD)/3rdparty/amd64/windows/vlc-$(WINDOWS_VLC_VERSION)/sdk/lib/pkgconfig
all: streampanel-linux-amd64 streampanel-linux-arm64 streampanel-android streampanel-windows
all: streampanel-linux-amd64 streampanel-linux-arm64 streampanel-android-arm64 streampanel-windows
$(GOPATH)/bin/pkg-config-wrapper:
go install github.com/xaionaro-go/pkg-config-wrapper@4cf60a2b85ad0b917e217b13ebcf04b28cc24334
go install github.com/xaionaro-go/pkg-config-wrapper@5dd443e6c18336416c49047e2ba0002e26a85278
3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION):
mkdir -p 3rdparty/arm64
@@ -73,7 +73,7 @@ streampanel-macos-arm64: builddir
3rdparty/arm64/termux-packages:
mkdir -p 3rdparty/arm64/
cd 3rdparty/arm64 && git clone https://github.com/termux/termux-packages
cd 3rdparty/arm64 && git clone --depth=1 -b feat/static_libav https://github.com/xaionaro/termux-packages
3rdparty/arm64/termux-packages/environment-ready: 3rdparty/arm64/termux-packages
cd 3rdparty/arm64/termux-packages && \
@@ -85,11 +85,43 @@ streampanel-macos-arm64: builddir
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./scripts/setup-android-sdk.sh
# downloading dependencies (e.g. we do not need ccls,
# but we need the most of the dependencies of ccls)
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I gettext
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I rust
./scripts/run-docker.sh ./build-package.sh -I ccls
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I termux-api
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I xdotool
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I xdg-utils
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I liblzma
if ! [ -f /data/data/com.termux/files/usr/lib/liblzma.a ]; then \
cd 3rdparty/arm64/termux-packages; \
./scripts/run-docker.sh rm -f /data/data/.built-packages/liblzma; \
./scripts/run-docker.sh ./build-package.sh -I liblzma; \
fi
if ! [ -f /data/data/com.termux/files/usr/lib/libiconv.a ]; then \
cd 3rdparty/arm64/termux-packages; \
./scripts/run-docker.sh rm -f /data/data/.built-packages/libiconv; \
./scripts/run-docker.sh ./build-package.sh -I libiconv; \
fi
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh -I libx11
# building what we need
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh ffmpeg
@@ -97,8 +129,10 @@ streampanel-macos-arm64: builddir
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh libxxf86vm
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh ./build-package.sh vlc
#cd 3rdparty/arm64/termux-packages && \
#./scripts/run-docker.sh ./build-package.sh vlc
# installing fyne
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh sudo apt update
@@ -109,13 +143,21 @@ streampanel-macos-arm64: builddir
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh go install fyne.io/fyne/v2/cmd/fyne@latest
# avoiding fyne loading wrong GL libraries:
rm -f /data/data/com.termux/files/usr/lib/*lib*GL*
# marking to do not redo all the work above next time:
touch 3rdparty/arm64/termux-packages/environment-ready
dockerbuild-streampanel-android: 3rdparty/arm64/termux-packages/environment-ready
dockerbuild-streampanel-android-arm64: 3rdparty/arm64/termux-packages/environment-ready
cd 3rdparty/arm64/termux-packages && \
./scripts/run-docker.sh make ENABLE_VLC="$(ENABLE_VLC)" ENABLE_LIBAV="$(ENABLE_LIBAV)" FORCE_DEBUG="$(FORCE_DEBUG)" -C /project streampanel-android-in-docker
./scripts/run-docker.sh make ENABLE_VLC="$(ENABLE_VLC)" ENABLE_LIBAV="$(ENABLE_LIBAV)" FORCE_DEBUG="$(FORCE_DEBUG)" -C /project streampanel-android-arm64-in-docker
checkconfig-android-in-docker:
@if [ "$(ENABLE_VLC)" != 'false' ]; then \
echo "VLC is not supported for Android builds, yet, please disable it with ENABLE_VLC=false."; \
exit 1; \
fi
checkconfig-android:
@if [ "$(ENABLE_VLC)" != 'false' ]; then \
@@ -123,25 +165,68 @@ checkconfig-android:
exit 1; \
fi
@if [ "$(ENABLE_LIBAV)" != 'false' ]; then \
echo "LibAV is not supported for Android builds, yet, please disable it with ENABLE_LIBAV=false."; \
echo "Building with LibAV support is not supported outside of the docker container yet. Please either disable LibAV with ENABLE_LIBAV=false or use `make dockerbuild-streampanel-android-arm64` instead."; \
exit 1; \
fi
streampanel-android-in-docker: checkconfig-android builddir $(GOPATH)/bin/pkg-config-wrapper
streampanel-android-arm64-in-docker: build-streampanel-android-arm64-in-docker check-streampanel-android-arm64-static-cgo
build-streampanel-android-arm64-in-docker: checkconfig-android-in-docker builddir $(GOPATH)/bin/pkg-config-wrapper
go mod tidy
$(eval ANDROID_NDK_HOME=$(shell ls -d /home/builder/lib/android-ndk-*))
cd cmd/streampanel && PKG_CONFIG_LIBS_FORCE_STATIC='libav*,libvlc' PKG_CONFIG_ERASE="-fopenmp=*,-landroid" PKG_CONFIG='$(GOPATH)/bin/pkg-config-wrapper' PKG_CONFIG_PATH='/data/data/com.termux/files/usr/lib/pkgconfig' CGO_CFLAGS='-I$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/ -I/data/data/com.termux/files/usr/include -Wno-incompatible-function-pointer-types -Wno-unused-result -Wno-xor-used-as-pow' CGO_LDFLAGS='-L$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/ -L/data/data/com.termux/files/usr/lib' ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" PATH="${PATH}:${HOME}/go/bin" fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel.apk ../../build/
cd cmd/streampanel && \
PKG_CONFIG_WRAPPER_LOG='/tmp/pkg_config_wrapper.log' \
PKG_CONFIG_WRAPPER_LOG_LEVEL='trace' \
PKG_CONFIG_LIBS_FORCE_STATIC='libav*,libvlc' \
PKG_CONFIG_ERASE="-fopenmp=*,-landroid" \
PKG_CONFIG='$(GOPATH)/bin/pkg-config-wrapper' \
PKG_CONFIG_PATH='/data/data/com.termux/files/usr/lib/pkgconfig' \
CGO_CFLAGS='-I$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/ -I/data/data/com.termux/files/usr/include -Wno-incompatible-function-pointer-types -Wno-unused-result -Wno-xor-used-as-pow' \
CGO_LDFLAGS='-ldl -lc -L$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/ -L/data/data/com.termux/files/usr/lib' \
ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" \
PATH="${PATH}:${HOME}/go/bin" \
fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel.apk ../../build/streampanel-arm64.apk
streampanel-android-static-cgo: checkconfig-android builddir $(GOPATH)/bin/pkg-config-wrapper 3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION) 3rdparty/arm64/termux
streampanel-android-arm64-static-cgo: build-streampanel-android-arm64-static-cgo check-streampanel-android-arm64-static-cgo
build-streampanel-android-arm64-static-cgo: builddir $(GOPATH)/bin/pkg-config-wrapper 3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION) 3rdparty/arm64/termux
$(eval ANDROID_NDK_HOME=$(PWD)/3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION))
cd cmd/streampanel && PKG_CONFIG_LIBS_FORCE_STATIC='libav*,libvlc' PKG_CONFIG_ERASE="-fopenmp=*,-landroid" PKG_CONFIG='$(GOPATH)/bin/pkg-config-wrapper' PKG_CONFIG_PATH='$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/lib/pkgconfig' CGO_CFLAGS='-I$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/ -I$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/include -Wno-incompatible-function-pointer-types -Wno-unused-result -Wno-xor-used-as-pow' CGO_LDFLAGS='-L$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/ -L$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/lib' ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel.apk ../../build/
cd cmd/streampanel && \
PKG_CONFIG_LIBS_FORCE_STATIC='libav*,libvlc' \
PKG_CONFIG_ERASE="-fopenmp=*,-landroid" \
PKG_CONFIG='$(GOPATH)/bin/pkg-config-wrapper' \
PKG_CONFIG_PATH='$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/lib/pkgconfig' \
CGO_CFLAGS='-I$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/ -I$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/include -Wno-incompatible-function-pointer-types -Wno-unused-result -Wno-xor-used-as-pow' \
CGO_LDFLAGS='-ldl -lc -L$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/ -L$(PWD)/3rdparty/arm64/termux/data/data/com.termux/files/usr/lib' \
ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" \
fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel.apk ../../build/streampanel-arm64.apk
streampanel-android: checkconfig-android builddir 3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION)
check-streampanel-android-arm64-static-cgo:
$(eval TEMP_DIR:=$(shell mktemp -d))
@echo "temp_dir:<$(TEMP_DIR)>"
@cp build/streampanel-arm64.apk "$(TEMP_DIR)/streampanel.zip"
@cd "$(TEMP_DIR)" && unzip streampanel.zip >/dev/null
@if readelf -d "$(TEMP_DIR)/lib/arm64-v8a/libstreampanel.so" | grep STATIC_TLS; then \
readelf -d "$(TEMP_DIR)/lib/arm64-v8a/libstreampanel.so"; \
echo "The resulting APK is linked in a wrong way, 'readlink -d' showed flag 'STATIC_TLS', so the application will crash when you will try to launch it."; \
rm -rf "$(TEMP_DIR)"; \
exit 1; \
fi
@if ! readelf -d "$(TEMP_DIR)/lib/arm64-v8a/libstreampanel.so" | grep libdl.so >/dev/null; then \
readelf -d "$(TEMP_DIR)/lib/arm64-v8a/libstreampanel.so"; \
echo "The resulting APK is linked in a wrong way, 'readlink -d' showed it does not use 'libdl.so', which likely means some wacky stuff happened and the application is not guaranteed to work."; \
rm -rf "$(TEMP_DIR)"; \
exit 1; \
fi
rm -rf "$(TEMP_DIR)"
streampanel-android-arm64: checkconfig-android builddir 3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION)
$(eval ANDROID_NDK_HOME=$(PWD)/3rdparty/arm64/android-ndk-$(ANDROID_NDK_VERSION))
cd cmd/streampanel && ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel.apk ../../build/
cd cmd/streampanel && ANDROID_NDK_HOME="$(ANDROID_NDK_HOME)" fyne package $(FYNEBUILD_FLAGS) -release -os android/arm64 && mv streampanel-arm64.apk ../../build/
install-android:
install-android-arm64:
adb shell pm uninstall center.dx.streampanel >/dev/null 2>&1 || /bin/true
adb install build/streampanel.apk
adb install build/streampanel-arm64.apk
streampanel-ios: builddir
cd cmd/streampanel && fyne package $(GOBUILD_FLAGS) -release -os ios && mv streampanel.ipa ../../build/

View File

@@ -5,4 +5,4 @@ Website = "https://github.com/xaionaro/streamctl"
Name = "streampanel"
ID = "center.dx.streampanel"
Version = "0.1.0"
Build = 188
Build = 300

8
go.mod
View File

@@ -7,14 +7,18 @@ toolchain go1.22.3
// The original go-yaml is very slow, using the improved version instead
replace github.com/goccy/go-yaml v1.11.3 => github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b
replace code.cloudfoundry.org/bytefmt => github.com/cloudfoundry/bytefmt v0.0.0-20211005130812-5bb3c17173e5
replace github.com/andreykaipov/goobs v1.4.1 => github.com/xaionaro-go/goobs v0.0.0-20241009130652-ffb0e76ad260
replace github.com/adrg/libvlc-go/v3 v3.1.5 => github.com/xaionaro-go/libvlc-go/v3 v3.0.0-20241011194409-0fe4e2a9d901
replace fyne.io/fyne/v2 v2.5.0 => github.com/xaionaro-go/fyne/v2 v2.0.0-20241012203222-61bfd3b898c0
replace code.cloudfoundry.org/bytefmt => github.com/cloudfoundry/bytefmt v0.0.0-20211005130812-5bb3c17173e5
replace github.com/pion/ice/v2 => github.com/aler9/ice/v2 v2.0.0-20241006110309-c973995af023
replace github.com/pion/webrtc/v3 => github.com/aler9/webrtc/v3 v3.0.0-20240610104456-eaec24056d06
require (
github.com/facebookincubator/go-belt v0.0.0-20240804203001-846c4409d41c
github.com/go-git/go-billy/v5 v5.5.0

28
go.sum
View File

@@ -73,6 +73,10 @@ github.com/abema/go-mp4 v1.2.0 h1:gi4X8xg/m179N/J15Fn5ugywN9vtI6PLk6iLldHGLAk=
github.com/abema/go-mp4 v1.2.0/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aler9/ice/v2 v2.0.0-20241006110309-c973995af023 h1:z/HjA8RgJDfvF2vca08z77PJvz0jDFK9QDJaZkaOXgk=
github.com/aler9/ice/v2 v2.0.0-20241006110309-c973995af023/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/aler9/webrtc/v3 v3.0.0-20240610104456-eaec24056d06 h1:WtKhXOpd8lgTeXF3RQVOzkNRuy83ygvWEpMYD2aoY3Q=
github.com/aler9/webrtc/v3 v3.0.0-20240610104456-eaec24056d06/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY=
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/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync=
@@ -518,7 +522,6 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw=
@@ -535,47 +538,34 @@ github.com/phuslu/goid v1.0.1 h1:74sob8Rch+WJCROvccSbvxn0Pz5RBvIf57MvesjbPNo=
github.com/phuslu/goid v1.0.1/go.mod h1:txc2fUIdrdnn+v9Vq+QpiPQ3dnrXEchjoVDgic+r+L0=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg=
github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks=
github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI=
github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
github.com/pion/sctp v1.8.13/go.mod h1:YKSgO/bO/6aOMP9LCie1DuD7m+GamiK2yIiPM6vH+GA=
github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY=
github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
@@ -589,8 +579,6 @@ github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uP
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.23 h1:GbqEuxBbVLFhXk0GwxKAoaIJYiEa9TyoZPEZC+2HZxM=
github.com/pion/webrtc/v3 v3.2.23/go.mod h1:1CaT2fcZzZ6VZA+O1i9yK2DU4EOcXVvSbWG9pr5jefs=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -639,7 +627,6 @@ github.com/rymdport/portal v0.2.6 h1:HWmU3gORu7vWcpr7VSwUS2Xx1HtJXVcUuTqEZcMEsIg
github.com/rymdport/portal v0.2.6/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
@@ -813,7 +800,6 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
@@ -914,13 +900,11 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
@@ -1020,7 +1004,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1038,13 +1021,11 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
@@ -1065,7 +1046,6 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

View File

@@ -0,0 +1,27 @@
package livego
import (
"context"
"github.com/xaionaro-go/streamctl/pkg/recoder"
)
type Input struct {
URL string
}
var _ recoder.Input = (*Input)(nil)
func (r *Recoder) NewInputFromURL(
ctx context.Context,
url string,
cfg recoder.InputConfig,
) (recoder.Input, error) {
return &Input{
URL: url,
}, nil
}
func (r *Input) Close() error {
return nil
}

View File

@@ -0,0 +1,25 @@
package livego
import (
"context"
"github.com/xaionaro-go/streamctl/pkg/recoder"
)
type Output struct {
URL string
}
func (r *Recoder) NewOutputFromURL(
ctx context.Context,
url string,
cfg recoder.OutputConfig,
) (recoder.Output, error) {
return &Output{
URL: url,
}, nil
}
func (r *Output) Close() error {
return nil
}

View File

@@ -0,0 +1,72 @@
package livego
import (
"context"
"fmt"
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/xsync"
)
type Recoder struct {
Locker xsync.Mutex
Relay *rtmprelay.RtmpRelay
}
var _ recoder.Recoder = (*Recoder)(nil)
func (r *Recoder) StartRecoding(
ctx context.Context,
inputIface recoder.Input,
outputIface recoder.Output,
) error {
input, ok := inputIface.(*Input)
if !ok {
return fmt.Errorf("expected Input of type %T, but received %T", input, inputIface)
}
output, ok := outputIface.(*Output)
if !ok {
return fmt.Errorf("expected Input of type %T, but received %T", output, outputIface)
}
return xsync.DoR1(ctx, &r.Locker, func() error {
relay := rtmprelay.NewRtmpRelay(&input.URL, &output.URL)
if err := relay.Start(); err != nil {
return fmt.Errorf("unable to start RTMP relay from '%s' to '%s': %w", input.URL, output.URL, err)
}
r.Relay = relay
return nil
})
}
func (r *Recoder) WaitForRecordingEnd(
ctx context.Context,
) error {
return xsync.DoR1(ctx, &r.Locker, func() error {
if r.Relay != nil {
return fmt.Errorf("recoder is not started (or is closed)")
}
panic("do not know how to implement this, without changing the upstream library, yet")
})
}
func (r *Recoder) GetStats(
ctx context.Context,
) (*recoder.Stats, error) {
return &recoder.Stats{}, nil
}
func (r *Recoder) Close() error {
ctx := context.TODO()
return xsync.DoR1(ctx, &r.Locker, func() error {
if r.Relay != nil {
return fmt.Errorf("recoder is not started (or is closed)")
}
err := r.Relay.Start()
r.Relay = nil
return err
})
}

View File

@@ -0,0 +1,19 @@
package livego
import (
"context"
"github.com/xaionaro-go/streamctl/pkg/recoder"
)
type RecoderFactory struct{}
var _ recoder.Factory = (*RecoderFactory)(nil)
func NewRecoderFactory() *RecoderFactory {
return &RecoderFactory{}
}
func (RecoderFactory) New(ctx context.Context, cfg recoder.Config) (recoder.Recoder, error) {
return &Recoder{}, nil
}

9
pkg/recoder/input.go Normal file
View File

@@ -0,0 +1,9 @@
package recoder
import (
"io"
)
type Input interface {
io.Closer
}

View File

@@ -1,11 +1,11 @@
package streamforward
package libav
import (
"context"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder"
)
type Recoder struct {

View File

@@ -11,7 +11,7 @@ import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
"github.com/spf13/pflag"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/recoder"
)
func main() {

View File

@@ -9,10 +9,10 @@ import (
"github.com/asticode/go-astiav"
"github.com/asticode/go-astikit"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder/types"
"github.com/xaionaro-go/streamctl/pkg/recoder"
)
type OutputConfig = types.OutputConfig
type OutputConfig = recoder.OutputConfig
type Output struct {
*astikit.Closer

View File

@@ -9,10 +9,11 @@ import (
"github.com/asticode/go-astiav"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder/types"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/recoder/types"
)
type RecoderConfig = types.RecoderConfig
type RecoderConfig = recoder.Config
type Packet = types.Packet
type RecoderStats struct {

View File

@@ -1,11 +1,11 @@
package streamforward
package libav
import (
"context"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder"
)
type RecoderFactory struct{}

View File

@@ -703,6 +703,176 @@ func (x *NewRecoderReply) GetId() uint64 {
return 0
}
type CloseInputRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InputID uint64 `protobuf:"varint,1,opt,name=inputID,proto3" json:"inputID,omitempty"`
}
func (x *CloseInputRequest) Reset() {
*x = CloseInputRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseInputRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseInputRequest) ProtoMessage() {}
func (x *CloseInputRequest) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseInputRequest.ProtoReflect.Descriptor instead.
func (*CloseInputRequest) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{13}
}
func (x *CloseInputRequest) GetInputID() uint64 {
if x != nil {
return x.InputID
}
return 0
}
type CloseInputReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *CloseInputReply) Reset() {
*x = CloseInputReply{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseInputReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseInputReply) ProtoMessage() {}
func (x *CloseInputReply) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseInputReply.ProtoReflect.Descriptor instead.
func (*CloseInputReply) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{14}
}
type CloseOutputRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
OutputID uint64 `protobuf:"varint,2,opt,name=outputID,proto3" json:"outputID,omitempty"`
}
func (x *CloseOutputRequest) Reset() {
*x = CloseOutputRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseOutputRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseOutputRequest) ProtoMessage() {}
func (x *CloseOutputRequest) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseOutputRequest.ProtoReflect.Descriptor instead.
func (*CloseOutputRequest) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{15}
}
func (x *CloseOutputRequest) GetOutputID() uint64 {
if x != nil {
return x.OutputID
}
return 0
}
type CloseOutputReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *CloseOutputReply) Reset() {
*x = CloseOutputReply{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseOutputReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseOutputReply) ProtoMessage() {}
func (x *CloseOutputReply) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseOutputReply.ProtoReflect.Descriptor instead.
func (*CloseOutputReply) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{16}
}
type GetRecoderStatsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -714,7 +884,7 @@ type GetRecoderStatsRequest struct {
func (x *GetRecoderStatsRequest) Reset() {
*x = GetRecoderStatsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[13]
mi := &file_recoder_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -727,7 +897,7 @@ func (x *GetRecoderStatsRequest) String() string {
func (*GetRecoderStatsRequest) ProtoMessage() {}
func (x *GetRecoderStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[13]
mi := &file_recoder_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -740,7 +910,7 @@ func (x *GetRecoderStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetRecoderStatsRequest.ProtoReflect.Descriptor instead.
func (*GetRecoderStatsRequest) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{13}
return file_recoder_proto_rawDescGZIP(), []int{17}
}
func (x *GetRecoderStatsRequest) GetRecoderID() uint64 {
@@ -762,7 +932,7 @@ type GetRecoderStatsReply struct {
func (x *GetRecoderStatsReply) Reset() {
*x = GetRecoderStatsReply{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[14]
mi := &file_recoder_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -775,7 +945,7 @@ func (x *GetRecoderStatsReply) String() string {
func (*GetRecoderStatsReply) ProtoMessage() {}
func (x *GetRecoderStatsReply) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[14]
mi := &file_recoder_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -788,7 +958,7 @@ func (x *GetRecoderStatsReply) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetRecoderStatsReply.ProtoReflect.Descriptor instead.
func (*GetRecoderStatsReply) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{14}
return file_recoder_proto_rawDescGZIP(), []int{18}
}
func (x *GetRecoderStatsReply) GetBytesCountRead() uint64 {
@@ -818,7 +988,7 @@ type StartRecodingRequest struct {
func (x *StartRecodingRequest) Reset() {
*x = StartRecodingRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[15]
mi := &file_recoder_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -831,7 +1001,7 @@ func (x *StartRecodingRequest) String() string {
func (*StartRecodingRequest) ProtoMessage() {}
func (x *StartRecodingRequest) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[15]
mi := &file_recoder_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -844,7 +1014,7 @@ func (x *StartRecodingRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use StartRecodingRequest.ProtoReflect.Descriptor instead.
func (*StartRecodingRequest) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{15}
return file_recoder_proto_rawDescGZIP(), []int{19}
}
func (x *StartRecodingRequest) GetRecoderID() uint64 {
@@ -877,7 +1047,7 @@ type StartRecodingReply struct {
func (x *StartRecodingReply) Reset() {
*x = StartRecodingReply{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[16]
mi := &file_recoder_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -890,7 +1060,7 @@ func (x *StartRecodingReply) String() string {
func (*StartRecodingReply) ProtoMessage() {}
func (x *StartRecodingReply) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[16]
mi := &file_recoder_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -903,7 +1073,7 @@ func (x *StartRecodingReply) ProtoReflect() protoreflect.Message {
// Deprecated: Use StartRecodingReply.ProtoReflect.Descriptor instead.
func (*StartRecodingReply) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{16}
return file_recoder_proto_rawDescGZIP(), []int{20}
}
type RecodingEndedChanRequest struct {
@@ -917,7 +1087,7 @@ type RecodingEndedChanRequest struct {
func (x *RecodingEndedChanRequest) Reset() {
*x = RecodingEndedChanRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[17]
mi := &file_recoder_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -930,7 +1100,7 @@ func (x *RecodingEndedChanRequest) String() string {
func (*RecodingEndedChanRequest) ProtoMessage() {}
func (x *RecodingEndedChanRequest) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[17]
mi := &file_recoder_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -943,7 +1113,7 @@ func (x *RecodingEndedChanRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RecodingEndedChanRequest.ProtoReflect.Descriptor instead.
func (*RecodingEndedChanRequest) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{17}
return file_recoder_proto_rawDescGZIP(), []int{21}
}
func (x *RecodingEndedChanRequest) GetRecoderID() uint64 {
@@ -962,7 +1132,7 @@ type RecodingEndedChanReply struct {
func (x *RecodingEndedChanReply) Reset() {
*x = RecodingEndedChanReply{}
if protoimpl.UnsafeEnabled {
mi := &file_recoder_proto_msgTypes[18]
mi := &file_recoder_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -975,7 +1145,7 @@ func (x *RecodingEndedChanReply) String() string {
func (*RecodingEndedChanReply) ProtoMessage() {}
func (x *RecodingEndedChanReply) ProtoReflect() protoreflect.Message {
mi := &file_recoder_proto_msgTypes[18]
mi := &file_recoder_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -988,7 +1158,7 @@ func (x *RecodingEndedChanReply) ProtoReflect() protoreflect.Message {
// Deprecated: Use RecodingEndedChanReply.ProtoReflect.Descriptor instead.
func (*RecodingEndedChanReply) Descriptor() ([]byte, []int) {
return file_recoder_proto_rawDescGZIP(), []int{18}
return file_recoder_proto_rawDescGZIP(), []int{22}
}
var File_recoder_proto protoreflect.FileDescriptor
@@ -1037,84 +1207,102 @@ var file_recoder_proto_rawDesc = []byte{
0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x52, 0x65,
0x63, 0x6f, 0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x36, 0x0a, 0x16, 0x47, 0x65,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49,
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x49, 0x44, 0x22, 0x68, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x62, 0x79,
0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
0x61, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
0x57, 0x72, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x62, 0x79, 0x74,
0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x72, 0x6f, 0x74, 0x65, 0x22, 0x6a, 0x0a, 0x14,
0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49,
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20,
0x01, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x72,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x38,
0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43,
0x68, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65,
0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72,
0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f,
0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x2a, 0xc3, 0x01, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65,
0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65,
0x76, 0x65, 0x6c, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67,
0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x10, 0x01,
0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x50, 0x61, 0x6e, 0x69, 0x63, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69,
0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x12, 0x14,
0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x57, 0x61,
0x72, 0x6e, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c,
0x65, 0x76, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f,
0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10,
0x06, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65,
0x6c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x10, 0x07, 0x32, 0xee, 0x04, 0x0a, 0x07, 0x52, 0x65, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x12, 0x5d, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x69,
0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65,
0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e,
0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74,
0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12,
0x1d, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e,
0x65, 0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65,
0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4b, 0x0a,
0x09, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74,
0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x65, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74,
0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0a, 0x4e, 0x65,
0x77, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0f, 0x47, 0x65,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x24, 0x2e,
0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74,
0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72,
0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61,
0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0d, 0x53, 0x74, 0x61,
0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x22, 0x2e, 0x72, 0x65, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52,
0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74,
0x61, 0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x65, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e,
0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x12, 0x26, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65,
0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45,
0x6e, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x24, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e,
0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x6f, 0x2f,
0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2d, 0x0a, 0x11, 0x43, 0x6c,
0x6f, 0x73, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x07, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x30, 0x0a, 0x12,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44, 0x22, 0x12,
0x0a, 0x10, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x36, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,
0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x22, 0x68, 0x0a, 0x14, 0x47, 0x65,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
0x52, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x62, 0x79, 0x74, 0x65,
0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x79,
0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x72, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x57,
0x72, 0x6f, 0x74, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63,
0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,
0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44,
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x44,
0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e,
0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69,
0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44,
0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65,
0x64, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2a, 0xc3, 0x01, 0x0a, 0x0c, 0x4c,
0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x10, 0x4c,
0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4e, 0x6f, 0x6e, 0x65, 0x10,
0x00, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65,
0x6c, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67,
0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x50, 0x61, 0x6e, 0x69, 0x63, 0x10, 0x02, 0x12,
0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x45,
0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x57, 0x61, 0x72, 0x6e, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10,
0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f,
0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76,
0x65, 0x6c, 0x44, 0x65, 0x62, 0x75, 0x67, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x6f, 0x67,
0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x10, 0x07,
0x32, 0x91, 0x06, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x12, 0x5d, 0x0a, 0x0f,
0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12,
0x24, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f,
0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c,
0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x08, 0x4e,
0x65, 0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65,
0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x09, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70,
0x63, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70,
0x63, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x12, 0x1f, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e,
0x4e, 0x65, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63,
0x2e, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74,
0x12, 0x1f, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x12, 0x51, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x12, 0x20, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72,
0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22,
0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63,
0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x22, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f,
0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69,
0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65,
0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x65, 0x0a,
0x11, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x68,
0x61, 0x6e, 0x12, 0x26, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70,
0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43,
0x68, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x72, 0x65, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x64, 0x69,
0x6e, 0x67, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x30, 0x01, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x5f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1130,7 +1318,7 @@ func file_recoder_proto_rawDescGZIP() []byte {
}
var file_recoder_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_recoder_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
var file_recoder_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_recoder_proto_goTypes = []interface{}{
(LoggingLevel)(0), // 0: recoder_grpc.LoggingLevel
(*SetLoggingLevelRequest)(nil), // 1: recoder_grpc.SetLoggingLevelRequest
@@ -1146,12 +1334,16 @@ var file_recoder_proto_goTypes = []interface{}{
(*RecoderConfig)(nil), // 11: recoder_grpc.RecoderConfig
(*NewRecoderRequest)(nil), // 12: recoder_grpc.NewRecoderRequest
(*NewRecoderReply)(nil), // 13: recoder_grpc.NewRecoderReply
(*GetRecoderStatsRequest)(nil), // 14: recoder_grpc.GetRecoderStatsRequest
(*GetRecoderStatsReply)(nil), // 15: recoder_grpc.GetRecoderStatsReply
(*StartRecodingRequest)(nil), // 16: recoder_grpc.StartRecodingRequest
(*StartRecodingReply)(nil), // 17: recoder_grpc.StartRecodingReply
(*RecodingEndedChanRequest)(nil), // 18: recoder_grpc.RecodingEndedChanRequest
(*RecodingEndedChanReply)(nil), // 19: recoder_grpc.RecodingEndedChanReply
(*CloseInputRequest)(nil), // 14: recoder_grpc.CloseInputRequest
(*CloseInputReply)(nil), // 15: recoder_grpc.CloseInputReply
(*CloseOutputRequest)(nil), // 16: recoder_grpc.CloseOutputRequest
(*CloseOutputReply)(nil), // 17: recoder_grpc.CloseOutputReply
(*GetRecoderStatsRequest)(nil), // 18: recoder_grpc.GetRecoderStatsRequest
(*GetRecoderStatsReply)(nil), // 19: recoder_grpc.GetRecoderStatsReply
(*StartRecodingRequest)(nil), // 20: recoder_grpc.StartRecodingRequest
(*StartRecodingReply)(nil), // 21: recoder_grpc.StartRecodingReply
(*RecodingEndedChanRequest)(nil), // 22: recoder_grpc.RecodingEndedChanRequest
(*RecodingEndedChanReply)(nil), // 23: recoder_grpc.RecodingEndedChanReply
}
var file_recoder_proto_depIdxs = []int32{
0, // 0: recoder_grpc.SetLoggingLevelRequest.level:type_name -> recoder_grpc.LoggingLevel
@@ -1164,18 +1356,22 @@ var file_recoder_proto_depIdxs = []int32{
6, // 7: recoder_grpc.Recoder.NewInput:input_type -> recoder_grpc.NewInputRequest
9, // 8: recoder_grpc.Recoder.NewOutput:input_type -> recoder_grpc.NewOutputRequest
12, // 9: recoder_grpc.Recoder.NewRecoder:input_type -> recoder_grpc.NewRecoderRequest
14, // 10: recoder_grpc.Recoder.GetRecoderStats:input_type -> recoder_grpc.GetRecoderStatsRequest
16, // 11: recoder_grpc.Recoder.StartRecoding:input_type -> recoder_grpc.StartRecodingRequest
18, // 12: recoder_grpc.Recoder.RecodingEndedChan:input_type -> recoder_grpc.RecodingEndedChanRequest
2, // 13: recoder_grpc.Recoder.SetLoggingLevel:output_type -> recoder_grpc.SetLoggingLevelReply
7, // 14: recoder_grpc.Recoder.NewInput:output_type -> recoder_grpc.NewInputReply
10, // 15: recoder_grpc.Recoder.NewOutput:output_type -> recoder_grpc.NewOutputReply
13, // 16: recoder_grpc.Recoder.NewRecoder:output_type -> recoder_grpc.NewRecoderReply
15, // 17: recoder_grpc.Recoder.GetRecoderStats:output_type -> recoder_grpc.GetRecoderStatsReply
17, // 18: recoder_grpc.Recoder.StartRecoding:output_type -> recoder_grpc.StartRecodingReply
19, // 19: recoder_grpc.Recoder.RecodingEndedChan:output_type -> recoder_grpc.RecodingEndedChanReply
13, // [13:20] is the sub-list for method output_type
6, // [6:13] is the sub-list for method input_type
14, // 10: recoder_grpc.Recoder.CloseInput:input_type -> recoder_grpc.CloseInputRequest
16, // 11: recoder_grpc.Recoder.CloseOutput:input_type -> recoder_grpc.CloseOutputRequest
18, // 12: recoder_grpc.Recoder.GetRecoderStats:input_type -> recoder_grpc.GetRecoderStatsRequest
20, // 13: recoder_grpc.Recoder.StartRecoding:input_type -> recoder_grpc.StartRecodingRequest
22, // 14: recoder_grpc.Recoder.RecodingEndedChan:input_type -> recoder_grpc.RecodingEndedChanRequest
2, // 15: recoder_grpc.Recoder.SetLoggingLevel:output_type -> recoder_grpc.SetLoggingLevelReply
7, // 16: recoder_grpc.Recoder.NewInput:output_type -> recoder_grpc.NewInputReply
10, // 17: recoder_grpc.Recoder.NewOutput:output_type -> recoder_grpc.NewOutputReply
13, // 18: recoder_grpc.Recoder.NewRecoder:output_type -> recoder_grpc.NewRecoderReply
15, // 19: recoder_grpc.Recoder.CloseInput:output_type -> recoder_grpc.CloseInputReply
17, // 20: recoder_grpc.Recoder.CloseOutput:output_type -> recoder_grpc.CloseOutputReply
19, // 21: recoder_grpc.Recoder.GetRecoderStats:output_type -> recoder_grpc.GetRecoderStatsReply
21, // 22: recoder_grpc.Recoder.StartRecoding:output_type -> recoder_grpc.StartRecodingReply
23, // 23: recoder_grpc.Recoder.RecodingEndedChan:output_type -> recoder_grpc.RecodingEndedChanReply
15, // [15:24] is the sub-list for method output_type
6, // [6:15] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
@@ -1344,7 +1540,7 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRecoderStatsRequest); i {
switch v := v.(*CloseInputRequest); i {
case 0:
return &v.state
case 1:
@@ -1356,7 +1552,7 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRecoderStatsReply); i {
switch v := v.(*CloseInputReply); i {
case 0:
return &v.state
case 1:
@@ -1368,7 +1564,7 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartRecodingRequest); i {
switch v := v.(*CloseOutputRequest); i {
case 0:
return &v.state
case 1:
@@ -1380,7 +1576,7 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartRecodingReply); i {
switch v := v.(*CloseOutputReply); i {
case 0:
return &v.state
case 1:
@@ -1392,7 +1588,7 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RecodingEndedChanRequest); i {
switch v := v.(*GetRecoderStatsRequest); i {
case 0:
return &v.state
case 1:
@@ -1404,6 +1600,54 @@ func file_recoder_proto_init() {
}
}
file_recoder_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRecoderStatsReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_recoder_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartRecodingRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_recoder_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartRecodingReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_recoder_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RecodingEndedChanRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_recoder_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RecodingEndedChanReply); i {
case 0:
return &v.state
@@ -1425,7 +1669,7 @@ func file_recoder_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_recoder_proto_rawDesc,
NumEnums: 1,
NumMessages: 19,
NumMessages: 23,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -26,6 +26,8 @@ type RecoderClient interface {
NewInput(ctx context.Context, in *NewInputRequest, opts ...grpc.CallOption) (*NewInputReply, error)
NewOutput(ctx context.Context, in *NewOutputRequest, opts ...grpc.CallOption) (*NewOutputReply, error)
NewRecoder(ctx context.Context, in *NewRecoderRequest, opts ...grpc.CallOption) (*NewRecoderReply, error)
CloseInput(ctx context.Context, in *CloseInputRequest, opts ...grpc.CallOption) (*CloseInputReply, error)
CloseOutput(ctx context.Context, in *CloseOutputRequest, opts ...grpc.CallOption) (*CloseOutputReply, error)
GetRecoderStats(ctx context.Context, in *GetRecoderStatsRequest, opts ...grpc.CallOption) (*GetRecoderStatsReply, error)
StartRecoding(ctx context.Context, in *StartRecodingRequest, opts ...grpc.CallOption) (*StartRecodingReply, error)
RecodingEndedChan(ctx context.Context, in *RecodingEndedChanRequest, opts ...grpc.CallOption) (Recoder_RecodingEndedChanClient, error)
@@ -75,6 +77,24 @@ func (c *recoderClient) NewRecoder(ctx context.Context, in *NewRecoderRequest, o
return out, nil
}
func (c *recoderClient) CloseInput(ctx context.Context, in *CloseInputRequest, opts ...grpc.CallOption) (*CloseInputReply, error) {
out := new(CloseInputReply)
err := c.cc.Invoke(ctx, "/recoder_grpc.Recoder/CloseInput", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *recoderClient) CloseOutput(ctx context.Context, in *CloseOutputRequest, opts ...grpc.CallOption) (*CloseOutputReply, error) {
out := new(CloseOutputReply)
err := c.cc.Invoke(ctx, "/recoder_grpc.Recoder/CloseOutput", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *recoderClient) GetRecoderStats(ctx context.Context, in *GetRecoderStatsRequest, opts ...grpc.CallOption) (*GetRecoderStatsReply, error) {
out := new(GetRecoderStatsReply)
err := c.cc.Invoke(ctx, "/recoder_grpc.Recoder/GetRecoderStats", in, out, opts...)
@@ -133,6 +153,8 @@ type RecoderServer interface {
NewInput(context.Context, *NewInputRequest) (*NewInputReply, error)
NewOutput(context.Context, *NewOutputRequest) (*NewOutputReply, error)
NewRecoder(context.Context, *NewRecoderRequest) (*NewRecoderReply, error)
CloseInput(context.Context, *CloseInputRequest) (*CloseInputReply, error)
CloseOutput(context.Context, *CloseOutputRequest) (*CloseOutputReply, error)
GetRecoderStats(context.Context, *GetRecoderStatsRequest) (*GetRecoderStatsReply, error)
StartRecoding(context.Context, *StartRecodingRequest) (*StartRecodingReply, error)
RecodingEndedChan(*RecodingEndedChanRequest, Recoder_RecodingEndedChanServer) error
@@ -155,6 +177,12 @@ func (UnimplementedRecoderServer) NewOutput(context.Context, *NewOutputRequest)
func (UnimplementedRecoderServer) NewRecoder(context.Context, *NewRecoderRequest) (*NewRecoderReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method NewRecoder not implemented")
}
func (UnimplementedRecoderServer) CloseInput(context.Context, *CloseInputRequest) (*CloseInputReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseInput not implemented")
}
func (UnimplementedRecoderServer) CloseOutput(context.Context, *CloseOutputRequest) (*CloseOutputReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseOutput not implemented")
}
func (UnimplementedRecoderServer) GetRecoderStats(context.Context, *GetRecoderStatsRequest) (*GetRecoderStatsReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRecoderStats not implemented")
}
@@ -249,6 +277,42 @@ func _Recoder_NewRecoder_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _Recoder_CloseInput_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CloseInputRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RecoderServer).CloseInput(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/recoder_grpc.Recoder/CloseInput",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RecoderServer).CloseInput(ctx, req.(*CloseInputRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Recoder_CloseOutput_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CloseOutputRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RecoderServer).CloseOutput(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/recoder_grpc.Recoder/CloseOutput",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RecoderServer).CloseOutput(ctx, req.(*CloseOutputRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Recoder_GetRecoderStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRecoderStatsRequest)
if err := dec(in); err != nil {
@@ -329,6 +393,14 @@ var Recoder_ServiceDesc = grpc.ServiceDesc{
MethodName: "NewRecoder",
Handler: _Recoder_NewRecoder_Handler,
},
{
MethodName: "CloseInput",
Handler: _Recoder_CloseInput_Handler,
},
{
MethodName: "CloseOutput",
Handler: _Recoder_CloseOutput_Handler,
},
{
MethodName: "GetRecoderStats",
Handler: _Recoder_GetRecoderStats_Handler,

View File

@@ -7,6 +7,8 @@ service Recoder {
rpc NewInput(NewInputRequest) returns (NewInputReply) {}
rpc NewOutput(NewOutputRequest) returns (NewOutputReply) {}
rpc NewRecoder(NewRecoderRequest) returns (NewRecoderReply) {}
rpc CloseInput(CloseInputRequest) returns (CloseInputReply) {}
rpc CloseOutput(CloseOutputRequest) returns (CloseOutputReply) {}
rpc GetRecoderStats(GetRecoderStatsRequest) returns (GetRecoderStatsReply) {}
rpc StartRecoding(StartRecodingRequest) returns (StartRecodingReply) {}
rpc RecodingEndedChan(RecodingEndedChanRequest) returns (stream RecodingEndedChanReply) {}
@@ -72,6 +74,15 @@ message NewRecoderReply {
uint64 id = 1;
}
message CloseInputRequest {
uint64 inputID = 1;
}
message CloseInputReply {}
message CloseOutputRequest {
uint64 outputID = 2;
}
message CloseOutputReply {}
message GetRecoderStatsRequest {
uint64 recoderID = 1;
}

View File

@@ -3,7 +3,7 @@ package saferecoder
import (
"context"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process"
)
type InputID = process.InputID
@@ -28,3 +28,7 @@ func (p *Process) NewInputFromURL(
ID: inputID,
}, nil
}
func (input *Input) Close() error {
return input.Process.Client.CloseInput(context.Background(), input.ID)
}

View File

@@ -3,7 +3,7 @@ package saferecoder
import (
"context"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process"
)
type OutputID = process.OutputID
@@ -28,3 +28,7 @@ func (p *Process) NewOutputFromURL(
ID: outputID,
}, nil
}
func (output *Output) Close() error {
return output.Process.Client.CloseOutput(context.Background(), output.ID)
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process"
)
type processBackend = process.Recoder

View File

@@ -7,9 +7,8 @@ import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder/types"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/grpc/go/recoder_grpc"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
@@ -54,7 +53,7 @@ func (c *Client) SetLoggingLevel(
return nil
}
type InputConfig = types.InputConfig
type InputConfig = recoder.InputConfig
type InputID uint64
func (c *Client) NewInputFromURL(
@@ -83,6 +82,26 @@ func (c *Client) NewInputFromURL(
return InputID(resp.GetId()), nil
}
func (c *Client) CloseInput(
ctx context.Context,
inputID InputID,
) error {
client, conn, err := c.grpcClient()
if err != nil {
return err
}
defer conn.Close()
_, err = client.CloseInput(ctx, &recoder_grpc.CloseInputRequest{
InputID: uint64(inputID),
})
if err != nil {
return fmt.Errorf("query error: %w", err)
}
return nil
}
type OutputID uint64
type OutputConfig = recoder.OutputConfig
@@ -112,8 +131,28 @@ func (c *Client) NewOutputFromURL(
return OutputID(resp.GetId()), nil
}
func (c *Client) CloseOutput(
ctx context.Context,
outputID OutputID,
) error {
client, conn, err := c.grpcClient()
if err != nil {
return err
}
defer conn.Close()
_, err = client.CloseOutput(ctx, &recoder_grpc.CloseOutputRequest{
OutputID: uint64(outputID),
})
if err != nil {
return fmt.Errorf("query error: %w", err)
}
return nil
}
type RecoderID uint64
type RecoderConfig = types.RecoderConfig
type RecoderConfig = recoder.Config
func (c *Client) NewRecoder(
ctx context.Context,

View File

@@ -2,7 +2,7 @@ package client
import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/grpc/go/recoder_grpc"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
)
func logLevelGo2Protobuf(logLevel logger.Level) recoder_grpc.LoggingLevel {

View File

@@ -11,7 +11,7 @@ import (
"os"
"github.com/facebookincubator/go-belt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process/server"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process/server"
)
const (

View File

@@ -14,8 +14,8 @@ import (
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process/client"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process/client"
)
type InputID = client.InputID

View File

@@ -7,7 +7,7 @@ import (
"context"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
)
type Recoder struct {

View File

@@ -2,7 +2,7 @@ package server
import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/grpc/go/recoder_grpc"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
)
func logLevelProtobuf2Go(logLevel recoder_grpc.LoggingLevel) logger.Level {

View File

@@ -8,8 +8,8 @@ import (
"github.com/facebookincubator/go-belt"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/grpc/go/recoder_grpc"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
"github.com/xaionaro-go/streamctl/pkg/xcontext"
"github.com/xaionaro-go/streamctl/pkg/xsync"
"google.golang.org/grpc"
@@ -122,6 +122,26 @@ func (srv *GRPCServer) newInputByURL(
}, nil
}
func (srv *GRPCServer) CloseInput(
ctx context.Context,
req *recoder_grpc.CloseInputRequest,
) (*recoder_grpc.CloseInputReply, error) {
inputID := InputID(req.GetInputID())
err := xsync.DoR1(ctx, &srv.InputLocker, func() error {
input := srv.Input[inputID]
if input == nil {
return fmt.Errorf("there is no open input with ID %d", inputID)
}
input.Close()
delete(srv.Input, inputID)
return nil
})
if err != nil {
return nil, err
}
return &recoder_grpc.CloseInputReply{}, nil
}
func (srv *GRPCServer) NewOutput(
ctx context.Context,
req *recoder_grpc.NewOutputRequest,
@@ -156,6 +176,26 @@ func (srv *GRPCServer) newOutputByURL(
}, nil
}
func (srv *GRPCServer) CloseOutput(
ctx context.Context,
req *recoder_grpc.CloseOutputRequest,
) (*recoder_grpc.CloseOutputReply, error) {
outputID := OutputID(req.GetOutputID())
err := xsync.DoR1(ctx, &srv.InputLocker, func() error {
output := srv.Output[outputID]
if output == nil {
return fmt.Errorf("there is no open output with ID %d", outputID)
}
output.Close()
delete(srv.Output, outputID)
return nil
})
if err != nil {
return nil, err
}
return &recoder_grpc.CloseOutputReply{}, nil
}
func (srv *GRPCServer) NewRecoder(
ctx context.Context,
req *recoder_grpc.NewRecoderRequest,

View File

@@ -4,9 +4,9 @@ import (
"context"
"fmt"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/recoder/types"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/libav/saferecoder/process"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/recoder/types"
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process"
)
type Packet = types.Packet

9
pkg/recoder/output.go Normal file
View File

@@ -0,0 +1,9 @@
package recoder
import (
"io"
)
type Output interface {
io.Closer
}

View File

@@ -3,12 +3,9 @@ package recoder
import (
"context"
"io"
)
type Stats struct {
BytesCountRead uint64
BytesCountWrote uint64
}
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
)
type Recoder interface {
io.Closer
@@ -20,6 +17,10 @@ type Recoder interface {
GetStats(context.Context) (*Stats, error)
}
type NewInputFromPublisherer interface {
NewInputFromPublisher(context.Context, types.Publisher, InputConfig) (Input, error)
}
type Factory interface {
New(context.Context, Config) (Recoder, error)
}

6
pkg/recoder/stats.go Normal file
View File

@@ -0,0 +1,6 @@
package recoder
type Stats struct {
BytesCountRead uint64
BytesCountWrote uint64
}

View File

@@ -0,0 +1,107 @@
package xaionarogortmp
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/go-rtmp"
rtmpmsg "github.com/xaionaro-go/go-rtmp/message"
"github.com/xaionaro-go/streamctl/pkg/recoder"
xaionarogortmp "github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
"github.com/xaionaro-go/streamctl/pkg/xsync"
)
type Input struct {
xsync.Mutex
Client *rtmp.ClientConn
Pubsub *xaionarogortmp.Pubsub
}
var _ recoder.Input = (*Input)(nil)
func streamID2LocalAppName(
streamID types.StreamID,
) types.AppKey {
streamIDParts := strings.Split(string(streamID), "/")
localAppName := string(streamID)
if len(streamIDParts) == 2 {
localAppName = streamIDParts[1]
}
return types.AppKey(localAppName)
}
func (r *Recoder) NewInputFromPublisher(
ctx context.Context,
publisherIface types.Publisher,
cfg recoder.InputConfig,
) (recoder.Input, error) {
publisher, ok := publisherIface.(*xaionarogortmp.Pubsub)
if !ok {
return nil, fmt.Errorf("expected a publisher or type %T, but received %T", publisherIface, publisher)
}
return &Input{
Pubsub: publisher,
}, nil
}
func (r *Recoder) NewInputFromURL(
ctx context.Context,
urlString string,
cfg recoder.InputConfig,
) (_ recoder.Input, _err error) {
inClient, err := newRTMPClient(ctx, urlString)
if err != nil {
return nil, fmt.Errorf("unable to connect to the input endpoint '%s': %w", urlString, err)
}
defer func() {
if _err != nil {
err := inClient.Close()
if err != nil {
logger.Errorf(ctx, "unable to close the client for the input endpoint: %w", err)
}
}
}()
url, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("unable to parse URL '%s': %w", urlString, err)
}
remoteAppName, _ := getAppNameAndKey(url.Path)
tcURL := *url
tcURL.Path = "/" + remoteAppName
if tcURL.Port() == "1935" {
tcURL.Host = tcURL.Hostname()
}
err = inClient.Connect(ctx, &rtmpmsg.NetConnectionConnect{
Command: rtmpmsg.NetConnectionConnectCommand{
App: remoteAppName,
Type: "nonprivate",
FlashVer: "StreamPanel",
TCURL: tcURL.String(),
},
})
if err != nil {
return nil, fmt.Errorf("got an error on command 'Connect' to the input endpoint '%s': %w", urlString, err)
}
return nil, fmt.Errorf("not implemented, yet")
}
func (input *Input) Close() error {
var err error
ctx := context.TODO()
input.Do(ctx, func() {
if input.Client == nil {
return
}
err = input.Client.Close()
input.Client = nil
})
return err
}

View File

@@ -1,10 +1,11 @@
package streamforward
package xaionarogortmp
import (
"context"
"fmt"
"net/http"
"net/url"
"runtime/debug"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/go-rtmp"
@@ -13,8 +14,24 @@ import (
func newRTMPClient(
ctx context.Context,
url url.URL,
) (*rtmp.ClientConn, error) {
urlString string,
) (_ *rtmp.ClientConn, _err error) {
defer func() {
if r := recover(); r != nil {
_err = fmt.Errorf("got panic: %v", r)
}
if _err == nil {
return
}
logger.FromCtx(ctx).
WithField("error_event_exception_stack_trace", string(debug.Stack())).Errorf("%v", _err)
}()
url, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("unable to parse URL '%s': %w", urlString, err)
}
logger.Debugf(ctx, "connecting to '%s'", url.String())
if url.Port() == "" {
switch url.Scheme {
@@ -44,5 +61,6 @@ func newRTMPClient(
return nil, fmt.Errorf("unable to connect to '%s': %w", url.String(), err)
}
logger.Debugf(ctx, "connected to '%s'", url)
return client, nil
}

View File

@@ -0,0 +1,90 @@
package xaionarogortmp
import (
"context"
"fmt"
"net/url"
"runtime/debug"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/hashicorp/go-multierror"
"github.com/xaionaro-go/go-rtmp"
rtmpmsg "github.com/xaionaro-go/go-rtmp/message"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/xsync"
)
type Output struct {
xsync.Mutex
Client *rtmp.ClientConn
AppKey string
}
var _ recoder.Output = (*Output)(nil)
func (r *Recoder) NewOutputFromURL(
ctx context.Context,
urlString string,
cfg recoder.OutputConfig,
) (_ recoder.Output, _err error) {
var output *Output
defer func() {
if r := recover(); r != nil {
_err = fmt.Errorf("got panic: %v", r)
}
if _err == nil {
return
}
logger.FromCtx(ctx).
WithField("error_event_exception_stack_trace", string(debug.Stack())).Errorf("%v", _err)
output.Close()
}()
client, err := newRTMPClient(ctx, urlString)
if err != nil {
return nil, fmt.Errorf("unable to connect to the output endpoint '%s': %w", urlString, err)
}
output.Client = client
url, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("unable to parse URL '%s': %w", urlString, err)
}
tcURL := *url
remoteAppName, appKey := getAppNameAndKey(tcURL.Path)
tcURL.Path = "/" + remoteAppName
if tcURL.Port() == "1935" {
tcURL.Host = tcURL.Hostname()
}
output.AppKey = appKey
if err := client.Connect(ctx, &rtmpmsg.NetConnectionConnect{
Command: rtmpmsg.NetConnectionConnectCommand{
App: remoteAppName,
Type: "nonprivate",
FlashVer: "StreamPanel",
TCURL: tcURL.String(),
},
}); err != nil {
return nil, fmt.Errorf("unable to connect the endpoint '%s': %w", urlString, err)
}
logger.Debugf(ctx, "connected the stream to '%s'", urlString)
return output, nil
}
func (output *Output) Close() error {
var err *multierror.Error
ctx := context.TODO()
output.Do(ctx, func() {
if output.Client != nil {
err = multierror.Append(
err,
output.Client.Close(),
)
output.Client = nil
}
})
return err.ErrorOrNil()
}

View File

@@ -1,20 +1,12 @@
package streamforward
package xaionarogortmp
import (
"context"
"io"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
flvtag "github.com/yutopp/go-flv/tag"
)
type StreamServer interface {
types.WithConfiger
types.WaitPublisherChaner
types.PubsubNameser
types.GetPortServerser
}
type Sub interface {
io.Closer
ClosedChan() <-chan struct{}

View File

@@ -0,0 +1,176 @@
package xaionarogortmp
import (
"context"
"fmt"
"runtime/debug"
"strings"
"sync/atomic"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/hashicorp/go-multierror"
"github.com/xaionaro-go/go-rtmp"
rtmpmsg "github.com/xaionaro-go/go-rtmp/message"
"github.com/xaionaro-go/streamctl/pkg/recoder"
yutoppgortmp "github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/xsync"
flvtag "github.com/yutopp/go-flv/tag"
)
const (
chunkSize = 128
)
type Recoder struct {
Locker xsync.Mutex
Stream *rtmp.Stream
CancelFunc context.CancelFunc
ReadCount atomic.Uint64
WriteCount atomic.Uint64
Sub *yutoppgortmp.Sub
eventChan chan *flvtag.FlvTag
}
var _ recoder.Recoder = (*Recoder)(nil)
var _ recoder.NewInputFromPublisherer = (*Recoder)(nil)
func (RecoderFactory) New(
ctx context.Context,
cfg recoder.Config,
) (recoder.Recoder, error) {
return &Recoder{
eventChan: make(chan *flvtag.FlvTag),
}, nil
}
func (r *Recoder) StartRecoding(
ctx context.Context,
inputIface recoder.Input,
outputIface recoder.Output,
) (_err error) {
defer func() {
if r := recover(); r != nil {
_err = fmt.Errorf("got panic: %v", r)
}
if _err == nil {
return
}
logger.FromCtx(ctx).
WithField("error_event_exception_stack_trace", string(debug.Stack())).Errorf("%v", _err)
r.Close()
}()
input, ok := inputIface.(*Input)
if !ok {
return fmt.Errorf("expected Input of type %T, but received %T", input, inputIface)
}
output, ok := outputIface.(*Output)
if !ok {
return fmt.Errorf("expected Input of type %T, but received %T", output, outputIface)
}
err := xsync.DoR1(ctx, &r.Locker, func() error {
if r.CancelFunc != nil {
return fmt.Errorf("recoding is already running")
}
stream, err := output.Client.CreateStream(ctx, &rtmpmsg.NetConnectionCreateStream{}, chunkSize)
if err != nil {
return fmt.Errorf("unable to create a stream on the remote side: %w", err)
}
r.Stream = stream
logger.Debugf(ctx, "calling Publish")
if err := r.Stream.Publish(ctx, &rtmpmsg.NetStreamPublish{
PublishingName: output.AppKey,
PublishingType: "live",
}); err != nil {
return fmt.Errorf("unable to send the Publish to the remote endpoint: %w", err)
}
logger.Debugf(ctx, "starting publishing")
switch {
case input.Pubsub != nil:
r.Sub = input.Pubsub.Sub(output.Client, r.subCallback(r.Stream))
default:
return fmt.Errorf("this case is not implemented, yet")
}
logger.Debugf(ctx, "started publishing")
return nil
})
if err != nil {
return nil
}
logger.Debugf(ctx, "the source stopped, so stopped also publishing")
return nil
}
func (r *Recoder) WaitForRecordingEnd(
ctx context.Context,
) error {
var closeChan <-chan struct{}
err := xsync.DoR1(ctx, &r.Locker, func() error {
if r.CancelFunc != nil {
return fmt.Errorf("recoding is not started (or was already closed)")
}
switch {
case r.Sub != nil:
closeChan = r.Sub.ClosedChan()
default:
return fmt.Errorf("this case is not implemented, yet")
}
return nil
})
if err != nil {
return err
}
<-closeChan
return nil
}
func (r *Recoder) GetStats(context.Context) (*recoder.Stats, error) {
return &recoder.Stats{
BytesCountRead: r.ReadCount.Load(),
BytesCountWrote: r.WriteCount.Load(),
}, nil
}
func (r *Recoder) Close() (_err error) {
ctx := context.TODO()
logger.Debug(ctx, "closing the Recoder")
defer func() { logger.Debugf(ctx, "closed the Recoder: %v", _err) }()
return xsync.DoR1(ctx, &r.Locker, func() error {
var result *multierror.Error
if r.CancelFunc == nil {
return fmt.Errorf("the stream was not started yet")
}
r.CancelFunc()
r.CancelFunc = nil
if r.Stream != nil {
result = multierror.Append(result, r.Stream.Close())
r.Stream = nil
}
if r.Sub != nil {
result = multierror.Append(result, r.Sub.Close())
r.Sub = nil
}
return result.ErrorOrNil()
})
}
func getAppNameAndKey(
remotePath string,
) (string, string) {
remoteAppName := "live"
pathParts := strings.SplitN(remotePath, "/", -2)
apiKey := pathParts[len(pathParts)-1]
if len(pathParts) >= 2 {
remoteAppName = strings.Trim(strings.Join(pathParts[:len(pathParts)-1], "/"), "/")
}
return remoteAppName, apiKey
}

View File

@@ -0,0 +1,11 @@
package xaionarogortmp
import "github.com/xaionaro-go/streamctl/pkg/recoder"
type RecoderFactory struct{}
var _ recoder.Factory = (*RecoderFactory)(nil)
func NewRecoderFactory() *RecoderFactory {
return &RecoderFactory{}
}

View File

@@ -0,0 +1,15 @@
package xaionarogortmp
import (
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
)
type StreamForwards = streamforward.StreamForwards
func NewStreamForwards(
s StreamServer,
platformsController types.PlatformsController,
) *StreamForwards {
return streamforward.NewStreamForwards(s, NewRecoderFactory(), platformsController)
}

View File

@@ -0,0 +1,105 @@
package xaionarogortmp
import (
"bytes"
"context"
"fmt"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/go-rtmp"
rtmpmsg "github.com/xaionaro-go/go-rtmp/message"
flvtag "github.com/yutopp/go-flv/tag"
)
func (r *Recoder) subCallback(
stream *rtmp.Stream,
) func(
ctx context.Context,
flv *flvtag.FlvTag,
) error {
return func(
ctx context.Context,
flv *flvtag.FlvTag,
) error {
logger.Tracef(ctx, "flvtag == %#+v", *flv)
var buf bytes.Buffer
switch d := flv.Data.(type) {
case *flvtag.AudioData:
// Consume flv payloads (d)
if err := flvtag.EncodeAudioData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %w", *d, err)
return err
}
payloadLen := uint64(buf.Len())
r.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: Fix these values
chunkStreamID := 5
if err := stream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.AudioMessage{
Payload: &buf,
}); err != nil {
err = fmt.Errorf("stream.Write (%T) return an error: %w", d, err)
return err
}
case *flvtag.VideoData:
// Consume flv payloads (d)
if err := flvtag.EncodeVideoData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %w", *d, err)
return err
}
payloadLen := uint64(buf.Len())
r.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: Fix these values
chunkStreamID := 6
if err := stream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.VideoMessage{
Payload: &buf,
}); err != nil {
err = fmt.Errorf("stream.Write (%T) return an error: %w", d, err)
return err
}
case *flvtag.ScriptData:
// Consume flv payloads (d)
if err := flvtag.EncodeScriptData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %v", *d, err)
return err
}
payloadLen := uint64(buf.Len())
r.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: hide these implementation
amdBuf := new(bytes.Buffer)
amfEnc := rtmpmsg.NewAMFEncoder(amdBuf, rtmpmsg.EncodingTypeAMF0)
if err := rtmpmsg.EncodeBodyAnyValues(amfEnc, &rtmpmsg.NetStreamSetDataFrame{
Payload: buf.Bytes(),
}); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; payload len == %d; err == %v", *d, payloadLen, err)
return err
}
// TODO: Fix these values
chunkStreamID := 8
if err := stream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.DataMessage{
Name: "@setDataFrame", // TODO: fix
Encoding: rtmpmsg.EncodingTypeAMF0,
Body: amdBuf,
}); err != nil {
err = fmt.Errorf("stream.Write (%T) return an error: %w", d, err)
return err
}
default:
logger.Errorf(ctx, "unexpected data type: %T", flv.Data)
}
return nil
}
}

View File

@@ -0,0 +1,8 @@
package xaionarogortmp
import (
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
)
type StreamServer = streamforward.StreamServer
type ActiveStreamForwarding = streamforward.ActiveStreamForwarding

View File

@@ -1,4 +1,4 @@
package streamforward
package xaionarogortmp
func ptr[T any](in T) *T {
return &in

View File

@@ -1,6 +1,7 @@
package streamforward
import (
"github.com/xaionaro-go/streamctl/pkg/recoder/libav"
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
)
@@ -11,5 +12,5 @@ func NewStreamForwards(
s StreamServer,
platformsController types.PlatformsController,
) *StreamForwards {
return streamforward.NewStreamForwards(s, NewRecoderFactory(), platformsController)
return streamforward.NewStreamForwards(s, libav.NewRecoderFactory(), platformsController)
}

View File

@@ -1,38 +0,0 @@
package streamserver
import (
"context"
"fmt"
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
)
type ActiveStreamForwarding struct {
StreamID types.StreamID
DestinationID types.DestinationID
RtmpRelay *rtmprelay.RtmpRelay
}
func newActiveStreamForward(
_ context.Context,
streamID types.StreamID,
dstID types.DestinationID,
urlSrc string,
urlDst string,
) (*ActiveStreamForwarding, error) {
relay := rtmprelay.NewRtmpRelay(&urlSrc, &urlDst)
if err := relay.Start(); err != nil {
return nil, fmt.Errorf("unable to start RTMP relay from '%s' to '%s': %w", urlSrc, urlDst, err)
}
return &ActiveStreamForwarding{
StreamID: streamID,
DestinationID: dstID,
RtmpRelay: relay,
}, nil
}
func (fwd *ActiveStreamForwarding) Close() error {
fwd.RtmpRelay.Stop()
return nil
}

View File

@@ -1,435 +0,0 @@
package streamforward
import (
"bytes"
"context"
"fmt"
"net/url"
"runtime/debug"
"strings"
"sync/atomic"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/hashicorp/go-multierror"
"github.com/xaionaro-go/go-rtmp"
rtmpmsg "github.com/xaionaro-go/go-rtmp/message"
"github.com/xaionaro-go/streamctl/pkg/observability"
yutoppgortmp "github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
"github.com/xaionaro-go/streamctl/pkg/xsync"
flvtag "github.com/yutopp/go-flv/tag"
)
const (
chunkSize = 128
)
type Unlocker interface {
Unlock()
}
type StreamForward = types.StreamForward[*ActiveStreamForwarding]
type ActiveStreamForwarding struct {
*StreamForwards
Locker xsync.Mutex
StreamID types.StreamID
DestinationID types.DestinationID
URL *url.URL
InClient *rtmp.ClientConn
OutClient *rtmp.ClientConn
OutStream *rtmp.Stream
Sub Sub
CancelFunc context.CancelFunc
ReadCount atomic.Uint64
WriteCount atomic.Uint64
PauseFunc func(ctx context.Context, fwd *ActiveStreamForwarding)
eventChan chan *flvtag.FlvTag
}
func (fwds *StreamForwards) NewActiveStreamForward(
ctx context.Context,
streamID types.StreamID,
dstID types.DestinationID,
urlString string,
pauseFunc func(ctx context.Context, fwd *ActiveStreamForwarding),
) (_ret *ActiveStreamForwarding, _err error) {
logger.Debugf(ctx, "NewActiveStreamForward(ctx, '%s', '%s', '%s', relayService, pauseFunc)", streamID, dstID, urlString)
defer func() {
logger.Debugf(ctx, "/NewActiveStreamForward(ctx, '%s', '%s', '%s', relayService, pauseFunc): %#+v %v", streamID, dstID, urlString, _ret, _err)
}()
urlParsed, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("unable to parse URL '%s': %w", urlString, err)
}
fwd := &ActiveStreamForwarding{
StreamForwards: fwds,
StreamID: streamID,
DestinationID: dstID,
URL: urlParsed,
PauseFunc: pauseFunc,
eventChan: make(chan *flvtag.FlvTag),
}
if err := fwd.Start(ctx); err != nil {
return nil, fmt.Errorf("unable to start the forwarder: %w", err)
}
return fwd, nil
}
func (fwd *ActiveStreamForwarding) Start(ctx context.Context) (_err error) {
logger.Debugf(ctx, "Start")
defer func() { logger.Debugf(ctx, "/Start: %v", _err) }()
return xsync.DoA1R1(ctx, &fwd.Locker, fwd.start, ctx)
}
func (fwd *ActiveStreamForwarding) start(ctx context.Context) (_err error) {
if fwd.CancelFunc != nil {
return fmt.Errorf("the stream forwarder is already running")
}
ctx, cancelFn := context.WithCancel(ctx)
fwd.CancelFunc = cancelFn
observability.Go(ctx, func() {
for {
err := fwd.waitForPublisherAndStart(
ctx,
)
select {
case <-ctx.Done():
fwd.Close()
return
default:
}
if err != nil {
logger.Errorf(ctx, "%s", err)
}
}
})
return nil
}
func (fwd *ActiveStreamForwarding) Stop() error {
return fwd.Close()
}
func (fwd *ActiveStreamForwarding) getAppNameAndKey() (types.AppKey, string, string) {
remoteAppName := "live"
pathParts := strings.SplitN(fwd.URL.Path, "/", -2)
apiKey := pathParts[len(pathParts)-1]
if len(pathParts) >= 2 {
remoteAppName = strings.Trim(strings.Join(pathParts[:len(pathParts)-1], "/"), "/")
}
streamID := fwd.StreamID
streamIDParts := strings.Split(string(streamID), "/")
localAppName := string(streamID)
if len(streamIDParts) == 2 {
localAppName = streamIDParts[1]
}
return types.AppKey(localAppName), remoteAppName, apiKey
}
func (fwd *ActiveStreamForwarding) WaitForPublisher(
ctx context.Context,
) (types.Publisher, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
logger.Debugf(ctx, "wait for stream '%s'", fwd.StreamID)
ch, err := fwd.StreamServer.WaitPublisherChan(ctx, fwd.StreamID, false)
if err != nil {
return nil, fmt.Errorf("unable to get the publisher wait chan: %w", err)
}
logger.Debugf(ctx, "wait for stream '%s' result: %#+v", fwd.StreamID, ch)
fwd.PauseFunc(ctx, fwd)
logger.Debugf(ctx, "no pauses or pauses ended")
return <-ch, nil
}
func (fwd *ActiveStreamForwarding) waitForPublisherAndStart(
ctx context.Context,
) (_ret error) {
defer func() {
if r := recover(); r != nil {
_ret = fmt.Errorf("got panic: %v", r)
}
if _ret == nil {
return
}
logger.FromCtx(ctx).
WithField("error_event_exception_stack_trace", string(debug.Stack())).Errorf("%v", _ret)
}()
publisher, err := fwd.WaitForPublisher(ctx)
if err != nil {
return fmt.Errorf("unable to get publisher: %w", err)
}
logger.Debugf(ctx, "DestinationStreamingLocker.Lock(ctx, '%s')", fwd.DestinationID)
destinationUnlocker := fwd.StreamForwards.DestinationStreamingLocker.Lock(ctx, fwd.DestinationID)
defer func() {
if destinationUnlocker != nil { // if ctx was cancelled before we locked then the unlocker is nil
destinationUnlocker.Unlock()
}
logger.Debugf(ctx, "DestinationStreamingLocker.Unlock(ctx, '%s')", fwd.DestinationID)
}()
logger.Debugf(ctx, "/DestinationStreamingLocker.Lock(ctx, '%s')", fwd.DestinationID)
select {
case <-ctx.Done():
return ctx.Err()
default:
}
_, remoteAppName, apiKey := fwd.getAppNameAndKey()
outClient, err := newRTMPClient(ctx, *fwd.URL)
if err != nil {
return fmt.Errorf("unable to connect to the output endpoint '%s': %w", fwd.URL, err)
}
logger.Debugf(ctx, "connected to '%s'", fwd.URL.String())
fwd.Locker.Do(ctx, func() {
fwd.OutClient = outClient
})
defer func() {
fwd.Locker.Do(ctx, func() {
if fwd.OutClient == nil {
return
}
err := fwd.OutClient.Close()
if err != nil {
logger.Warnf(ctx, "unable to close fwd.Client: %v", err)
}
fwd.OutClient = nil
})
}()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
tcURL := *fwd.URL
tcURL.Path = "/" + remoteAppName
if tcURL.Port() == "1935" {
tcURL.Host = tcURL.Hostname()
}
var closedChan <-chan struct{}
err = xsync.DoR1(ctx, &fwd.Locker, func() error {
if err := outClient.Connect(ctx, &rtmpmsg.NetConnectionConnect{
Command: rtmpmsg.NetConnectionConnectCommand{
App: remoteAppName,
Type: "nonprivate",
FlashVer: "StreamPanel",
TCURL: tcURL.String(),
},
}); err != nil {
return fmt.Errorf("unable to connect the stream to '%s': %w", fwd.URL.String(), err)
}
logger.Debugf(ctx, "connected the stream to '%s'", fwd.URL.String())
fwd.OutStream, err = outClient.CreateStream(ctx, &rtmpmsg.NetConnectionCreateStream{}, chunkSize)
if err != nil {
return fmt.Errorf("unable to create a stream to '%s': %w", fwd.URL.String(), err)
}
logger.Debugf(ctx, "calling Publish at '%s'", fwd.URL.String())
if err := fwd.OutStream.Publish(ctx, &rtmpmsg.NetStreamPublish{
PublishingName: apiKey,
PublishingType: "live",
}); err != nil {
return fmt.Errorf("unable to send the Publish message to '%s': %w", fwd.URL.String(), err)
}
logger.Debugf(ctx, "starting publishing to '%s'", fwd.URL.String())
switch publisher := publisher.(type) {
case *yutoppgortmp.Pubsub:
fwd.Sub = publisher.Sub(outClient, fwd.subCallback)
closedChan = fwd.Sub.ClosedChan()
default:
closedChan, err = fwd.connectToLocalhostAndStartReadingStream(ctx)
if err != nil {
return fmt.Errorf("unable to connect to the input endpoint (publisher type: %T): %w", publisher, err)
}
}
logger.Debugf(ctx, "started publishing to '%s'", fwd.URL.String())
return nil
})
if err != nil {
return nil
}
<-closedChan
logger.Debugf(ctx, "the source stopped, so stopped also publishing to '%s'", fwd.URL.String())
return nil
}
func (fwd *ActiveStreamForwarding) connectToLocalhostAndStartReadingStream(
ctx context.Context,
) (_ret <-chan struct{}, _err error) {
urlParsed, err := fwd.StreamForwards.getLocalhostRTMP(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get the input endpoint URL: %w", err)
}
inClient, err := newRTMPClient(ctx, *urlParsed)
if err != nil {
return nil, fmt.Errorf("unable to connect to the input endpoint '%s': %w", fwd.URL, err)
}
defer func() {
if _err == nil {
fwd.Locker.Do(ctx, func() {
fwd.InClient = inClient
})
} else {
err := inClient.Close()
if err != nil {
logger.Errorf(ctx, "unable to close the client for the input endpoint: %w", err)
}
}
}()
_, remoteAppName, _ := fwd.getAppNameAndKey()
tcURL := *urlParsed
tcURL.Path = "/" + remoteAppName
if tcURL.Port() == "1935" {
tcURL.Host = tcURL.Hostname()
}
err = inClient.Connect(ctx, &rtmpmsg.NetConnectionConnect{
Command: rtmpmsg.NetConnectionConnectCommand{
App: remoteAppName,
Type: "nonprivate",
FlashVer: "StreamPanel",
TCURL: tcURL.String(),
},
})
if err != nil {
return nil, fmt.Errorf("got an error on command 'Connect' to the input endpoint '%s': %w", urlParsed, err)
}
return nil, fmt.Errorf("not implemented, yet")
}
func (fwd *ActiveStreamForwarding) subCallback(ctx context.Context, flv *flvtag.FlvTag) error {
logger.Tracef(ctx, "flvtag == %#+v", *flv)
var buf bytes.Buffer
switch d := flv.Data.(type) {
case *flvtag.AudioData:
// Consume flv payloads (d)
if err := flvtag.EncodeAudioData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %w", *d, err)
return err
}
payloadLen := uint64(buf.Len())
fwd.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: Fix these values
chunkStreamID := 5
if err := fwd.OutStream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.AudioMessage{
Payload: &buf,
}); err != nil {
err = fmt.Errorf("fwd.OutStream.Write (%T) return an error: %w", d, err)
return err
}
case *flvtag.VideoData:
// Consume flv payloads (d)
if err := flvtag.EncodeVideoData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %w", *d, err)
return err
}
payloadLen := uint64(buf.Len())
fwd.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: Fix these values
chunkStreamID := 6
if err := fwd.OutStream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.VideoMessage{
Payload: &buf,
}); err != nil {
err = fmt.Errorf("fwd.OutStream.Write (%T) return an error: %w", d, err)
return err
}
case *flvtag.ScriptData:
// Consume flv payloads (d)
if err := flvtag.EncodeScriptData(&buf, d); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; err == %v", *d, err)
return err
}
payloadLen := uint64(buf.Len())
fwd.WriteCount.Add(payloadLen)
logger.Tracef(ctx, "flvtag.Data == %#+v; payload len == %d", *d, payloadLen)
// TODO: hide these implementation
amdBuf := new(bytes.Buffer)
amfEnc := rtmpmsg.NewAMFEncoder(amdBuf, rtmpmsg.EncodingTypeAMF0)
if err := rtmpmsg.EncodeBodyAnyValues(amfEnc, &rtmpmsg.NetStreamSetDataFrame{
Payload: buf.Bytes(),
}); err != nil {
err = fmt.Errorf("flvtag.Data == %#+v; payload len == %d; err == %v", *d, payloadLen, err)
return err
}
// TODO: Fix these values
chunkStreamID := 8
if err := fwd.OutStream.Write(ctx, chunkStreamID, flv.Timestamp, &rtmpmsg.DataMessage{
Name: "@setDataFrame", // TODO: fix
Encoding: rtmpmsg.EncodingTypeAMF0,
Body: amdBuf,
}); err != nil {
err = fmt.Errorf("fwd.OutStream.Write (%T) return an error: %w", d, err)
return err
}
default:
logger.Errorf(ctx, "unexpected data type: %T", flv.Data)
}
return nil
}
func (fwd *ActiveStreamForwarding) Close() error {
ctx := context.TODO()
return xsync.DoR1(ctx, &fwd.Locker, func() error {
if fwd.CancelFunc == nil {
return fmt.Errorf("the stream was not started yet")
}
var result *multierror.Error
if fwd.CancelFunc != nil {
fwd.CancelFunc()
fwd.CancelFunc = nil
}
if fwd.Sub != nil {
result = multierror.Append(result, fwd.Sub.Close())
fwd.Sub = nil
}
if fwd.OutClient != nil {
result = multierror.Append(result, fwd.OutClient.Close())
fwd.OutClient = nil
}
if fwd.InClient != nil {
result = multierror.Append(result, fwd.InClient.Close())
fwd.InClient = nil
}
return result.ErrorOrNil()
})
}
func (fwd *ActiveStreamForwarding) String() string {
return fmt.Sprintf("%s->%s", fwd.StreamID, fwd.DestinationID)
}

View File

@@ -1,682 +1,16 @@
package streamforward
import (
"context"
"fmt"
"net/url"
"time"
"github.com/facebookincubator/go-belt"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/lockmap"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
"github.com/xaionaro-go/streamctl/pkg/streamd/memoize"
"github.com/xaionaro-go/streamctl/pkg/recoder/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
"github.com/xaionaro-go/streamctl/pkg/xsync"
"github.com/xaionaro-go/typing/ordered"
)
type ForwardingKey struct {
StreamID types.StreamID
DestinationID types.DestinationID
}
type StreamForwards struct {
StreamServer
types.PlatformsController
Mutex xsync.RWMutex
ActiveStreamForwardings map[ForwardingKey]*ActiveStreamForwarding
DestinationStreamingLocker *lockmap.LockMap
StreamDestinations []types.StreamDestination
}
type StreamForwards = streamforward.StreamForwards
func NewStreamForwards(
s StreamServer,
pc types.PlatformsController,
platformsController types.PlatformsController,
) *StreamForwards {
return &StreamForwards{
StreamServer: s,
PlatformsController: pc,
DestinationStreamingLocker: lockmap.NewLockMap(),
ActiveStreamForwardings: map[ForwardingKey]*ActiveStreamForwarding{},
}
}
func (s *StreamForwards) Init(
ctx context.Context,
opts ...types.InitOption,
) error {
return xsync.DoR1(ctx, &s.Mutex, func() error {
return s.init(ctx, opts...)
})
}
func (s *StreamForwards) init(
ctx context.Context,
_ ...types.InitOption,
) (_ret error) {
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
for dstID, dstCfg := range cfg.Destinations {
err := s.addActiveStreamDestination(ctx, dstID, dstCfg.URL)
if err != nil {
_ret = fmt.Errorf("unable to initialize stream destination '%s' to %#+v: %w", dstID, dstCfg, err)
return
}
}
for streamID, streamCfg := range cfg.Streams {
for dstID, fwd := range streamCfg.Forwardings {
if !fwd.Disabled {
_, err := s.newActiveStreamForward(ctx, streamID, dstID, fwd.Quirks)
if err != nil {
_ret = fmt.Errorf("unable to launch stream forward from '%s' to '%s': %w", streamID, dstID, err)
return
}
}
}
}
})
return
}
func (s *StreamForwards) AddStreamForward(
ctx context.Context,
streamID types.StreamID,
destinationID types.DestinationID,
enabled bool,
quirks types.ForwardingQuirks,
) (*StreamForward, error) {
return xsync.DoR2(ctx, &s.Mutex, func() (*StreamForward, error) {
return s.addStreamForward(ctx, streamID, destinationID, enabled, quirks)
})
}
func (s *StreamForwards) addStreamForward(
ctx context.Context,
streamID types.StreamID,
destinationID types.DestinationID,
enabled bool,
quirks types.ForwardingQuirks,
) (*StreamForward, error) {
ctx = belt.WithField(ctx, "module", "StreamServer")
var (
streamConfig *types.StreamConfig
err error
)
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
streamConfig = cfg.Streams[streamID]
if _, ok := streamConfig.Forwardings[destinationID]; ok {
err = fmt.Errorf("the forwarding %s->%s already exists", streamID, destinationID)
return
}
streamConfig.Forwardings[destinationID] = types.ForwardingConfig{
Disabled: !enabled,
Quirks: quirks,
}
})
if err != nil {
return nil, err
}
if enabled {
fwd, err := s.newActiveStreamForward(ctx, streamID, destinationID, quirks)
if err != nil {
return nil, err
}
return fwd, nil
}
return &StreamForward{
StreamID: streamID,
DestinationID: destinationID,
Enabled: enabled,
Quirks: quirks,
}, nil
}
func (s *StreamForwards) getLocalhostRTMP(ctx context.Context) (*url.URL, error) {
portSrvs, err := s.StreamServer.GetPortServers(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get port servers info: %w", err)
}
portSrv := portSrvs[0]
urlString := fmt.Sprintf("%s://%s", portSrv.Type, portSrv.Addr)
urlParsed, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("unable to parse '%s': %w", urlString, err)
}
return urlParsed, nil
}
func (s *StreamForwards) newActiveStreamForward(
ctx context.Context,
streamID types.StreamID,
destinationID types.DestinationID,
quirks types.ForwardingQuirks,
) (*StreamForward, error) {
ctx = belt.WithField(ctx, "stream_forward", fmt.Sprintf("%s->%s", streamID, destinationID))
key := ForwardingKey{
StreamID: streamID,
DestinationID: destinationID,
}
if _, ok := s.ActiveStreamForwardings[key]; ok {
return nil, fmt.Errorf("there is already an active stream forwarding to '%s'", destinationID)
}
dst, err := s.findStreamDestinationByID(ctx, destinationID)
if err != nil {
return nil, fmt.Errorf("unable to find stream destination '%s': %w", destinationID, err)
}
urlParsed, err := url.Parse(dst.URL)
if err != nil {
return nil, fmt.Errorf("unable to parse URL '%s': %w", dst.URL, err)
}
if urlParsed.Host == "" {
urlParsed, err = s.getLocalhostRTMP(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get the URL of the output endpoint: %w", err)
}
}
result := &StreamForward{
StreamID: streamID,
DestinationID: destinationID,
Enabled: true,
Quirks: quirks,
NumBytesWrote: 0,
NumBytesRead: 0,
}
fwd, err := s.NewActiveStreamForward(
ctx,
streamID,
destinationID,
urlParsed.String(),
func(
ctx context.Context,
fwd *ActiveStreamForwarding,
) {
if quirks.StartAfterYoutubeRecognizedStream.Enabled {
if quirks.RestartUntilYoutubeRecognizesStream.Enabled {
logger.Errorf(ctx, "StartAfterYoutubeRecognizedStream should not be used together with RestartUntilYoutubeRecognizesStream")
} else {
logger.Debugf(ctx, "fwd %s->%s is waiting for YouTube to recognize the stream", streamID, destinationID)
started, err := s.PlatformsController.CheckStreamStartedByPlatformID(
memoize.SetNoCache(ctx, true),
youtube.ID,
)
logger.Debugf(ctx, "youtube status check: %v %v", started, err)
if started {
return
}
t := time.NewTicker(time.Second)
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
started, err := s.PlatformsController.CheckStreamStartedByPlatformID(
ctx,
youtube.ID,
)
logger.Debugf(ctx, "youtube status check: %v %v", started, err)
if started {
return
}
}
}
}
},
)
if err != nil {
return nil, fmt.Errorf("unable to run the stream forwarding: %w", err)
}
s.ActiveStreamForwardings[key] = fwd
result.ActiveForwarding = fwd
if quirks.RestartUntilYoutubeRecognizesStream.Enabled {
observability.Go(ctx, func() {
s.restartUntilYoutubeRecognizesStream(
ctx,
result,
quirks.RestartUntilYoutubeRecognizesStream,
)
})
}
return result, nil
}
func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
ctx context.Context,
fwd *StreamForward,
cfg types.RestartUntilYoutubeRecognizesStream,
) {
ctx = belt.WithField(ctx, "module", "restartUntilYoutubeRecognizesStream")
ctx = belt.WithField(ctx, "stream_forward", fmt.Sprintf("%s->%s", fwd.StreamID, fwd.DestinationID))
logger.Debugf(ctx, "restartUntilYoutubeRecognizesStream(ctx, %#+v, %#+v)", fwd, cfg)
defer func() { logger.Debugf(ctx, "restartUntilYoutubeRecognizesStream(ctx, %#+v, %#+v)", fwd, cfg) }()
if !cfg.Enabled {
logger.Errorf(ctx, "an attempt to start restartUntilYoutubeRecognizesStream when the hack is disabled for this stream forwarder: %#+v", cfg)
return
}
if s.PlatformsController == nil {
logger.Errorf(ctx, "PlatformsController is nil")
return
}
if fwd.ActiveForwarding == nil {
logger.Error(ctx, "ActiveForwarding is nil")
return
}
_, err := fwd.ActiveForwarding.WaitForPublisher(ctx)
if err != nil {
logger.Error(ctx, err)
return
}
for {
select {
case <-ctx.Done():
return
case <-time.After(cfg.StartTimeout):
}
logger.Debugf(ctx, "waited %v, checking if the remote platform accepted the stream", cfg.StartTimeout)
for {
streamOK, err := s.PlatformsController.CheckStreamStartedByPlatformID(
memoize.SetNoCache(ctx, true),
youtube.ID,
)
logger.Debugf(ctx, "the result of checking the stream on the remote platform: %v %v", streamOK, err)
if err != nil {
logger.Errorf(ctx, "unable to check if the stream with URL '%s' is started: %v", fwd.ActiveForwarding.URL, err)
time.Sleep(time.Second)
continue
}
if streamOK {
logger.Debugf(ctx, "waiting %v to recheck if the stream will be still OK", cfg.StopStartDelay)
select {
case <-ctx.Done():
return
case <-time.After(cfg.StopStartDelay):
}
streamOK, err := s.PlatformsController.CheckStreamStartedByPlatformID(
memoize.SetNoCache(ctx, true),
youtube.ID,
)
logger.Debugf(ctx, "the result of checking the stream on the remote platform: %v %v", streamOK, err)
if err != nil {
logger.Errorf(ctx, "unable to check if the stream with URL '%s' is started: %v", fwd.ActiveForwarding.URL, err)
time.Sleep(time.Second)
continue
}
if streamOK {
return
}
}
break
}
logger.Infof(ctx, "the remote platform still does not see the stream, restarting the stream forwarding: stopping...")
err := fwd.ActiveForwarding.Stop()
if err != nil {
logger.Errorf(ctx, "unable to stop stream forwarding: %v", err)
}
select {
case <-ctx.Done():
return
case <-time.After(cfg.StopStartDelay):
}
logger.Infof(ctx, "the remote platform still does not see the stream, restarting the stream forwarding: starting...")
err = fwd.ActiveForwarding.Start(ctx)
if err != nil {
logger.Errorf(ctx, "unable to start stream forwarding: %v", err)
}
}
}
func (s *StreamForwards) UpdateStreamForward(
ctx context.Context,
streamID types.StreamID,
destinationID types.DestinationID,
enabled bool,
quirks types.ForwardingQuirks,
) (*StreamForward, error) {
return xsync.DoR2(ctx, &s.Mutex, func() (*StreamForward, error) {
return s.updateStreamForward(ctx, streamID, destinationID, enabled, quirks)
})
}
func (s *StreamForwards) updateStreamForward(
ctx context.Context,
streamID types.StreamID,
destinationID types.DestinationID,
enabled bool,
quirks types.ForwardingQuirks,
) (_ret *StreamForward, _err error) {
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
streamConfig := cfg.Streams[streamID]
fwdCfg, ok := streamConfig.Forwardings[destinationID]
if !ok {
_err = fmt.Errorf("the forwarding %s->%s does not exist", streamID, destinationID)
return
}
var fwd *StreamForward
if fwdCfg.Disabled && enabled {
var err error
fwd, err = s.newActiveStreamForward(ctx, streamID, destinationID, quirks)
if err != nil {
_err = fmt.Errorf("unable to active the stream: %w", err)
return
}
}
if !fwdCfg.Disabled && !enabled {
err := s.removeActiveStreamForward(ctx, streamID, destinationID)
if err != nil {
_err = fmt.Errorf("unable to deactivate the stream: %w", err)
return
}
}
streamConfig.Forwardings[destinationID] = types.ForwardingConfig{
Disabled: !enabled,
Quirks: quirks,
}
r := &StreamForward{
StreamID: streamID,
DestinationID: destinationID,
Enabled: enabled,
Quirks: quirks,
NumBytesWrote: 0,
NumBytesRead: 0,
}
if fwd != nil {
r.ActiveForwarding = fwd.ActiveForwarding
}
_ret = r
})
return
}
func (s *StreamForwards) ListStreamForwards(
ctx context.Context,
) (_ret []StreamForward, _err error) {
defer func() {
logger.Tracef(ctx, "/ListStreamForwards(): %#+v %v", _ret, _err)
}()
return xsync.DoR2(ctx, &s.Mutex, func() ([]StreamForward, error) {
return s.getStreamForwards(ctx, func(si types.StreamID, di ordered.Optional[types.DestinationID]) bool {
return true
})
})
}
func (s *StreamForwards) getStreamForwards(
ctx context.Context,
filterFunc func(types.StreamID, ordered.Optional[types.DestinationID]) bool,
) (_ret []StreamForward, _err error) {
activeStreamForwards, err := s.listActiveStreamForwards(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get the list of active stream forwardings: %w", err)
}
logger.Tracef(ctx, "len(activeStreamForwards) == %d", len(activeStreamForwards))
type fwdID struct {
StreamID types.StreamID
DestID types.DestinationID
}
m := map[fwdID]*StreamForward{}
for idx := range activeStreamForwards {
fwd := &activeStreamForwards[idx]
if !filterFunc(fwd.StreamID, ordered.Opt(fwd.DestinationID)) {
continue
}
m[fwdID{
StreamID: fwd.StreamID,
DestID: fwd.DestinationID,
}] = fwd
}
var result []StreamForward
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
logger.Tracef(ctx, "len(s.Config.Streams) == %d", len(cfg.Streams))
for streamID, stream := range cfg.Streams {
if !filterFunc(streamID, ordered.Optional[types.DestinationID]{}) {
continue
}
logger.Tracef(ctx, "len(s.Config.Streams[%s].Forwardings) == %d", streamID, len(stream.Forwardings))
for dstID, cfg := range stream.Forwardings {
if !filterFunc(streamID, ordered.Opt(dstID)) {
continue
}
item := StreamForward{
StreamID: streamID,
DestinationID: dstID,
Enabled: !cfg.Disabled,
Quirks: cfg.Quirks,
}
if activeFwd, ok := m[fwdID{
StreamID: streamID,
DestID: dstID,
}]; ok {
item.NumBytesWrote = activeFwd.NumBytesWrote
item.NumBytesRead = activeFwd.NumBytesRead
}
logger.Tracef(ctx, "stream forwarding '%s->%s': %#+v", streamID, dstID, cfg)
result = append(result, item)
}
}
})
return result, nil
}
func (s *StreamForwards) listActiveStreamForwards(
_ context.Context,
) ([]StreamForward, error) {
var result []StreamForward
for _, fwd := range s.ActiveStreamForwardings {
result = append(result, StreamForward{
StreamID: fwd.StreamID,
DestinationID: fwd.DestinationID,
Enabled: true,
NumBytesWrote: fwd.WriteCount.Load(),
NumBytesRead: fwd.ReadCount.Load(),
})
}
return result, nil
}
func (s *StreamForwards) RemoveStreamForward(
ctx context.Context,
streamID types.StreamID,
dstID types.DestinationID,
) error {
ctx = belt.WithField(ctx, "module", "StreamServer")
return xsync.DoA3R1(ctx, &s.Mutex, s.removeStreamForward, ctx, streamID, dstID)
}
func (s *StreamForwards) removeStreamForward(
ctx context.Context,
streamID types.StreamID,
dstID types.DestinationID,
) (err error) {
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
streamCfg := cfg.Streams[streamID]
if _, ok := streamCfg.Forwardings[dstID]; !ok {
err = fmt.Errorf("the forwarding %s->%s does not exist", streamID, dstID)
return
}
delete(streamCfg.Forwardings, dstID)
err = s.removeActiveStreamForward(ctx, streamID, dstID)
})
return
}
func (s *StreamForwards) removeActiveStreamForward(
_ context.Context,
streamID types.StreamID,
dstID types.DestinationID,
) error {
key := ForwardingKey{
StreamID: streamID,
DestinationID: dstID,
}
fwd := s.ActiveStreamForwardings[key]
if fwd == nil {
return nil
}
delete(s.ActiveStreamForwardings, key)
err := fwd.Close()
if err != nil {
return fmt.Errorf("unable to close stream forwarding: %w", err)
}
return nil
}
func (s *StreamForwards) GetStreamForwardsByDestination(
ctx context.Context,
destID types.DestinationID,
) (_ret []StreamForward, _err error) {
ctx = belt.WithField(ctx, "module", "StreamServer")
logger.Debugf(ctx, "GetStreamForwardsByDestination()")
defer func() {
logger.Debugf(ctx, "/GetStreamForwardsByDestination(): %#+v %v", _ret, _err)
}()
return xsync.DoR2(ctx, &s.Mutex, func() ([]StreamForward, error) {
return s.getStreamForwards(ctx, func(streamID types.StreamID, dstID ordered.Optional[types.DestinationID]) bool {
return !dstID.IsSet() || dstID.Get() == destID
})
})
}
func (s *StreamForwards) ListStreamDestinations(
ctx context.Context,
) ([]types.StreamDestination, error) {
ctx = belt.WithField(ctx, "module", "StreamServer")
return xsync.DoA1R2(ctx, &s.Mutex, s.listStreamDestinations, ctx)
}
func (s *StreamForwards) listStreamDestinations(
_ context.Context,
) ([]types.StreamDestination, error) {
c := make([]types.StreamDestination, len(s.StreamDestinations))
copy(c, s.StreamDestinations)
return c, nil
}
func (s *StreamForwards) AddStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
url string,
) error {
ctx = belt.WithField(ctx, "module", "StreamServer")
return xsync.DoA3R1(ctx, &s.Mutex, s.addStreamDestination, ctx, destinationID, url)
}
func (s *StreamForwards) addStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
url string,
) (_ret error) {
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
err := s.addActiveStreamDestination(ctx, destinationID, url)
if err != nil {
_ret = fmt.Errorf("unable to add an active stream destination: %w", err)
return
}
cfg.Destinations[destinationID] = &types.DestinationConfig{URL: url}
})
return
}
func (s *StreamForwards) addActiveStreamDestination(
_ context.Context,
destinationID types.DestinationID,
url string,
) error {
s.StreamDestinations = append(s.StreamDestinations, types.StreamDestination{
ID: destinationID,
URL: url,
})
return nil
}
func (s *StreamForwards) RemoveStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
) error {
ctx = belt.WithField(ctx, "module", "StreamServer")
return xsync.DoA2R1(ctx, &s.Mutex, s.removeStreamDestination, ctx, destinationID)
}
func (s *StreamForwards) removeStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
) (err error) {
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
for _, streamCfg := range cfg.Streams {
delete(streamCfg.Forwardings, destinationID)
}
delete(cfg.Destinations, destinationID)
err = s.removeActiveStreamDestination(ctx, destinationID)
})
return
}
func (s *StreamForwards) removeActiveStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
) error {
streamForwards, err := s.ListStreamForwards(ctx)
if err != nil {
return fmt.Errorf("unable to list stream forwardings: %w", err)
}
for _, fwd := range streamForwards {
if fwd.DestinationID == destinationID {
s.RemoveStreamForward(ctx, fwd.StreamID, fwd.DestinationID)
}
}
for i := range s.StreamDestinations {
if s.StreamDestinations[i].ID == destinationID {
s.StreamDestinations = append(s.StreamDestinations[:i], s.StreamDestinations[i+1:]...)
return nil
}
}
return fmt.Errorf("have not found stream destination with id %s", destinationID)
}
func (s *StreamForwards) findStreamDestinationByID(
_ context.Context,
destinationID types.DestinationID,
) (types.StreamDestination, error) {
for _, dst := range s.StreamDestinations {
if dst.ID == destinationID {
return dst, nil
}
}
return types.StreamDestination{}, fmt.Errorf("unable to find a stream destination by StreamID '%s'", destinationID)
return streamforward.NewStreamForwards(s, xaionarogortmp.NewRecoderFactory(), platformsController)
}

View File

@@ -0,0 +1,8 @@
package streamforward
import (
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
)
type StreamServer = streamforward.StreamServer
type ActiveStreamForwarding = streamforward.ActiveStreamForwarding

View File

@@ -15,6 +15,7 @@ import (
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/player"
playertypes "github.com/xaionaro-go/streamctl/pkg/player/types"
xaionarogortmp "github.com/xaionaro-go/streamctl/pkg/recoder/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/streamplayer"
yutoppgortmp "github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/xaionaro-go-rtmp"
"github.com/xaionaro-go/streamctl/pkg/streamserver/implementations/xaionaro-go-rtmp/streamforward"
@@ -123,7 +124,7 @@ func (s *StreamServer) WithConfig(
func (s *StreamServer) WaitPubsub(
ctx context.Context,
appKey types.AppKey,
) streamforward.Pubsub {
) xaionarogortmp.Pubsub {
return &pubsubAdapter{s.RelayService.WaitPubsub(ctx, appKey, false)}
}
@@ -131,12 +132,12 @@ type pubsubAdapter struct {
*yutoppgortmp.Pubsub
}
var _ streamforward.Pubsub = (*pubsubAdapter)(nil)
var _ xaionarogortmp.Pubsub = (*pubsubAdapter)(nil)
func (pubsub *pubsubAdapter) Sub(
conn io.Closer,
callback func(ctx context.Context, flv *flvtag.FlvTag) error,
) streamforward.Sub {
) xaionarogortmp.Sub {
return pubsub.Pubsub.Sub(conn, callback)
}

View File

@@ -1,3 +0,0 @@
package recoder
type Input interface{}

View File

@@ -1,3 +0,0 @@
package recoder
type Output interface{}

View File

@@ -12,7 +12,7 @@ import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/hashicorp/go-multierror"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
"github.com/xaionaro-go/streamctl/pkg/xsync"
)
@@ -205,17 +205,47 @@ func (fwd *ActiveStreamForwarding) waitForPublisherAndStart(
return fmt.Errorf("unable to initialize a recoder: %w", err)
}
input, err := fwd.openInputFor(ctx, recoderInstance)
select {
case <-ctx.Done():
return ctx.Err()
default:
}
input, err := fwd.openInputFor(ctx, recoderInstance, publisher)
if err != nil {
errmon.ObserveErrorCtx(ctx, recoderInstance.Close())
return fmt.Errorf("unable to open the input: %w", err)
}
defer func() {
err := input.Close()
if err != nil {
logger.Errorf(ctx, "unable to close the input: %w", err)
}
}()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
output, err := fwd.openOutputFor(ctx, recoderInstance)
if err != nil {
errmon.ObserveErrorCtx(ctx, recoderInstance.Close())
return fmt.Errorf("unable to open the output: %w", err)
}
defer func() {
err := output.Close()
if err != nil {
logger.Errorf(ctx, "unable to close the output: %w", err)
}
}()
select {
case <-ctx.Done():
return ctx.Err()
default:
}
recodingFinished := make(chan struct{})
defer func() {
@@ -280,6 +310,7 @@ func (fwd *ActiveStreamForwarding) waitForPublisherAndStart(
func (fwd *ActiveStreamForwarding) openInputFor(
ctx context.Context,
recoderInstance recoder.Recoder,
publisher types.Publisher,
) (recoder.Input, error) {
inputURL, err := fwd.getLocalhostEndpoint(ctx)
if err != nil {
@@ -288,7 +319,13 @@ func (fwd *ActiveStreamForwarding) openInputFor(
inputURL.Path = "/" + string(fwd.StreamID)
input, err := recoderInstance.NewInputFromURL(ctx, inputURL.String(), recoder.InputConfig{})
var input recoder.Input
inputCfg := recoder.InputConfig{}
if newInputFromStreamIDer, ok := recoderInstance.(recoder.NewInputFromPublisherer); ok {
input, err = newInputFromStreamIDer.NewInputFromPublisher(ctx, publisher, inputCfg)
} else {
input, err = recoderInstance.NewInputFromURL(ctx, inputURL.String(), inputCfg)
}
if err != nil {
return nil, fmt.Errorf("unable to open '%s' as the input: %w", inputURL, err)
}

View File

@@ -10,9 +10,9 @@ import (
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/lockmap"
"github.com/xaionaro-go/streamctl/pkg/observability"
"github.com/xaionaro-go/streamctl/pkg/recoder"
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
"github.com/xaionaro-go/streamctl/pkg/streamd/memoize"
"github.com/xaionaro-go/streamctl/pkg/streamserver/recoder"
"github.com/xaionaro-go/streamctl/pkg/streamserver/types"
"github.com/xaionaro-go/streamctl/pkg/xsync"
"github.com/xaionaro-go/typing/ordered"

View File

@@ -1,2 +1 @@
package types