mirror of
				https://github.com/DigitalDevices/pvr.octonet.git
				synced 2025-03-01 10:53:09 +00:00 
			
		
		
		
	Compare commits
	
		
			23 Commits
		
	
	
		
			Matrix
			...
			21.0.0-Ome
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 945b74cf56 | ||
|  | 60c8704c10 | ||
|  | 5622e10c72 | ||
|  | 7e3a8bd8ed | ||
|  | 738507d43a | ||
|  | 6872797278 | ||
|  | b721b165de | ||
|  | fe90a75afa | ||
|  | db1d467987 | ||
|  | 89f42854b8 | ||
|  | d9ae217e68 | ||
|  | 28f645bd3d | ||
|  | d8460d2a8f | ||
|  | c2a3daa691 | ||
|  | 51cd4c4c90 | ||
|  | 23b4f3eecf | ||
|  | 2a4230567c | ||
|  | 567232b5fb | ||
|  | fb17e25ef6 | ||
|  | 41a9829054 | ||
|  | 046acf1636 | ||
|  | df13aef650 | ||
|  | bacefe5194 | 
							
								
								
									
										21
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,33 +11,26 @@ jobs: | ||||
|       matrix: | ||||
|         include: | ||||
|         - name: "Debian package test" | ||||
|           os: ubuntu-18.04 | ||||
|           os: ubuntu-latest | ||||
|           CC: gcc | ||||
|           CXX: g++ | ||||
|           DEBIAN_BUILD: true | ||||
|         - os: ubuntu-18.04 | ||||
|           CC: gcc | ||||
|           CXX: g++ | ||||
|         - os: ubuntu-18.04 | ||||
|           CC: clang | ||||
|           CXX: clang++ | ||||
|         - os: macos-10.15 | ||||
|     steps: | ||||
|     - name: Install needed ubuntu depends | ||||
|       env: | ||||
|         DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }} | ||||
|       run: | | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/ppa; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi | ||||
|     - name: Checkout Kodi repo | ||||
|       uses: actions/checkout@v2 | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
|         repository: xbmc/xbmc | ||||
|         ref: Matrix | ||||
|         ref: master | ||||
|         path: xbmc | ||||
|     - name: Checkout pvr.argustv repo | ||||
|       uses: actions/checkout@v2 | ||||
|     - name: Checkout add-on repo | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
|         path: ${{ env.app_id }} | ||||
|     - name: Configure | ||||
| @@ -48,7 +41,7 @@ jobs: | ||||
|       run: | | ||||
|         if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir -p build && cd build; fi | ||||
|         if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=${{ github.workspace }} -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/xbmc/addons -DPACKAGE_ZIP=1 ${{ github.workspace }}/xbmc/cmake/addons; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/Matrix/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/master/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi | ||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep ${{ github.workspace }}/${app_id}; fi | ||||
|     - name: Build | ||||
|       env: | ||||
|   | ||||
							
								
								
									
										30
									
								
								.github/workflows/changelog-and-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/changelog-and-release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -39,28 +39,29 @@ jobs: | ||||
