mirror of
				https://github.com/DigitalDevices/pvr.octonet.git
				synced 2025-03-01 10:53:09 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			21.0.0-Ome
			...
			Matrix
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 80ea2539ec | ||
|  | fcb500b4df | ||
|  | 43b1881487 | 
							
								
								
									
										21
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,26 +11,33 @@ jobs: | |||||||
|       matrix: |       matrix: | ||||||
|         include: |         include: | ||||||
|         - name: "Debian package test" |         - name: "Debian package test" | ||||||
|           os: ubuntu-latest |           os: ubuntu-18.04 | ||||||
|           CC: gcc |           CC: gcc | ||||||
|           CXX: g++ |           CXX: g++ | ||||||
|           DEBIAN_BUILD: true |           DEBIAN_BUILD: true | ||||||
|  |         - os: ubuntu-18.04 | ||||||
|  |           CC: gcc | ||||||
|  |           CXX: g++ | ||||||
|  |         - os: ubuntu-18.04 | ||||||
|  |           CC: clang | ||||||
|  |           CXX: clang++ | ||||||
|  |         - os: macos-10.15 | ||||||
|     steps: |     steps: | ||||||
|     - name: Install needed ubuntu depends |     - name: Install needed ubuntu depends | ||||||
|       env: |       env: | ||||||
|         DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }} |         DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }} | ||||||
|       run: | |       run: | | ||||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi |         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 update; fi | ||||||
|         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi |         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi | ||||||
|     - name: Checkout Kodi repo |     - name: Checkout Kodi repo | ||||||
|       uses: actions/checkout@v4 |       uses: actions/checkout@v2 | ||||||
|       with: |       with: | ||||||
|         repository: xbmc/xbmc |         repository: xbmc/xbmc | ||||||
|         ref: master |         ref: Matrix | ||||||
|         path: xbmc |         path: xbmc | ||||||
|     - name: Checkout add-on repo |     - name: Checkout pvr.argustv repo | ||||||
|       uses: actions/checkout@v4 |       uses: actions/checkout@v2 | ||||||
|       with: |       with: | ||||||
|         path: ${{ env.app_id }} |         path: ${{ env.app_id }} | ||||||
|     - name: Configure |     - name: Configure | ||||||
| @@ -41,7 +48,7 @@ jobs: | |||||||
|       run: | |       run: | | ||||||
|         if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir -p build && cd build; fi |         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 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/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 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 ${{ github.workspace }}/${app_id}; fi |         if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep ${{ github.workspace }}/${app_id}; fi | ||||||
|     - name: Build |     - name: Build | ||||||
|       env: |       env: | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								.github/workflows/changelog-and-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/changelog-and-release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -39,29 +39,28 @@ jobs: | |||||||
|  |  | ||||||
|       # Checkout the current repository into a directory (repositories name) |       # Checkout the current repository into a directory (repositories name) | ||||||
|       - name: Checkout Repository |       - name: Checkout Repository | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|           path: ${{ github.event.repository.name }} |           path: ${{ github.event.repository.name }} | ||||||
|  |  | ||||||
|       # Checkout the required scripts from xbmc/binary-addon-scripts into the 'scripts' directory |       # Checkout the required scripts from kodi-pvr/pvr-scripts into the 'scripts' directory | ||||||
|       - name: Checkout Scripts |       - name: Checkout Scripts | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|           repository: xbmc/binary-addon-scripts |           repository: kodi-pvr/pvr-scripts | ||||||
|           path: scripts |           path: scripts | ||||||
|  |  | ||||||
|       # Install all dependencies required by the following steps |       # Install all dependencies required by the following steps | ||||||
|       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in |       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           sudo apt-get update |  | ||||||
|           sudo apt-get install libxml2-utils xmlstarlet |           sudo apt-get install libxml2-utils xmlstarlet | ||||||
|  |  | ||||||
|       # Setup python version 3.9 |       # Setup python version 3.9 | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: '3.9' |           python-version: '3.9' | ||||||
|  |  | ||||||
| @@ -85,7 +84,6 @@ jobs: | |||||||
|       # - steps.required-variables.outputs.version: version element from addon.xml.in |       # - 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.branch: branch of the triggering ref | ||||||
|       # - steps.required-variables.outputs.today: today's date in format '%Y-%m-%d' |       # - 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 |       - name: Get required variables | ||||||
|         id: required-variables |         id: required-variables | ||||||
|         run: | |         run: | | ||||||
| @@ -94,15 +92,17 @@ jobs: | |||||||
|           then |           then | ||||||
|             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') |             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') | ||||||
|           fi |           fi | ||||||
|           EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) |           changes="${changes//'%'/'%25'}" | ||||||
|           echo "changes<<$EOF" >> $GITHUB_OUTPUT |           changes="${changes//$'\n'/'%0A'}" | ||||||
|           echo "$changes" >> $GITHUB_OUTPUT |           changes="${changes//$'\r'/'%0D'}" | ||||||
|           echo "$EOF" >> $GITHUB_OUTPUT |           changes="${changes//$'\\n'/'%0A'}" | ||||||
|  |           changes="${changes//$'\\r'/'%0D'}" | ||||||
|  |           echo ::set-output name=changes::$changes | ||||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') |           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||||
|           echo "version=$version" >> $GITHUB_OUTPUT |           echo ::set-output name=version::$version | ||||||
|           branch=$(echo ${GITHUB_REF#refs/heads/}) |           branch=$(echo ${GITHUB_REF#refs/heads/}) | ||||||
|           echo "branch=$branch" >> $GITHUB_OUTPUT |           echo ::set-output name=branch::$branch | ||||||
|           echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT |           echo ::set-output name=today::$(date +'%Y-%m-%d') | ||||||
|         working-directory: ${{ github.event.repository.name }} |         working-directory: ${{ github.event.repository.name }} | ||||||
|  |  | ||||||
|       # Create a commit of the incremented version and changelog, news changes |       # Create a commit of the incremented version and changelog, news changes | ||||||
| @@ -133,7 +133,7 @@ jobs: | |||||||
|         shell: bash |         shell: bash | ||||||
|  |  | ||||||
|       # Create a release at {steps.required-variables.outputs.branch} |       # 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. 21.0.0-Omega |       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix | ||||||
|       # - release body: {steps.required-variables.outputs.changes} |       # - release body: {steps.required-variables.outputs.changes} | ||||||
|       - name: Create Release |       - name: Create Release | ||||||
|         id: 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: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ Matrix, Nexus, Omega ] |     branches: [ Matrix, Nexus ] | ||||||
|     paths: |     paths: | ||||||
|       - '**resource.language.**strings.po' |       - '**resource.language.**strings.po' | ||||||
|  |  | ||||||
| @@ -15,20 +15,20 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|  |  | ||||||
|       - name: Checkout Repository |       - name: Checkout Repository | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|           path: ${{ github.event.repository.name }} |           path: ${{ github.event.repository.name }} | ||||||
|  |  | ||||||
|       - name: Checkout Scripts |       - name: Checkout Scripts | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|           repository: xbmc/weblate-supplementary-scripts |           repository: xbmc/weblate-supplementary-scripts | ||||||
|           path: scripts |           path: scripts | ||||||
|  |  | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: '3.9' |           python-version: '3.9' | ||||||
|  |  | ||||||
| @@ -42,14 +42,13 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           sudo apt-get update |  | ||||||
|           sudo apt-get install libxml2-utils xmlstarlet |           sudo apt-get install libxml2-utils xmlstarlet | ||||||
|  |  | ||||||
|       - name: Get required variables |       - name: Get required variables | ||||||
|         id: required-variables |         id: required-variables | ||||||
|         run: | |         run: | | ||||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') |           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||||
|           echo "version=$version" >> $GITHUB_OUTPUT |           echo ::set-output name=version::$version | ||||||
|         working-directory: ${{ github.event.repository.name }} |         working-directory: ${{ github.event.repository.name }} | ||||||
|  |  | ||||||
|       - name: Create PR for incrementing add-on versions |       - 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) |       # Checkout the current repository into a directory (repositories name) | ||||||
|       - name: Checkout Repository |       - name: Checkout Repository | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|           path: ${{ github.event.repository.name }} |           path: ${{ github.event.repository.name }} | ||||||
| @@ -23,14 +23,12 @@ jobs: | |||||||
|       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in |       # - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: | |         run: | | ||||||
|           sudo apt-get update |  | ||||||
|           sudo apt-get install libxml2-utils xmlstarlet |           sudo apt-get install libxml2-utils xmlstarlet | ||||||
|  |  | ||||||
|       # Create the variables required by the following steps |       # 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.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.version: version element from addon.xml.in | ||||||
|       # - steps.required-variables.outputs.branch: branch of the triggering ref |       # - 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 |       - name: Get required variables | ||||||
|         id: required-variables |         id: required-variables | ||||||
|         run: | |         run: | | ||||||
| @@ -39,18 +37,20 @@ jobs: | |||||||
|           then |           then | ||||||
|             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') |             changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1') | ||||||
|           fi |           fi | ||||||
|           EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) |           changes="${changes//'%'/'%25'}" | ||||||
|           echo "changes<<$EOF" >> $GITHUB_OUTPUT |           changes="${changes//$'\n'/'%0A'}" | ||||||
|           echo "$changes" >> $GITHUB_OUTPUT |           changes="${changes//$'\r'/'%0D'}" | ||||||
|           echo "$EOF" >> $GITHUB_OUTPUT |           changes="${changes//$'\\n'/'%0A'}" | ||||||
|  |           changes="${changes//$'\\r'/'%0D'}" | ||||||
|  |           echo ::set-output name=changes::$changes | ||||||
|           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') |           version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)') | ||||||
|           echo "version=$version" >> $GITHUB_OUTPUT |           echo ::set-output name=version::$version | ||||||
|           branch=$(echo ${GITHUB_REF#refs/heads/}) |           branch=$(echo ${GITHUB_REF#refs/heads/}) | ||||||
|           echo "branch=$branch" >> $GITHUB_OUTPUT |           echo ::set-output name=branch::$branch | ||||||
|         working-directory: ${{ github.event.repository.name }} |         working-directory: ${{ github.event.repository.name }} | ||||||
|  |  | ||||||
|       # Create a release at {steps.required-variables.outputs.branch} |       # 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. 21.0.0-Omega |       # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix | ||||||
|       # - release body: {steps.required-variables.outputs.changes} |       # - release body: {steps.required-variables.outputs.changes} | ||||||
|       - name: Create Release |       - name: Create Release | ||||||
|         id: create-release |         id: create-release | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ name: Sync addon metadata translations | |||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ Matrix, Nexus, Omega ] |     branches: [ Matrix, Nexus ] | ||||||
|     paths: |     paths: | ||||||
|       - '**addon.xml.in' |       - '**addon.xml.in' | ||||||
|       - '**resource.language.**strings.po' |       - '**resource.language.**strings.po' | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   default: |   default: | ||||||
|     if: github.repository == 'kodi-pvr/pvr.octonet' |     if: github.repository == 'DigitalDevices/pvr.octonet' | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|     strategy: |     strategy: | ||||||
| @@ -21,18 +21,18 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|  |  | ||||||
|       - name: Checkout repository |       - name: Checkout repository | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           path: project |           path: project | ||||||
|  |  | ||||||
|       - name: Checkout sync_addon_metadata_translations repository |       - name: Checkout sync_addon_metadata_translations repository | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           repository: xbmc/sync_addon_metadata_translations |           repository: xbmc/sync_addon_metadata_translations | ||||||
|           path: sync_addon_metadata_translations |           path: sync_addon_metadata_translations | ||||||
|  |  | ||||||
|       - name: Set up Python ${{ matrix.python-version }} |       - name: Set up Python ${{ matrix.python-version }} | ||||||
|         uses: actions/setup-python@v5 |         uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: ${{ matrix.python-version }} |           python-version: ${{ matrix.python-version }} | ||||||
|  |  | ||||||
| @@ -55,4 +55,3 @@ jobs: | |||||||
|           branch: amt-sync |           branch: amt-sync | ||||||
|           delete-branch: true |           delete-branch: true | ||||||
|           path: ./project |           path: ./project | ||||||
|           reviewers: gade01 |  | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | 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,10 +12,14 @@ include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Ko | |||||||
| set(DEPLIBS ${JSONCPP_LIBRARIES}) | set(DEPLIBS ${JSONCPP_LIBRARIES}) | ||||||
|  |  | ||||||
| set(OCTONET_SOURCES src/addon.cpp | set(OCTONET_SOURCES src/addon.cpp | ||||||
|                     src/OctonetData.cpp) |                     src/OctonetData.cpp | ||||||
|  |                     src/Socket.cpp | ||||||
|  |                     src/rtsp_client.cpp) | ||||||
|  |  | ||||||
| set(OCTONET_HEADERS src/addon.h | set(OCTONET_HEADERS src/addon.h | ||||||
|                     src/OctonetData.h) |                     src/OctonetData.h | ||||||
|  |                     src/Socket.h | ||||||
|  |                     src/rtsp_client.hpp) | ||||||
|  |  | ||||||
| addon_version(pvr.octonet OCTONET) | addon_version(pvr.octonet OCTONET) | ||||||
| add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION}) | add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION}) | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1 @@ | |||||||
| buildPlugin(version: "Nexus") | buildPlugin(version: "Matrix") | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | |||||||
| # Octonet PVR | # Octonet PVR | ||||||
| Digital Devices [Octonet](http://www.digital-devices.eu/shop/de/netzwerk-tv/) PVR client addon for [Kodi](http://kodi.tv) | Digital Devices [Octonet](http://www.digital-devices.eu/shop/de/netzwerk-tv/) PVR client addon for [Kodi](http://kodi.tv) | ||||||
|  |  | ||||||
| [](LICENSE.md) | | Platform | Status | | ||||||
| [](https://github.com/DigitalDevices/pvr.octonet/actions/workflows/build.yml) | |----------|--------| | ||||||
| [](https://jenkins.kodi.tv/blue/organizations/jenkins/DigitalDevices%2Fpvr.octonet/branches/) | | 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) | | ||||||
|  |  | ||||||
| # Building | # Building | ||||||
|  |  | ||||||
| @@ -13,13 +14,13 @@ adjusted according to your OS (`/` vs `\`). We use Linux paths here as an exampl | |||||||
| Clone the `pvr.octonet` repository: | Clone the `pvr.octonet` repository: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| $ git clone https://github.com/DigitalDevices/pvr.octonet.git | $ git clone --branch Matrix https://github.com/DigitalDevices/pvr.octonet.git | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Clone the Kodi repository: | Clone the Kodi repository: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| $ git clone --branch master https://github.com/xbmc/xbmc.git | $ git clone --branch Matrix https://github.com/xbmc/xbmc.git | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								appveyor.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								appveyor.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | 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% | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| #!/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,12 +1,10 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <addon | <addon | ||||||
|   id="pvr.octonet" |   id="pvr.octonet" | ||||||
|   version="21.0.0" |   version="19.0.0" | ||||||
|   name="Digital Devices Octopus NET Client" |   name="Digital Devices Octopus NET Client" | ||||||
|   provider-name="digitaldevices"> |   provider-name="digitaldevices"> | ||||||
|   <requires>@ADDON_DEPENDS@ |   <requires>@ADDON_DEPENDS@</requires> | ||||||
|     <import addon="inputstream.ffmpegdirect" minversion="21.0.0"/> |  | ||||||
|   </requires> |  | ||||||
|   <extension |   <extension | ||||||
|     point="kodi.pvrclient" |     point="kodi.pvrclient" | ||||||
|     library_@PLATFORM@="@LIBRARY_FILENAME@"/> |     library_@PLATFORM@="@LIBRARY_FILENAME@"/> | ||||||
|   | |||||||
| @@ -1,4 +0,0 @@ | |||||||
| v21.0.0 |  | ||||||
| - Change add-on to use inputstream.ffmpegdirect which also enables timeshifting |  | ||||||
| - Fix jsoncpp deprecation warnings |  | ||||||
| - Add build script for mac |  | ||||||
| @@ -27,41 +27,3 @@ msgstr "" | |||||||
| msgctxt "#30001" | msgctxt "#30001" | ||||||
| msgid "Could not load chanellist" | msgid "Could not load chanellist" | ||||||
| msgstr "" | 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"?> | <?xml version="1.0" encoding="utf-8" standalone="yes"?> | ||||||
| <settings version="1"> | <settings version="1"> | ||||||
|   <section id="pvr.octonet"> |   <section id="pvr.octonet"> | ||||||
|     <category id="main" label="128" help="30601"> |     <category id="main" label="128" help="-1"> | ||||||
|       <group id="1" label="-1"> |       <group id="1" label="-1"> | ||||||
|         <!-- Octonet Server Address --> |         <!-- Octonet Server Address --> | ||||||
|         <setting id="octonetAddress" type="string" label="30000" help="30600"> |         <setting id="octonetAddress" type="string" label="30000" help="-1"> | ||||||
|           <level>0</level> |           <level>0</level> | ||||||
|           <default></default> |           <default></default> | ||||||
|           <constraints> |           <constraints> | ||||||
| @@ -14,26 +14,5 @@ | |||||||
|         </setting> |         </setting> | ||||||
|       </group> |       </group> | ||||||
|     </category> |     </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> |   </section> | ||||||
| </settings> | </settings> | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| #include "OctonetData.h" | #include "OctonetData.h" | ||||||
|  |  | ||||||
|  | #include "rtsp_client.hpp" | ||||||
|  |  | ||||||
| #include <json/json.h> | #include <json/json.h> | ||||||
| #include <kodi/Filesystem.h> | #include <kodi/Filesystem.h> | ||||||
| #include <kodi/General.h> | #include <kodi/General.h> | ||||||
| @@ -21,23 +23,35 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| OctonetData::OctonetData(const std::string& octonetAddress, | OctonetData::OctonetData(const std::string& octonetAddress, | ||||||
|                          bool enableTimeshift, |                          KODI_HANDLE instance, | ||||||
|                          const kodi::addon::IInstanceInfo& instance) |                          const std::string& kodiVersion) | ||||||
|   : kodi::addon::CInstancePVRClient(instance) |   : kodi::addon::CInstancePVRClient(instance, kodiVersion) | ||||||
| { | { | ||||||
|   m_serverAddress = octonetAddress; |   m_serverAddress = octonetAddress; | ||||||
|   m_enableTimeshift = enableTimeshift; |  | ||||||
|   m_channels.clear(); |   m_channels.clear(); | ||||||
|   m_groups.clear(); |   m_groups.clear(); | ||||||
|   m_lastEpgLoad = 0; |   m_lastEpgLoad = 0; | ||||||
|  |  | ||||||
|   if (!LoadChannelList()) |   if (!LoadChannelList()) | ||||||
|     kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30001).c_str(), |     kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::GetLocalizedString(30001).c_str(), | ||||||
|                                      m_channels.size()); |                                      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) | OctonetData::~OctonetData(void) | ||||||
| { | { | ||||||
|  |   /* | ||||||
|  |   m_running = false; | ||||||
|  |   if (m_thread.joinable()) | ||||||
|  |     m_thread.join(); | ||||||
|  |   */ | ||||||
| } | } | ||||||
|  |  | ||||||
| PVR_ERROR OctonetData::GetCapabilities(kodi::addon::PVRCapabilities& capabilities) | PVR_ERROR OctonetData::GetCapabilities(kodi::addon::PVRCapabilities& capabilities) | ||||||
| @@ -114,11 +128,9 @@ bool OctonetData::LoadChannelList() | |||||||
|   f.Close(); |   f.Close(); | ||||||
|  |  | ||||||
|   Json::Value root; |   Json::Value root; | ||||||
|   JSONCPP_STRING err; |   Json::Reader reader; | ||||||
|   Json::CharReaderBuilder builder; |  | ||||||
|   const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); |  | ||||||
|  |  | ||||||
|   if (!reader->parse(jsonContent.c_str(), jsonContent.c_str() + jsonContent.length(), &root, &err)) |   if (!reader.parse(jsonContent, root, false)) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   const Json::Value groupList = root["GroupList"]; |   const Json::Value groupList = root["GroupList"]; | ||||||
| @@ -204,11 +216,9 @@ bool OctonetData::LoadEPG(void) | |||||||
|   f.Close(); |   f.Close(); | ||||||
|  |  | ||||||
|   Json::Value root; |   Json::Value root; | ||||||
|   JSONCPP_STRING err; |   Json::Reader reader; | ||||||
|   Json::CharReaderBuilder builder; |  | ||||||
|   const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); |  | ||||||
|  |  | ||||||
|   if (!reader->parse(jsonContent.c_str(), jsonContent.c_str() + jsonContent.length(), &root, &err)) |   if (!reader.parse(jsonContent, root, false)) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   const Json::Value eventList = root["EventList"]; |   const Json::Value eventList = root["EventList"]; | ||||||
| @@ -245,6 +255,11 @@ bool OctonetData::LoadEPG(void) | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void OctonetData::Process() | ||||||
|  | { | ||||||
|  |   return; | ||||||
|  | } | ||||||
|  |  | ||||||
| PVR_ERROR OctonetData::GetChannelsAmount(int& amount) | PVR_ERROR OctonetData::GetChannelsAmount(int& amount) | ||||||
| { | { | ||||||
|   amount = m_channels.size(); |   amount = m_channels.size(); | ||||||
| @@ -273,25 +288,6 @@ PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet | |||||||
|   return PVR_ERROR_NO_ERROR; |   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, | PVR_ERROR OctonetData::GetEPGForChannel(int channelUid, | ||||||
|                                         time_t start, |                                         time_t start, | ||||||
|                                         time_t end, |                                         time_t end, | ||||||
| @@ -440,8 +436,23 @@ OctonetGroup* OctonetData::FindGroup(const std::string& name) | |||||||
|       return &group; |       return &group; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   kodi::Log(ADDON_LOG_ERROR, "Could not find group: %s, in available groups from the server"); |  | ||||||
|  |  | ||||||
|   return nullptr; |   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; |   std::vector<int> members; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient | class ATTRIBUTE_HIDDEN OctonetData : public kodi::addon::CInstancePVRClient | ||||||
| { | { | ||||||
| public: | public: | ||||||
|   OctonetData(const std::string& octonetAddress, |   OctonetData(const std::string& octonetAddress, | ||||||
|               bool enableTimeshift, |               KODI_HANDLE instance, | ||||||
|               const kodi::addon::IInstanceInfo& instance); |               const std::string& kodiVersion); | ||||||
|   ~OctonetData() override; |   ~OctonetData() override; | ||||||
|  |  | ||||||
|   PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; |   PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override; | ||||||
| @@ -67,14 +67,19 @@ public: | |||||||
|   PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override; |   PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override; | ||||||
|   PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, |   PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, | ||||||
|                                    kodi::addon::PVRChannelGroupMembersResultSet& results) override; |                                    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, |   PVR_ERROR GetEPGForChannel(int channelUid, | ||||||
|                              time_t start, |                              time_t start, | ||||||
|                              time_t end, |                              time_t end, | ||||||
|                              kodi::addon::PVREPGTagsResultSet& results) override; |                              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: | protected: | ||||||
|  |   void Process(); | ||||||
|  |  | ||||||
|   const std::string& GetUrl(int id) const; |   const std::string& GetUrl(int id) const; | ||||||
|   const std::string& GetName(int id) const; |   const std::string& GetName(int id) const; | ||||||
|  |  | ||||||
| @@ -87,9 +92,11 @@ protected: | |||||||
|  |  | ||||||
| private: | private: | ||||||
|   std::string m_serverAddress; |   std::string m_serverAddress; | ||||||
|   bool m_enableTimeshift = false; |  | ||||||
|   std::vector<OctonetChannel> m_channels; |   std::vector<OctonetChannel> m_channels; | ||||||
|   std::vector<OctonetGroup> m_groups; |   std::vector<OctonetGroup> m_groups; | ||||||
|  |  | ||||||
|   time_t m_lastEpgLoad; |   time_t m_lastEpgLoad; | ||||||
|  |  | ||||||
|  |   std::atomic<bool> m_running = {false}; | ||||||
|  |   std::thread m_thread; | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,728 @@ | |||||||
|  | /* | ||||||
|  |  *  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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								src/Socket.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,284 @@ | |||||||
|  | /* | ||||||
|  |  *  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,42 +13,45 @@ | |||||||
| #include "OctonetData.h" | #include "OctonetData.h" | ||||||
|  |  | ||||||
| ADDON_STATUS COctonetAddon::SetSetting(const std::string& settingName, | ADDON_STATUS COctonetAddon::SetSetting(const std::string& settingName, | ||||||
|                                        const kodi::addon::CSettingValue& settingValue) |                                        const kodi::CSettingValue& settingValue) | ||||||
| { | { | ||||||
|   /* For simplicity do a full addon restart whenever settings are |   /* For simplicity do a full addon restart whenever settings are | ||||||
|    * changed */ |    * changed */ | ||||||
|   return ADDON_STATUS_NEED_RESTART; |   return ADDON_STATUS_NEED_RESTART; | ||||||
| } | } | ||||||
|  |  | ||||||
| ADDON_STATUS COctonetAddon::CreateInstance(const kodi::addon::IInstanceInfo& instance, | ADDON_STATUS COctonetAddon::CreateInstance(int instanceType, | ||||||
|                                            KODI_ADDON_INSTANCE_HDL& hdl) |                                            const std::string& instanceID, | ||||||
|  |                                            KODI_HANDLE instance, | ||||||
|  |                                            const std::string& version, | ||||||
|  |                                            KODI_HANDLE& addonInstance) | ||||||
| { | { | ||||||
|   if (instance.IsType(ADDON_INSTANCE_PVR)) |   if (instanceType == ADDON_INSTANCE_PVR) | ||||||
|   { |   { | ||||||
|     kodi::Log(ADDON_LOG_DEBUG, "%s: Creating octonet pvr instance", __func__); |     kodi::Log(ADDON_LOG_DEBUG, "%s: Creating octonet pvr instance", __func__); | ||||||
|  |  | ||||||
|     /* IP or hostname of the octonet to be connected to */ |     /* IP or hostname of the octonet to be connected to */ | ||||||
|     std::string octonetAddress = kodi::addon::GetSettingString("octonetAddress"); |     std::string octonetAddress = kodi::GetSettingString("octonetAddress"); | ||||||
|     bool enableTimeshift = kodi::addon::GetSettingBoolean("timeshiftEnabled"); |  | ||||||
|  |  | ||||||
|     OctonetData* usedInstance = new OctonetData(octonetAddress, enableTimeshift, instance); |     OctonetData* usedInstance = new OctonetData(octonetAddress, instance, version); | ||||||
|     hdl = usedInstance; |     addonInstance = usedInstance; | ||||||
|  |  | ||||||
|     m_usedInstances.emplace(instance.GetID(), usedInstance); |     m_usedInstances.emplace(instanceID, usedInstance); | ||||||
|     return ADDON_STATUS_OK; |     return ADDON_STATUS_OK; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ADDON_STATUS_UNKNOWN; |   return ADDON_STATUS_UNKNOWN; | ||||||
| } | } | ||||||
|  |  | ||||||
| void COctonetAddon::DestroyInstance(const kodi::addon::IInstanceInfo& instance, | void COctonetAddon::DestroyInstance(int instanceType, | ||||||
|                                     const KODI_ADDON_INSTANCE_HDL hdl) |                                     const std::string& instanceID, | ||||||
|  |                                     KODI_HANDLE addonInstance) | ||||||
| { | { | ||||||
|   if (instance.IsType(ADDON_INSTANCE_PVR)) |   if (instanceType == ADDON_INSTANCE_PVR) | ||||||
|   { |   { | ||||||
|     kodi::Log(ADDON_LOG_DEBUG, "%s: Destoying octonet pvr instance", __func__); |     kodi::Log(ADDON_LOG_DEBUG, "%s: Destoying octonet pvr instance", __func__); | ||||||
|  |  | ||||||
|     const auto& it = m_usedInstances.find(instance.GetID()); |     const auto& it = m_usedInstances.find(instanceID); | ||||||
|     if (it != m_usedInstances.end()) |     if (it != m_usedInstances.end()) | ||||||
|     { |     { | ||||||
|       m_usedInstances.erase(it); |       m_usedInstances.erase(it); | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								src/addon.h
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/addon.h
									
									
									
									
									
								
							| @@ -15,17 +15,21 @@ | |||||||
|  |  | ||||||
| class OctonetData; | class OctonetData; | ||||||
|  |  | ||||||
| class ATTR_DLL_LOCAL COctonetAddon : public kodi::addon::CAddonBase | class ATTRIBUTE_HIDDEN COctonetAddon : public kodi::addon::CAddonBase | ||||||
| { | { | ||||||
| public: | public: | ||||||
|   COctonetAddon() = default; |   COctonetAddon() = default; | ||||||
|  |  | ||||||
|   ADDON_STATUS SetSetting(const std::string& settingName, |   ADDON_STATUS SetSetting(const std::string& settingName, | ||||||
|                           const kodi::addon::CSettingValue& settingValue) override; |                           const kodi::CSettingValue& settingValue) override; | ||||||
|   ADDON_STATUS CreateInstance(const kodi::addon::IInstanceInfo& instance, |   ADDON_STATUS CreateInstance(int instanceType, | ||||||
|                               KODI_ADDON_INSTANCE_HDL& hdl) override; |                               const std::string& instanceID, | ||||||
|   void DestroyInstance(const kodi::addon::IInstanceInfo& instance, |                               KODI_HANDLE instance, | ||||||
|                        const KODI_ADDON_INSTANCE_HDL hdl) override; |                               const std::string& version, | ||||||
|  |                               KODI_HANDLE& addonInstance) override; | ||||||
|  |   void DestroyInstance(int instanceType, | ||||||
|  |                        const std::string& instanceID, | ||||||
|  |                        KODI_HANDLE addonInstance) override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   std::unordered_map<std::string, OctonetData*> m_usedInstances; |   std::unordered_map<std::string, OctonetData*> m_usedInstances; | ||||||
|   | |||||||
							
								
								
									
										548
									
								
								src/rtsp_client.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										548
									
								
								src/rtsp_client.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,548 @@ | |||||||
|  | /* | ||||||
|  |  *  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); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/rtsp_client.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/rtsp_client.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | /* | ||||||
|  |  *  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