Compare commits
75 Commits
renovate/g
...
v0.11.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03d5b88d58 | ||
|
|
b59e442783 | ||
|
|
54c89eed51 | ||
|
|
42278541ec | ||
|
|
4c78705215 | ||
|
|
8bb184357e | ||
|
|
f58412f346 | ||
|
|
3048b95704 | ||
|
|
3fd08c9b39 | ||
|
|
351e43eebc | ||
|
|
b13e6923d7 | ||
|
|
6f84fd704e | ||
|
|
b7ed874c09 | ||
|
|
ef76e562f4 | ||
|
|
631c6a0fee | ||
|
|
ab9dea63a0 | ||
|
|
178fe800fc | ||
|
|
dd389437c2 | ||
|
|
a55a2b0f05 | ||
|
|
cb3d57ce55 | ||
|
|
44d26dc889 | ||
|
|
680b76080c | ||
|
|
c7c52096b3 | ||
|
|
7de78925d2 | ||
|
|
8d9ac2e457 | ||
|
|
57f8d9672b | ||
|
|
a412c3ecb6 | ||
|
|
479a284745 | ||
|
|
d0d57a8e1d | ||
|
|
d3aa064904 | ||
|
|
29f6c6cb4e | ||
|
|
cecb752f34 | ||
|
|
4dd57774e8 | ||
|
|
f7dafcb30c | ||
|
|
6c75a1e3aa | ||
|
|
539cfd7e89 | ||
|
|
d26751a2b1 | ||
|
|
5f9362f540 | ||
|
|
b4bc28ac57 | ||
|
|
d33e6da226 | ||
|
|
32ed6c2fd8 | ||
|
|
3f16234162 | ||
|
|
92d702301b | ||
|
|
8693c84c54 | ||
|
|
b1a650e696 | ||
|
|
5f6a17383c | ||
|
|
3bd42863e2 | ||
|
|
b3c5d29726 | ||
|
|
b145ccd025 | ||
|
|
f942eeade4 | ||
|
|
067cd68a33 | ||
|
|
c6af538420 | ||
|
|
01f9b7cf52 | ||
|
|
3e82c9d088 | ||
|
|
ff1f9175bf | ||
|
|
805fc178ea | ||
|
|
296dbc0114 | ||
|
|
1d28707cf7 | ||
|
|
438089d145 | ||
|
|
a8c0b80bb8 | ||
|
|
ec6fe7795a | ||
|
|
717c87f8b7 | ||
|
|
6820bd0722 | ||
|
|
997de857e2 | ||
|
|
a67c486385 | ||
|
|
8aa0c5a18e | ||
|
|
4c5eab8d62 | ||
|
|
5e016b38b0 | ||
|
|
5e4e135239 | ||
|
|
8c1d6c30de | ||
|
|
ec83ec0540 | ||
|
|
32dd0ae3ce | ||
|
|
e258ae8113 | ||
|
|
c11d26c887 | ||
|
|
afa1f918ee |
@@ -1,16 +1,138 @@
|
||||
variables:
|
||||
- group: 'jellyfin'
|
||||
|
||||
trigger:
|
||||
- master
|
||||
- release
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- release*
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
gradleWrapperFile: 'gradlew'
|
||||
tasks: 'build'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
sonarQubeRunAnalysis: false
|
||||
jobs:
|
||||
- job: Test
|
||||
displayName: 'Test'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
displayName: 'Run Tests'
|
||||
inputs:
|
||||
gradleWrapperFile: 'gradlew'
|
||||
tasks: 'test'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
sonarQubeRunAnalysis: false
|
||||
|
||||
- job: Build
|
||||
displayName: 'Build'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
displayName: 'Build Debug'
|
||||
inputs:
|
||||
gradleWrapperFile: 'gradlew'
|
||||
tasks: 'assembleDebug'
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
sonarQubeRunAnalysis: false
|
||||
|
||||
- task: Gradle@2
|
||||
displayName: 'Build Release'
|
||||
inputs:
|
||||
gradleWrapperFile: 'gradlew'
|
||||
tasks: 'assembleRelease'
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
sonarQubeRunAnalysis: false
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy APKs'
|
||||
inputs:
|
||||
SourceFolder: 'app/build/outputs/apk/'
|
||||
Contents: '**/*.apk'
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)'
|
||||
flattenFolders: true
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish APKs'
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'jellyfin-androidtv'
|
||||
publishLocation: 'Container'
|
||||
|
||||
- job: Publish
|
||||
displayName: 'Publish'
|
||||
|
||||
dependsOn: Build
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: 'echo "##vso[task.setvariable variable=TAG]$(git describe --tags)"'
|
||||
displayName: 'Set Tag Variable'
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download APKs'
|
||||
inputs:
|
||||
source: 'current'
|
||||
artifact: 'jellyfin-androidtv'
|
||||
path: '$(System.ArtifactsDirectory)'
|
||||
runVersion: 'latest'
|
||||
|
||||
- task: DownloadSecureFile@1
|
||||
displayName: 'Download KeyStore'
|
||||
name: 'KeyStore'
|
||||
inputs:
|
||||
secureFile: 'keystore'
|
||||
|
||||
- script: '$(find / -name apksigner -print -quit) sign --ks ${KEYSTORE_SECUREFILEPATH} --ks-pass pass:${PASSWORD} --out ${SYSTEM_ARTIFACTSDIRECTORY}/jellyfin-androidtv_release_${TAG}.apk ${SYSTEM_ARTIFACTSDIRECTORY}/jellyfin-androidtv_release-unsigned_${TAG}.apk'
|
||||
displayName: 'Sign Release APK'
|
||||
env:
|
||||
PASSWORD: $(KeyStorePassword)
|
||||
|
||||
- task: DeleteFiles@1
|
||||
displayName: 'Remove Unsigned APK'
|
||||
inputs:
|
||||
sourceFolder: '$(System.ArtifactsDirectory)'
|
||||
contents: 'jellyfin-androidtv_release-unsigned_$(TAG).apk'
|
||||
|
||||
- task: GithubRelease@0
|
||||
displayName: 'GitHub Upload'
|
||||
inputs:
|
||||
gitHubConnection: Jellyfin Release Download
|
||||
repositoryName: jellyfin/jellyfin-androidtv
|
||||
assets: '$(System.ArtifactsDirectory)/*.apk'
|
||||
action: 'edit'
|
||||
assetUploadMode: 'replace'
|
||||
tag: '$(TAG)'
|
||||
|
||||
- task: CopyFilesOverSSH@0
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
sourceFolder: '$(System.ArtifactsDirectory)'
|
||||
contents: '**'
|
||||
targetFolder: '/srv/repository/releases/client/androidtv/versions/$(TAG)'
|
||||
|
||||
- task: SSH@0
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'inline'
|
||||
inline: 'cd /srv/repository/releases/client/androidtv && rm -rf *.apk && ln -s versions/$(TAG)/jellyfin-androidtv_*_$(TAG).apk .'
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
def version = "0.11.0"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion "28.0.3"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 905
|
||||
versionName version
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def acraVersion = '5.4.0'
|
||||
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
|
||||
// This is split like this because this is a modular gradle project on jitpack
|
||||
// see https://github.com/jitpack/gradle-modular
|
||||
// The split allows dependency substitution to work in the settings.gradle file,
|
||||
// at least for the 'library' part. 'android' part is still broken somehow, but this
|
||||
// is better than nothing.
|
||||
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:master-SNAPSHOT'
|
||||
implementation 'com.github.jellyfin.jellyfin-apiclient-java:library:master-SNAPSHOT'
|
||||
implementation 'com.amazon.android:exoplayer:2.10.6'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'androidx.leanback:leanback:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.palette:palette:1.0.0'
|
||||
implementation 'com.squareup.picasso:picasso:2.3.2'
|
||||
implementation 'com.github.bumptech.glide:glide:3.7.0'
|
||||
implementation 'com.squareup.okhttp:okhttp:2.7.0'
|
||||
implementation 'com.squareup.okhttp:okhttp-urlconnection:2.7.0'
|
||||
implementation 'com.google.guava:guava:18.0'
|
||||
implementation 'com.flaviofaria:kenburnsview:1.0.6'
|
||||
implementation 'org.videolan.android:libvlc-all:3.1.12'
|
||||
implementation "ch.acra:acra-http:$acraVersion"
|
||||
implementation "ch.acra:acra-dialog:$acraVersion"
|
||||
implementation "ch.acra:acra-limiter:$acraVersion"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.tvprovider:tvprovider:1.0.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
92
app/build.gradle.kts
Normal file
92
app/build.gradle.kts
Normal file
@@ -0,0 +1,92 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("kotlin-android-extensions")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion(29)
|
||||
|
||||
defaultConfig {
|
||||
// Android version targets
|
||||
minSdkVersion(21)
|
||||
targetSdkVersion(29)
|
||||
|
||||
// Release version
|
||||
versionCode = 910
|
||||
versionName = "0.11.5"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
// Use Java 1.8 features
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
|
||||
getByName("debug") {
|
||||
// Use different application id to run release and debug at the same time
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
variant.outputs.all {
|
||||
val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
output.outputFileName = output.outputFileName
|
||||
.replace("app-", "jellyfin-androidtv_")
|
||||
.replace(".apk", "_${variant.versionName}.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Jellyfin
|
||||
implementation("com.github.jellyfin.jellyfin-apiclient-java:android:v0.6.4")
|
||||
|
||||
// Kotlin
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
val kotlinCoroutinesVersion = "1.3.3"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion")
|
||||
|
||||
// Android(x)
|
||||
implementation("androidx.core:core-ktx:1.2.0")
|
||||
implementation("androidx.leanback:leanback:1.0.0")
|
||||
implementation("androidx.appcompat:appcompat:1.1.0")
|
||||
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
||||
implementation("androidx.palette:palette:1.0.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
|
||||
implementation("androidx.recyclerview:recyclerview:1.1.0")
|
||||
|
||||
// Media players
|
||||
implementation("com.amazon.android:exoplayer:2.11.3")
|
||||
implementation("org.videolan.android:libvlc-all:3.3.2")
|
||||
|
||||
// Image utility
|
||||
implementation("com.squareup.picasso:picasso:2.3.2")
|
||||
implementation("com.github.bumptech.glide:glide:3.7.0")
|
||||
implementation("com.flaviofaria:kenburnsview:1.0.6")
|
||||
|
||||
// HTTP utility
|
||||
// NOTE: This is used by Picasso through reflection and can cause weird caching issues if removed!
|
||||
val okhttpVersion = "2.7.5"
|
||||
implementation("com.squareup.okhttp:okhttp:$okhttpVersion")
|
||||
implementation("com.squareup.okhttp:okhttp-urlconnection:$okhttpVersion")
|
||||
|
||||
// Crash Reporting
|
||||
val acraVersion = "5.4.0"
|
||||
implementation("ch.acra:acra-http:$acraVersion")
|
||||
implementation("ch.acra:acra-dialog:$acraVersion")
|
||||
implementation("ch.acra:acra-limiter:$acraVersion")
|
||||
|
||||
// Testing
|
||||
testImplementation("junit:junit:4.12")
|
||||
testImplementation("org.mockito:mockito-core:3.2.4")
|
||||
}
|
||||
@@ -12,12 +12,16 @@ import android.graphics.drawable.GradientDrawable;
|
||||
import android.media.AudioManager;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.annotation.AcraCore;
|
||||
import org.acra.annotation.AcraDialog;
|
||||
import org.acra.annotation.AcraHttpSender;
|
||||
import org.acra.annotation.AcraLimiter;
|
||||
import org.acra.sender.HttpSender;
|
||||
import org.jellyfin.androidtv.base.AuthenticatedUserCallbacks;
|
||||
import org.jellyfin.androidtv.base.BaseActivity;
|
||||
import org.jellyfin.androidtv.livetv.TvManager;
|
||||
import org.jellyfin.androidtv.model.DisplayPriorityType;
|
||||
@@ -47,9 +51,6 @@ import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
@AcraCore(buildConfigClass = BuildConfig.class)
|
||||
@AcraHttpSender(
|
||||
uri = "https://collector.tracepot.com/a2eda9d9",
|
||||
@@ -134,6 +135,8 @@ public class TvApp extends Application {
|
||||
roboto = Typeface.createFromAsset(getAssets(), "fonts/Roboto-Light.ttf");
|
||||
setCurrentBackgroundGradient(new int[] {ContextCompat.getColor(this, R.color.lb_default_brand_color_dark), ContextCompat.getColor(this, R.color.lb_default_brand_color)});
|
||||
|
||||
registerActivityLifecycleCallbacks(new AuthenticatedUserCallbacks());
|
||||
|
||||
logger.Info("Application object created");
|
||||
}
|
||||
|
||||
@@ -207,7 +210,7 @@ public class TvApp extends Application {
|
||||
|
||||
public void setAudioMuted(boolean value) {
|
||||
audioMuted = value;
|
||||
getLogger().Info("Setting mute state to: "+audioMuted);
|
||||
getLogger().Info("Setting mute state to: %b", audioMuted);
|
||||
if (DeviceUtils.is60()) {
|
||||
audioManager.adjustVolume(audioMuted ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0);
|
||||
|
||||
@@ -443,7 +446,7 @@ public class TvApp extends Application {
|
||||
public void updateDisplayPrefs(String app, DisplayPreferences preferences) {
|
||||
displayPrefsCache.put(preferences.getId(), preferences);
|
||||
getApiClient().UpdateDisplayPreferencesAsync(preferences, getCurrentUser().getId(), app, new EmptyResponse());
|
||||
logger.Debug("Display prefs updated for "+preferences.getId()+" isFavorite: "+preferences.getCustomPrefs().get("FavoriteOnly"));
|
||||
logger.Debug("Display prefs updated for %s isFavorite: %s", preferences.getId(), preferences.getCustomPrefs().get("FavoriteOnly"));
|
||||
}
|
||||
|
||||
public void getDisplayPrefsAsync(String key, Response<DisplayPreferences> response) {
|
||||
@@ -452,7 +455,7 @@ public class TvApp extends Application {
|
||||
|
||||
public void getDisplayPrefsAsync(final String key, String app, final Response<DisplayPreferences> outerResponse) {
|
||||
if (displayPrefsCache.containsKey(key)) {
|
||||
logger.Debug("Display prefs loaded from cache "+key);
|
||||
logger.Debug("Display prefs loaded from cache %s", key);
|
||||
outerResponse.onResponse(displayPrefsCache.get(key));
|
||||
} else {
|
||||
getApiClient().GetDisplayPreferencesAsync(key, getCurrentUser().getId(), app, new Response<DisplayPreferences>(){
|
||||
@@ -461,7 +464,7 @@ public class TvApp extends Application {
|
||||
if (response.getSortBy() == null) response.setSortBy("SortName");
|
||||
if (response.getCustomPrefs() == null) response.setCustomPrefs(new HashMap<String, String>());
|
||||
displayPrefsCache.put(key, response);
|
||||
logger.Debug("Display prefs loaded and saved in cache " + key);
|
||||
logger.Debug("Display prefs loaded and saved in cache %s", key);
|
||||
outerResponse.onResponse(response);
|
||||
}
|
||||
|
||||
@@ -490,7 +493,7 @@ public class TvApp extends Application {
|
||||
@Override
|
||||
public void onResponse(Long response) {
|
||||
autoBitrate = response.intValue();
|
||||
logger.Info("Auto bitrate set to: "+autoBitrate);
|
||||
logger.Info("Auto bitrate set to: %d", autoBitrate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.jellyfin.androidtv.base
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
|
||||
import org.jellyfin.androidtv.TvApp
|
||||
import org.jellyfin.androidtv.startup.DpadPwActivity
|
||||
import org.jellyfin.androidtv.startup.SelectServerActivity
|
||||
import org.jellyfin.androidtv.startup.SelectUserActivity
|
||||
import org.jellyfin.androidtv.startup.StartupActivity
|
||||
|
||||
private const val LOG_TAG = "AuthUserCallbacks"
|
||||
|
||||
/**
|
||||
* ActivityLifecycleCallback that bounces to the StartupActivity if an Activity is created and
|
||||
* the currentUser is null.
|
||||
*/
|
||||
class AuthenticatedUserCallbacks : Application.ActivityLifecycleCallbacks {
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
|
||||
when (activity) {
|
||||
// Ignore startup activities
|
||||
is DpadPwActivity,
|
||||
is SelectServerActivity,
|
||||
is SelectUserActivity,
|
||||
is StartupActivity,
|
||||
is org.acra.dialog.CrashReportDialog -> return
|
||||
// All other activities should have a current user
|
||||
else -> {
|
||||
TvApp.getApplication().apply {
|
||||
if (currentUser == null) {
|
||||
Log.w(LOG_TAG, "Current user is null, bouncing to StartupActivity")
|
||||
activity.startActivity(Intent(this, StartupActivity::class.java))
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jellyfin.androidtv.ui.shared
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
|
||||
class KeyboardFocusChangeListener : View.OnFocusChangeListener {
|
||||
override fun onFocusChange(v : View, hasFocus : Boolean) {
|
||||
(v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).apply {
|
||||
if (!hasFocus) hideSoftInputFromWindow(v.windowToken, 0)
|
||||
else showSoftInput(v, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class CustomViewFragment extends BrowseFolderFragment {
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
includeType = getActivity().getIntent().getStringExtra("IncludeType");
|
||||
TvApp.getApplication().getLogger().Debug("Item type: "+includeType);
|
||||
TvApp.getApplication().getLogger().Debug("Item type: %s", includeType);
|
||||
showViews = false;
|
||||
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
@@ -402,7 +402,7 @@ public class StdBrowseFragment extends BrowseSupportFragment implements IRowLoad
|
||||
|
||||
private void refreshCurrentItem() {
|
||||
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemType.UserView && mCurrentItem.getBaseItemType() != BaseItemType.CollectionFolder) {
|
||||
TvApp.getApplication().getLogger().Debug("Refresh item "+mCurrentItem.getFullName());
|
||||
TvApp.getApplication().getLogger().Debug("Refresh item \"%s\"", mCurrentItem.getFullName());
|
||||
mCurrentItem.refresh(new EmptyResponse() {
|
||||
@Override
|
||||
public void onResponse() {
|
||||
|
||||
@@ -265,7 +265,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
|
||||
mCardHeight = autoHeight;
|
||||
setNumberOfRows();
|
||||
createGrid();
|
||||
TvApp.getApplication().getLogger().Debug("Auto card height is "+mCardHeight);
|
||||
TvApp.getApplication().getLogger().Debug("Auto card height is %d", mCardHeight);
|
||||
buildAdapter(rowDef);
|
||||
}
|
||||
mGridAdapter.setSortBy(getSortOption(mDisplayPrefs.getSortBy()));
|
||||
@@ -294,7 +294,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
|
||||
}
|
||||
|
||||
protected int getAutoCardHeight(Integer size) {
|
||||
TvApp.getApplication().getLogger().Debug("Result size for auto card height is " + size);
|
||||
TvApp.getApplication().getLogger().Debug("Result size for auto card height is %d", size);
|
||||
if (size > 35)
|
||||
return getCardHeight(PosterSize.SMALL);
|
||||
else if (size > 10)
|
||||
@@ -592,7 +592,7 @@ public class StdGridFragment extends HorizontalGridFragment implements IGridLoad
|
||||
}
|
||||
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemType.Photo && mCurrentItem.getBaseItemType() != BaseItemType.PhotoAlbum
|
||||
&& mCurrentItem.getBaseItemType() != BaseItemType.MusicArtist && mCurrentItem.getBaseItemType() != BaseItemType.MusicAlbum) {
|
||||
TvApp.getApplication().getLogger().Debug("Refresh item "+mCurrentItem.getFullName());
|
||||
TvApp.getApplication().getLogger().Debug("Refresh item \"%s\"", mCurrentItem.getFullName());
|
||||
mCurrentItem.refresh(new EmptyResponse() {
|
||||
@Override
|
||||
public void onResponse() {
|
||||
|
||||
@@ -106,6 +106,9 @@ class ChannelManager {
|
||||
item.canResume -> {
|
||||
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
|
||||
setLastPlaybackPositionMillis((item.resumePositionTicks / TICKS_IN_MILLISECOND).toInt())
|
||||
}
|
||||
// Episode runtime has been determined
|
||||
item.runTimeTicks != null -> {
|
||||
setDurationMillis((item.runTimeTicks / TICKS_IN_MILLISECOND).toInt())
|
||||
}
|
||||
// First episode of the season
|
||||
|
||||
@@ -10,7 +10,9 @@ public enum HomeSectionType {
|
||||
RESUME_AUDIO("resumeaudio"),
|
||||
ACTIVE_RECORDINGS("activerecordings"),
|
||||
NEXT_UP("nextup"),
|
||||
LIVE_TV("livetv");
|
||||
LIVE_TV("livetv"),
|
||||
|
||||
NONE("none");
|
||||
|
||||
public static HomeSectionType getByName(String name) {
|
||||
for (HomeSectionType type : HomeSectionType.values()) {
|
||||
@@ -25,4 +27,4 @@ public enum HomeSectionType {
|
||||
HomeSectionType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
|
||||
}
|
||||
|
||||
protected void addAdditionalRows(ArrayObjectAdapter adapter) {
|
||||
TvApp.getApplication().getLogger().Debug("Item type: " + mBaseItem.getBaseItemType());
|
||||
TvApp.getApplication().getLogger().Debug("Item type: %s", mBaseItem.getBaseItemType().toString());
|
||||
switch (mBaseItem.getBaseItemType()) {
|
||||
case Movie:
|
||||
|
||||
@@ -648,7 +648,7 @@ public class FullDetailsActivity extends BaseActivity implements IRecordingIndic
|
||||
if (mBaseItem.getPeople() != null && mBaseItem.getPeople().length > 0) {
|
||||
List<BaseItemPerson> guests = new ArrayList<>();
|
||||
for (BaseItemPerson person : mBaseItem.getPeople()) {
|
||||
if (PersonType.GuestStar.equals(person.getType())) guests.add(person);
|
||||
if (person.getPersonType() == PersonType.GuestStar) guests.add(person);
|
||||
}
|
||||
if (guests.size() > 0) {
|
||||
ItemRowAdapter castAdapter = new ItemRowAdapter(guests.toArray(new BaseItemPerson[guests.size()]), new CardPresenter(true, 260), adapter);
|
||||
|
||||
@@ -260,7 +260,7 @@ public class ItemListActivity extends BaseActivity {
|
||||
private AudioEventListener mAudioEventListener = new AudioEventListener() {
|
||||
@Override
|
||||
public void onPlaybackStateChange(PlaybackController.PlaybackState newState, BaseItemDto currentItem) {
|
||||
TvApp.getApplication().getLogger().Info("Got playback state change event "+newState+" for item "+(currentItem != null ? currentItem.getName() : "<unknown>"));
|
||||
TvApp.getApplication().getLogger().Info("Got playback state change event %s for item %s", newState.toString(), currentItem != null ? currentItem.getName() : "<unknown>");
|
||||
|
||||
if (newState != PlaybackController.PlaybackState.PLAYING || currentItem == null) {
|
||||
if (mCurrentlyPlayingRow != null) mCurrentlyPlayingRow.updateCurrentTime(-1);
|
||||
@@ -578,7 +578,10 @@ public class ItemListActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mItems.size() > 0) {
|
||||
if (mBaseItem.getId().equals(VIDEO_QUEUE) || mBaseItem.getId().equals(FAV_SONGS) || "Playlist".equals(mBaseItem.getType())) {
|
||||
if (mBaseItem.getId().equals(VIDEO_QUEUE)
|
||||
|| mBaseItem.getId().equals(FAV_SONGS)
|
||||
|| mBaseItem.getBaseItemType() == BaseItemType.Playlist
|
||||
|| mBaseItem.getBaseItemType() == BaseItemType.MusicAlbum) {
|
||||
List<BaseItemDto> shuffled = new ArrayList<>(mItems);
|
||||
Collections.shuffle(shuffled);
|
||||
play(shuffled);
|
||||
|
||||
@@ -350,7 +350,7 @@ public class PhotoPlayerActivity extends BaseActivity {
|
||||
public void onSuccess() {
|
||||
if (target == nextImage) isLoadingNext = false;
|
||||
if (target == prevImage) isLoadingPrev = false;
|
||||
TvApp.getApplication().getLogger().Debug("Loaded item "+photo.getName());
|
||||
TvApp.getApplication().getLogger().Debug("Loaded item %s", photo.getName());
|
||||
if (play){
|
||||
currentImageView().resume();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@@ -366,7 +366,7 @@ public class PhotoPlayerActivity extends BaseActivity {
|
||||
public void onError() {
|
||||
if (target == nextImage) isLoadingNext = false;
|
||||
if (target == prevImage) isLoadingPrev = false;
|
||||
TvApp.getApplication().getLogger().Debug("Error loading item "+photo.getName());
|
||||
TvApp.getApplication().getLogger().Debug("Error loading item %s", photo.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class TvApiEventListener extends ApiEventListener {
|
||||
|
||||
@Override
|
||||
public void onGeneralCommand(ApiClient client, GeneralCommand command) {
|
||||
TvApp.getApplication().getLogger().Info("General command is: "+command.getName());
|
||||
TvApp.getApplication().getLogger().Info("General command is: %s", command.getName());
|
||||
switch (command.getName().toLowerCase()) {
|
||||
case "mute":
|
||||
TvApp.getApplication().setAudioMuted(true);
|
||||
|
||||
@@ -51,7 +51,7 @@ public class ItemLauncher {
|
||||
case BaseItem:
|
||||
final BaseItemDto baseItem = rowItem.getBaseItem();
|
||||
try {
|
||||
TvApp.getApplication().getLogger().Debug("Item selected: " + rowItem.getIndex() + " - " + baseItem.getName() + " (" + baseItem.getBaseItemType() + ")");
|
||||
TvApp.getApplication().getLogger().Debug("Item selected: %d - %s (%s)", rowItem.getIndex(), baseItem.getName(), baseItem.getBaseItemType().toString());
|
||||
} catch (Exception e) {
|
||||
//swallow it
|
||||
}
|
||||
@@ -67,12 +67,12 @@ public class ItemLauncher {
|
||||
if (baseItem.getCollectionType() == null) {
|
||||
baseItem.setCollectionType("unknown");
|
||||
}
|
||||
TvApp.getApplication().getLogger().Debug("**** Collection type: " + baseItem.getCollectionType());
|
||||
TvApp.getApplication().getLogger().Debug("**** Collection type: %s", baseItem.getCollectionType());
|
||||
switch (baseItem.getCollectionType()) {
|
||||
case "movies":
|
||||
case "tvshows":
|
||||
case "music":
|
||||
TvApp.getApplication().getLogger().Debug("**** View Type Pref: " + response.getCustomPrefs().get("DefaultView"));
|
||||
TvApp.getApplication().getLogger().Debug("**** View Type Pref: %s", response.getCustomPrefs().get("DefaultView"));
|
||||
if (ViewType.GRID.equals(response.getCustomPrefs().get("DefaultView"))) {
|
||||
// open grid browsing
|
||||
Intent folderIntent = new Intent(activity, GenericGridActivity.class);
|
||||
@@ -266,7 +266,7 @@ public class ItemLauncher {
|
||||
application.getApiClient().GetItemAsync(hint.getItemId(), application.getCurrentUser().getId(), new Response<BaseItemDto>() {
|
||||
@Override
|
||||
public void onResponse(BaseItemDto response) {
|
||||
if ((response.getIsFolderItem() && response.getBaseItemType() != BaseItemType.Series) || response.getBaseItemType() == BaseItemType.MusicArtist) {
|
||||
if (response.getIsFolderItem() && response.getBaseItemType() != BaseItemType.Series) {
|
||||
// open generic folder browsing
|
||||
Intent intent = new Intent(activity, GenericGridActivity.class);
|
||||
intent.putExtra("Folder", TvApp.getApplication().getSerializer().SerializeToString(response));
|
||||
|
||||
@@ -512,7 +512,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
|
||||
}
|
||||
|
||||
if (pos >= itemsLoaded - 20) {
|
||||
TvApp.getApplication().getLogger().Debug("Loading more items starting at " + itemsLoaded);
|
||||
TvApp.getApplication().getLogger().Debug("Loading more items starting at %d", itemsLoaded);
|
||||
RetrieveNext();
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
|
||||
}
|
||||
|
||||
if (retrieve) {
|
||||
TvApp.getApplication().getLogger().Info("Re-retrieving row of type " + queryType);
|
||||
TvApp.getApplication().getLogger().Info("Re-retrieving row of type %s", queryType.toString());
|
||||
Retrieve();
|
||||
}
|
||||
|
||||
@@ -1124,12 +1124,12 @@ public class ItemRowAdapter extends ArrayObjectAdapter {
|
||||
}
|
||||
}
|
||||
if (existing == null) {
|
||||
TvApp.getApplication().getLogger().Debug("Adding new episode 1 to premieres " + item.getSeriesName());
|
||||
TvApp.getApplication().getLogger().Debug("Adding new episode 1 to premieres %s", item.getSeriesName());
|
||||
adapter.add(new BaseRowItem(i++, item, preferParentThumb, true));
|
||||
|
||||
} else if (existing.getBaseItem().getParentIndexNumber() > item.getParentIndexNumber()) {
|
||||
//Replace the newer item with the earlier season
|
||||
TvApp.getApplication().getLogger().Debug("Replacing newer episode 1 with an older season for " + item.getSeriesName());
|
||||
TvApp.getApplication().getLogger().Debug("Replacing newer episode 1 with an older season for %s", item.getSeriesName());
|
||||
adapter.replace(existingPos, new BaseRowItem(i++, item, preferParentThumb, false));
|
||||
} // otherwise, just ignore this newer season premiere since we have the older one already
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ public class LiveTvGuideActivity extends BaseActivity implements ILiveTvGuide {
|
||||
|
||||
private void pageGuideTo(long startTime) {
|
||||
if (startTime < System.currentTimeMillis()) startTime = System.currentTimeMillis(); // don't allow the past
|
||||
TvApp.getApplication().getLogger().Info("page to "+new Date(startTime));
|
||||
TvApp.getApplication().getLogger().Info("page to %s", (new Date(startTime)).toString());
|
||||
TvManager.forceReload(); // don't allow cache
|
||||
if (mSelectedProgram != null) {
|
||||
mFirstFocusChannelId = mSelectedProgram.getChannelId();
|
||||
|
||||
@@ -352,7 +352,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
|
||||
private AudioEventListener audioEventListener = new AudioEventListener() {
|
||||
@Override
|
||||
public void onPlaybackStateChange(PlaybackController.PlaybackState newState, BaseItemDto currentItem) {
|
||||
mApplication.getLogger().Debug("**** Got playstate change: " + newState);
|
||||
mApplication.getLogger().Debug("**** Got playstate change: %s" + newState.toString());
|
||||
if (newState == PlaybackController.PlaybackState.PLAYING && currentItem != mBaseItem) {
|
||||
// new item started
|
||||
loadItem();
|
||||
@@ -406,7 +406,7 @@ public class AudioNowPlayingActivity extends BaseActivity {
|
||||
if (posterHeight < 10) posterWidth = Utils.convertDpToPixel(mActivity, 150); //Guard against zero size images causing picasso to barf
|
||||
|
||||
String primaryImageUrl = ImageUtils.getPrimaryImageUrl(mBaseItem, mApplication.getApiClient(), false, posterHeight);
|
||||
mApplication.getLogger().Debug("Audio Poster url: " + primaryImageUrl);
|
||||
mApplication.getLogger().Debug("Audio Poster url: %s", primaryImageUrl);
|
||||
Picasso.with(mActivity)
|
||||
.load(primaryImageUrl)
|
||||
.skipMemoryCache()
|
||||
|
||||
@@ -767,7 +767,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
|
||||
// give back audio focus
|
||||
mAudioManager.abandonAudioFocus(mAudioFocusChanged);
|
||||
mApplication.getLogger().Debug("Fragment pausing. IsFinishing: "+mActivity.isFinishing());
|
||||
mApplication.getLogger().Debug("Fragment pausing. IsFinishing: %b", mActivity.isFinishing());
|
||||
if (!mActivity.isFinishing()) mActivity.finish(); // user hit "home" we want to back out
|
||||
}
|
||||
|
||||
@@ -1355,7 +1355,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
boolean hasMultiAudio = StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size() > 1;
|
||||
|
||||
if (hasMultiAudio) {
|
||||
mApplication.getLogger().Debug("Multiple Audio tracks found: "+ StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size());
|
||||
mApplication.getLogger().Debug("Multiple Audio tracks found: %d", StreamHelper.getAudioStreams(mPlaybackController.getCurrentMediaSource()).size());
|
||||
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_select_audio, mButtonSize, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -1388,7 +1388,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
audioMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
mApplication.getLogger().Debug("Selected stream " + item.getTitle());
|
||||
mApplication.getLogger().Debug("Selected stream %s", item.getTitle().toString());
|
||||
mPlaybackController.switchAudioStream(item.getItemId());
|
||||
return true;
|
||||
}
|
||||
@@ -1401,7 +1401,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
}
|
||||
|
||||
if (hasSubs) {
|
||||
mApplication.getLogger().Debug("Subtitle tracks found: " + mPlaybackController.getSubtitleStreams().size());
|
||||
mApplication.getLogger().Debug("Subtitle tracks found: %d", mPlaybackController.getSubtitleStreams().size());
|
||||
mButtonRow.addView(new ImageButton(mActivity, R.drawable.ic_select_subtitle, mButtonSize, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -1430,7 +1430,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
subMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
mApplication.getLogger().Debug("Selected subtitle " + item.getTitle());
|
||||
mApplication.getLogger().Debug("Selected subtitle %s", item.getTitle().toString());
|
||||
mPlaybackController.switchSubtitleStream(item.getItemId());
|
||||
return true;
|
||||
}
|
||||
@@ -1525,10 +1525,10 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
|
||||
private int getCurrentChapterIndex(BaseItemDto item, long pos) {
|
||||
int ndx = 0;
|
||||
TvApp.getApplication().getLogger().Debug("*** looking for chapter at pos: "+pos);
|
||||
TvApp.getApplication().getLogger().Debug("*** looking for chapter at pos: %d", pos);
|
||||
if (item.getChapters() != null) {
|
||||
for (ChapterInfoDto chapter : item.getChapters()) {
|
||||
TvApp.getApplication().getLogger().Debug("*** chapter "+ndx+" has pos: "+chapter.getStartPositionTicks());
|
||||
TvApp.getApplication().getLogger().Debug("*** chapter %d has pos: %d", ndx, chapter.getStartPositionTicks());
|
||||
if (chapter.getStartPositionTicks() > pos) return ndx - 1;
|
||||
ndx++;
|
||||
}
|
||||
@@ -1756,7 +1756,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
|
||||
|
||||
@Override
|
||||
public void nextItemThresholdHit(BaseItemDto nextItem) {
|
||||
mApplication.getLogger().Debug("Next Item is " + nextItem.getName());
|
||||
mApplication.getLogger().Debug("Next Item is %s", nextItem.getName());
|
||||
// need to retrieve full item for all info
|
||||
mApplication.getApiClient().GetItemAsync(nextItem.getId(), mApplication.getCurrentUser().getId(), new Response<BaseItemDto>() {
|
||||
@Override
|
||||
|
||||
@@ -65,11 +65,13 @@ public class ExternalPlayerActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
long playerFinishedTime = System.currentTimeMillis();
|
||||
mApplication.getLogger().Debug("Returned from player... "+ resultCode);
|
||||
mApplication.getLogger().Debug("Returned from player... %d", resultCode);
|
||||
//MX Player will return position
|
||||
int pos = data != null ? data.getIntExtra("position", 0) : 0;
|
||||
if (pos > 0) mApplication.getLogger().Info("Player returned position: "+pos);
|
||||
if (pos > 0) mApplication.getLogger().Info("Player returned position: %d", pos);
|
||||
Long reportPos = (long) pos * 10000;
|
||||
|
||||
stopReportLoop();
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.text.InputType;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
@@ -140,11 +139,11 @@ public class MediaManager {
|
||||
|
||||
public static void addAudioEventListener(AudioEventListener listener) {
|
||||
mAudioEventListeners.add(listener);
|
||||
TvApp.getApplication().getLogger().Debug("Added event listener. Total listeners: "+mAudioEventListeners.size());
|
||||
TvApp.getApplication().getLogger().Debug("Added event listener. Total listeners: %d", mAudioEventListeners.size());
|
||||
}
|
||||
public static void removeAudioEventListener(AudioEventListener listener) {
|
||||
mAudioEventListeners.remove(listener);
|
||||
TvApp.getApplication().getLogger().Debug("Removed event listener. Total listeners: " + mAudioEventListeners.size());
|
||||
TvApp.getApplication().getLogger().Debug("Removed event listener. Total listeners: %d", mAudioEventListeners.size());
|
||||
}
|
||||
|
||||
public static boolean initAudio() {
|
||||
@@ -190,7 +189,7 @@ public class MediaManager {
|
||||
|
||||
//fire external listener if there
|
||||
for (AudioEventListener listener : mAudioEventListeners) {
|
||||
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item completion. "+ mCurrentAudioItem.getName());
|
||||
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item completion. %s", mCurrentAudioItem.getName());
|
||||
listener.onPlaybackStateChange(PlaybackController.PlaybackState.IDLE, mCurrentAudioItem);
|
||||
}
|
||||
|
||||
@@ -202,7 +201,7 @@ public class MediaManager {
|
||||
// Create a new media player based on platform
|
||||
if (DeviceUtils.is60()) {
|
||||
nativeMode = true;
|
||||
mExoPlayer = ExoPlayerFactory.newSimpleInstance(TvApp.getApplication());
|
||||
mExoPlayer = new SimpleExoPlayer.Builder(TvApp.getApplication()).build();
|
||||
mExoPlayer.addListener(new Player.EventListener() {
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
@@ -228,8 +227,14 @@ public class MediaManager {
|
||||
mLibVLC = new LibVLC(TvApp.getApplication(), options);
|
||||
|
||||
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
|
||||
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
if(!Utils.downMixAudio()) {
|
||||
mVlcPlayer.setAudioOutput("android_audiotrack");
|
||||
mVlcPlayer.setAudioDigitalOutputEnabled(true);
|
||||
} else {
|
||||
mVlcPlayer.setAudioOutput("opensles_android");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
}
|
||||
|
||||
|
||||
mVlcHandler.setOnProgressListener(new PlaybackListener() {
|
||||
@Override
|
||||
@@ -304,7 +309,7 @@ public class MediaManager {
|
||||
|
||||
private static void fireQueueStatusChange() {
|
||||
for (AudioEventListener listener : mAudioEventListeners) {
|
||||
TvApp.getApplication().getLogger().Info("Firing queue state change listener. "+ hasAudioQueueItems());
|
||||
TvApp.getApplication().getLogger().Info("Firing queue state change listener. %b", hasAudioQueueItems());
|
||||
listener.onQueueStatusChanged(hasAudioQueueItems());
|
||||
}
|
||||
|
||||
@@ -354,7 +359,7 @@ public class MediaManager {
|
||||
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
TvApp.getApplication().getLogger().Debug(exception.toString());
|
||||
TvApp.getApplication().getLogger().ErrorException("Exception creating playlist", exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -593,7 +598,7 @@ public class MediaManager {
|
||||
mExoPlayer.setPlayWhenReady(true);
|
||||
mExoPlayer.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(response.ToUrl(apiClient.getApiUrl(), apiClient.getAccessToken()))));
|
||||
} else {
|
||||
TvApp.getApplication().getLogger().Info("Playback attempt via VLC of " + response.getMediaUrl());
|
||||
TvApp.getApplication().getLogger().Info("Playback attempt via VLC of %s", response.getMediaUrl());
|
||||
Media media = new Media(mLibVLC, Uri.parse(response.getMediaUrl()));
|
||||
media.parse();
|
||||
mVlcPlayer.setMedia(media);
|
||||
@@ -612,7 +617,7 @@ public class MediaManager {
|
||||
|
||||
ReportingHelper.reportStart(item, mCurrentAudioPosition * 10000);
|
||||
for (AudioEventListener listener : mAudioEventListeners) {
|
||||
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item start. " + mCurrentAudioItem.getName());
|
||||
TvApp.getApplication().getLogger().Info("Firing playback state change listener for item start. %s", mCurrentAudioItem.getName());
|
||||
listener.onPlaybackStateChange(PlaybackController.PlaybackState.PLAYING, mCurrentAudioItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class PlaybackController {
|
||||
}
|
||||
|
||||
public BaseItemDto getCurrentlyPlayingItem() {
|
||||
return mItems.get(mCurrentIndex);
|
||||
return mItems.size() > mCurrentIndex ? mItems.get(mCurrentIndex) : null;
|
||||
}
|
||||
public MediaSourceInfo getCurrentMediaSource() { return mCurrentStreamInfo != null && mCurrentStreamInfo.getMediaSource() != null ? mCurrentStreamInfo.getMediaSource() : getCurrentlyPlayingItem().getMediaSources().get(0);}
|
||||
public StreamInfo getCurrentStreamInfo() { return mCurrentStreamInfo; }
|
||||
@@ -210,7 +210,7 @@ public class PlaybackController {
|
||||
mDisplayModes = display.getSupportedModes();
|
||||
mApplication.getLogger().Info("** Available display refresh rates:");
|
||||
for (Display.Mode mDisplayMode : mDisplayModes) {
|
||||
mApplication.getLogger().Info(Float.toString(mDisplayMode.getRefreshRate()));
|
||||
mApplication.getLogger().Info("%f", mDisplayMode.getRefreshRate());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -259,7 +259,7 @@ public class PlaybackController {
|
||||
}
|
||||
|
||||
private void play(long position, int transcodedSubtitle) {
|
||||
mApplication.getLogger().Debug("Play called with pos: " + position + " and sub index: "+transcodedSubtitle);
|
||||
mApplication.getLogger().Debug("Play called with pos: %d and sub index: %d", position, transcodedSubtitle);
|
||||
|
||||
if (position < 0) {
|
||||
mApplication.getLogger().Info("Negative start requested - adjusting to zero");
|
||||
@@ -375,7 +375,7 @@ public class PlaybackController {
|
||||
internalOptions.setProfile(internalProfile);
|
||||
|
||||
mDefaultSubIndex = transcodedSubtitle;
|
||||
TvApp.getApplication().getLogger().Debug("Max bitrate is: " + Utils.getMaxBitrate());
|
||||
TvApp.getApplication().getLogger().Debug("Max bitrate is: %d", Utils.getMaxBitrate());
|
||||
|
||||
playInternal(getCurrentlyPlayingItem(), position, vlcOptions, internalOptions);
|
||||
mPlaybackState = PlaybackState.BUFFERING;
|
||||
@@ -400,7 +400,7 @@ public class PlaybackController {
|
||||
//std 30 min episode or less
|
||||
mNextItemThreshold = duration - NEXT_UP_DURATION;
|
||||
}
|
||||
TvApp.getApplication().getLogger().Debug("Next item threshold set to " + mNextItemThreshold);
|
||||
TvApp.getApplication().getLogger().Debug("Next item threshold set to %d", mNextItemThreshold);
|
||||
} else {
|
||||
mNextItemThreshold = Long.MAX_VALUE;
|
||||
}
|
||||
@@ -465,11 +465,11 @@ public class PlaybackController {
|
||||
mApplication.getPlaybackManager().getVideoStreamInfo(apiClient.getServerInfo().getId(), vlcOptions, position * 10000, false, apiClient, new Response<StreamInfo>() {
|
||||
@Override
|
||||
public void onResponse(final StreamInfo vlcResponse) {
|
||||
mApplication.getLogger().Info("VLC would " + (vlcResponse.getPlayMethod().equals(PlayMethod.Transcode) ? "transcode" : "direct stream"));
|
||||
mApplication.getLogger().Info("VLC would %s", vlcResponse.getPlayMethod().equals(PlayMethod.Transcode) ? "transcode" : "direct stream");
|
||||
mApplication.getPlaybackManager().getVideoStreamInfo(apiClient.getServerInfo().getId(), internalOptions, position * 10000, false, apiClient, new Response<StreamInfo>() {
|
||||
@Override
|
||||
public void onResponse(StreamInfo internalResponse) {
|
||||
mApplication.getLogger().Info("Internal player would " + (internalResponse.getPlayMethod().equals(PlayMethod.Transcode) ? "transcode" : "direct stream"));
|
||||
mApplication.getLogger().Info("Internal player would %s", internalResponse.getPlayMethod().equals(PlayMethod.Transcode) ? "transcode" : "direct stream");
|
||||
boolean useDeinterlacing = vlcResponse.getMediaSource().getVideoStream() != null &&
|
||||
vlcResponse.getMediaSource().getVideoStream().getIsInterlaced() &&
|
||||
(vlcResponse.getMediaSource().getVideoStream().getWidth() == null ||
|
||||
@@ -478,7 +478,7 @@ public class PlaybackController {
|
||||
|
||||
String preferredVideoPlayer = mApplication.getPrefs().getString("pref_video_player", "auto");
|
||||
|
||||
mApplication.getLogger().Info("User preferred player is: " + preferredVideoPlayer);
|
||||
mApplication.getLogger().Info("User preferred player is: %s", preferredVideoPlayer);
|
||||
|
||||
if (preferredVideoPlayer.equals("vlc")) {
|
||||
// Force VLC
|
||||
@@ -695,7 +695,7 @@ public class PlaybackController {
|
||||
mCurrentOptions.setAudioStreamIndex(index);
|
||||
if (mVideoManager.isNativeMode()) {
|
||||
startSpinner();
|
||||
mApplication.getLogger().Debug("Setting audio index to: " + index);
|
||||
mApplication.getLogger().Debug("Setting audio index to: %d", index);
|
||||
mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId());
|
||||
stop();
|
||||
playInternal(getCurrentlyPlayingItem(), mCurrentPosition, mCurrentOptions, mCurrentOptions);
|
||||
@@ -709,7 +709,7 @@ public class PlaybackController {
|
||||
private boolean burningSubs = false;
|
||||
|
||||
public void switchSubtitleStream(int index) {
|
||||
mApplication.getLogger().Debug("Setting subtitle index to: " + index);
|
||||
mApplication.getLogger().Debug("Setting subtitle index to: %d", index);
|
||||
mCurrentOptions.setSubtitleStreamIndex(index >= 0 ? index : null);
|
||||
|
||||
if (index < 0) {
|
||||
@@ -853,7 +853,7 @@ public class PlaybackController {
|
||||
if (mCurrentIndex < mItems.size() - 1) {
|
||||
stop();
|
||||
mCurrentIndex++;
|
||||
mApplication.getLogger().Debug("Moving to index: " + mCurrentIndex + " out of " + mItems.size() + " total items.");
|
||||
mApplication.getLogger().Debug("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size());
|
||||
spinnerOff = false;
|
||||
play(0);
|
||||
}
|
||||
@@ -864,8 +864,8 @@ public class PlaybackController {
|
||||
}
|
||||
|
||||
public void seek(final long pos) {
|
||||
mApplication.getLogger().Debug("Seeking to " + pos);
|
||||
mApplication.getLogger().Debug("Container: "+mCurrentStreamInfo.getContainer());
|
||||
mApplication.getLogger().Debug("Seeking to %d", pos);
|
||||
mApplication.getLogger().Debug("Container: %s", mCurrentStreamInfo.getContainer());
|
||||
if (mPlaybackMethod == PlayMethod.Transcode && ContainerTypes.MKV.equals(mCurrentStreamInfo.getContainer())) {
|
||||
//mkv transcodes require re-start of stream for seek
|
||||
mVideoManager.stopPlayback();
|
||||
@@ -998,13 +998,24 @@ public class PlaybackController {
|
||||
mReportLoop = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BaseItemDto currentItem = getCurrentlyPlayingItem();
|
||||
if (currentItem == null) {
|
||||
// Loop was called while nothing was playing!
|
||||
stopReportLoop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPlaybackState != PlaybackState.PLAYING) {
|
||||
// Playback was stopped, don't report progress anymore
|
||||
return;
|
||||
}
|
||||
|
||||
long currentTime = isLiveTv ? getTimeShiftedProgress() : mVideoManager.getCurrentPosition();
|
||||
if (isLiveTv && !directStreamLiveTv) {
|
||||
mFragment.setSecondaryTime(getRealTimeProgress());
|
||||
}
|
||||
|
||||
ReportingHelper.reportProgress(getCurrentlyPlayingItem(), getCurrentStreamInfo(), currentTime * 10000, true);
|
||||
ReportingHelper.reportProgress(currentItem, getCurrentStreamInfo(), currentTime * 10000, true);
|
||||
mHandler.postDelayed(this, PROGRESS_REPORTING_PAUSE_INTERVAL);
|
||||
}
|
||||
};
|
||||
@@ -1067,7 +1078,7 @@ public class PlaybackController {
|
||||
if (mCurrentIndex < mItems.size() - 1) {
|
||||
// move to next in queue
|
||||
mCurrentIndex++;
|
||||
mApplication.getLogger().Debug("Moving to next queue item. Index: "+mCurrentIndex);
|
||||
mApplication.getLogger().Debug("Moving to next queue item. Index: %d", mCurrentIndex);
|
||||
spinnerOff = false;
|
||||
play(0);
|
||||
} else {
|
||||
@@ -1093,7 +1104,7 @@ public class PlaybackController {
|
||||
mFragment.finish();
|
||||
} else {
|
||||
String msg = mApplication.getString(R.string.video_error_unknown_error);
|
||||
mApplication.getLogger().Error("Playback error - " + msg);
|
||||
mApplication.getLogger().Error("Playback error - %s", msg);
|
||||
playerErrorEncountered();
|
||||
}
|
||||
|
||||
@@ -1123,7 +1134,7 @@ public class PlaybackController {
|
||||
if (currentIndex != null && currentIndex == mDefaultSubIndex) {
|
||||
mApplication.getLogger().Info("Not selecting default subtitle stream because it is already selected");
|
||||
} else {
|
||||
mApplication.getLogger().Info("Selecting default sub stream: " + mDefaultSubIndex);
|
||||
mApplication.getLogger().Info("Selecting default sub stream: %d", mDefaultSubIndex);
|
||||
switchSubtitleStream(mDefaultSubIndex);
|
||||
}
|
||||
} else {
|
||||
@@ -1132,7 +1143,7 @@ public class PlaybackController {
|
||||
}
|
||||
|
||||
if (!mVideoManager.isNativeMode() && mDefaultAudioIndex >= 0) {
|
||||
TvApp.getApplication().getLogger().Info("Selecting default audio stream: " + mDefaultAudioIndex);
|
||||
TvApp.getApplication().getLogger().Info("Selecting default audio stream: %d", mDefaultAudioIndex);
|
||||
switchAudioStream(mDefaultAudioIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package org.jellyfin.androidtv.playback;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.jellyfin.androidtv.TvApp;
|
||||
import org.jellyfin.androidtv.base.BaseActivity;
|
||||
import org.jellyfin.androidtv.util.Utils;
|
||||
import org.jellyfin.apiclient.interaction.ApiClient;
|
||||
import org.jellyfin.apiclient.interaction.Response;
|
||||
import org.jellyfin.apiclient.interaction.ResponseStreamInfo;
|
||||
import org.jellyfin.apiclient.model.entities.MediaStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -14,11 +16,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.jellyfin.apiclient.interaction.ApiClient;
|
||||
import org.jellyfin.apiclient.interaction.Response;
|
||||
import org.jellyfin.apiclient.interaction.ResponseStreamInfo;
|
||||
import org.jellyfin.apiclient.model.entities.MediaStream;
|
||||
|
||||
/**
|
||||
* Created by Eric on 7/19/2015.
|
||||
*/
|
||||
@@ -49,7 +46,7 @@ public class SubtitleHelper {
|
||||
|
||||
String url = (stream.getIsExternalUrl() != null && !stream.getIsExternalUrl()) ? apiClient.GetApiUrl(stream.getDeliveryUrl()) : stream.getDeliveryUrl();
|
||||
|
||||
TvApp.getApplication().getLogger().Info("Subtitle url: "+url);
|
||||
TvApp.getApplication().getLogger().Info("Subtitle url: %s", url);
|
||||
|
||||
apiClient.getResponseStream(url, new Response<ResponseStreamInfo>(response){
|
||||
|
||||
@@ -58,7 +55,8 @@ public class SubtitleHelper {
|
||||
InputStream initialStream = info.Stream;
|
||||
|
||||
try {
|
||||
Files.createParentDirs(file);
|
||||
// Create parent directories
|
||||
file.getParentFile().mkdirs();
|
||||
OutputStream outStream = new FileOutputStream(file);
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package org.jellyfin.androidtv.playback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.text.TextOutput;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
@@ -27,9 +31,9 @@ import org.jellyfin.androidtv.util.Utils;
|
||||
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
|
||||
import org.jellyfin.apiclient.model.entities.MediaStream;
|
||||
import org.jellyfin.apiclient.model.entities.MediaStreamType;
|
||||
import org.videolan.libvlc.IVLCVout;
|
||||
import org.videolan.libvlc.LibVLC;
|
||||
import org.videolan.libvlc.Media;
|
||||
import org.videolan.libvlc.interfaces.IVLCVout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -69,6 +73,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
private long mForcedTime = -1;
|
||||
private long mLastTime = -1;
|
||||
private long mMetaDuration = -1;
|
||||
private long lastExoPlayerPosition = -1;
|
||||
|
||||
private boolean nativeMode = false;
|
||||
private boolean mSurfaceReady = false;
|
||||
@@ -92,7 +97,15 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
} else {
|
||||
mSubtitlesSurface.setVisibility(View.GONE);
|
||||
}
|
||||
mExoPlayer = ExoPlayerFactory.newSimpleInstance(TvApp.getApplication());
|
||||
|
||||
mExoPlayer = new SimpleExoPlayer.Builder(TvApp.getApplication(), new DefaultRenderersFactory(TvApp.getApplication()) {
|
||||
@Override
|
||||
protected void buildTextRenderers(Context context, TextOutput output, Looper outputLooper, int extensionRendererMode, ArrayList<Renderer> out) {
|
||||
// Do not add text renderers since we handle subtitles
|
||||
}
|
||||
}).build();
|
||||
|
||||
|
||||
mExoPlayerView = view.findViewById(R.id.exoPlayerView);
|
||||
mExoPlayerView.setPlayer(mExoPlayer);
|
||||
mExoPlayer.addListener(new Player.EventListener() {
|
||||
@@ -169,7 +182,15 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
}
|
||||
|
||||
public long getCurrentPosition() {
|
||||
if (nativeMode) return mExoPlayer.getCurrentPosition();
|
||||
if (nativeMode) {
|
||||
if (mExoPlayer == null) {
|
||||
return lastExoPlayerPosition;
|
||||
} else {
|
||||
long mExoPlayerCurrentPosition = mExoPlayer.getCurrentPosition();
|
||||
lastExoPlayerPosition = mExoPlayerCurrentPosition;
|
||||
return mExoPlayerCurrentPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (mVlcPlayer == null) return 0;
|
||||
|
||||
@@ -254,14 +275,14 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
public long seekTo(long pos) {
|
||||
if (nativeMode) {
|
||||
Long intPos = pos;
|
||||
TvApp.getApplication().getLogger().Info("Exo length in seek is: " + mExoPlayer.getDuration());
|
||||
TvApp.getApplication().getLogger().Info("Exo length in seek is: %d", mExoPlayer.getDuration());
|
||||
mExoPlayer.seekTo(intPos.intValue());
|
||||
return pos;
|
||||
} else {
|
||||
if (mVlcPlayer == null || !mVlcPlayer.isSeekable()) return -1;
|
||||
mForcedTime = pos;
|
||||
mLastTime = mVlcPlayer.getTime();
|
||||
TvApp.getApplication().getLogger().Info("VLC length in seek is: " + mVlcPlayer.getLength());
|
||||
TvApp.getApplication().getLogger().Info("VLC length in seek is: %d", mVlcPlayer.getLength());
|
||||
try {
|
||||
if (getDuration() > 0) mVlcPlayer.setPosition((float)pos / getDuration()); else mVlcPlayer.setTime(pos);
|
||||
|
||||
@@ -278,7 +299,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
public void setVideoPath(String path) {
|
||||
mCurrentVideoPath = path;
|
||||
try {
|
||||
TvApp.getApplication().getLogger().Info("Video path set to: "+path);
|
||||
TvApp.getApplication().getLogger().Info("Video path set to: %s", path);
|
||||
|
||||
} catch(Exception e){
|
||||
TvApp.getApplication().getLogger().ErrorException("Error writing path to log",e);
|
||||
@@ -349,7 +370,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
return false;
|
||||
}
|
||||
|
||||
TvApp.getApplication().getLogger().Info("Setting Vlc sub to "+vlcSub.name);
|
||||
TvApp.getApplication().getLogger().Info("Setting Vlc sub to %s", vlcSub.name);
|
||||
return mVlcPlayer.setSpuTrack(vlcSub.id);
|
||||
|
||||
}
|
||||
@@ -388,9 +409,9 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
return;
|
||||
}
|
||||
//debug
|
||||
TvApp.getApplication().getLogger().Debug("Setting VLC audio track index to: "+vlcIndex + "/" + vlcTrack.id);
|
||||
TvApp.getApplication().getLogger().Debug("Setting VLC audio track index to: %d / %d", vlcIndex, vlcTrack.id);
|
||||
for (org.videolan.libvlc.MediaPlayer.TrackDescription track : mVlcPlayer.getAudioTracks()) {
|
||||
TvApp.getApplication().getLogger().Debug("VLC Audio Track: "+track.name+"/"+track.id);
|
||||
TvApp.getApplication().getLogger().Debug("VLC Audio Track: %s / %d", track.name, track.id);
|
||||
}
|
||||
//
|
||||
if (mVlcPlayer.setAudioTrack(vlcTrack.id)) {
|
||||
@@ -409,7 +430,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
if (!mVlcPlayer.setAudioDelay(value * 1000)) {
|
||||
TvApp.getApplication().getLogger().Error("Error setting audio delay");
|
||||
} else {
|
||||
TvApp.getApplication().getLogger().Info("Audio delay set to "+value);
|
||||
TvApp.getApplication().getLogger().Info("Audio delay set to %d", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,16 +438,25 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
public long getAudioDelay() { return mVlcPlayer != null ? mVlcPlayer.getAudioDelay() / 1000 : 0;}
|
||||
|
||||
public void setCompatibleAudio() {
|
||||
if (!nativeMode) {
|
||||
mVlcPlayer.setAudioOutput("opensles_android");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
}
|
||||
if (!nativeMode) {
|
||||
mVlcPlayer.setAudioOutput("opensles_android");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
}
|
||||
}
|
||||
|
||||
public void setAudioMode() {
|
||||
if (!nativeMode) {
|
||||
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
setVlcAudioOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private void setVlcAudioOptions() {
|
||||
|
||||
if(!Utils.downMixAudio()) {
|
||||
mVlcPlayer.setAudioOutput("android_audiotrack");
|
||||
mVlcPlayer.setAudioDigitalOutputEnabled(true);
|
||||
} else {
|
||||
setCompatibleAudio();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +464,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
if (!nativeMode && mediaSource != null && mediaSource.getMediaStreams() != null) {
|
||||
for (MediaStream stream : mediaSource.getMediaStreams()) {
|
||||
if (stream.getType() == MediaStreamType.Video && stream.getIndex() >= 0) {
|
||||
TvApp.getApplication().getLogger().Debug("Setting video index to: "+stream.getIndex());
|
||||
TvApp.getApplication().getLogger().Debug("Setting video index to: %d", stream.getIndex());
|
||||
mVlcPlayer.setVideoTrack(stream.getIndex());
|
||||
return;
|
||||
}
|
||||
@@ -479,11 +509,10 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
options.add("-v");
|
||||
|
||||
mLibVLC = new LibVLC(TvApp.getApplication(), options);
|
||||
TvApp.getApplication().getLogger().Info("Network buffer set to " + buffer);
|
||||
TvApp.getApplication().getLogger().Info("Network buffer set to %d", buffer);
|
||||
|
||||
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
|
||||
mVlcPlayer.setAudioOutput(Utils.downMixAudio() ? "opensles_android" : "android_audiotrack");
|
||||
mVlcPlayer.setAudioOutputDevice("hdmi");
|
||||
setVlcAudioOptions();
|
||||
|
||||
|
||||
mSurfaceHolder.addCallback(mSurfaceCallback);
|
||||
@@ -627,7 +656,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
|
||||
}
|
||||
|
||||
TvApp.getApplication().getLogger().Debug("Surface sized "+ lp.width+"x"+lp.height);
|
||||
TvApp.getApplication().getLogger().Debug("Surface sized %d x %d ", lp.width, lp.height);
|
||||
mSurfaceView.invalidate();
|
||||
if (hasSubtitlesSurface) mSubtitlesSurface.invalidate();
|
||||
}
|
||||
|
||||
@@ -205,7 +205,9 @@ public class HorizontalGridPresenter extends Presenter {
|
||||
vh.getGridView().setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
|
||||
@Override
|
||||
public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) {
|
||||
selectChildView(gridViewHolder, child.itemView);
|
||||
if (child != null) {
|
||||
selectChildView(gridViewHolder, child.itemView);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class PositionableListRowPresenter extends CustomListRowPresenter impleme
|
||||
}
|
||||
|
||||
public void setPosition(int ndx) {
|
||||
TvApp.getApplication().getLogger().Debug("Setting position to: "+ndx);
|
||||
TvApp.getApplication().getLogger().Debug("Setting position to: %d", ndx);
|
||||
if (viewHolder != null && viewHolder.getGridView() != null) viewHolder.getGridView().setSelectedPosition(ndx);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import android.text.Layout;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.SoundEffectConstants;
|
||||
@@ -15,6 +13,9 @@ import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.TvApp;
|
||||
import org.jellyfin.androidtv.details.ExpandedTextActivity;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
|
||||
/**
|
||||
* Created by spam on 9/30/2016.
|
||||
*/
|
||||
|
||||
@@ -2,10 +2,11 @@ package org.jellyfin.androidtv.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemType;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
/**
|
||||
* Created by Eric on 2/21/2015.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.jellyfin.androidtv.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
@@ -10,6 +9,8 @@ import android.widget.TextView;
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.model.GotFocusEvent;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
/**
|
||||
* Created by Eric on 2/20/2015.
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package org.jellyfin.androidtv.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.appcompat.widget.AppCompatButton;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.util.Utils;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatButton;
|
||||
|
||||
/**
|
||||
* Created by Eric on 4/30/2015.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.TvApp;
|
||||
@@ -45,17 +46,11 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.leanback.widget.ArrayObjectAdapter;
|
||||
import androidx.leanback.widget.ListRow;
|
||||
import androidx.leanback.widget.OnItemViewClickedListener;
|
||||
import androidx.leanback.widget.Presenter;
|
||||
import androidx.leanback.widget.Row;
|
||||
import androidx.leanback.widget.RowPresenter;
|
||||
|
||||
public class HomeFragment extends StdBrowseFragment {
|
||||
// Copied from jellyfin-web (homesections.js#getDefaultSection)
|
||||
@@ -65,7 +60,8 @@ public class HomeFragment extends StdBrowseFragment {
|
||||
HomeSectionType.RESUME_AUDIO,
|
||||
HomeSectionType.LIVE_TV,
|
||||
HomeSectionType.NEXT_UP,
|
||||
HomeSectionType.LATEST_MEDIA
|
||||
HomeSectionType.LATEST_MEDIA,
|
||||
HomeSectionType.NONE
|
||||
};
|
||||
|
||||
private List<HomeFragmentRow> rows = new ArrayList<>();
|
||||
@@ -234,8 +230,14 @@ public class HomeFragment extends StdBrowseFragment {
|
||||
Pattern pattern = Pattern.compile("^homesection(\\d+)$");
|
||||
|
||||
// Add sections to map first to make sure they stay in the correct order
|
||||
Map<Integer, HomeSectionType> sections = new TreeMap<>();
|
||||
ArrayMap<Integer, HomeSectionType> sections = new ArrayMap<>();
|
||||
|
||||
// Set defaults
|
||||
for (int i = 0; i < DEFAULT_SECTIONS.length; i++) {
|
||||
sections.put(i, DEFAULT_SECTIONS[i]);
|
||||
}
|
||||
|
||||
// Overwrite with user-preferred
|
||||
for (String key : prefs.keySet()) {
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (!matcher.matches()) continue;
|
||||
@@ -250,14 +252,10 @@ public class HomeFragment extends StdBrowseFragment {
|
||||
// Fallback when no customization is done by the user
|
||||
rows.clear();
|
||||
|
||||
if (sections.size() > 0) {
|
||||
for (HomeSectionType section : sections.values()) {
|
||||
// Actually add the sections
|
||||
for (HomeSectionType section : sections.values()) {
|
||||
if (section != HomeSectionType.NONE)
|
||||
addSection(section);
|
||||
}
|
||||
} else {
|
||||
for (HomeSectionType section : DEFAULT_SECTIONS) {
|
||||
addSection(section);
|
||||
}
|
||||
}
|
||||
|
||||
loadRows();
|
||||
|
||||
@@ -45,6 +45,9 @@ public class ImageUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (item.getBaseItemType() == BaseItemType.UserView && item.getHasPrimaryImage())
|
||||
return ImageUtils.ASPECT_RATIO_16_9;
|
||||
|
||||
return item.getPrimaryImageAspectRatio() != null ? item.getPrimaryImageAspectRatio() : ASPECT_RATIO_7_9;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ public class KeyProcessor {
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
TvApp.getApplication().getLogger().Debug("Menu for: "+rowItem.getFullName());
|
||||
TvApp.getApplication().getLogger().Debug("Menu for: %s", rowItem.getFullName());
|
||||
|
||||
//Create a contextual menu based on item
|
||||
switch (rowItem.getItemType()) {
|
||||
|
||||
@@ -33,8 +33,7 @@ public class MediaCodecCapabilitiesTest {
|
||||
|
||||
private boolean checkDecoder(String mime, int profile, int level) {
|
||||
if (!hasDecoder(mime, profile, level)) {
|
||||
TvApp.getApplication().getLogger().Info("no " + mime + " decoder for profile "
|
||||
+ profile + " and level " + level);
|
||||
TvApp.getApplication().getLogger().Info("no %s decoder for profile %d and level %d", mime, profile, level);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -14,13 +14,13 @@ public class MediaUtils {
|
||||
|
||||
public static boolean check(boolean result, String message) {
|
||||
if (!result) {
|
||||
TvApp.getApplication().getLogger().Info(message);
|
||||
TvApp.getApplication().getLogger().Info("%s", message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static boolean canDecode(MediaFormat format) {
|
||||
if (sMCL.findDecoderForFormat(format) == null) {
|
||||
TvApp.getApplication().getLogger().Info("no decoder for " + format);
|
||||
TvApp.getApplication().getLogger().Info("no decoder for %s", format.toString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -31,7 +31,7 @@ public class MediaUtils {
|
||||
private static boolean hasCodecForMimes(boolean encoder, String[] mimes) {
|
||||
for (String mime : mimes) {
|
||||
if (!hasCodecForMime(encoder, mime)) {
|
||||
TvApp.getApplication().getLogger().Info("no " + (encoder ? "encoder" : "decoder") + " for mime " + mime);
|
||||
TvApp.getApplication().getLogger().Info("no %s for %s", encoder ? "encoder" : "decoder", mime);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class MediaUtils {
|
||||
}
|
||||
for (String type : info.getSupportedTypes()) {
|
||||
if (type.equalsIgnoreCase(mime)) {
|
||||
TvApp.getApplication().getLogger().Info("found codec " + info.getName() + " for mime " + mime);
|
||||
TvApp.getApplication().getLogger().Info("found codec %s for mime %s", info.getName(), mime);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ProfileHelper {
|
||||
|
||||
profile.setName("Android");
|
||||
profile.setMaxStreamingBitrate(20000000);
|
||||
profile.setMaxStaticBitrate(30000000);
|
||||
profile.setMaxStaticBitrate(100000000);
|
||||
|
||||
List<TranscodingProfile> transcodingProfiles = new ArrayList<>();
|
||||
|
||||
@@ -172,7 +172,6 @@ public class ProfileHelper {
|
||||
ContainerTypes.OGM,
|
||||
ContainerTypes.OGV,
|
||||
ContainerTypes.M2V,
|
||||
ContainerTypes.AVI,
|
||||
ContainerTypes.MPG,
|
||||
ContainerTypes.MPEG,
|
||||
ContainerTypes.MP4,
|
||||
@@ -236,33 +235,24 @@ public class ProfileHelper {
|
||||
new ProfileCondition(ProfileConditionType.GreaterThanEqual, ProfileConditionValue.RefFrames, "2"),
|
||||
});
|
||||
|
||||
ContainerProfile videoContainerProfile = new ContainerProfile();
|
||||
videoContainerProfile.setType(DlnaProfileType.Video);
|
||||
videoContainerProfile.setContainer(ContainerTypes.AVI);
|
||||
videoContainerProfile.setConditions(new ProfileCondition[]
|
||||
{
|
||||
new ProfileCondition(ProfileConditionType.NotEquals, ProfileConditionValue.VideoCodecTag, "xvid"),
|
||||
});
|
||||
|
||||
CodecProfile videoAudioCodecProfile = new CodecProfile();
|
||||
videoAudioCodecProfile.setType(CodecType.VideoAudio);
|
||||
videoAudioCodecProfile.setConditions(new ProfileCondition[]{new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioChannels, "6")});
|
||||
videoAudioCodecProfile.setConditions(new ProfileCondition[]{new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.AudioChannels, "8")});
|
||||
|
||||
profile.setCodecProfiles(new CodecProfile[]{getHevcProfile(), h264MainProfile, videoAudioCodecProfile});
|
||||
profile.setContainerProfiles(new ContainerProfile[] {videoContainerProfile});
|
||||
profile.setSubtitleProfiles(new SubtitleProfile[]{
|
||||
getSubtitleProfile("srt", SubtitleDeliveryMethod.External),
|
||||
getSubtitleProfile("srt", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("subrip", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("ass", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("ssa", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("pgs", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("pgssub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("dvdsub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("vtt", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("sub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("smi", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("idx", SubtitleDeliveryMethod.Embed)
|
||||
getSubtitleProfile("srt", SubtitleDeliveryMethod.External),
|
||||
getSubtitleProfile("srt", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("subrip", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("ass", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("ssa", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("pgs", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("pgssub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("dvdsub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("vtt", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("sub", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("smi", SubtitleDeliveryMethod.Embed),
|
||||
getSubtitleProfile("idx", SubtitleDeliveryMethod.Embed)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class RemoteControlReceiver extends BroadcastReceiver {
|
||||
//Respond to media button presses
|
||||
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
|
||||
KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||
TvApp.getApplication().getLogger().Debug("****** In remote receiver. Keycode: " + event.getKeyCode());
|
||||
TvApp.getApplication().getLogger().Debug("****** In remote receiver. Keycode: %d", event.getKeyCode());
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.jellyfin.androidtv.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
@@ -9,14 +10,18 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeUtils {
|
||||
private static final int MILLIS_PER_SEC = 1000;
|
||||
private static final int MILLIS_PER_MIN = 60 * MILLIS_PER_SEC;
|
||||
private static final int MILLIS_PER_HR = 60 * MILLIS_PER_MIN;
|
||||
private static final long MILLIS_PER_MIN = TimeUnit.MINUTES.toMillis(1);
|
||||
private static final long MILLIS_PER_HR = TimeUnit.HOURS.toMillis(1);
|
||||
|
||||
private static final int SECS_PER_MIN = 60;
|
||||
private static final int SECS_PER_HR = 60 * SECS_PER_MIN;
|
||||
private static final long SECS_PER_HR = TimeUnit.HOURS.toSeconds(1);
|
||||
|
||||
private static final String DURATION_TIME_FORMAT_NO_HOURS = "%d:%02d";
|
||||
private static final String DURATION_TIME_FORMAT_WITH_HOURS = "%d:%02d:%02d";
|
||||
|
||||
public static long secondsToMillis(double seconds) {
|
||||
return Math.round(seconds * MILLIS_PER_SEC);
|
||||
@@ -33,37 +38,22 @@ public class TimeUtils {
|
||||
/**
|
||||
* Formats time in milliseconds to hh:mm:ss string format.
|
||||
*
|
||||
* @param millis
|
||||
* @return
|
||||
* @param millis Time in milliseconds
|
||||
* @return Formatted time
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static String formatMillis(long millis) {
|
||||
long hr = millis / MILLIS_PER_HR;
|
||||
long hr = TimeUnit.MILLISECONDS.toHours(millis);
|
||||
millis %= MILLIS_PER_HR;
|
||||
long min = millis / MILLIS_PER_MIN;
|
||||
long min = TimeUnit.MILLISECONDS.toMinutes(millis);
|
||||
millis %= MILLIS_PER_MIN;
|
||||
long sec = millis / MILLIS_PER_SEC;
|
||||
long sec = TimeUnit.MILLISECONDS.toSeconds(millis);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
// Hours
|
||||
if (hr > 0) {
|
||||
builder.append(hr)
|
||||
.append(":");
|
||||
if(hr > 0) {
|
||||
return String.format(DURATION_TIME_FORMAT_WITH_HOURS, hr, min, sec);
|
||||
} else {
|
||||
return String.format(DURATION_TIME_FORMAT_NO_HOURS, min, sec);
|
||||
}
|
||||
// Minutes
|
||||
if (min >= 0) {
|
||||
if (min < 9 && hr > 0) {
|
||||
builder.append("0");
|
||||
}
|
||||
builder.append(min)
|
||||
.append(":");
|
||||
}
|
||||
// Seconds
|
||||
if (sec < 10) {
|
||||
builder.append("0");
|
||||
}
|
||||
builder.append(sec);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String formatSeconds(int seconds) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jellyfin.androidtv.model.LogonCredentials;
|
||||
import org.jellyfin.androidtv.startup.SelectServerActivity;
|
||||
import org.jellyfin.androidtv.startup.SelectUserActivity;
|
||||
import org.jellyfin.androidtv.util.DelayedMessage;
|
||||
import org.jellyfin.androidtv.ui.shared.KeyboardFocusChangeListener;
|
||||
import org.jellyfin.androidtv.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,6 +40,7 @@ public class AuthenticationHelper {
|
||||
final EditText address = new EditText(activity);
|
||||
address.setHint(activity.getString(R.string.lbl_ip_hint));
|
||||
address.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||
address.setOnFocusChangeListener(new KeyboardFocusChangeListener());
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.lbl_enter_server_address))
|
||||
.setMessage(activity.getString(R.string.lbl_valid_server_address))
|
||||
@@ -50,8 +52,10 @@ public class AuthenticationHelper {
|
||||
}).setPositiveButton(activity.getString(R.string.lbl_ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
String addressValue = address.getText().toString();
|
||||
TvApp.getApplication().getLogger().Debug("Entered address: " + addressValue);
|
||||
signInToServer(TvApp.getApplication().getConnectionManager(), addressValue, activity);
|
||||
TvApp.getApplication().getLogger().Debug("Entered address: %s", addressValue);
|
||||
if (!addressValue.isEmpty()) {
|
||||
signInToServer(TvApp.getApplication().getConnectionManager(), addressValue, activity);
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
@@ -59,6 +63,7 @@ public class AuthenticationHelper {
|
||||
public static void enterManualUser(final Activity activity) {
|
||||
final EditText userName = new EditText(activity);
|
||||
userName.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
userName.setOnFocusChangeListener(new KeyboardFocusChangeListener());
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.lbl_enter_user_name))
|
||||
.setView(userName)
|
||||
@@ -69,9 +74,10 @@ public class AuthenticationHelper {
|
||||
}).setPositiveButton(activity.getString(R.string.lbl_ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
String userValue = userName.getText().toString();
|
||||
TvApp.getApplication().getLogger().Debug("Entered user: " + userValue);
|
||||
TvApp.getApplication().getLogger().Debug("Entered user: %s", userValue);
|
||||
final EditText userPw = new EditText(activity);
|
||||
userPw.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
userPw.setOnFocusChangeListener(new KeyboardFocusChangeListener());
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.lbl_enter_user_pw))
|
||||
.setView(userPw)
|
||||
@@ -118,7 +124,7 @@ public class AuthenticationHelper {
|
||||
activity.startActivity(userIntent);
|
||||
break;
|
||||
default:
|
||||
TvApp.getApplication().getLogger().Error("Unexpected response " + serverResult.getState() + " trying to sign in to specific server " + address);
|
||||
TvApp.getApplication().getLogger().Error("Unexpected response %s trying to sign in to specific server %s", serverResult.getState().toString(), address);
|
||||
Utils.showToast(activity, activity.getString(R.string.msg_error_connecting_server));
|
||||
}
|
||||
}
|
||||
@@ -152,7 +158,7 @@ public class AuthenticationHelper {
|
||||
@Override
|
||||
public void onResponse(AuthenticationResult authenticationResult) {
|
||||
TvApp application = TvApp.getApplication();
|
||||
application.getLogger().Debug("Signed in as " + authenticationResult.getUser().getName());
|
||||
application.getLogger().Debug("Signed in as %s", authenticationResult.getUser().getName());
|
||||
application.setCurrentUser(authenticationResult.getUser());
|
||||
if (directEntryItemId == null) {
|
||||
Intent intent = new Intent(activity, MainActivity.class);
|
||||
@@ -213,7 +219,7 @@ public class AuthenticationHelper {
|
||||
Utils.showToast(activity, R.string.msg_error_server_unavailable);
|
||||
break;
|
||||
case ServerSignIn:
|
||||
logger.Debug("Sign in with server " + response.getServers().get(0).getName() + " total: " + response.getServers().size());
|
||||
logger.Debug("Sign in with server %s total: %d", response.getServers().get(0).getName(), response.getServers().size());
|
||||
signInToServer(connectionManager, response.getServers().get(0).getAddress(), activity);
|
||||
break;
|
||||
case SignedIn:
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.jellyfin.apiclient.model.dto.ChapterInfoDto;
|
||||
import org.jellyfin.apiclient.model.dto.ImageOptions;
|
||||
import org.jellyfin.apiclient.model.entities.ImageType;
|
||||
import org.jellyfin.apiclient.model.entities.LocationType;
|
||||
import org.jellyfin.apiclient.model.entities.PersonType;
|
||||
import org.jellyfin.apiclient.model.library.PlayAccess;
|
||||
import org.jellyfin.apiclient.model.livetv.SeriesTimerInfoDto;
|
||||
|
||||
@@ -98,10 +99,10 @@ public class BaseItemUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static BaseItemPerson getFirstPerson(BaseItemDto item, String type) {
|
||||
public static BaseItemPerson getFirstPerson(BaseItemDto item, PersonType searchedType) {
|
||||
if (item.getPeople() != null && item.getPeople().length > 0) {
|
||||
for (BaseItemPerson person : item.getPeople()) {
|
||||
if (type.equals(person.getType())) {
|
||||
if (person.getPersonType() == searchedType) {
|
||||
return person;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ public class PlaybackHelper {
|
||||
public void onResponse(ItemsResult response) {
|
||||
if (response.getTotalRecordCount() > 0){
|
||||
Collections.addAll(items, response.getItems());
|
||||
TvApp.getApplication().getLogger().Info(response.getTotalRecordCount() + " intro items added for playback.");
|
||||
TvApp.getApplication().getLogger().Info("%d intro items added for playback.", response.getTotalRecordCount());
|
||||
TvApp.getApplication().setPlayingIntros(true);
|
||||
} else {
|
||||
TvApp.getApplication().setPlayingIntros(false);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ReportingHelper {
|
||||
startInfo.setItemId(item.getId());
|
||||
startInfo.setPositionTicks(pos);
|
||||
TvApp.getApplication().getPlaybackManager().reportPlaybackStart(startInfo, false, TvApp.getApplication().getApiClient(), new EmptyResponse());
|
||||
TvApp.getApplication().getLogger().Info("Playback of " + item.getName() + " started.");
|
||||
TvApp.getApplication().getLogger().Info("Playback of %s started.", item.getName());
|
||||
}
|
||||
|
||||
public static void reportProgress(BaseItemDto item, StreamInfo currentStreamInfo, Long position, boolean isPaused) {
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
android:title="@string/pref_enable_acra"
|
||||
android:summaryOn="@string/pref_acra_enabled"
|
||||
android:summaryOff="@string/pref_acra_disabled"
|
||||
android:defaultValue="false"/>
|
||||
android:defaultValue="true"/>
|
||||
<CheckBoxPreference android:key="acra.alwaysaccept"
|
||||
android:title="@string/pref_acra_alwaysaccept"
|
||||
android:summaryOn="@string/pref_acra_alwaysaccept_enabled"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.jellyfin.androidtv.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TimeUtilsTest {
|
||||
|
||||
@Test
|
||||
@@ -28,9 +29,18 @@ public class TimeUtilsTest {
|
||||
|
||||
@Test
|
||||
public void formatMillis() {
|
||||
assertEquals("0:00", TimeUtils.formatMillis(0));
|
||||
assertEquals("0:13", TimeUtils.formatMillis(13000));
|
||||
assertEquals("5:00", TimeUtils.formatMillis(300000));
|
||||
assertEquals("9:01", TimeUtils.formatMillis(541000));
|
||||
assertEquals("9:59", TimeUtils.formatMillis(599000));
|
||||
assertEquals("26:00", TimeUtils.formatMillis(1560000));
|
||||
assertEquals("26:01", TimeUtils.formatMillis(1561000));
|
||||
assertEquals("26:43", TimeUtils.formatMillis(1603000));
|
||||
assertEquals("1:00:00", TimeUtils.formatMillis(3600000));
|
||||
assertEquals("1:01:01", TimeUtils.formatMillis(3661000));
|
||||
assertEquals("1:09:15", TimeUtils.formatMillis(4155000));
|
||||
assertEquals("1:16:03", TimeUtils.formatMillis(4563489));
|
||||
assertEquals("12:00:00", TimeUtils.formatMillis(43200000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
build.gradle
27
build.gradle
@@ -1,27 +0,0 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.61'
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
maven {
|
||||
url 'https://dl.bintray.com/videolan/Android'
|
||||
}
|
||||
}
|
||||
}
|
||||
22
build.gradle.kts
Normal file
22
build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:3.5.3")
|
||||
classpath(kotlin("gradle-plugin", "1.3.61"))
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven("https://jitpack.io")
|
||||
maven("https://dl.bintray.com/videolan/Android")
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ bash gradlew ${RFLAG}
|
||||
|
||||
# Move the artifacts out
|
||||
mkdir -p ${ARTIFACT_DIR}/apk
|
||||
mmv "${SOURCE_DIR}/app/build/outputs/apk/*/app-*.apk" "${ARTIFACT_DIR}/apk/jellyfin-androidtv_#2.apk"
|
||||
mmv "${SOURCE_DIR}/app/build/outputs/apk/*/jellyfin-androidtv_*.apk" "${ARTIFACT_DIR}/apk/"
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Gradle
|
||||
org.gradle.jvmargs=-Xmx768m
|
||||
|
||||
# Kotlin compiler
|
||||
kotlin.incremental=true
|
||||
|
||||
# Android
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 20 21:31:53 CET 2019
|
||||
#Sun Feb 09 19:36:45 CET 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
|
||||
53
gradlew
vendored
53
gradlew
vendored
@@ -1,13 +1,27 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
set -o errexit
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -30,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@@ -111,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
@@ -140,19 +154,19 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -161,14 +175,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
include ':app'
|
||||
|
||||
def apiclientLocation = '../jellyfin-apiclient-java'
|
||||
|
||||
if (new File(apiclientLocation).exists()) {
|
||||
includeBuild(apiclientLocation) {
|
||||
dependencySubstitution {
|
||||
substitute module('com.github.jellyfin.jellyfin-apiclient-java:library') with project(':library')
|
||||
}
|
||||
}
|
||||
}
|
||||
21
settings.gradle.kts
Normal file
21
settings.gradle.kts
Normal file
@@ -0,0 +1,21 @@
|
||||
include(":app")
|
||||
|
||||
// Load properties from local.properties
|
||||
val properties = java.util.Properties().apply {
|
||||
val location = file("local.properties")
|
||||
if (location.exists())
|
||||
load(location.inputStream())
|
||||
}
|
||||
|
||||
// Get value for dependency substitution
|
||||
val enableDependencySubstitution = properties.getProperty("enable.dependency.substitution", "true").equals("true", true)
|
||||
|
||||
// Replace apiclient dependency with local version
|
||||
val apiclientLocation = "../jellyfin-apiclient-java"
|
||||
if (File(apiclientLocation).exists() && enableDependencySubstitution) {
|
||||
includeBuild(apiclientLocation) {
|
||||
dependencySubstitution {
|
||||
substitute(module("com.github.jellyfin.jellyfin-apiclient-java:android")).with(project(":android"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user