|  | ||||
|       # Checkout the current repository into a directory (repositories name) | ||||
|       - name: Checkout Repository | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           path: ${{ github.event.repository.name }} | ||||
|  | ||||
|       # Checkout the required scripts from kodi-pvr/pvr-scripts into the 'scripts' directory | ||||
|       # Checkout the required scripts from xbmc/binary-addon-scripts into the 'scripts' directory | ||||
|       - name: Checkout Scripts | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           repository: kodi-pvr/pvr-scripts | ||||
|           repository: xbmc/binary-addon-scripts | ||||
|           path: scripts | ||||
|  | ||||
|       # Install all dependencies required by the following steps | ||||
|       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libxml2-utils xmlstarlet | ||||
|  | ||||
|       # Setup python version 3.9 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v2 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: '3.9' | ||||
|  | ||||
| @@ -84,6 +85,7 @@ jobs: | ||||
|       # - steps.required-variables.outputs.version: version element from addon.xml.in | ||||
|       # - steps.required-variables.outputs.branch: branch of the triggering ref | ||||
|       # - steps.required-variables.outputs.today: today's date in format '%Y-%m-%d' | ||||
|       # Note: we use a random EOF for 'changes' as is best practice for for multiline variables | ||||
|       - name: Get required variables | ||||
|         id: required-variables | ||||
|         run: | | ||||
| @@ -92,17 +94,15 @@ jobs: | ||||
|           then | ||||
|             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') | ||||
|           fi | ||||
|           changes="${changes//'%'/'%25'}" | ||||
|           changes="${changes//$'\n'/'%0A'}" | ||||
|           changes="${changes//$'\r'/'%0D'}" | ||||
|           changes="${changes//$'\\n'/'%0A'}" | ||||
|           changes="${changes//$'\\r'/'%0D'}" | ||||
|           echo ::set-output name=changes::$changes | ||||
|           EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | ||||
|           echo "changes<<$EOF" >> $GITHUB_OUTPUT | ||||
|           echo "$changes" >> $GITHUB_OUTPUT | ||||
|           echo "$EOF" >> $GITHUB_OUTPUT | ||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||
|           echo ::set-output name=version::$version | ||||
|           echo "version=$version" >> $GITHUB_OUTPUT | ||||
|           branch=$(echo ${GITHUB_REF#refs/heads/}) | ||||
|           echo ::set-output name=branch::$branch | ||||
|           echo ::set-output name=today::$(date +'%Y-%m-%d') | ||||
|           echo "branch=$branch" >> $GITHUB_OUTPUT | ||||
|           echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT | ||||
|         working-directory: ${{ github.event.repository.name }} | ||||
|  | ||||
|       # Create a commit of the incremented version and changelog, news changes | ||||
| @@ -133,7 +133,7 @@ jobs: | ||||
|         shell: bash | ||||
|  | ||||
|       # Create a release at {steps.required-variables.outputs.branch} | ||||
|       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix | ||||
|       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 21.0.0-Omega | ||||
|       # - release body: {steps.required-variables.outputs.changes} | ||||
|       - name: Create Release | ||||
|         id: create-release | ||||
|   | ||||
							
								
								
									
										11
									
								
								.github/workflows/increment-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/increment-version.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: Increment version when languages are updated | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ Matrix, Nexus ] | ||||
|     branches: [ Matrix, Nexus, Omega ] | ||||
|     paths: | ||||
|       - '**resource.language.**strings.po' | ||||
|  | ||||
| @@ -15,20 +15,20 @@ jobs: | ||||
|     steps: | ||||
|  | ||||
|       - name: Checkout Repository | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           path: ${{ github.event.repository.name }} | ||||
|  | ||||
|       - name: Checkout Scripts | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           repository: xbmc/weblate-supplementary-scripts | ||||
|           path: scripts | ||||
|  | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v2 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: '3.9' | ||||
|  | ||||
| @@ -42,13 +42,14 @@ jobs: | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libxml2-utils xmlstarlet | ||||
|  | ||||
|       - name: Get required variables | ||||
|         id: required-variables | ||||
|         run: | | ||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||
|           echo ::set-output name=version::$version | ||||
|           echo "version=$version" >> $GITHUB_OUTPUT | ||||
|         working-directory: ${{ github.event.repository.name }} | ||||
|  | ||||
|       - name: Create PR for incrementing add-on versions | ||||
|   | ||||
							
								
								
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,7 +14,7 @@ jobs: | ||||
|  | ||||
|       # Checkout the current repository into a directory (repositories name) | ||||
|       - name: Checkout Repository | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           path: ${{ github.event.repository.name }} | ||||
| @@ -23,12 +23,14 @@ jobs: | ||||
|       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libxml2-utils xmlstarlet | ||||
|  | ||||
|       # Create the variables required by the following steps | ||||
|       # - steps.required-variables.outputs.changes: latest entry in the changelog.txt (if exists), or addon.xml.in news element | ||||
|       # - steps.required-variables.outputs.version: version element from addon.xml.in | ||||
|       # - steps.required-variables.outputs.branch: branch of the triggering ref | ||||
|       # Note: we use a random EOF for 'changes' as is best practice for for multiline variables | ||||
|       - name: Get required variables | ||||
|         id: required-variables | ||||
|         run: | | ||||
| @@ -37,20 +39,18 @@ jobs: | ||||
|           then | ||||
|             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') | ||||
|           fi | ||||
|           changes="${changes//'%'/'%25'}" | ||||
|           changes="${changes//$'\n'/'%0A'}" | ||||
|           changes="${changes//$'\r'/'%0D'}" | ||||
|           changes="${changes//$'\\n'/'%0A'}" | ||||
|           changes="${changes//$'\\r'/'%0D'}" | ||||
|           echo ::set-output name=changes::$changes | ||||
|           EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | ||||
|           echo "changes<<$EOF" >> $GITHUB_OUTPUT | ||||
|           echo "$changes" >> $GITHUB_OUTPUT | ||||
|           echo "$EOF" >> $GITHUB_OUTPUT | ||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||
|           echo ::set-output name=version::$version | ||||
|           echo "version=$version" >> $GITHUB_OUTPUT | ||||
|           branch=$(echo ${GITHUB_REF#refs/heads/}) | ||||
|           echo ::set-output name=branch::$branch | ||||
|           echo "branch=$branch" >> $GITHUB_OUTPUT | ||||
|         working-directory: ${{ github.event.repository.name }} | ||||
|  | ||||
|       # Create a release at {steps.required-variables.outputs.branch} | ||||
|       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix | ||||
|       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 21.0.0-Omega | ||||
|       # - release body: {steps.required-variables.outputs.changes} | ||||
|       - name: Create Release | ||||
|         id: create-release | ||||
|   | ||||
| @@ -2,14 +2,14 @@ name: Sync addon metadata translations | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ Matrix, Nexus ] | ||||
|     branches: [ Matrix, Nexus, Omega ] | ||||
|     paths: | ||||
|       - '**addon.xml.in' | ||||
|       - '**resource.language.**strings.po' | ||||
|  | ||||
| jobs: | ||||
|   default: | ||||
|     if: github.repository == 'DigitalDevices/pvr.octonet' | ||||
|     if: github.repository == 'kodi-pvr/pvr.octonet' | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     strategy: | ||||
| @@ -21,18 +21,18 @@ jobs: | ||||
|     steps: | ||||
|  | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           path: project | ||||
|  | ||||
|       - name: Checkout sync_addon_metadata_translations repository | ||||
|         uses: actions/checkout@v2 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           repository: xbmc/sync_addon_metadata_translations | ||||
|           path: sync_addon_metadata_translations | ||||
|  | ||||
|       - name: Set up Python ${{ matrix.python-version }} | ||||
|         uses: actions/setup-python@v2 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: ${{ matrix.python-version }} | ||||
|  | ||||
| @@ -55,3 +55,4 @@ jobs: | ||||
|           branch: amt-sync | ||||
|           delete-branch: true | ||||
|           path: ./project | ||||
|           reviewers: gade01 | ||||
|   | ||||
							
								
								
									
										54
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,54 +0,0 @@ | ||||
| language: cpp | ||||
|  | ||||
| # | ||||
| # Define the builds to get up to date versions of cmake and gcc | ||||
| # | ||||
| env: | ||||
|   global: | ||||
|     - app_id=pvr.octonet | ||||
|  | ||||
| matrix: | ||||
|   include: | ||||
|     - os: linux | ||||
|       dist: bionic | ||||
|       sudo: required | ||||
|       compiler: gcc | ||||
|     - os: linux | ||||
|       dist: bionic | ||||
|       sudo: required | ||||
|       compiler: clang | ||||
|     - os: linux | ||||
|       dist: bionic | ||||
|       sudo: required | ||||
|       compiler: gcc | ||||
|       env: DEBIAN_BUILD=true | ||||
|     - os: linux | ||||
|       dist: focal | ||||
|       sudo: required | ||||
|       compiler: gcc | ||||
|       env: DEBIAN_BUILD=true | ||||
|     - os: osx | ||||
|       osx_image: xcode10.2 | ||||
|  | ||||
| before_install: | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/ppa; fi | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi | ||||
|  | ||||
| # | ||||
| # The addon source is automatically checked out in $TRAVIS_BUILD_DIR, | ||||
| # we'll put the Kodi source on the same level | ||||
| # | ||||
| before_script: | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then cd $TRAVIS_BUILD_DIR/..; fi | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then git clone --branch Matrix --depth=1 https://github.com/xbmc/xbmc.git; fi | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir build && cd build; fi | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then mkdir -p definition/${app_id}; fi | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt; fi | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons; fi | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/Matrix/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep $TRAVIS_BUILD_DIR; fi | ||||
|  | ||||
| script:  | ||||
|   - if [[ $DEBIAN_BUILD != true ]]; then make; fi | ||||
|   - if [[ $DEBIAN_BUILD == true ]]; then ./debian-addon-package-test.sh $TRAVIS_BUILD_DIR; fi | ||||
| @@ -12,14 +12,10 @@ include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Ko | ||||
| set(DEPLIBS ${JSONCPP_LIBRARIES}) | ||||
|  | ||||
| set(OCTONET_SOURCES src/addon.cpp | ||||
|                     src/OctonetData.cpp | ||||
|                     src/Socket.cpp | ||||
|                     src/rtsp_client.cpp) | ||||
|                     src/OctonetData.cpp) | ||||
|  | ||||
| set(OCTONET_HEADERS src/addon.h | ||||
|                     src/OctonetData.h | ||||
|                     src/Socket.h | ||||
|                     src/rtsp_client.hpp) | ||||
|                     src/OctonetData.h) | ||||
|  | ||||
| addon_version(pvr.octonet OCTONET) | ||||
| add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION}) | ||||
|   | ||||
							
								
								
									
										2
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1 @@ | ||||
| buildPlugin(version: "Matrix") | ||||
| buildPlugin(version: "Nexus") | ||||
|   | ||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,10 +1,9 @@ | ||||
| # Octonet PVR | ||||
| Digital Devices [Octonet](http://www.digital-devices.eu/shop/de/netzwerk-tv/) PVR client addon for [Kodi](http://kodi.tv) | ||||
|  | ||||
| | Platform | Status | | ||||
| |----------|--------| | ||||
| | Linux + OS X (github) | [](https://github.com/kodi-pvr/pvr.octonet/actions/workflows/build.yml) | | ||||
| | Windows (AppVeyor) | [](https://ci.appveyor.com/project/julianscheel/pvr-octonet) | | ||||
| [](LICENSE.md) | ||||
| [](https://github.com/DigitalDevices/pvr.octonet/actions/workflows/build.yml) | ||||
| [](https://jenkins.kodi.tv/blue/organizations/jenkins/DigitalDevices%2Fpvr.octonet/branches/) | ||||
|  | ||||
| # Building | ||||
|  | ||||
| @@ -14,13 +13,13 @@ adjusted according to your OS (`/` vs `\`). We use Linux paths here as an exampl | ||||
| Clone the `pvr.octonet` repository: | ||||
|  | ||||
| ``` | ||||
| $ git clone --branch Matrix https://github.com/DigitalDevices/pvr.octonet.git | ||||
| $ git clone https://github.com/DigitalDevices/pvr.octonet.git | ||||
| ``` | ||||
|  | ||||
| Clone the Kodi repository: | ||||
|  | ||||
| ``` | ||||
| $ git clone --branch Matrix https://github.com/xbmc/xbmc.git | ||||
| $ git clone --branch master https://github.com/xbmc/xbmc.git | ||||
| ``` | ||||
|  | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										33
									
								
								appveyor.yml
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								appveyor.yml
									
									
									
									
									
								
							| @@ -1,33 +0,0 @@ | ||||
| version: BuildNr.{build} | ||||
|  | ||||
| image: Visual Studio 2017 | ||||
|  | ||||
| shallow_clone: true | ||||
|  | ||||
| clone_folder: c:\projects\pvr.octonet | ||||
|  | ||||
| environment: | ||||
|   app_id: pvr.octonet | ||||
|  | ||||
|   matrix: | ||||
|     - GENERATOR: "Visual Studio 15" | ||||
|       CONFIG: Release | ||||
|     - GENERATOR: "Visual Studio 15 Win64" | ||||
|       CONFIG: Release | ||||
|     - GENERATOR: "Visual Studio 15 Win64" | ||||
|       CONFIG: Release | ||||
|       WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0" | ||||
|     - GENERATOR: "Visual Studio 15 ARM" | ||||
|       CONFIG: Release | ||||
|       WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0" | ||||
|  | ||||
| build_script: | ||||
|   - cd .. | ||||
|   - git clone --branch Matrix --depth=1 https://github.com/xbmc/xbmc.git | ||||
|   - cd %app_id% | ||||
|   - mkdir build | ||||
|   - cd build | ||||
|   - mkdir -p definition\%app_id% | ||||
|   - echo %app_id% %APPVEYOR_BUILD_FOLDER% %APPVEYOR_REPO_COMMIT% > definition\%app_id%\%app_id%.txt | ||||
|   - cmake -T host=x64 -G "%GENERATOR%" %WINSTORE% -DADDONS_TO_BUILD=%app_id% -DCMAKE_BUILD_TYPE=%CONFIG% -DADDONS_DEFINITION_DIR=%APPVEYOR_BUILD_FOLDER%/build/definition -DADDON_SRC_PREFIX=../.. -DCMAKE_INSTALL_PREFIX=../../xbmc/addons -DPACKAGE_ZIP=1 ../../xbmc/cmake/addons | ||||
|   - cmake --build . --config %CONFIG% --target %app_id% | ||||
							
								
								
									
										59
									
								
								build-install-mac.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								build-install-mac.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| set -e | ||||
|  | ||||
| if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then | ||||
|   echo "Usage: $0 <XBMC-SRC-DIR>" >&2 | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| if [[ "$OSTYPE" != "darwin"* ]]; then | ||||
|   echo "Error: Script only for use on MacOSX" >&2 | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" | ||||
| if [[ "$1" = /* ]] | ||||
| then | ||||
|   #absolute path | ||||
|   SCRIPT_DIR=""   | ||||
| else | ||||
|   #relative | ||||
|   SCRIPT_DIR="$SCRIPT_DIR/" | ||||
| fi | ||||
|  | ||||
| BINARY_ADDONS_TARGET_DIR="$1/tools/depends/target/binary-addons" | ||||
| MACOSX_BINARY_ADDONS_TARGET_DIR="" | ||||
| KODI_ADDONS_DIR="$HOME/Library/Application Support/Kodi/addons" | ||||
| ADDON_NAME=`basename -s .git \`git config --get remote.origin.url\`` | ||||
|  | ||||
| if [ ! -d "$BINARY_ADDONS_TARGET_DIR" ]; then | ||||
|   echo "Error: Could not find binary addons directory at: $BINARY_ADDONS_TARGET_DIR" >&2 | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| for DIR in "$BINARY_ADDONS_TARGET_DIR/"macosx*; do | ||||
|     if [ -d "${DIR}" ]; then | ||||
| 	    MACOSX_BINARY_ADDONS_TARGET_DIR="${DIR}" | ||||
|       break | ||||
|     fi | ||||
| done | ||||
|  | ||||
| if [ -z "$MACOSX_BINARY_ADDONS_TARGET_DIR" ]; then | ||||
|   echo "Error: Could not find binary addons build directory at: $BINARY_ADDONS_TARGET_DIR/macosx*" >&2 | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| if [ ! -d "$KODI_ADDONS_DIR" ]; then | ||||
|   echo "Error: Kodi addons dir does not exist at: $KODI_ADDONS_DIR" >&2 | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| cd "$MACOSX_BINARY_ADDONS_TARGET_DIR" | ||||
| make | ||||
|  | ||||
| XBMC_BUILD_ADDON_INSTALL_DIR=$(cd "$SCRIPT_DIR$1/addons/$ADDON_NAME" 2> /dev/null && pwd -P) | ||||
| rm -rf "$KODI_ADDONS_DIR/$ADDON_NAME" | ||||
| echo "Removed previous addon build from: $KODI_ADDONS_DIR" | ||||
| cp -rf "$XBMC_BUILD_ADDON_INSTALL_DIR" "$KODI_ADDONS_DIR" | ||||
| echo "Copied new addon build to: $KODI_ADDONS_DIR" | ||||
| @@ -1,10 +1,12 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <addon | ||||
|   id="pvr.octonet" | ||||
|   version="19.0.0" | ||||
|   version="21.0.0" | ||||
|   name="Digital Devices Octopus NET Client" | ||||
|   provider-name="digitaldevices"> | ||||
|   <requires>@ADDON_DEPENDS@</requires> | ||||
|   <requires>@ADDON_DEPENDS@ | ||||
|     <import addon="inputstream.ffmpegdirect" minversion="21.0.0"/> | ||||
|   </requires> | ||||
|   <extension | ||||
|     point="kodi.pvrclient" | ||||
|     library_@PLATFORM@="@LIBRARY_FILENAME@"/> | ||||
|   | ||||
							
								
								
									
										4
									
								
								pvr.octonet/changelog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								pvr.octonet/changelog.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| v21.0.0 | ||||
| - Change add-on to use inputstream.ffmpegdirect which also enables timeshifting | ||||
| - Fix jsoncpp deprecation warnings | ||||
| - Add build script for mac | ||||
| @@ -27,3 +27,41 @@ msgstr "" | ||||
| msgctxt "#30001" | ||||
| msgid "Could not load chanellist" | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30002" | ||||
| msgid "Timeshift" | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30003" | ||||
| msgid "Enable timeshift" | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30004" | ||||
| msgid "- Modify inputstream.ffmpegdirect settings..." | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30005" | ||||
| msgid "Inputstream settings" | ||||
| msgstr "" | ||||
|  | ||||
| #empty strings from id 30006 to 30599 | ||||
|  | ||||
| msgctxt "#30600" | ||||
| msgid "The IP/address of the octonet server. Note that in case of an address, the protocol (http://) has to be omitted." | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30601" | ||||
| msgid "General settings required by the add-on." | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30602" | ||||
| msgid "Timeshift settings for pausing/rewinding and fast-forwarding live streams." | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30603" | ||||
| msgid "Enable the timeshift feature." | ||||
| msgstr "" | ||||
|  | ||||
| msgctxt "#30604" | ||||
| msgid "Open settings dialog for inputstream.ffmpegdirect for modification of timeshift and other settings." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8" standalone="yes"?> | ||||
| <settings version="1"> | ||||
|   <section id="pvr.octonet"> | ||||
|     <category id="main" label="128" help="-1"> | ||||
|     <category id="main" label="128" help="30601"> | ||||
|       <group id="1" label="-1"> | ||||
|         <!-- Octonet Server Address --> | ||||
|         <setting id="octonetAddress" type="string" label="30000" help="-1"> | ||||
|         <setting id="octonetAddress" type="string" label="30000" help="30600"> | ||||
|           <level>0</level> | ||||
|           <default></default> | ||||
|           <constraints> | ||||
| @@ -14,5 +14,26 @@ | ||||
|         </setting> | ||||
|       </group> | ||||
|     </category> | ||||
|     <category id="timeshift" label="30002" help="30602"> | ||||
|       <group id="1" label="30002"> | ||||
|         <setting id="timeshiftEnabled" type="boolean" label="30003" help="30603"> | ||||
|           <level>0</level> | ||||
|           <default>false</default> | ||||
|           <control type="toggle" /> | ||||
|         </setting> | ||||
|       </group> | ||||
|       <group id="2" label="30005"> | ||||
|         <setting id="ffmpegdirectSettings" type="action" label="30004" help="30604"> | ||||
|           <level>0</level> | ||||
|           <data>Addon.OpenSettings(inputstream.ffmpegdirect)</data> | ||||
|           <dependencies> | ||||
|             <dependency type="enable" setting="timeshiftEnabled" operator="is">true</dependency> | ||||
|           </dependencies> | ||||
|           <control type="button" format="action"> | ||||
|             <close>true</close> | ||||
|           </control> | ||||
|         </setting> | ||||
|       </group> | ||||
|     </category> | ||||
|   </section> | ||||
| </settings> | ||||
|   | ||||
| @@ -10,8 +10,6 @@ | ||||
|  | ||||
| #include "OctonetData.h" | ||||
|  | ||||
| #include "rtsp_client.hpp" | ||||
|  | ||||
| #include <json/json.h> | ||||
| #include <kodi/Filesystem.h> | ||||
| #include <kodi/General.h> | ||||
| @@ -23,35 +21,23 @@ | ||||
| #endif | ||||
|  | ||||
| OctonetData::OctonetData(const std::string& octonetAddress, | ||||
|                          KODI_HANDLE instance, | ||||
|                          const std::string& kodiVersion) | ||||
|   : kodi::addon::CInstancePVRClient(instance, kodiVersion) | ||||
|                          bool enableTimeshift, | ||||
|                          const kodi::addon::IInstanceInfo& instance) | ||||
|   : kodi::addon::CInstancePVRClient(instance) | ||||
| { | ||||
|   m_serverAddress = octonetAddress; | ||||
|   m_enableTimeshift = enableTimeshift; | ||||
|   m_channels.clear(); | ||||
|   m_groups.clear(); | ||||
|   m_lastEpgLoad = 0; | ||||
|  | ||||
|   if (!LoadChannelList()) | ||||
|     kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::GetLocalizedString(30001).c_str(), | ||||
|     kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30001).c_str(), | ||||
|                                      m_channels.size()); | ||||
|  | ||||
|   /* | ||||
|   // Currently unused, as thread was already present before with | ||||
|   // p8platform, by remove of them was it added as C++11 thread way. | ||||
|   kodi::Log(ADDON_LOG_INFO, "%s Starting separate client update thread...", __func__); | ||||
|   m_running = true; | ||||
|   m_thread = std::thread([&] { Process(); }); | ||||
|   */ | ||||
| } | ||||
|  | ||||
| OctonetData::~OctonetData(void) | ||||
| { | ||||
|   /* | ||||
|   m_running = false; | ||||
|   if (m_thread.joinable()) | ||||
|     m_thread.join(); | ||||
|   */ | ||||
| } | ||||
|  | ||||
| PVR_ERROR OctonetData::GetCapabilities(kodi::addon::PVRCapabilities& capabilities) | ||||
| @@ -128,9 +114,11 @@ bool OctonetData::LoadChannelList() | ||||
|   f.Close(); | ||||
|  | ||||
|   Json::Value root; | ||||
|   Json::Reader reader; | ||||
|   JSONCPP_STRING err; | ||||
|   Json::CharReaderBuilder builder; | ||||
|   const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); | ||||
|  | ||||
|   if (!reader.parse(jsonContent, root, false)) | ||||
|   if (!reader->parse(jsonContent.c_str(), jsonContent.c_str() + jsonContent.length(), &root, &err)) | ||||
|     return false; | ||||
|  | ||||
|   const Json::Value groupList = root["GroupList"]; | ||||
| @@ -216,9 +204,11 @@ bool OctonetData::LoadEPG(void) | ||||
|   f.Close(); | ||||
|  | ||||
|   Json::Value root; | ||||
|   Json::Reader reader; | ||||
|   JSONCPP_STRING err; | ||||
|   Json::CharReaderBuilder builder; | ||||
|   const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); | ||||
|  | ||||
|   if (!reader.parse(jsonContent, root, false)) | ||||
|   if (!reader->parse(jsonContent.c_str(), jsonContent.c_str() + jsonContent.length(), &root, &err)) | ||||
|     return false; | ||||
|  | ||||
|   const Json::Value eventList = root["EventList"]; | ||||
| @@ -255,11 +245,6 @@ bool OctonetData::LoadEPG(void) | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void OctonetData::Process() | ||||
| { | ||||
|   return; | ||||
| } | ||||
|  | ||||
| PVR_ERROR OctonetData::GetChannelsAmount(int& amount) | ||||
| { | ||||
|   amount = m_channels.size(); | ||||
| @@ -288,6 +273,25 @@ PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet | ||||
|   return PVR_ERROR_NO_ERROR; | ||||
| } | ||||
|  | ||||
| PVR_ERROR OctonetData::GetChannelStreamProperties(const kodi::addon::PVRChannel& channelinfo, std::vector<kodi::addon::PVRStreamProperty>& properties) | ||||
| { | ||||
|   properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, "inputstream.ffmpegdirect"); | ||||
|   properties.emplace_back("inputstream.ffmpegdirect.is_realtime_stream", "true"); | ||||
|   properties.emplace_back("inputstream.ffmpegdirect.open_mode", "ffmpeg"); | ||||
|   if (m_enableTimeshift) | ||||
|   { | ||||
|     // This property is required to support timeshifting for Radio channels | ||||
|     properties.emplace_back("inputstream-player", "videodefaultplayer"); | ||||
|     properties.emplace_back("inputstream.ffmpegdirect.stream_mode", "timeshift"); | ||||
|   } | ||||
|   properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, "video/x-mpegts"); | ||||
|   properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, GetUrl(channelinfo.GetUniqueId())); | ||||
|  | ||||
|   kodi::Log(ADDON_LOG_INFO, "Playing channel - name: %s, url: %s, and using inputstream.ffmpegdirect", GetName(channelinfo.GetUniqueId()).c_str(), GetUrl(channelinfo.GetUniqueId()).c_str()); | ||||
|  | ||||
|   return PVR_ERROR_NO_ERROR; | ||||
| } | ||||
|  | ||||
| PVR_ERROR OctonetData::GetEPGForChannel(int channelUid, | ||||
|                                         time_t start, | ||||
|                                         time_t end, | ||||
| @@ -436,23 +440,8 @@ OctonetGroup* OctonetData::FindGroup(const std::string& name) | ||||
|       return &group; | ||||
|   } | ||||
|  | ||||
|   kodi::Log(ADDON_LOG_ERROR, "Could not find group: %s, in available groups from the server"); | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| /* PVR stream handling */ | ||||
| /* entirely unused, as we use standard RTSP+TS mux, which can be handlded by | ||||
|  * Kodi core */ | ||||
| bool OctonetData::OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) | ||||
| { | ||||
|   return rtsp_open(GetName(channelinfo.GetUniqueId()), GetUrl(channelinfo.GetUniqueId())); | ||||
| } | ||||
|  | ||||
| int OctonetData::ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) | ||||
| { | ||||
|   return rtsp_read(pBuffer, iBufferSize); | ||||
| } | ||||
|  | ||||
| void OctonetData::CloseLiveStream() | ||||
| { | ||||
|   rtsp_close(); | ||||
| } | ||||
|   | ||||
| @@ -43,12 +43,12 @@ struct OctonetGroup | ||||
|   std::vector<int> members; | ||||
| }; | ||||
|  | ||||
| class ATTRIBUTE_HIDDEN OctonetData : public kodi::addon::CInstancePVRClient | ||||
| class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient | ||||
| { | ||||
| public: | ||||
|   OctonetData(const std::string& octonetAddress, | ||||
|               KODI_HANDLE instance, | ||||
|               const std::string& kodiVersion); | ||||
|               bool enableTimeshift, | ||||
|               const kodi::addon::IInstanceInfo& instance); | ||||
|   ~OctonetData() override; | ||||
|  | ||||
|   PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; | ||||
| @@ -67,19 +67,14 @@ public: | ||||
|   PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override; | ||||
|   PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, | ||||
|                                    kodi::addon::PVRChannelGroupMembersResultSet& results) override; | ||||
|   PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, std::vector<kodi::addon::PVRStreamProperty>& properties) override; | ||||
|  | ||||
|   PVR_ERROR GetEPGForChannel(int channelUid, | ||||
|                              time_t start, | ||||
|                              time_t end, | ||||
|                              kodi::addon::PVREPGTagsResultSet& results) override; | ||||
|  | ||||
|   bool OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) override; | ||||
|   int ReadLiveStream(unsigned char* buffer, unsigned int size) override; | ||||
|   void CloseLiveStream() override; | ||||
|  | ||||
| protected: | ||||
|   void Process(); | ||||
|  | ||||
|   const std::string& GetUrl(int id) const; | ||||
|   const std::string& GetName(int id) const; | ||||
|  | ||||
| @@ -92,11 +87,9 @@ protected: | ||||
|  | ||||
| private: | ||||
|   std::string m_serverAddress; | ||||
|   bool m_enableTimeshift = false; | ||||
|   std::vector<OctonetChannel> m_channels; | ||||
|   std::vector<OctonetGroup> m_groups; | ||||
|  | ||||
|   time_t m_lastEpgLoad; | ||||
|  | ||||
|   std::atomic<bool> m_running = {false}; | ||||
|   std::thread m_thread; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
								
							
							
						
						
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
								
							| @@ -1,728 +0,0 @@ | ||||
