├── .gitignore ├── .idea ├── codeStyleSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ ├── Kiall_Mac_Innes___Apache_2_0.xml │ └── profiles_settings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── .travis.yml ├── Jenkinsfile ├── Jenkinsfile.groovy ├── Jenkinsfile.tag ├── LICENSE ├── README.md ├── acra.gradle ├── app ├── .gitignore ├── build.gradle ├── libs │ └── .gitkeep ├── proguard-rules.pro └── src │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── ie │ │ │ └── macinnes │ │ │ └── tvheadend │ │ │ └── DevTestActivity.java │ └── res │ │ ├── drawable-mdpi │ │ └── banner_debug.png │ │ ├── layout │ │ └── activity_dev_test.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ └── strings.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── ie │ │ └── macinnes │ │ └── tvheadend │ │ ├── Application.java │ │ ├── BootCompletedReceiver.java │ │ ├── Constants.java │ │ ├── DvbMappings.java │ │ ├── MainActivity.java │ │ ├── MiscUtils.java │ │ ├── TvContractUtils.java │ │ ├── TvhMappings.java │ │ ├── account │ │ ├── AccountUtils.java │ │ ├── Authenticator.java │ │ ├── AuthenticatorActivity.java │ │ └── AuthenticatorService.java │ │ ├── migrate │ │ └── MigrateUtils.java │ │ ├── player │ │ ├── EventLogger.java │ │ ├── ExoPlayerUtils.java │ │ ├── HtspDataSource.java │ │ ├── HtspExtractor.java │ │ ├── HtspFileInputStreamDataSource.java │ │ ├── HtspSubscriptionDataSource.java │ │ ├── ShieldVideoRenderer.java │ │ ├── TvheadendExtractorsFactory.java │ │ ├── TvheadendPlayer.java │ │ ├── TvheadendRenderersFactory.java │ │ ├── TvheadendTrackSelector.java │ │ └── reader │ │ │ ├── AacStreamReader.java │ │ │ ├── Ac3StreamReader.java │ │ │ ├── DvbsubStreamReader.java │ │ │ ├── Eac3StreamReader.java │ │ │ ├── H264StreamReader.java │ │ │ ├── H265StreamReader.java │ │ │ ├── Mpeg2AudioStreamReader.java │ │ │ ├── Mpeg2VideoStreamReader.java │ │ │ ├── PlainStreamReader.java │ │ │ ├── StreamReader.java │ │ │ ├── StreamReaderUtils.java │ │ │ ├── StreamReadersFactory.java │ │ │ ├── TextsubStreamReader.java │ │ │ └── VorbisStreamReader.java │ │ ├── settings │ │ ├── SettingsActivity.java │ │ └── SettingsFragment.java │ │ ├── setup │ │ └── TvInputSetupActivity.java │ │ ├── sync │ │ ├── DvrDeleteTask.java │ │ ├── EpgSyncService.java │ │ └── EpgSyncTask.java │ │ └── tvinput │ │ ├── HtspRecordingSession.java │ │ ├── HtspSession.java │ │ └── TvInputService.java │ ├── play │ ├── contactEmail │ ├── contactPhone │ ├── contactWebsite │ ├── defaultLanguage │ └── en-GB │ │ ├── listing │ │ ├── featureGraphic │ │ │ └── feature-graphic.png │ │ ├── fulldescription │ │ ├── icon │ │ │ └── play-store-icon.png │ │ ├── phoneScreenshots │ │ │ ├── 01-guide.png │ │ │ ├── 02-playback-overlay.png │ │ │ ├── 03-guide.png │ │ │ ├── 04-playback.png │ │ │ └── 05-genres.png │ │ ├── shortdescription │ │ ├── title │ │ ├── tvBanner │ │ │ └── banner.png │ │ ├── tvScreenshots │ │ │ ├── 01-guide.png │ │ │ ├── 02-playback-overlay.png │ │ │ ├── 03-guide.png │ │ │ ├── 04-playback.png │ │ │ └── 05-genres.png │ │ └── video │ │ └── whatsnew │ └── res │ ├── drawable-hdpi │ └── default_event_icon.png │ ├── drawable-mdpi │ └── banner.png │ ├── layout │ ├── fragment_settings.xml │ ├── player_overlay_view.xml │ └── setup_progress.xml │ ├── mipmap-hdpi │ └── ic_tv_service.png │ ├── mipmap-mdpi │ └── ic_tv_service.png │ ├── mipmap-xhdpi │ └── ic_tv_service.png │ ├── mipmap-xxhdpi │ └── ic_tv_service.png │ ├── mipmap-xxxhdpi │ └── ic_tv_service.png │ ├── values │ ├── colors.xml │ ├── constants.xml │ ├── preferences.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── authenticatorservice.xml │ ├── backup.xml │ ├── preferences.xml │ └── tvinputservice.xml ├── artwork ├── banner.ai ├── logo-plain.ai └── play-store-icon.ai ├── build.gradle ├── debug-keystore.jks ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── play.gradle ├── properties.gradle ├── settings.gradle ├── signing.gradle ├── tools └── generate-changelog ├── version.gradle └── version.json /.gitignore: -------------------------------------------------------------------------------- 1 | google-services.json 2 | playServiceAccount.json 3 | acra.properties 4 | keystore.properties 5 | /projectFilesBackup/ 6 | */libs/*.aar 7 | 8 | # Created by https://www.gitignore.io/api/osx,linux,sublimetext,intellij,gradle,android,java 9 | 10 | ### Android ### 11 | # Built application files 12 | *.apk 13 | *.ap_ 14 | 15 | # Files for the ART/Dalvik VM 16 | *.dex 17 | 18 | # Java class files 19 | *.class 20 | 21 | # Generated files 22 | bin/ 23 | gen/ 24 | out/ 25 | 26 | # Gradle files 27 | .gradle/ 28 | build/ 29 | 30 | # Local configuration file (sdk path, etc) 31 | local.properties 32 | local-tvheadend.properties 33 | 34 | # Proguard folder generated by Eclipse 35 | proguard/ 36 | 37 | # Log Files 38 | *.log 39 | 40 | # Android Studio Navigation editor temp files 41 | .navigation/ 42 | 43 | # Android Studio captures folder 44 | captures/ 45 | 46 | # Intellij 47 | *.iml 48 | .idea/workspace.xml 49 | .idea/tasks.xml 50 | .idea/gradle.xml 51 | .idea/libraries 52 | .idea/caches 53 | 54 | # Keystore files 55 | *.jks 56 | 57 | # External native build folder generated in Android Studio 2.2 and later 58 | .externalNativeBuild 59 | 60 | # Google Services (e.g. APIs or Firebase) 61 | google-services.json 62 | 63 | #Freeline 64 | freeline.py 65 | freeline/ 66 | freeline_project_description.json 67 | 68 | ### Android Patch ### 69 | gen-external-apklibs 70 | 71 | ### Intellij ### 72 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 73 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 74 | 75 | # User-specific stuff: 76 | 77 | # Sensitive or high-churn files: 78 | .idea/dataSources/ 79 | .idea/dataSources.ids 80 | .idea/dataSources.xml 81 | .idea/dataSources.local.xml 82 | .idea/sqlDataSources.xml 83 | .idea/dynamic.xml 84 | .idea/uiDesigner.xml 85 | 86 | # Gradle: 87 | 88 | # Mongo Explorer plugin: 89 | .idea/mongoSettings.xml 90 | 91 | ## File-based project format: 92 | *.iws 93 | 94 | ## Plugin-specific files: 95 | 96 | # IntelliJ 97 | /out/ 98 | 99 | # mpeltonen/sbt-idea plugin 100 | .idea_modules/ 101 | 102 | # JIRA plugin 103 | atlassian-ide-plugin.xml 104 | 105 | # Crashlytics plugin (for Android Studio and IntelliJ) 106 | com_crashlytics_export_strings.xml 107 | crashlytics.properties 108 | crashlytics-build.properties 109 | fabric.properties 110 | 111 | ### Intellij Patch ### 112 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 113 | 114 | # *.iml 115 | # modules.xml 116 | .idea/misc.xml 117 | # *.ipr 118 | 119 | ### Java ### 120 | 121 | # BlueJ files 122 | *.ctxt 123 | 124 | # Mobile Tools for Java (J2ME) 125 | .mtj.tmp/ 126 | 127 | # Package Files # 128 | *.jar 129 | *.war 130 | *.ear 131 | 132 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 133 | hs_err_pid* 134 | 135 | ### Linux ### 136 | *~ 137 | 138 | # temporary files which can be created if a process still has a handle open of a deleted file 139 | .fuse_hidden* 140 | 141 | # KDE directory preferences 142 | .directory 143 | 144 | # Linux trash folder which might appear on any partition or disk 145 | .Trash-* 146 | 147 | # .nfs files are created when an open file is removed but is still being accessed 148 | .nfs* 149 | 150 | ### OSX ### 151 | *.DS_Store 152 | .AppleDouble 153 | .LSOverride 154 | 155 | # Icon must end with two \r 156 | Icon 157 | 158 | 159 | # Thumbnails 160 | ._* 161 | 162 | # Files that might appear in the root of a volume 163 | .DocumentRevisions-V100 164 | .fseventsd 165 | .Spotlight-V100 166 | .TemporaryItems 167 | .Trashes 168 | .VolumeIcon.icns 169 | .com.apple.timemachine.donotpresent 170 | 171 | # Directories potentially created on remote AFP share 172 | .AppleDB 173 | .AppleDesktop 174 | Network Trash Folder 175 | Temporary Items 176 | .apdisk 177 | 178 | ### SublimeText ### 179 | # cache files for sublime text 180 | *.tmlanguage.cache 181 | *.tmPreferences.cache 182 | *.stTheme.cache 183 | 184 | # workspace files are user-specific 185 | *.sublime-workspace 186 | 187 | # project files should be checked into the repository, unless a significant 188 | # proportion of contributors will probably not be using SublimeText 189 | # *.sublime-project 190 | 191 | # sftp configuration file 192 | sftp-config.json 193 | 194 | # Package control specific files 195 | Package Control.last-run 196 | Package Control.ca-list 197 | Package Control.ca-bundle 198 | Package Control.system-ca-bundle 199 | Package Control.cache/ 200 | Package Control.ca-certs/ 201 | Package Control.merged-ca-bundle 202 | Package Control.user-ca-bundle 203 | oscrypto-ca-bundle.crt 204 | bh_unicode_properties.cache 205 | 206 | # Sublime-github package stores a github token in this file 207 | # https://packagecontrol.io/packages/sublime-github 208 | GitHub.sublime-settings 209 | 210 | ### Gradle ### 211 | .gradle 212 | /build/ 213 | 214 | # Ignore Gradle GUI config 215 | gradle-app.setting 216 | 217 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 218 | !gradle-wrapper.jar 219 | 220 | # Cache of project 221 | .gradletasknamecache 222 | 223 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 224 | # gradle/wrapper/gradle-wrapper.properties 225 | 226 | # End of https://www.gitignore.io/api/osx,linux,sublimetext,intellij,gradle,android,java 227 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/Kiall_Mac_Innes___Apache_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | branches: 4 | only: 5 | - master 6 | - develop 7 | 8 | jdk: 9 | - oraclejdk8 10 | 11 | android: 12 | components: 13 | # Uncomment the lines below if you want to 14 | # use the latest revision of Android SDK Tools 15 | - tools 16 | - platform-tools 17 | 18 | # The BuildTools version used by your project 19 | - build-tools-25.0.2 20 | 21 | # The SDK version used to compile your project 22 | - android-25 23 | 24 | # Additional components 25 | - extra-google-google_play_services 26 | - extra-google-m2repository 27 | - extra-android-m2repository 28 | # - addon-google_apis-google-19 29 | 30 | # Specify at least one system image, 31 | # if you need to run emulator(s) during your tests 32 | #- sys-img-x86-android-tv-24 33 | 34 | addons: 35 | artifacts: 36 | s3_region: "eu-west-1" 37 | paths: 38 | - app/build/outputs/ 39 | - app/build/reports/ 40 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | node ('android-slave') { 4 | stage('Preparation') { 5 | step([$class: 'WsCleanup']) 6 | checkout scm 7 | } 8 | 9 | def common = load 'Jenkinsfile.groovy' 10 | 11 | stage('Assemble') { 12 | if (env.JOB_NAME.contains("PR-")) { 13 | common.assemble() 14 | } else { 15 | withCredentials([ 16 | [$class: 'FileBinding', credentialsId: 'android-keystore-tvheadend', variable: 'ANDROID_KEYSTORE'], 17 | [$class: 'StringBinding', credentialsId: 'android-keystore-tvheadend-password', variable: 'ANDROID_KEYSTORE_PASSWORD'], 18 | [$class: 'StringBinding', credentialsId: 'acra-report-uri-tvheadend', variable: 'ACRA_REPORT_URI'], 19 | ]) { 20 | writeFile file: 'local-tvheadend.properties', text: "ie.macinnes.tvheadend.acraReportUri=$ACRA_REPORT_URI\nie.macinnes.tvheadend.keystoreFile=$ANDROID_KEYSTORE\nie.macinnes.tvheadend.keystorePassword=$ANDROID_KEYSTORE_PASSWORD\nie.macinnes.tvheadend.keyAlias=Kiall Mac Innes\nie.macinnes.tvheadend.keyPassword=$ANDROID_KEYSTORE_PASSWORD\n" 21 | 22 | common.assemble() 23 | } 24 | } 25 | } 26 | 27 | stage('Lint') { 28 | common.lint() 29 | } 30 | 31 | stage('Archive APK') { 32 | common.archive() 33 | } 34 | 35 | if (!env.JOB_NAME.contains("PR-")) { 36 | stage('Publish') { 37 | if (env.JOB_NAME.contains("master")) { 38 | common.publishApkToStore('beta') 39 | } else if (env.JOB_NAME.contains("develop")) { 40 | common.publishApkToStore('alpha') 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Jenkinsfile.groovy: -------------------------------------------------------------------------------- 1 | import jenkins.model.* 2 | 3 | def assemble() { 4 | sh './gradlew assemble -PbuildNumber=' + env.BUILD_NUMBER 5 | } 6 | 7 | def archive() { 8 | archiveArtifacts artifacts: 'app/build/outputs/apk/*.apk', fingerprint: true 9 | stash includes: 'app/build/outputs/apk/*.apk', name: 'built-apk' 10 | } 11 | 12 | def lint() { 13 | sh './gradlew lint -PbuildNumber=' + env.BUILD_NUMBER 14 | androidLint canComputeNew: false, canRunOnFailed: true, defaultEncoding: '', healthy: '', pattern: '**/lint-results*.xml', unHealthy: '' 15 | } 16 | 17 | def publishApkToStore(String trackName) { 18 | withCredentials([ 19 | [$class: 'FileBinding', credentialsId: 'android-keystore-tvheadend', variable: 'ANDROID_KEYSTORE'], 20 | [$class: 'StringBinding', credentialsId: 'android-keystore-tvheadend-password', variable: 'ANDROID_KEYSTORE_PASSWORD'], 21 | [$class: 'FileBinding', credentialsId: 'android-playserviceaccount-tvheadend', variable: 'ANDROID_PLAY_SERVICE_ACCOUNT'], 22 | ]) { 23 | writeFile file: 'local-tvheadend.properties', text: "ie.macinnes.tvheadend.keystoreFile=$ANDROID_KEYSTORE\nie.macinnes.tvheadend.keystorePassword=$ANDROID_KEYSTORE_PASSWORD\nie.macinnes.tvheadend.keyAlias=Kiall Mac Innes\nie.macinnes.tvheadend.keyPassword=$ANDROID_KEYSTORE_PASSWORD\nie.macinnes.tvheadend.playServiceAccountFile=$ANDROID_PLAY_SERVICE_ACCOUNT\n" 24 | 25 | // Publish everything when doing a production or beta release 26 | if (trackName == 'production' || trackName == 'beta') { 27 | sh './gradlew publishRelease -PbuildNumber=' + env.BUILD_NUMBER + ' -PplayStoreTrack=' + trackName 28 | } else { 29 | sh './gradlew publishApkRelease -PbuildNumber=' + env.BUILD_NUMBER + ' -PplayStoreTrack=' + trackName 30 | } 31 | } 32 | } 33 | 34 | def publishApkToGitHub() { 35 | def tagName = sh(returnStdout: true, script: "git describe --tags --abbrev=0 --exact-match").trim() 36 | def changeLog = sh(returnStdout: true, script: "./tools/generate-changelog").trim().replaceAll(~/'/, "\'") 37 | 38 | withCredentials([ 39 | [$class: 'StringBinding', credentialsId: 'github-pat-kiall', variable: 'GITHUB_TOKEN'], 40 | ]) { 41 | sh(script: "github-release release --user kiall --repo android-tvheadend --tag ${tagName} --name ${tagName} --description '${changeLog}'") 42 | sh(script: "github-release upload --user kiall --repo android-tvheadend --tag ${tagName} --name ie.macinnes.tvheadend_${tagName}-release.apk --file app/build/outputs/apk/ie.macinnes.tvheadend-release.apk") 43 | } 44 | } 45 | 46 | def withGithubNotifier(Closure job) { 47 | notifyGithub('STARTED') 48 | catchError { 49 | currentBuild.result = 'SUCCESS' 50 | job() 51 | } 52 | notifyGithub(currentBuild.result) 53 | } 54 | 55 | def notifyGithub(String result) { 56 | switch (result) { 57 | case 'STARTED': 58 | setGitHubPullRequestStatus(context: env.JOB_NAME, message: "Build started", state: 'PENDING') 59 | break 60 | case 'FAILURE': 61 | setGitHubPullRequestStatus(context: env.JOB_NAME, message: "Build error", state: 'FAILURE') 62 | break 63 | case 'UNSTABLE': 64 | setGitHubPullRequestStatus(context: env.JOB_NAME, message: "Build unstable", state: 'FAILURE') 65 | break 66 | case 'SUCCESS': 67 | setGitHubPullRequestStatus(context: env.JOB_NAME, message: "Build finished successfully", state: 'SUCCESS') 68 | break 69 | } 70 | } 71 | 72 | return this; 73 | -------------------------------------------------------------------------------- /Jenkinsfile.tag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | def projectProperties = [ 4 | [$class: 'PipelineTriggersJobProperty', triggers: [ 5 | [$class: 'GitHubPushTrigger'] 6 | ]], 7 | [$class: 'DisableConcurrentBuildsJobProperty'], 8 | ] 9 | 10 | properties(projectProperties) 11 | 12 | node ('android-slave') { 13 | stage('Preparation') { 14 | step([$class: 'WsCleanup']) 15 | checkout scm 16 | } 17 | 18 | def common = load 'Jenkinsfile.groovy' 19 | 20 | stage('Assemble') { 21 | withCredentials([ 22 | [$class: 'FileBinding', credentialsId: 'android-keystore-tvheadend', variable: 'ANDROID_KEYSTORE'], 23 | [$class: 'StringBinding', credentialsId: 'android-keystore-tvheadend-password', variable: 'ANDROID_KEYSTORE_PASSWORD'], 24 | [$class: 'StringBinding', credentialsId: 'acra-report-uri-tvheadend', variable: 'ACRA_REPORT_URI'], 25 | ]) { 26 | writeFile file: 'local-tvheadend.properties', text: "ie.macinnes.tvheadend.acraReportUri=$ACRA_REPORT_URI\nie.macinnes.tvheadend.keystoreFile=$ANDROID_KEYSTORE\nie.macinnes.tvheadend.keystorePassword=$ANDROID_KEYSTORE_PASSWORD\nie.macinnes.tvheadend.keyAlias=Kiall Mac Innes\nie.macinnes.tvheadend.keyPassword=$ANDROID_KEYSTORE_PASSWORD\n" 27 | 28 | common.assemble() 29 | } 30 | } 31 | 32 | stage('Lint') { 33 | common.lint() 34 | } 35 | 36 | stage('Archive APK') { 37 | common.archive() 38 | } 39 | 40 | stage('Publish') { 41 | parallel ( 42 | playStore: { 43 | common.publishApkToStore('beta') 44 | }, 45 | githubRelease: { 46 | common.publishApkToGitHub() 47 | } 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android TV + TVHeadend Live Channel 2 | 3 | * Master [![Build Status](https://jenkins.macinnes.ie/buildStatus/icon?job=android-tvheadend/master)](https://jenkins.macinnes.ie/job/android-tvheadend/job/master/) 4 | * Develop [![Build Status](https://jenkins.macinnes.ie/buildStatus/icon?job=android-tvheadend/develop)](https://jenkins.macinnes.ie/job/android-tvheadend/job/develop/) 5 | 6 | Available on Google Play: 7 | 8 | 1. Download from Google Play: https://play.google.com/store/apps/details?id=ie.macinnes.tvheadend 9 | 10 | Sign up for Beta access (more frequent, bleeding edge releases): 11 | 12 | 1. Join the beta community (required!) https://plus.google.com/communities/102705346691784371187 13 | 2. Join the beta on Google Play: https://play.google.com/apps/testing/ie.macinnes.tvheadend 14 | 3. Download from Google Play: https://play.google.com/store/apps/details?id=ie.macinnes.tvheadend 15 | 16 | IRC Channel: 17 | 18 | Join #android-tvheadend on Freenode - not a general support channel, used for chatting with developers around specific bugs etc 19 | 20 | ![Guide](app/src/main/play/en-GB/listing/tvScreenshots/01-guide.png) 21 | ![Playback Overlay](app/src/main/play/en-GB/listing/tvScreenshots/02-playback-overlay.png) 22 | ![Guide](app/src/main/play/en-GB/listing/tvScreenshots/03-guide.png) 23 | ![Playback](app/src/main/play/en-GB/listing/tvScreenshots/04-playback.png) 24 | ![Genres](app/src/main/play/en-GB/listing/tvScreenshots/05-genres.png) 25 | 26 | # Build Properties 27 | 28 | Build customization can be performed via a `local-tvheadend.properties` file, for example: 29 | 30 | ie.macinnes.tvheadend.acraReportUri=https://crashreport.com/report/tvheadend 31 | ie.macinnes.tvheadend.keystoreFile=keystore.jks 32 | ie.macinnes.tvheadend.keystorePassword=MySecretPassword 33 | ie.macinnes.tvheadend.keyAlias=My TVHeadend Key 34 | ie.macinnes.tvheadend.keyPassword=MySecretPassword 35 | -------------------------------------------------------------------------------- /acra.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | defaultConfig { 3 | buildConfigField "boolean", "ACRA_ENABLED", "false" 4 | buildConfigField "String", "ACRA_REPORT_URI", "\"\"" 5 | } 6 | 7 | if (tvhHasProperty("acraReportUri")) { 8 | buildTypes { 9 | release { 10 | buildConfigField "boolean", "ACRA_ENABLED", "true" 11 | buildConfigField "String", "ACRA_REPORT_URI", "\"" + tvhProperty("acraReportUri") + "\"" 12 | } 13 | } 14 | } 15 | 16 | } 17 | 18 | dependencies { 19 | implementation 'ch.acra:acra:4.9.2' 20 | } 21 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply from: '../version.gradle' 4 | apply from: '../signing.gradle' 5 | apply from: '../acra.gradle' 6 | apply from: '../play.gradle' 7 | 8 | android { 9 | compileSdkVersion project.ext.compileSdkVersion 10 | buildToolsVersion project.ext.buildToolsVersion 11 | 12 | defaultConfig { 13 | applicationId "ie.macinnes.tvheadend" 14 | setProperty("archivesBaseName", applicationId) 15 | 16 | minSdkVersion project.ext.minSdkVersion 17 | targetSdkVersion project.ext.targetSdkVersion 18 | 19 | versionCode project.ext.versionCode 20 | versionName project.ext.versionName 21 | } 22 | 23 | buildTypes { 24 | debug { 25 | versionNameSuffix "-SNAPSHOT" 26 | } 27 | 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | lintOptions { 35 | disable 'GoogleAppIndexingWarning' 36 | } 37 | } 38 | 39 | repositories { 40 | flatDir { 41 | dirs 'libs' 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation 'ie.macinnes.htsp:android-htsp:v0.0.2' 47 | // Used for testing local HTSP lib builds 48 | // implementation(name: 'library-debug', ext: 'aar') 49 | 50 | implementation 'com.android.support:support-v4:27.1.0' 51 | implementation 'com.android.support:leanback-v17:27.1.0' 52 | implementation 'com.android.support:preference-leanback-v17:27.1.0' 53 | 54 | implementation 'com.google.android.exoplayer:exoplayer-core:2.7.1k1' 55 | implementation 'com.google.android.exoplayer:exoplayer-ui:2.7.1k1' 56 | implementation 'com.google.android.exoplayer:extension-ffmpeg:2.7.1k1' 57 | // Used for testing local exoplayer builds 58 | // implementation(name: 'library-core-release', ext: 'aar') 59 | // implementation(name: 'library-ui-release', ext: 'aar') 60 | // implementation(name: 'extension-ffmpeg-release', ext: 'aar') 61 | 62 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' 63 | releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' 64 | testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' 65 | } 66 | -------------------------------------------------------------------------------- /app/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiall/android-tvheadend/f46e894a58211a5aceb018c9a2cd24a54ef9f52b/app/libs/.gitkeep -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/kiall/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/debug/java/ie/macinnes/tvheadend/DevTestActivity.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Kiall Mac Innes 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | not use this file except in compliance with the License. You may obtain 5 | a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations 13 | under the License. 14 | */ 15 | package ie.macinnes.tvheadend; 16 | 17 | import android.accounts.Account; 18 | import android.accounts.AccountManager; 19 | import android.app.Activity; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | import android.view.View; 25 | import android.widget.TextView; 26 | 27 | import org.acra.ACRA; 28 | 29 | import ie.macinnes.tvheadend.account.AccountUtils; 30 | import ie.macinnes.tvheadend.settings.SettingsActivity; 31 | import ie.macinnes.tvheadend.sync.EpgSyncService; 32 | import ie.macinnes.tvheadend.tvinput.TvInputService; 33 | 34 | public class DevTestActivity extends Activity { 35 | private static final String TAG = DevTestActivity.class.getName(); 36 | private static final String NEWLINE = System.getProperty("line.separator"); 37 | 38 | private AccountManager mAccountManager; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | 44 | setContentView(R.layout.activity_dev_test); 45 | 46 | mAccountManager = AccountManager.get(getBaseContext()); 47 | } 48 | 49 | private void setRunning() { 50 | TextView v = findViewById(R.id.statusOutput); 51 | v.setText("RUNNING"); 52 | clearDebugOutput(); 53 | } 54 | 55 | private void setOk() { 56 | TextView v = findViewById(R.id.statusOutput); 57 | v.setText("OK"); 58 | } 59 | 60 | private void setFail() { 61 | TextView v = findViewById(R.id.statusOutput); 62 | v.setText("FAIL"); 63 | } 64 | 65 | private void clearDebugOutput() { 66 | TextView v = findViewById(R.id.debugOutput); 67 | v.setText(null); 68 | } 69 | 70 | private void setDebugOutput(String string) { 71 | TextView v = findViewById(R.id.debugOutput); 72 | v.setText(string); 73 | } 74 | 75 | private void appendDebugOutput(String string) { 76 | TextView v = findViewById(R.id.debugOutput); 77 | v.append(string + NEWLINE); 78 | } 79 | 80 | public void accountInfo(View view) { 81 | setRunning(); 82 | 83 | Account[] accounts = AccountUtils.getAllAccounts(this); 84 | 85 | appendDebugOutput("Number of Accounts: " + Integer.toString(accounts.length)); 86 | 87 | for (Account account : accounts) { 88 | appendDebugOutput("---"); 89 | appendDebugOutput("Account toString: " + account.toString()); 90 | 91 | String username = account.name; 92 | appendDebugOutput("Account UserName: " + username); 93 | 94 | String password = mAccountManager.getPassword(account); 95 | appendDebugOutput("Account Password: " + password); 96 | 97 | String hostname = mAccountManager.getUserData(account, Constants.KEY_HOSTNAME); 98 | appendDebugOutput("Account Hostname: " + hostname); 99 | 100 | String htspPort = mAccountManager.getUserData(account, Constants.KEY_HTSP_PORT); 101 | appendDebugOutput("Account HTSP Port: " + htspPort); 102 | } 103 | 104 | setOk(); 105 | } 106 | 107 | public void deleteChannels(View view) { 108 | setRunning(); 109 | 110 | Context context = getBaseContext(); 111 | Intent i = new Intent(context, EpgSyncService.class); 112 | context.stopService(i); 113 | 114 | i = new Intent(context, TvInputService.class); 115 | context.stopService(i); 116 | 117 | TvContractUtils.removeChannels(getBaseContext()); 118 | setOk(); 119 | } 120 | 121 | public void deleteRecordedPrograms(View view) { 122 | setRunning(); 123 | 124 | Context context = getBaseContext(); 125 | Intent i = new Intent(context, EpgSyncService.class); 126 | context.stopService(i); 127 | 128 | i = new Intent(context, TvInputService.class); 129 | context.stopService(i); 130 | 131 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 132 | TvContractUtils.removeRecordedProgram(getBaseContext()); 133 | } 134 | setOk(); 135 | } 136 | 137 | public void showPreferences(View view) { 138 | startActivity(SettingsActivity.getPreferencesIntent(this)); 139 | } 140 | 141 | public void restartEpgSyncService(View view) { 142 | Context context = getBaseContext(); 143 | Intent i = new Intent(context, EpgSyncService.class); 144 | context.stopService(i); 145 | context.startService(i); 146 | } 147 | 148 | public void restartTvInputService(View view) { 149 | Context context = getBaseContext(); 150 | Intent i = new Intent(context, TvInputService.class); 151 | context.stopService(i); 152 | context.startService(i); 153 | } 154 | 155 | public void sendCrashReport(View view) { 156 | Exception e = new Exception("Test Crash Report"); 157 | ACRA.getErrorReporter().handleException(e); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /app/src/debug/res/drawable-mdpi/banner_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiall/android-tvheadend/f46e894a58211a5aceb018c9a2cd24a54ef9f52b/app/src/debug/res/drawable-mdpi/banner_debug.png -------------------------------------------------------------------------------- /app/src/debug/res/layout/activity_dev_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 25 | 26 | 35 | 36 | 42 | 43 |