mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-15 12:00:41 +08:00
Start adapting new streamforward to Android
This commit is contained in:
121
Makefile
121
Makefile
@@ -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,39 +143,90 @@ 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 \
|
||||
echo "VLC is not supported for Android builds, yet, please disable it with ENABLE_VLC=false."; \
|
||||
exit 1; \
|
||||
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/
|
||||
|
@@ -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
8
go.mod
@@ -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
28
go.sum
@@ -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=
|
||||
|
27
pkg/recoder/.wip/livego/input.go
Normal file
27
pkg/recoder/.wip/livego/input.go
Normal 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
|
||||
}
|
25
pkg/recoder/.wip/livego/output.go
Normal file
25
pkg/recoder/.wip/livego/output.go
Normal 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
|
||||
}
|
72
pkg/recoder/.wip/livego/recoder.go
Normal file
72
pkg/recoder/.wip/livego/recoder.go
Normal 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
|
||||
})
|
||||
}
|
19
pkg/recoder/.wip/livego/recoder_factory.go
Normal file
19
pkg/recoder/.wip/livego/recoder_factory.go
Normal 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
9
pkg/recoder/input.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package recoder
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Input interface {
|
||||
io.Closer
|
||||
}
|
@@ -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 {
|
@@ -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() {
|
@@ -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
|
@@ -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 {
|
@@ -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{}
|
@@ -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,
|
||||
},
|
@@ -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,
|
@@ -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;
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
@@ -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,
|
@@ -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 {
|
@@ -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 (
|
@@ -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
|
@@ -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 {
|
@@ -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 {
|
@@ -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,
|
@@ -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
9
pkg/recoder/output.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package recoder
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Output interface {
|
||||
io.Closer
|
||||
}
|
@@ -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
6
pkg/recoder/stats.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package recoder
|
||||
|
||||
type Stats struct {
|
||||
BytesCountRead uint64
|
||||
BytesCountWrote uint64
|
||||
}
|
107
pkg/recoder/xaionaro-go-rtmp/input.go
Normal file
107
pkg/recoder/xaionaro-go-rtmp/input.go
Normal 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
|
||||
}
|
@@ -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
|
||||
}
|
90
pkg/recoder/xaionaro-go-rtmp/output.go
Normal file
90
pkg/recoder/xaionaro-go-rtmp/output.go
Normal 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()
|
||||
}
|
@@ -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{}
|
176
pkg/recoder/xaionaro-go-rtmp/recoder.go
Normal file
176
pkg/recoder/xaionaro-go-rtmp/recoder.go
Normal 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
|
||||
}
|
11
pkg/recoder/xaionaro-go-rtmp/recoder_factory.go
Normal file
11
pkg/recoder/xaionaro-go-rtmp/recoder_factory.go
Normal 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{}
|
||||
}
|
15
pkg/recoder/xaionaro-go-rtmp/stream_forwards.go
Normal file
15
pkg/recoder/xaionaro-go-rtmp/stream_forwards.go
Normal 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)
|
||||
}
|
105
pkg/recoder/xaionaro-go-rtmp/sub_callback.go
Normal file
105
pkg/recoder/xaionaro-go-rtmp/sub_callback.go
Normal 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
|
||||
}
|
||||
}
|
8
pkg/recoder/xaionaro-go-rtmp/type_alias.go
Normal file
8
pkg/recoder/xaionaro-go-rtmp/type_alias.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package xaionarogortmp
|
||||
|
||||
import (
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
|
||||
)
|
||||
|
||||
type StreamServer = streamforward.StreamServer
|
||||
type ActiveStreamForwarding = streamforward.ActiveStreamForwarding
|
@@ -1,4 +1,4 @@
|
||||
package streamforward
|
||||
package xaionarogortmp
|
||||
|
||||
func ptr[T any](in T) *T {
|
||||
return &in
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -0,0 +1,8 @@
|
||||
package streamforward
|
||||
|
||||
import (
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamserver/streamforward"
|
||||
)
|
||||
|
||||
type StreamServer = streamforward.StreamServer
|
||||
type ActiveStreamForwarding = streamforward.ActiveStreamForwarding
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
package recoder
|
||||
|
||||
type Input interface{}
|
@@ -1,3 +0,0 @@
|
||||
package recoder
|
||||
|
||||
type Output interface{}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -1,2 +1 @@
|
||||
package types
|
||||
|
||||
|
Reference in New Issue
Block a user