| /* | ||||
|  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) | ||||
|  * | ||||
|  *  SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  *  See LICENSE.md for more information. | ||||
|  */ | ||||
|  | ||||
| #include "Socket.h" | ||||
|  | ||||
| #include <cstdio> | ||||
| #include <kodi/General.h> | ||||
| #include <string> | ||||
|  | ||||
| using namespace std; | ||||
|  | ||||
| namespace OCTO | ||||
| { | ||||
|  | ||||
| /* Master defines for client control */ | ||||
| #define RECEIVE_TIMEOUT 6 //sec | ||||
|  | ||||
| Socket::Socket(const enum SocketFamily family, | ||||
|                const enum SocketDomain domain, | ||||
|                const enum SocketType type, | ||||
|                const enum SocketProtocol protocol) | ||||
| { | ||||
|   m_sd = INVALID_SOCKET; | ||||
|   m_family = family; | ||||
|   m_domain = domain; | ||||
|   m_type = type; | ||||
|   m_protocol = protocol; | ||||
|   m_port = 0; | ||||
|   memset(&m_sockaddr, 0, sizeof(m_sockaddr)); | ||||
| } | ||||
|  | ||||
|  | ||||
| Socket::Socket() | ||||
| { | ||||
|   // Default constructor, default settings | ||||
|   m_sd = INVALID_SOCKET; | ||||
|   m_family = af_inet; | ||||
|   m_domain = pf_inet; | ||||
|   m_type = sock_stream; | ||||
|   m_protocol = tcp; | ||||
|   m_port = 0; | ||||
|   memset(&m_sockaddr, 0, sizeof(m_sockaddr)); | ||||
| } | ||||
|  | ||||
|  | ||||
| Socket::~Socket() | ||||
| { | ||||
|   close(); | ||||
|   osCleanup(); | ||||
| } | ||||
|  | ||||
| bool Socket::setHostname(const std::string& host) | ||||
| { | ||||
|   m_hostname = host; | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool Socket::close() | ||||
| { | ||||
|   if (is_valid()) | ||||
|   { | ||||
|     if (m_sd != SOCKET_ERROR) | ||||
|       closesocket(m_sd); | ||||
|     m_sd = INVALID_SOCKET; | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool Socket::create() | ||||
| { | ||||
|   close(); | ||||
|  | ||||
|   if (!osInit()) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool Socket::bind(const unsigned short port) | ||||
| { | ||||
|  | ||||
|   if (is_valid()) | ||||
|   { | ||||
|     close(); | ||||
|   } | ||||
|  | ||||
|   m_sd = socket(m_family, m_type, m_protocol); | ||||
|   m_port = port; | ||||
|   m_sockaddr.sin_family = (sa_family_t)m_family; | ||||
|   m_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all | ||||
|   m_sockaddr.sin_port = htons(m_port); | ||||
|  | ||||
|   int bind_return = ::bind(m_sd, (sockaddr*)(&m_sockaddr), sizeof(m_sockaddr)); | ||||
|  | ||||
|   if (bind_return == -1) | ||||
|   { | ||||
|     errormessage(getLastError(), "Socket::bind"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool Socket::listen() const | ||||
| { | ||||
|  | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   int listen_return = ::listen(m_sd, SOMAXCONN); | ||||
|   //This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h. | ||||
|   //linux 128//MAXCONNECTIONS =1 | ||||
|  | ||||
|   if (listen_return == -1) | ||||
|   { | ||||
|     errormessage(getLastError(), "Socket::listen"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool Socket::accept(Socket& new_socket) const | ||||
| { | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   socklen_t addr_length = sizeof(m_sockaddr); | ||||
|   new_socket.m_sd = | ||||
|       ::accept(m_sd, const_cast<sockaddr*>((const sockaddr*)&m_sockaddr), &addr_length); | ||||
|  | ||||
| #ifdef TARGET_WINDOWS | ||||
|   if (new_socket.m_sd == INVALID_SOCKET) | ||||
| #else | ||||
|   if (new_socket.m_sd <= 0) | ||||
| #endif | ||||
|   { | ||||
|     errormessage(getLastError(), "Socket::accept"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::send(const std::string& data) | ||||
| { | ||||
|   return Socket::send((const char*)data.c_str(), (const unsigned int)data.size()); | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::send(const char* data, const unsigned int len) | ||||
| { | ||||
|   fd_set set_w, set_e; | ||||
|   struct timeval tv; | ||||
|   int result; | ||||
|  | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   // fill with new data | ||||
|   tv.tv_sec = 0; | ||||
|   tv.tv_usec = 0; | ||||
|  | ||||
|   FD_ZERO(&set_w); | ||||
|   FD_ZERO(&set_e); | ||||
|   FD_SET(m_sd, &set_w); | ||||
|   FD_SET(m_sd, &set_e); | ||||
|  | ||||
|   result = select(FD_SETSIZE, &set_w, nullptr, &set_e, &tv); | ||||
|  | ||||
|   if (result < 0) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::send  - select failed"); | ||||
|     close(); | ||||
|     return 0; | ||||
|   } | ||||
|   if (FD_ISSET(m_sd, &set_w)) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::send  - failed to send data"); | ||||
|     close(); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   int status = ::send(m_sd, data, len, 0); | ||||
|  | ||||
|   if (status == -1) | ||||
|   { | ||||
|     errormessage(getLastError(), "Socket::send"); | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::send  - failed to send data"); | ||||
|     close(); | ||||
|     return 0; | ||||
|   } | ||||
|   return status; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::sendto(const char* data, unsigned int size, bool sendcompletebuffer) | ||||
| { | ||||
|   int sentbytes = 0; | ||||
|   int i; | ||||
|  | ||||
|   do | ||||
|   { | ||||
|     i = ::sendto(m_sd, data, size, 0, (const struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr)); | ||||
|  | ||||
|     if (i <= 0) | ||||
|     { | ||||
|       errormessage(getLastError(), "Socket::sendto"); | ||||
|       osCleanup(); | ||||
|       return i; | ||||
|     } | ||||
|     sentbytes += i; | ||||
|   } while ((sentbytes < (int)size) && (sendcompletebuffer == true)); | ||||
|  | ||||
|   return i; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::receive(std::string& data, unsigned int minpacketsize) const | ||||
| { | ||||
|   char* buf = nullptr; | ||||
|   int status = 0; | ||||
|  | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   buf = new char[minpacketsize + 1]; | ||||
|   memset(buf, 0, minpacketsize + 1); | ||||
|  | ||||
|   status = receive(buf, minpacketsize, minpacketsize); | ||||
|  | ||||
|   data = buf; | ||||
|  | ||||
|   delete[] buf; | ||||
|   return status; | ||||
| } | ||||
|  | ||||
|  | ||||
| //Receive until error or \n | ||||
| bool Socket::ReadLine(string& line) | ||||
| { | ||||
|   fd_set set_r, set_e; | ||||
|   timeval timeout; | ||||
|   int retries = 6; | ||||
|   char buffer[2048]; | ||||
|  | ||||
|   if (!is_valid()) | ||||
|     return false; | ||||
|  | ||||
|   while (true) | ||||
|   { | ||||
|     size_t pos1 = line.find("\r\n", 0); | ||||
|     if (pos1 != std::string::npos) | ||||
|     { | ||||
|       line.erase(pos1, string::npos); | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     timeout.tv_sec = RECEIVE_TIMEOUT; | ||||
|     timeout.tv_usec = 0; | ||||
|  | ||||
|     // fill with new data | ||||
|     FD_ZERO(&set_r); | ||||
|     FD_ZERO(&set_e); | ||||
|     FD_SET(m_sd, &set_r); | ||||
|     FD_SET(m_sd, &set_e); | ||||
|     int result = select(FD_SETSIZE, &set_r, nullptr, &set_e, &timeout); | ||||
|  | ||||
|     if (result < 0) | ||||
|     { | ||||
|       kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__); | ||||
|       errormessage(getLastError(), __func__); | ||||
|       close(); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (result == 0) | ||||
|     { | ||||
|       if (retries != 0) | ||||
|       { | ||||
|         kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__, | ||||
|                   retries); | ||||
|         retries--; | ||||
|         continue; | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", | ||||
|                   __func__); | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     result = recv(m_sd, buffer, sizeof(buffer) - 1, 0); | ||||
|     if (result < 0) | ||||
|     { | ||||
|       kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__); | ||||
|       errormessage(getLastError(), __func__); | ||||
|       close(); | ||||
|       return false; | ||||
|     } | ||||
|     buffer[result] = 0; | ||||
|  | ||||
|     line.append(buffer); | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::receive(std::string& data) const | ||||
| { | ||||
|   char buf[MAXRECV + 1]; | ||||
|   int status = 0; | ||||
|  | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   memset(buf, 0, MAXRECV + 1); | ||||
|   status = receive(buf, MAXRECV, 0); | ||||
|   data = buf; | ||||
|  | ||||
|   return status; | ||||
| } | ||||
|  | ||||
| int Socket::receive(char* data, | ||||
|                     const unsigned int buffersize, | ||||
|                     const unsigned int minpacketsize) const | ||||
| { | ||||
|   unsigned int receivedsize = 0; | ||||
|  | ||||
|   if (!is_valid()) | ||||
|   { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   while ((receivedsize <= minpacketsize) && (receivedsize < buffersize)) | ||||
|   { | ||||
|     int status = ::recv(m_sd, data + receivedsize, (buffersize - receivedsize), 0); | ||||
|  | ||||
|     if (status == SOCKET_ERROR) | ||||
|     { | ||||
|       errormessage(getLastError(), "Socket::receive"); | ||||
|       return status; | ||||
|     } | ||||
|  | ||||
|     receivedsize += status; | ||||
|   } | ||||
|  | ||||
|   return receivedsize; | ||||
| } | ||||
|  | ||||
|  | ||||
| int Socket::recvfrom(char* data, | ||||
|                      const int buffersize, | ||||
|                      struct sockaddr* from, | ||||
|                      socklen_t* fromlen) const | ||||
| { | ||||
|   int status = ::recvfrom(m_sd, data, buffersize, 0, from, fromlen); | ||||
|  | ||||
|   return status; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool Socket::connect(const std::string& host, const unsigned short port) | ||||
| { | ||||
|   close(); | ||||
|  | ||||
|   if (!setHostname(host)) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str()); | ||||
|     return false; | ||||
|   } | ||||
|   m_port = port; | ||||
|  | ||||
|   char strPort[15]; | ||||
|   snprintf(strPort, 15, "%hu", port); | ||||
|  | ||||
|   struct addrinfo hints; | ||||
|   struct addrinfo* result = nullptr; | ||||
|   struct addrinfo* address = nullptr; | ||||
|   memset(&hints, 0, sizeof(hints)); | ||||
|   hints.ai_family = m_family; | ||||
|   hints.ai_socktype = m_type; | ||||
|   hints.ai_protocol = m_protocol; | ||||
|  | ||||
|   int retval = getaddrinfo(host.c_str(), strPort, &hints, &result); | ||||
|   if (retval != 0) | ||||
|   { | ||||
|     errormessage(getLastError(), "Socket::connect"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   for (address = result; address != nullptr; address = address->ai_next) | ||||
|   { | ||||
|     // Create the socket | ||||
|     m_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); | ||||
|  | ||||
|     if (m_sd == INVALID_SOCKET) | ||||
|     { | ||||
|       errormessage(getLastError(), "Socket::create"); | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     int status = ::connect(m_sd, address->ai_addr, address->ai_addrlen); | ||||
|     if (status == SOCKET_ERROR) | ||||
|     { | ||||
|       close(); | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     // We have a conection | ||||
|     break; | ||||
|   } | ||||
|  | ||||
|   freeaddrinfo(result); | ||||
|  | ||||
|   if (address == nullptr) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port); | ||||
|     errormessage(getLastError(), "Socket::connect"); | ||||
|     close(); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool Socket::reconnect() | ||||
| { | ||||
|   if (is_valid()) | ||||
|   { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   return connect(m_hostname, m_port); | ||||
| } | ||||
|  | ||||
| bool Socket::is_valid() const | ||||
| { | ||||
|   return (m_sd != INVALID_SOCKET); | ||||
| } | ||||
|  | ||||
| #if defined(TARGET_WINDOWS) | ||||
| bool Socket::set_non_blocking(const bool b) | ||||
| { | ||||
|   u_long iMode; | ||||
|  | ||||
|   if (b) | ||||
|     iMode = 1; // enable non_blocking | ||||
|   else | ||||
|     iMode = 0; // disable non_blocking | ||||
|  | ||||
|   if (ioctlsocket(m_sd, FIONBIO, &iMode) == -1) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", | ||||
|               iMode); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void Socket::errormessage(int errnum, const char* functionname) const | ||||
| { | ||||
|   const char* errmsg = nullptr; | ||||
|  | ||||
|   switch (errnum) | ||||
|   { | ||||
|     case WSANOTINITIALISED: | ||||
|       errmsg = "A successful WSAStartup call must occur before using this function."; | ||||
|       break; | ||||
|     case WSAENETDOWN: | ||||
|       errmsg = "The network subsystem or the associated service provider has failed"; | ||||
|       break; | ||||
|     case WSA_NOT_ENOUGH_MEMORY: | ||||
|       errmsg = "Insufficient memory available"; | ||||
|       break; | ||||
|     case WSA_INVALID_PARAMETER: | ||||
|       errmsg = "One or more parameters are invalid"; | ||||
|       break; | ||||
|     case WSA_OPERATION_ABORTED: | ||||
|       errmsg = "Overlapped operation aborted"; | ||||
|       break; | ||||
|     case WSAEINTR: | ||||
|       errmsg = "Interrupted function call"; | ||||
|       break; | ||||
|     case WSAEBADF: | ||||
|       errmsg = "File handle is not valid"; | ||||
|       break; | ||||
|     case WSAEACCES: | ||||
|       errmsg = "Permission denied"; | ||||
|       break; | ||||
|     case WSAEFAULT: | ||||
|       errmsg = "Bad address"; | ||||
|       break; | ||||
|     case WSAEINVAL: | ||||
|       errmsg = "Invalid argument"; | ||||
|       break; | ||||
|     case WSAENOTSOCK: | ||||
|       errmsg = "Socket operation on nonsocket"; | ||||
|       break; | ||||
|     case WSAEDESTADDRREQ: | ||||
|       errmsg = "Destination address required"; | ||||
|       break; | ||||
|     case WSAEMSGSIZE: | ||||
|       errmsg = "Message too long"; | ||||
|       break; | ||||
|     case WSAEPROTOTYPE: | ||||
|       errmsg = "Protocol wrong type for socket"; | ||||
|       break; | ||||
|     case WSAENOPROTOOPT: | ||||
|       errmsg = "Bad protocol option"; | ||||
|       break; | ||||
|     case WSAEPFNOSUPPORT: | ||||
|       errmsg = "Protocol family not supported"; | ||||
|       break; | ||||
|     case WSAEAFNOSUPPORT: | ||||
|       errmsg = "Address family not supported by protocol family"; | ||||
|       break; | ||||
|     case WSAEADDRINUSE: | ||||
|       errmsg = "Address already in use"; | ||||
|       break; | ||||
|     case WSAECONNRESET: | ||||
|       errmsg = "Connection reset by peer"; | ||||
|       break; | ||||
|     case WSAHOST_NOT_FOUND: | ||||
|       errmsg = "Authoritative answer host not found"; | ||||
|       break; | ||||
|     case WSATRY_AGAIN: | ||||
|       errmsg = "Nonauthoritative host not found, or server failure"; | ||||
|       break; | ||||
|     case WSAEISCONN: | ||||
|       errmsg = "Socket is already connected"; | ||||
|       break; | ||||
|     case WSAETIMEDOUT: | ||||
|       errmsg = "Connection timed out"; | ||||
|       break; | ||||
|     case WSAECONNREFUSED: | ||||
|       errmsg = "Connection refused"; | ||||
|       break; | ||||
|     case WSANO_DATA: | ||||
|       errmsg = "Valid name, no data record of requested type"; | ||||
|       break; | ||||
|     default: | ||||
|       errmsg = "WSA Error"; | ||||
|   } | ||||
|   kodi::Log(ADDON_LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg); | ||||
| } | ||||
|  | ||||
| int Socket::getLastError() const | ||||
| { | ||||
|   return WSAGetLastError(); | ||||
| } | ||||
|  | ||||
| int Socket::win_usage_count = 0; //Declared static in Socket class | ||||
|  | ||||
| bool Socket::osInit() | ||||
| { | ||||
|   win_usage_count++; | ||||
|   // initialize winsock: | ||||
|   if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   WORD wVersionRequested = MAKEWORD(2, 2); | ||||
|  | ||||
|   // check version | ||||
|   if (m_wsaData.wVersion != wVersionRequested) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void Socket::osCleanup() | ||||
| { | ||||
|   win_usage_count--; | ||||
|   if (win_usage_count == 0) | ||||
|   { | ||||
|     WSACleanup(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| #elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD | ||||
| bool Socket::set_non_blocking(const bool b) | ||||
| { | ||||
|   int opts; | ||||
|  | ||||
|   opts = fcntl(m_sd, F_GETFL); | ||||
|  | ||||
|   if (opts < 0) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (b) | ||||
|     opts = (opts | O_NONBLOCK); | ||||
|   else | ||||
|     opts = (opts & ~O_NONBLOCK); | ||||
|  | ||||
|   if (fcntl(m_sd, F_SETFL, opts) == -1) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void Socket::errormessage(int errnum, const char* functionname) const | ||||
| { | ||||
|   const char* errmsg = nullptr; | ||||
|  | ||||
|   switch (errnum) | ||||
|   { | ||||
|     case EAGAIN: //same as EWOULDBLOCK | ||||
|       errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block"; | ||||
|       break; | ||||
|     case EBADF: | ||||
|       errmsg = "EBADF: An invalid descriptor was specified"; | ||||
|       break; | ||||
|     case ECONNRESET: | ||||
|       errmsg = "ECONNRESET: Connection reset by peer"; | ||||
|       break; | ||||
|     case EDESTADDRREQ: | ||||
|       errmsg = "EDESTADDRREQ: The socket is not in connection mode and no peer address is set"; | ||||
|       break; | ||||
|     case EFAULT: | ||||
|       errmsg = "EFAULT: An invalid userspace address was specified for a parameter"; | ||||
|       break; | ||||
|     case EINTR: | ||||
|       errmsg = "EINTR: A signal occurred before data was transmitted"; | ||||
|       break; | ||||
|     case EINVAL: | ||||
|       errmsg = "EINVAL: Invalid argument passed"; | ||||
|       break; | ||||
|     case ENOTSOCK: | ||||
|       errmsg = "ENOTSOCK: The argument is not a valid socket"; | ||||
|       break; | ||||
|     case EMSGSIZE: | ||||
|       errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the " | ||||
|                "message to be sent made this impossible"; | ||||
|       break; | ||||
|     case ENOBUFS: | ||||
|       errmsg = "ENOBUFS: The output queue for a network interface was full"; | ||||
|       break; | ||||
|     case ENOMEM: | ||||
|       errmsg = "ENOMEM: No memory available"; | ||||
|       break; | ||||
|     case EPIPE: | ||||
|       errmsg = "EPIPE: The local end has been shut down on a connection oriented socket"; | ||||
|       break; | ||||
|     case EPROTONOSUPPORT: | ||||
|       errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported " | ||||
|                "within this domain"; | ||||
|       break; | ||||
|     case EAFNOSUPPORT: | ||||
|       errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family"; | ||||
|       break; | ||||
|     case ENFILE: | ||||
|       errmsg = "ENFILE: Not enough kernel memory to allocate a new socket structure"; | ||||
|       break; | ||||
|     case EMFILE: | ||||
|       errmsg = "EMFILE: Process file table overflow"; | ||||
|       break; | ||||
|     case EACCES: | ||||
|       errmsg = | ||||
|           "EACCES: Permission to create a socket of the specified type and/or protocol is denied"; | ||||
|       break; | ||||
|     case ECONNREFUSED: | ||||
|       errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically " | ||||
|                "because it is not running the requested service)"; | ||||
|       break; | ||||
|     case ENOTCONN: | ||||
|       errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not " | ||||
|                "been connected"; | ||||
|       break; | ||||
|     //case E: | ||||
|     //	errmsg = ""; | ||||
|     //	break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   kodi::Log(ADDON_LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg); | ||||
| } | ||||
|  | ||||
| int Socket::getLastError() const | ||||
| { | ||||
|   return errno; | ||||
| } | ||||
|  | ||||
| bool Socket::osInit() | ||||
| { | ||||
|   // Not needed for Linux | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void Socket::osCleanup() | ||||
| { | ||||
|   // Not needed for Linux | ||||
| } | ||||
| #endif //TARGET_WINDOWS || TARGET_LINUX || TARGET_DARWIN || TARGET_FREEBSD | ||||
|  | ||||
| } //namespace OCTO | ||||
							
								
								
									
										284
									
								
								src/Socket.h
									
									
									
									
									
								
							
							
						
						
									
										284
									
								
								src/Socket.h
									
									
									
									
									
								
							| @@ -1,284 +0,0 @@ | ||||
| /* | ||||
|  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) | ||||
|  * | ||||
|  *  SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  *  See LICENSE.md for more information. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| //Include platform specific datatypes, header files, defines and constants: | ||||
| #if defined TARGET_WINDOWS | ||||
| #define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support | ||||
| #pragma warning(disable : 4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition" | ||||
| #include <WS2tcpip.h> | ||||
| #include <winsock2.h> | ||||
| #pragma warning(default : 4005) | ||||
| #include <windows.h> | ||||
|  | ||||
| #ifndef NI_MAXHOST | ||||
| #define NI_MAXHOST 1025 | ||||
| #endif | ||||
|  | ||||
| #ifndef socklen_t | ||||
| typedef int socklen_t; | ||||
| #endif | ||||
| #ifndef ipaddr_t | ||||
| typedef unsigned long ipaddr_t; | ||||
| #endif | ||||
| #ifndef port_t | ||||
| typedef unsigned short port_t; | ||||
| #endif | ||||
| #ifndef sa_family_t | ||||
| #define sa_family_t ADDRESS_FAMILY | ||||
| #endif | ||||
| #elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD | ||||
| #ifdef SOCKADDR_IN | ||||
| #undef SOCKADDR_IN | ||||
| #endif | ||||
| #include <arpa/inet.h> /* for inet_pton */ | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <netdb.h> /* for gethostbyname */ | ||||
| #include <netinet/in.h> /* for htons */ | ||||
| #include <sys/socket.h> /* for socket,connect */ | ||||
| #include <sys/types.h> /* for socket,connect */ | ||||
| #include <sys/un.h> /* for Unix socket */ | ||||
| #include <unistd.h> /* for read, write, close */ | ||||
|  | ||||
| typedef int SOCKET; | ||||
| typedef sockaddr SOCKADDR; | ||||
| typedef sockaddr_in SOCKADDR_IN; | ||||
| #ifndef INVALID_SOCKET | ||||
| #define INVALID_SOCKET (-1) | ||||
| #endif | ||||
| #define SOCKET_ERROR (-1) | ||||
|  | ||||
| #define closesocket(sd) ::close(sd) | ||||
| #else | ||||
| #error Platform specific socket support is not yet available on this platform! | ||||
| #endif | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| namespace OCTO | ||||
| { | ||||
|  | ||||
| #define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused" | ||||
| #define MAXRECV 1500 ///< Maximum packet size | ||||
|  | ||||
| enum SocketFamily | ||||
| { | ||||
|   af_unspec = AF_UNSPEC, | ||||
|   af_inet = AF_INET, | ||||
|   af_inet6 = AF_INET6 | ||||
| }; | ||||
|  | ||||
| enum SocketDomain | ||||
| { | ||||
| #if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD | ||||
|   pf_unix = PF_UNIX, | ||||
|   pf_local = PF_LOCAL, | ||||
| #endif | ||||
|   pf_inet = PF_INET | ||||
| }; | ||||
|  | ||||
| enum SocketType | ||||
| { | ||||
|   sock_stream = SOCK_STREAM, | ||||
|   sock_dgram = SOCK_DGRAM | ||||
| }; | ||||
|  | ||||
| enum SocketProtocol | ||||
| { | ||||
|   tcp = IPPROTO_TCP, | ||||
|   udp = IPPROTO_UDP | ||||
| }; | ||||
|  | ||||
| class Socket | ||||
| { | ||||
| public: | ||||
|   /*! | ||||
|    * An unconnected socket may be created directly on the local | ||||
|    * machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and | ||||
|    * protocol may also be specified. | ||||
|    * If the socket cannot be created, an exception is thrown. | ||||
|    * | ||||
|    * \param family Socket family (IPv4 or IPv6) | ||||
|    * \param domain The domain parameter specifies a communications domain within which communication will take place; | ||||
|    * this selects the protocol family which should be used. | ||||
|    * \param type base type and protocol family of the socket. | ||||
|    * \param protocol specific protocol to apply. | ||||
|    */ | ||||
|   Socket(const enum SocketFamily family, | ||||
|          const enum SocketDomain domain, | ||||
|          const enum SocketType type, | ||||
|          const enum SocketProtocol protocol = tcp); | ||||
|   Socket(void); | ||||
|   virtual ~Socket(); | ||||
|  | ||||
|   //Socket settings | ||||
|  | ||||
|   /*! | ||||
|    * Socket setFamily | ||||
|    * \param family    Can be af_inet or af_inet6. Default: af_inet | ||||
|    */ | ||||
|   void setFamily(const enum SocketFamily family) { m_family = family; }; | ||||
|  | ||||
|   /*! | ||||
|    * Socket setDomain | ||||
|    * \param domain    Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet | ||||
|    */ | ||||
|   void setDomain(const enum SocketDomain domain) { m_domain = domain; }; | ||||
|  | ||||
|   /*! | ||||
|    * Socket setType | ||||
|    * \param type    Can be sock_stream or sock_dgram. Default: sock_stream. | ||||
|    */ | ||||
|   void setType(const enum SocketType type) { m_type = type; }; | ||||
|  | ||||
|   /*! | ||||
|    * Socket setProtocol | ||||
|    * \param protocol    Can be tcp or udp. Default: tcp. | ||||
|    */ | ||||
|   void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; }; | ||||
|  | ||||
|   /*! | ||||
|    * Socket setPort | ||||
|    * \param port    port number for socket communication | ||||
|    */ | ||||
|   void setPort(const unsigned short port) { m_sockaddr.sin_port = htons(port); }; | ||||
|  | ||||
|   bool setHostname(const std::string& host); | ||||
|  | ||||
|   // Server initialization | ||||
|  | ||||
|   /*! | ||||
|    * Socket create | ||||
|    * Create a new socket | ||||
|    * \return     True if succesful | ||||
|    */ | ||||
|   bool create(); | ||||
|  | ||||
|   /*! | ||||
|    * Socket close | ||||
|    * Close the socket | ||||
|    * \return     True if succesful | ||||
|    */ | ||||
|   bool close(); | ||||
|  | ||||
|   /*! | ||||
|    * Socket bind | ||||
|    */ | ||||
|   bool bind(const unsigned short port); | ||||
|   bool listen() const; | ||||
|   bool accept(Socket& socket) const; | ||||
|  | ||||
|   // Client initialization | ||||
|   bool connect(const std::string& host, const unsigned short port); | ||||
|  | ||||
|   bool reconnect(); | ||||
|  | ||||
|   // Data Transmission | ||||
|  | ||||
|   /*! | ||||
|    * Socket send function | ||||
|    * | ||||
|    * \param data    Reference to a std::string with the data to transmit | ||||
|    * \return    Number of bytes send or -1 in case of an error | ||||
|    */ | ||||
|   int send(const std::string& data); | ||||
|  | ||||
|   /*! | ||||
|    * Socket send function | ||||
|    * | ||||
|    * \param data    Pointer to a character array of size 'size' with the data to transmit | ||||
|    * \param size    Length of the data to transmit | ||||
|    * \return    Number of bytes send or -1 in case of an error | ||||
|    */ | ||||
|   int send(const char* data, const unsigned int size); | ||||
|  | ||||
|   /*! | ||||
|    * Socket sendto function | ||||
|    * | ||||
|    * \param data    Reference to a std::string with the data to transmit | ||||
|    * \param size    Length of the data to transmit | ||||
|    * \param sendcompletebuffer    If 'true': do not return until the complete buffer is transmitted | ||||
|    * \return    Number of bytes send or -1 in case of an error | ||||
|    */ | ||||
|   int sendto(const char* data, unsigned int size, bool sendcompletebuffer = false); | ||||
|   // Data Receive | ||||
|  | ||||
|   /*! | ||||
|    * Socket receive function | ||||
|    * | ||||
|    * \param data    Reference to a std::string for storage of the received data. | ||||
|    * \param minpacketsize    The minimum number of bytes that should be received before returning from this function | ||||
|    * \return    Number of bytes received or SOCKET_ERROR | ||||
|    */ | ||||
|   int receive(std::string& data, unsigned int minpacketsize) const; | ||||
|  | ||||
|   /*! | ||||
|    * Socket receive function | ||||
|    * | ||||
|    * \param data    Reference to a std::string for storage of the received data. | ||||
|    * \return    Number of bytes received or SOCKET_ERROR | ||||
|    */ | ||||
|   int receive(std::string& data) const; | ||||
|  | ||||
|   /*! | ||||
|    * Socket receive function | ||||
|    * | ||||
|    * \param data    Pointer to a character array of size buffersize. Used to store the received data. | ||||
|    * \param buffersize    Size of the 'data' buffer | ||||
|    * \param minpacketsize    Specifies the minimum number of bytes that need to be received before returning | ||||
|    * \return    Number of bytes received or SOCKET_ERROR | ||||
|    */ | ||||
|   int receive(char* data, const unsigned int buffersize, const unsigned int minpacketsize) const; | ||||
|  | ||||
|   /*! | ||||
|    * Socket recvfrom function | ||||
|    * | ||||
|    * \param data    Pointer to a character array of size buffersize. Used to store the received data. | ||||
|    * \param buffersize    Size of the 'data' buffer | ||||
|    * \param from        Optional: pointer to a sockaddr struct that will get the address from which the data is received | ||||
|    * \param fromlen    Optional, only required if 'from' is given: length of from struct | ||||
|    * \return    Number of bytes received or SOCKET_ERROR | ||||
|    */ | ||||
|   int recvfrom(char* data, | ||||
|                const int buffersize, | ||||
|                struct sockaddr* from = nullptr, | ||||
|                socklen_t* fromlen = nullptr) const; | ||||
|  | ||||
|   bool set_non_blocking(const bool); | ||||
|  | ||||
|   bool ReadLine(std::string& line); | ||||
|  | ||||
|   bool is_valid() const; | ||||
|  | ||||
| private: | ||||
|   SOCKET m_sd; ///< Socket Descriptor | ||||
|   SOCKADDR_IN m_sockaddr; ///< Socket Address | ||||
|   //struct addrinfo* m_addrinfo;         ///< Socket address info | ||||
|   std::string m_hostname; ///< Hostname | ||||
|   unsigned short m_port; ///< Port number | ||||
|  | ||||
|   enum SocketFamily m_family; ///< Socket Address Family | ||||
|   enum SocketProtocol m_protocol; ///< Socket Protocol | ||||
|   enum SocketType m_type; ///< Socket Type | ||||
|   enum SocketDomain m_domain; ///< Socket domain | ||||
|  | ||||
| #ifdef TARGET_WINDOWS | ||||
|   WSADATA m_wsaData; ///< Windows Socket data | ||||
|   static int | ||||
|       win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used | ||||
| #endif | ||||
|  | ||||
|   void errormessage(int errornum, const char* functionname = nullptr) const; | ||||
|   int getLastError(void) const; | ||||
|   bool osInit(); | ||||
|   void osCleanup(); | ||||
| }; | ||||
|  | ||||
| } //namespace OCTO | ||||
| @@ -13,45 +13,42 @@ | ||||
| #include "OctonetData.h" | ||||
|  | ||||
| ADDON_STATUS COctonetAddon::SetSetting(const std::string& settingName, | ||||
|                                        const kodi::CSettingValue& settingValue) | ||||
|                                        const kodi::addon::CSettingValue& settingValue) | ||||
| { | ||||
|   /* For simplicity do a full addon restart whenever settings are | ||||
|    * changed */ | ||||
|   return ADDON_STATUS_NEED_RESTART; | ||||
| } | ||||
|  | ||||
| ADDON_STATUS COctonetAddon::CreateInstance(int instanceType, | ||||
|                                            const std::string& instanceID, | ||||
|                                            KODI_HANDLE instance, | ||||
|                                            const std::string& version, | ||||
|                                            KODI_HANDLE& addonInstance) | ||||
| ADDON_STATUS COctonetAddon::CreateInstance(const kodi::addon::IInstanceInfo& instance, | ||||
|                                            KODI_ADDON_INSTANCE_HDL& hdl) | ||||
| { | ||||
|   if (instanceType == ADDON_INSTANCE_PVR) | ||||
|   if (instance.IsType(ADDON_INSTANCE_PVR)) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_DEBUG, "%s: Creating octonet pvr instance", __func__); | ||||
|  | ||||
|     /* IP or hostname of the octonet to be connected to */ | ||||
|     std::string octonetAddress = kodi::GetSettingString("octonetAddress"); | ||||
|     std::string octonetAddress = kodi::addon::GetSettingString("octonetAddress"); | ||||
|     bool enableTimeshift = kodi::addon::GetSettingBoolean("timeshiftEnabled"); | ||||
|  | ||||
|     OctonetData* usedInstance = new OctonetData(octonetAddress, instance, version); | ||||
|     addonInstance = usedInstance; | ||||
|     OctonetData* usedInstance = new OctonetData(octonetAddress, enableTimeshift, instance); | ||||
|     hdl = usedInstance; | ||||
|  | ||||
|     m_usedInstances.emplace(instanceID, usedInstance); | ||||
|     m_usedInstances.emplace(instance.GetID(), usedInstance); | ||||
|     return ADDON_STATUS_OK; | ||||
|   } | ||||
|  | ||||
|   return ADDON_STATUS_UNKNOWN; | ||||
| } | ||||
|  | ||||
| void COctonetAddon::DestroyInstance(int instanceType, | ||||
|                                     const std::string& instanceID, | ||||
|                                     KODI_HANDLE addonInstance) | ||||
| void COctonetAddon::DestroyInstance(const kodi::addon::IInstanceInfo& instance, | ||||
|                                     const KODI_ADDON_INSTANCE_HDL hdl) | ||||
| { | ||||
|   if (instanceType == ADDON_INSTANCE_PVR) | ||||
|   if (instance.IsType(ADDON_INSTANCE_PVR)) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_DEBUG, "%s: Destoying octonet pvr instance", __func__); | ||||
|  | ||||
|     const auto& it = m_usedInstances.find(instanceID); | ||||
|     const auto& it = m_usedInstances.find(instance.GetID()); | ||||
|     if (it != m_usedInstances.end()) | ||||
|     { | ||||
|       m_usedInstances.erase(it); | ||||
|   | ||||
							
								
								
									
										16
									
								
								src/addon.h
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/addon.h
									
									
									
									
									
								
							| @@ -15,21 +15,17 @@ | ||||
|  | ||||
| class OctonetData; | ||||
|  | ||||
| class ATTRIBUTE_HIDDEN COctonetAddon : public kodi::addon::CAddonBase | ||||
| class ATTR_DLL_LOCAL COctonetAddon : public kodi::addon::CAddonBase | ||||
| { | ||||
| public: | ||||
|   COctonetAddon() = default; | ||||
|  | ||||
|   ADDON_STATUS SetSetting(const std::string& settingName, | ||||
|                           const kodi::CSettingValue& settingValue) override; | ||||
|   ADDON_STATUS CreateInstance(int instanceType, | ||||
|                               const std::string& instanceID, | ||||
|                               KODI_HANDLE instance, | ||||
|                               const std::string& version, | ||||
|                               KODI_HANDLE& addonInstance) override; | ||||
|   void DestroyInstance(int instanceType, | ||||
|                        const std::string& instanceID, | ||||
|                        KODI_HANDLE addonInstance) override; | ||||
|                           const kodi::addon::CSettingValue& settingValue) override; | ||||
|   ADDON_STATUS CreateInstance(const kodi::addon::IInstanceInfo& instance, | ||||
|                               KODI_ADDON_INSTANCE_HDL& hdl) override; | ||||
|   void DestroyInstance(const kodi::addon::IInstanceInfo& instance, | ||||
|                        const KODI_ADDON_INSTANCE_HDL hdl) override; | ||||
|  | ||||
| private: | ||||
|   std::unordered_map<std::string, OctonetData*> m_usedInstances; | ||||
|   | ||||
| @@ -1,548 +0,0 @@ | ||||
| /* | ||||
|  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) | ||||
|  * | ||||
|  *  SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  *  See LICENSE.md for more information. | ||||
|  */ | ||||
|  | ||||
| #include "rtsp_client.hpp" | ||||
|  | ||||
| #include "Socket.h" | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <cctype> | ||||
| #include <cstring> | ||||
| #include <iterator> | ||||
| #include <sstream> | ||||
|  | ||||
| #if defined(_WIN32) || defined(_WIN64) | ||||
| #define strtok_r strtok_s | ||||
| #define strncasecmp _strnicmp | ||||
|  | ||||
| int vasprintf(char** sptr, char* fmt, va_list argv) | ||||
| { | ||||
|   int wanted = vsnprintf(*sptr = nullptr, 0, fmt, argv); | ||||
|   if ((wanted < 0) || ((*sptr = (char*)malloc(1 + wanted)) == nullptr)) | ||||
|     return -1; | ||||
|   return vsprintf(*sptr, fmt, argv); | ||||
| } | ||||
|  | ||||
| int asprintf(char** sptr, char* fmt, ...) | ||||
| { | ||||
|   int retval; | ||||
|   va_list argv; | ||||
|   va_start(argv, fmt); | ||||
|   retval = vasprintf(sptr, fmt, argv); | ||||
|   va_end(argv); | ||||
|   return retval; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #define RTSP_DEFAULT_PORT 554 | ||||
| #define RTSP_RECEIVE_BUFFER 2048 | ||||
| #define RTP_HEADER_SIZE 12 | ||||
| #define VLEN 100 | ||||
| #define KEEPALIVE_INTERVAL 60 | ||||
| #define KEEPALIVE_MARGIN 5 | ||||
| #define UDP_ADDRESS_LEN 16 | ||||
| #define RTCP_BUFFER_SIZE 1024 | ||||
|  | ||||
| using namespace std; | ||||
| using namespace OCTO; | ||||
|  | ||||
| enum rtsp_state | ||||
| { | ||||
|   RTSP_IDLE, | ||||
|   RTSP_DESCRIBE, | ||||
|   RTSP_SETUP, | ||||
|   RTSP_PLAY, | ||||
|   RTSP_RUNNING | ||||
| }; | ||||
|  | ||||
| enum rtsp_result | ||||
| { | ||||
|   RTSP_RESULT_OK = 200, | ||||
| }; | ||||
|  | ||||
| struct rtsp_client | ||||
| { | ||||
|   char* content_base; | ||||
|   char* control; | ||||
|   char session_id[64]; | ||||
|   uint16_t stream_id; | ||||
|   int keepalive_interval; | ||||
|  | ||||
|   char udp_address[UDP_ADDRESS_LEN]; | ||||
|   uint16_t udp_port; | ||||
|  | ||||
|   Socket tcp_sock; | ||||
|   Socket udp_sock; | ||||
|   Socket rtcp_sock; | ||||
|  | ||||
|   enum rtsp_state state; | ||||
|   int cseq; | ||||
|  | ||||
|   size_t fifo_size; | ||||
|   uint16_t last_seq_nr; | ||||
|  | ||||
|   string name; | ||||
|   int level; | ||||
|   int quality; | ||||
| }; | ||||
|  | ||||
| struct url | ||||
| { | ||||
|   string protocol; | ||||
|   string host; | ||||
|   int port; | ||||
|   string path; | ||||
| }; | ||||
|  | ||||
| struct rtcp_app | ||||
| { | ||||
|   uint8_t subtype; | ||||
|   uint8_t pt; | ||||
|   uint16_t len; | ||||
|   uint32_t ssrc; | ||||
|   char name[4]; | ||||
|   uint16_t identifier; | ||||
|   uint16_t string_len; | ||||
| }; | ||||
|  | ||||
| static rtsp_client* rtsp = nullptr; | ||||
|  | ||||
| static url parse_url(const std::string& str) | ||||
| { | ||||
|   static const string prot_end = "://"; | ||||
|   static const string host_end = "/"; | ||||
|   url result; | ||||
|  | ||||
|   string::const_iterator begin = str.begin(); | ||||
|   string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end()); | ||||
|   result.protocol.reserve(distance(begin, end)); | ||||
|   transform(begin, end, back_inserter(result.protocol), ::tolower); | ||||
|   advance(end, prot_end.size()); | ||||
|   begin = end; | ||||
|  | ||||
|   end = search(begin, str.end(), host_end.begin(), host_end.end()); | ||||
|   result.host.reserve(distance(begin, end)); | ||||
|   transform(begin, end, back_inserter(result.host), ::tolower); | ||||
|   advance(end, host_end.size()); | ||||
|   begin = end; | ||||
|  | ||||
|   result.port = RTSP_DEFAULT_PORT; | ||||
|  | ||||
|   result.path.reserve(distance(begin, str.end())); | ||||
|   transform(begin, str.end(), back_inserter(result.path), ::tolower); | ||||
|  | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| void split_string(const string& s, char delim, vector<string>& elems) | ||||
| { | ||||
|   stringstream ss; | ||||
|   ss.str(s); | ||||
|  | ||||
|   string item; | ||||
|   while (getline(ss, item, delim)) | ||||
|   { | ||||
|     elems.push_back(item); | ||||
|   } | ||||
| } | ||||
|  | ||||
| static int tcp_sock_read_line(string& line) | ||||
| { | ||||
|   static string buf; | ||||
|  | ||||
|   while (true) | ||||
|   { | ||||
|     string::size_type pos = buf.find("\r\n"); | ||||
|     if (pos != string::npos) | ||||
|     { | ||||
|       line = buf.substr(0, pos); | ||||
|       buf.erase(0, pos + 2); | ||||
|       return 0; | ||||
|     } | ||||
|  | ||||
|     char tmp_buf[2048]; | ||||
|     int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1); | ||||
|     if (size <= 0) | ||||
|     { | ||||
|       return 1; | ||||
|     } | ||||
|  | ||||
|     buf.append(&tmp_buf[0], &tmp_buf[size]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| static string compose_url(const url& u) | ||||
| { | ||||
|   stringstream res; | ||||
|   res << u.protocol << "://" << u.host; | ||||
|   if (u.port > 0) | ||||
|     res << ":" << u.port; | ||||
|   res << "/" << u.path; | ||||
|  | ||||
|   return res.str(); | ||||
| } | ||||
|  | ||||
| static void parse_session(char* request_line, char* session, unsigned max, int* timeout) | ||||
| { | ||||
|   char* state; | ||||
|   char* tok; | ||||
|  | ||||
|   tok = strtok_r(request_line, ";", &state); | ||||
|   if (tok == nullptr) | ||||
|     return; | ||||
|   strncpy(session, tok, min(strlen(tok), (size_t)(max - 1))); | ||||
|  | ||||
|   while ((tok = strtok_r(nullptr, ";", &state)) != nullptr) | ||||
|   { | ||||
|     if (strncmp(tok, "timeout=", 8) == 0) | ||||
|     { | ||||
|       *timeout = atoi(tok + 8); | ||||
|       if (*timeout > 5) | ||||
|         *timeout -= KEEPALIVE_MARGIN; | ||||
|       else if (*timeout > 0) | ||||
|         *timeout = 1; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| static int parse_port(char* str, uint16_t* port) | ||||
| { | ||||
|   int p = atoi(str); | ||||
|   if (p < 0 || p > UINT16_MAX) | ||||
|     return -1; | ||||
|  | ||||
|   *port = p; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| static int parse_transport(char* request_line) | ||||
| { | ||||
|   char* state; | ||||
|   char* tok; | ||||
|   int err; | ||||
|  | ||||
|   tok = strtok_r(request_line, ";", &state); | ||||
|   if (tok == nullptr || strncmp(tok, "RTP/AVP", 7) != 0) | ||||
|     return -1; | ||||
|  | ||||
|   tok = strtok_r(nullptr, ";", &state); | ||||
|   if (tok == nullptr || strncmp(tok, "multicast", 9) != 0) | ||||
|     return 0; | ||||
|  | ||||
|   while ((tok = strtok_r(nullptr, ";", &state)) != nullptr) | ||||
|   { | ||||
|     if (strncmp(tok, "destination=", 12) == 0) | ||||
|     { | ||||
|       strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1))); | ||||
|     } | ||||
|     else if (strncmp(tok, "port=", 5) == 0) | ||||
|     { | ||||
|       char port[6]; | ||||
|       char* end; | ||||
|  | ||||
|       memset(port, 0x00, 6); | ||||
|       strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5)); | ||||
|       if ((end = strstr(port, "-")) != nullptr) | ||||
|         *end = '\0'; | ||||
|       err = parse_port(port, &rtsp->udp_port); | ||||
|       if (err) | ||||
|         return err; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| #define skip_whitespace(x) \ | ||||
|   while (*x == ' ') \ | ||||
|   x++ | ||||
| static enum rtsp_result rtsp_handle() | ||||
| { | ||||
|   uint8_t buffer[512]; | ||||
|   int rtsp_result = 0; | ||||
|   bool have_header = false; | ||||
|   size_t content_length = 0; | ||||
|   size_t read = 0; | ||||
|   char *in, *val; | ||||
|   string in_str; | ||||
|  | ||||
|   /* Parse header */ | ||||
|   while (!have_header) | ||||
|   { | ||||
|     if (tcp_sock_read_line(in_str) < 0) | ||||
|       break; | ||||
|     in = const_cast<char*>(in_str.c_str()); | ||||
|  | ||||
|     if (strncmp(in, "RTSP/1.0 ", 9) == 0) | ||||
|     { | ||||
|       rtsp_result = atoi(in + 9); | ||||
|     } | ||||
|     else if (strncmp(in, "Content-Base:", 13) == 0) | ||||
|     { | ||||
|       free(rtsp->content_base); | ||||
|  | ||||
|       val = in + 13; | ||||
|       skip_whitespace(val); | ||||
|  | ||||
|       rtsp->content_base = strdup(val); | ||||
|     } | ||||
|     else if (strncmp(in, "Content-Length:", 15) == 0) | ||||
|     { | ||||
|       val = in + 16; | ||||
|       skip_whitespace(val); | ||||
|  | ||||
|       content_length = atoi(val); | ||||
|     } | ||||
|     else if (strncmp("Session:", in, 8) == 0) | ||||
|     { | ||||
|       val = in + 8; | ||||
|       skip_whitespace(val); | ||||
|  | ||||
|       parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval); | ||||
|     } | ||||
|     else if (strncmp("Transport:", in, 10) == 0) | ||||
|     { | ||||
|       val = in + 10; | ||||
|       skip_whitespace(val); | ||||
|  | ||||
|       if (parse_transport(val) != 0) | ||||
|       { | ||||
|         rtsp_result = -1; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     else if (strncmp("com.ses.streamID:", in, 17) == 0) | ||||
|     { | ||||
|       val = in + 17; | ||||
|       skip_whitespace(val); | ||||
|  | ||||
|       rtsp->stream_id = atoi(val); | ||||
|     } | ||||
|     else if (in[0] == '\0') | ||||
|     { | ||||
|       have_header = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /* Discard further content */ | ||||
|   while (content_length > 0 && (read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer), | ||||
|                                                               min(sizeof(buffer), content_length)))) | ||||
|     content_length -= read; | ||||
|  | ||||
|   return (enum rtsp_result)rtsp_result; | ||||
| } | ||||
|  | ||||
| bool rtsp_open(const string& name, const string& url_str) | ||||
| { | ||||
|   string setup_url_str; | ||||
|   const char* psz_setup_url; | ||||
|   stringstream setup_ss; | ||||
|   stringstream play_ss; | ||||
|   url setup_url; | ||||
|  | ||||
|   rtsp_close(); | ||||
|   rtsp = new rtsp_client(); | ||||
|   if (rtsp == nullptr) | ||||
|     return false; | ||||
|  | ||||
|   rtsp->name = name; | ||||
|   rtsp->level = 0; | ||||
|   rtsp->quality = 0; | ||||
|  | ||||
|   kodi::Log(ADDON_LOG_DEBUG, "try to open '%s'", url_str.c_str()); | ||||
|  | ||||
|   url dst = parse_url(url_str); | ||||
|   kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str()); | ||||
|  | ||||
|   if (!rtsp->tcp_sock.connect(dst.host, dst.port)) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(), | ||||
|               dst.port); | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   // TODO: tcp keep alive? | ||||
|  | ||||
|   if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), dst.port) < 0) | ||||
|   { | ||||
|     rtsp->content_base = nullptr; | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   rtsp->last_seq_nr = 0; | ||||
|   rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN); | ||||
|  | ||||
|   setup_url = dst; | ||||
|  | ||||
|   // reverse the satip protocol trick, as SAT>IP believes to be RTSP | ||||
|   if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) | ||||
|   { | ||||
|     setup_url.protocol = "rtsp"; | ||||
|   } | ||||
|  | ||||
|   setup_url_str = compose_url(setup_url); | ||||
|   psz_setup_url = setup_url_str.c_str(); | ||||
|  | ||||
|   // TODO: Find available port | ||||
|   rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp); | ||||
|   rtsp->udp_port = 6785; | ||||
|   if (!rtsp->udp_sock.bind(rtsp->udp_port)) | ||||
|   { | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   setup_ss << "SETUP " << setup_url_str << " RTSP/1.0\r\n"; | ||||
|   setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n"; | ||||
|   setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-" | ||||
|            << (rtsp->udp_port + 1) << "\r\n\r\n"; | ||||
|   rtsp->tcp_sock.send(setup_ss.str()); | ||||
|  | ||||
|   if (rtsp_handle() != RTSP_RESULT_OK) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Failed to setup RTSP session"); | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0) | ||||
|   { | ||||
|     rtsp->control = nullptr; | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n"; | ||||
|   play_ss << "CSeq: " << rtsp->cseq++ << "\r\n"; | ||||
|   play_ss << "Session: " << rtsp->session_id << "\r\n\r\n"; | ||||
|   rtsp->tcp_sock.send(play_ss.str()); | ||||
|  | ||||
|   if (rtsp_handle() != RTSP_RESULT_OK) | ||||
|   { | ||||
|     kodi::Log(ADDON_LOG_ERROR, "Failed to play RTSP session"); | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp); | ||||
|   if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1)) | ||||
|   { | ||||
|     goto error; | ||||
|   } | ||||
|   if (!rtsp->rtcp_sock.set_non_blocking(true)) | ||||
|   { | ||||
|     goto error; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
|  | ||||
| error: | ||||
|   rtsp_close(); | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| static void parse_rtcp(const char* buf, int size) | ||||
| { | ||||
|   int offset = 0; | ||||
|   while (size > 4) | ||||
|   { | ||||
|     const rtcp_app* app = reinterpret_cast<const rtcp_app*>(buf + offset); | ||||
|     uint16_t len = 4 * (ntohs(app->len) + 1); | ||||
|  | ||||
|     if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0)) | ||||
|     { | ||||
|       size -= len; | ||||
|       offset += len; | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     uint16_t string_len = ntohs(app->string_len); | ||||
|     string app_data(&buf[offset + sizeof(rtcp_app)], string_len); | ||||
|  | ||||
|     vector<string> elems; | ||||
|     split_string(app_data, ';', elems); | ||||
|     if (elems.size() != 4) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     vector<string> tuner; | ||||
|     split_string(elems[2], ',', tuner); | ||||
|     if (tuner.size() < 4) | ||||
|     { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     rtsp->level = atoi(tuner[1].c_str()); | ||||
|     rtsp->quality = atoi(tuner[3].c_str()); | ||||
|  | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| int rtsp_read(void* buf, unsigned buf_size) | ||||
| { | ||||
|   sockaddr addr; | ||||
|   socklen_t addr_len = sizeof(addr); | ||||
|   int ret = rtsp->udp_sock.recvfrom((char*)buf, buf_size, (sockaddr*)&addr, &addr_len); | ||||
|  | ||||
|   char rtcp_buf[RTCP_BUFFER_SIZE]; | ||||
|   int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr*)&addr, &addr_len); | ||||
|   parse_rtcp(rtcp_buf, rtcp_len); | ||||
|  | ||||
|   // TODO: check ip | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| static void rtsp_teardown() | ||||
| { | ||||
|   if (!rtsp->tcp_sock.is_valid()) | ||||
|   { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (rtsp->session_id[0] > 0) | ||||
|   { | ||||
|     char* msg; | ||||
|     int len; | ||||
|     stringstream ss; | ||||
|  | ||||
|     rtsp->udp_sock.close(); | ||||
|  | ||||
|     ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n"; | ||||
|     ss << "CSeq: " << rtsp->cseq++ << "\r\n"; | ||||
|     ss << "Session: " << rtsp->session_id << "\r\n\r\n"; | ||||
|     rtsp->tcp_sock.send(ss.str()); | ||||
|  | ||||
|     if (rtsp_handle() != RTSP_RESULT_OK) | ||||
|     { | ||||
|       kodi::Log(ADDON_LOG_ERROR, "Failed to teardown RTSP session"); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void rtsp_close() | ||||
| { | ||||
|   if (rtsp) | ||||
|   { | ||||
|     rtsp_teardown(); | ||||
|     rtsp->tcp_sock.close(); | ||||
|     rtsp->udp_sock.close(); | ||||
|     rtsp->rtcp_sock.close(); | ||||
|     delete rtsp; | ||||
|     rtsp = nullptr; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status) | ||||
| { | ||||
|   if (rtsp) | ||||
|   { | ||||
|     signal_status.SetAdapterName(rtsp->name); | ||||
|     signal_status.SetSNR(0x1111 * rtsp->quality); | ||||
|     signal_status.SetSignal(0x101 * rtsp->level); | ||||
|   } | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| /* | ||||
|  *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) | ||||
|  * | ||||
|  *  SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  *  See LICENSE.md for more information. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <kodi/addon-instance/pvr/Channels.h> | ||||
| #include <string> | ||||
|  | ||||
| bool rtsp_open(const std::string& name, const std::string& url_str); | ||||
| void rtsp_close(); | ||||
| int rtsp_read(void* buf, unsigned buf_size); | ||||
| void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status); | ||||
		Reference in New Issue
	
	Block a user