Compare commits

...

60 Commits

Author SHA1 Message Date
Niels van Velzen
ab9dea63a0 Merge pull request #542 from thornbill/eleven-four
Release 0.11.4
2020-08-11 20:41:31 +02:00
Bill Thornton
178fe800fc Update output filename to published format 2020-08-11 14:15:01 -04:00
Bill Thornton
dd389437c2 Fix DeleteFiles task inputs 2020-08-11 13:06:12 -04:00
Bill Thornton
a55a2b0f05 Add publish job 2020-08-11 13:06:06 -04:00
Bill Thornton
cb3d57ce55 Use separate azure jobs for tests and build 2020-08-11 13:06:00 -04:00
Bill Thornton
44d26dc889 Fix test reporting 2020-08-11 13:05:53 -04:00
Bill Thornton
680b76080c Update Azure config for publishing 2020-08-11 13:05:46 -04:00
Bill Thornton
c7c52096b3 Bump version number 2020-08-11 13:00:31 -04:00
Victor Nidens
7de78925d2 fix #505 Fix time format for current and resume time 2020-08-11 12:54:22 -04:00
Sacha Korban
8d9ac2e457 Fix crash upon loading resumable episodes without runtime field 2020-08-11 12:53:07 -04:00
Froghut
57f8d9672b stop progress report if playback was already stopped 2020-08-11 11:01:41 -04:00
Niels van Velzen
a412c3ecb6 Fix NPE when finished playing media 2020-08-11 11:00:51 -04:00
Niels van Velzen
479a284745 Update apiclient version 2020-08-10 16:15:39 -04:00
Niels van Velzen
d0d57a8e1d Update apiclient to version 0.6.2 2020-08-10 16:14:57 -04:00
Niels van Velzen
d3aa064904 Add the "v" prefix 2020-08-10 16:13:45 -04:00
Niels van Velzen
29f6c6cb4e Pin the apiclient version to 0.6.0 2020-08-10 16:13:37 -04:00
Niels van Velzen
cecb752f34 Remove dependency on apiclient library 2020-08-10 16:07:57 -04:00
Vardex
4dd57774e8 Set higher max static bitrate 2020-08-10 16:02:45 -04:00
Niels van Velzen
f7dafcb30c Merge pull request #428 from thornbill/release-0.11.3
Release 0.11.3
2020-04-13 19:51:57 +02:00
Bill Thornton
6c75a1e3aa Update CI triggers 2020-04-13 13:40:35 -04:00
Bill Thornton
539cfd7e89 Bump okhttp version 2020-04-13 13:35:23 -04:00
Bill Thornton
d26751a2b1 Update app version to 0.11.3 2020-04-11 01:13:58 -04:00
Bill Thornton
5f9362f540 Add note on why okhttp is needed 2020-04-11 01:12:50 -04:00
Bill Thornton
b4bc28ac57 Revert "Remove (unused) okhttp dependency"
This reverts commit 1a4f9d29d3.
2020-04-11 01:12:29 -04:00
Niels van Velzen
d33e6da226 Merge pull request #393 from thornbill/bump-version-0.11.2
Update app version to 0.11.2
2020-03-10 16:06:26 +01:00
Niels van Velzen
32ed6c2fd8 Merge pull request #392 from thornbill/bump-apiclient
Update apiclient version to v0.6.0
2020-03-10 15:57:27 +01:00
Bill Thornton
3f16234162 Update app version to 0.11.2 2020-03-10 10:43:59 -04:00
Bill Thornton
92d702301b Update apiclient version to v0.6.0 2020-03-10 10:38:41 -04:00
Bill Thornton
8693c84c54 Merge pull request #389 from thornbill/release-me-maybe
v0.11.2 changes
2020-03-10 10:34:45 -04:00
Niels van Velzen
b1a650e696 Fix missing super call 2020-03-09 17:20:14 -04:00
Niels van Velzen
5f6a17383c Revert "Remove androidx.appcompat dependency"
This reverts commit 11e96f1d
2020-03-09 17:20:06 -04:00
Bill Thornton
3bd42863e2 Update libvlc 2020-03-09 17:03:02 -04:00
Bill Thornton
b3c5d29726 Fix shuffle all option for albums 2020-03-09 16:49:05 -04:00
Bill Thornton
b145ccd025 Fix crash when selecting a music artist in search results 2020-03-09 16:48:48 -04:00
Niels van Velzen
f942eeade4 Upgrade Exoplayer to version 2.11.3 2020-03-09 16:12:04 -04:00
Bill Thornton
067cd68a33 Add ActivityLifecycleCallbacks implementation to check for null current user 2020-03-09 16:11:34 -04:00
Andreas
c6af538420 Refactor PersonType into a proper enum 2020-03-09 16:05:35 -04:00
Niels van Velzen
01f9b7cf52 Make enableDependencySubstitution configurable (enabled by default) 2020-03-09 16:00:24 -04:00
Niels van Velzen
3e82c9d088 Add back mockito 2020-03-09 16:00:17 -04:00
Niels van Velzen
ff1f9175bf Remove androidx.appcompat dependency 2020-03-09 16:00:10 -04:00
Niels van Velzen
805fc178ea Remove guava dependency 2020-03-09 16:00:03 -04:00
Niels van Velzen
296dbc0114 Update android gradle build tools version 2020-03-09 15:59:57 -04:00
Niels van Velzen
1d28707cf7 Update androidx dependencies 2020-03-09 15:59:49 -04:00
Niels van Velzen
438089d145 Remove (unused) okhttp dependency 2020-03-09 15:59:43 -04:00
Niels van Velzen
a8c0b80bb8 Organize dependencies 2020-03-09 15:59:35 -04:00
Niels van Velzen
ec6fe7795a Convert Gradle files to Kotlin 2020-03-09 15:57:40 -04:00
Niels van Velzen
717c87f8b7 Update Gradle and Gradle wrapper to latest version 2020-03-09 14:42:38 -04:00
Andreas
6820bd0722 Fix hopefully all the incorrect usages of the ILogger interface 2020-03-09 14:42:19 -04:00
Andreas
997de857e2 Fix broken logger call 2020-03-09 14:42:10 -04:00
Bill Thornton
a67c486385 Merge pull request #311 from jellyfin/v0.11.1
v0.11.1
2020-02-06 23:40:00 -05:00
Bill Thornton
8aa0c5a18e Disable all subtitle rendering in exoplayer 2020-02-06 23:23:20 -05:00
Florianisme
4c5eab8d62 Fix wrong last position when media player is closed and progress still reported
(cherry picked from commit 8cd11c888c)
2020-02-06 14:58:43 +01:00
Bill Thornton
5e016b38b0 Fix crash on empty server address 2020-02-06 01:09:41 -05:00
Niels van Velzen
5e4e135239 Fix default home sections not used when the user partially changed them 2020-02-06 00:08:45 -05:00
Andreas
8c1d6c30de Fix displaying ACRA as enabled by default in preferences 2020-02-06 00:08:36 -05:00
Niels van Velzen
ec83ec0540 Update version to 0.11.1 2020-02-06 00:06:50 -05:00
Bill Thornton
32dd0ae3ce Merge pull request #289 from nielsvanvelzen/jitpack-finally-passed
Set apiclient version to 0.5.0
2020-02-04 08:22:52 -05:00
Niels van Velzen
e258ae8113 Set apiclient version to 0.5.0 2020-02-04 10:05:01 +01:00
Bill Thornton
c11d26c887 Merge remote-tracking branch 'upstream/master' into release 2020-02-03 17:06:58 -05:00
Bill Thornton
afa1f918ee Set fixed apiclient version 2019-12-11 00:06:38 -05:00
53 changed files with 596 additions and 312 deletions

View File

@@ -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 .'

View File

@@ -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
View 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 = 909
versionName = "0.11.4"
}
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.2.5")
// 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")
}

View File

@@ -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);
}
});
}

View File

@@ -0,0 +1,59 @@
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 -> 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) {
}
}

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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());
}
});
}

View File

@@ -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);

View File

@@ -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));

View File

@@ -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

View File

@@ -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();

View File

@@ -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()

View File

@@ -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

View File

@@ -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();

View File

@@ -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) {
@@ -304,7 +303,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 +353,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 +592,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 +611,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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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;
@@ -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,14 @@ 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 +181,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 +274,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 +298,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 +369,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 +408,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 +429,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);
}
}
}
@@ -434,7 +454,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,7 +499,7 @@ 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");
@@ -627,7 +647,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();
}

View File

@@ -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);
}
}
});

View File

@@ -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);
}

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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();

View File

@@ -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()) {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -33,7 +33,7 @@ public class ProfileHelper {
profile.setName("Android");
profile.setMaxStreamingBitrate(20000000);
profile.setMaxStaticBitrate(30000000);
profile.setMaxStaticBitrate(100000000);
List<TranscodingProfile> transcodingProfiles = new ArrayList<>();

View File

@@ -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:

View File

@@ -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) {

View File

@@ -50,8 +50,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();
}
@@ -69,7 +71,7 @@ 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);
new AlertDialog.Builder(activity)
@@ -118,7 +120,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 +154,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 +215,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:

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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));
}
}
}

View File

@@ -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
View 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")
}
}

View File

@@ -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/"

View File

@@ -1,4 +1,9 @@
# Gradle
org.gradle.jvmargs=-Xmx768m
# Kotlin compiler
kotlin.incremental=true
# Android
android.enableJetifier=true
android.useAndroidX=true

Binary file not shown.

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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
View 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"))
}
}
}