diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 02aaade313..a7bafe3ff3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,6 +15,7 @@ - [3l0w](https://github.com/3l0w) - [MajMongoose](https://github.com/majmongoose) - [Olaren15](https://github.com/Olaren15) + - [Kinhelm](https://github.com/Kinhelm) # Emby Contributors diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt index cb66204911..70e274c918 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt @@ -27,6 +27,7 @@ import org.jellyfin.androidtv.ui.navigation.NavigationAction import org.jellyfin.androidtv.ui.navigation.NavigationRepository import org.jellyfin.androidtv.ui.screensaver.InAppScreensaver import org.jellyfin.androidtv.ui.startup.StartupActivity +import org.jellyfin.androidtv.util.WatchTracker import org.jellyfin.androidtv.util.applyTheme import org.jellyfin.androidtv.util.isMediaSessionKeyEvent import org.koin.android.ext.android.inject @@ -165,6 +166,7 @@ class MainActivity : FragmentActivity() { onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event) override fun onUserInteraction() { + WatchTracker.onUserInteraction() super.onUserInteraction() screensaverViewModel.notifyInteraction(false) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java index 622d75da05..319a349a58 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java @@ -26,6 +26,7 @@ import org.jellyfin.androidtv.ui.livetv.TvManager; import org.jellyfin.androidtv.util.TimeUtils; import org.jellyfin.androidtv.util.Utils; +import org.jellyfin.androidtv.util.WatchTracker; import org.jellyfin.androidtv.util.apiclient.ReportingHelper; import org.jellyfin.androidtv.util.profile.ExoPlayerProfile; import org.jellyfin.androidtv.util.sdk.compat.JavaCompat; @@ -1168,6 +1169,7 @@ public void onError() { @Override public void onCompletion() { + WatchTracker.INSTANCE.onEpisodeWatched(); Timber.d("On Completion fired"); itemComplete(); } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java index 28e9015264..47b5128c37 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java @@ -44,6 +44,7 @@ import org.jellyfin.androidtv.data.compat.StreamInfo; import org.jellyfin.androidtv.preference.UserPreferences; import org.jellyfin.androidtv.preference.constant.ZoomMode; +import org.jellyfin.androidtv.util.WatchTracker; import org.jellyfin.sdk.api.client.ApiClient; import org.jellyfin.sdk.model.api.MediaStream; import org.jellyfin.sdk.model.api.MediaStreamType; @@ -162,6 +163,8 @@ public void onTracksChanged(Tracks tracks) { Timber.d("Tracks changed"); } }); + + WatchTracker.INSTANCE.startWatchTime(activity, this); } public void subscribe(@NonNull PlaybackControllerNotifiable notifier) { @@ -276,16 +279,19 @@ public void start() { _helper.getFragment().closePlayer(); return; } + WatchTracker.INSTANCE.startWatchTime(this.mActivity, this); mExoPlayer.setPlayWhenReady(true); normalWidth = mExoPlayerView.getLayoutParams().width; normalHeight = mExoPlayerView.getLayoutParams().height; } public void play() { + WatchTracker.INSTANCE.startWatchTime(this.mActivity, this); mExoPlayer.setPlayWhenReady(true); } public void pause() { + WatchTracker.INSTANCE.stopWatchTime(); mExoPlayer.setPlayWhenReady(false); } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/VideoPlayerAdapter.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/VideoPlayerAdapter.java index 41b39d32ef..33fc7cc023 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/VideoPlayerAdapter.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/VideoPlayerAdapter.java @@ -7,6 +7,7 @@ import org.jellyfin.androidtv.ui.playback.CustomPlaybackOverlayFragment; import org.jellyfin.androidtv.ui.playback.PlaybackController; import org.jellyfin.androidtv.util.Utils; +import org.jellyfin.androidtv.util.WatchTracker; import org.jellyfin.androidtv.util.apiclient.StreamHelper; import org.jellyfin.sdk.model.api.ChapterInfo; import org.jellyfin.sdk.model.api.MediaSourceInfo; diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/action/PlayPauseAction.kt b/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/action/PlayPauseAction.kt index c5e23e98c6..896a9642ca 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/action/PlayPauseAction.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/action/PlayPauseAction.kt @@ -3,6 +3,7 @@ package org.jellyfin.androidtv.ui.playback.overlay.action import android.content.Context import androidx.leanback.widget.PlaybackControlsRow import org.jellyfin.androidtv.ui.playback.overlay.VideoPlayerAdapter +import org.jellyfin.androidtv.util.WatchTracker class PlayPauseAction(context: Context) : PlaybackControlsRow.PlayPauseAction(context), AndroidAction { diff --git a/app/src/main/java/org/jellyfin/androidtv/util/WatchTracker.kt b/app/src/main/java/org/jellyfin/androidtv/util/WatchTracker.kt new file mode 100644 index 0000000000..4ed44151d0 --- /dev/null +++ b/app/src/main/java/org/jellyfin/androidtv/util/WatchTracker.kt @@ -0,0 +1,120 @@ +package org.jellyfin.androidtv.util + +import android.app.AlertDialog +import android.content.Context +import android.os.Handler +import android.os.Looper +import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.ui.playback.VideoManager +import java.util.logging.Logger + +interface PromptUserCallback { + fun onResult(result: Boolean) +} + +object WatchTracker { + private var episodeCount = 0 + private var watchTime = 0L + private var lastUpdateTime= 0L + private var lastInteractionTime = System.currentTimeMillis() + private val watchTimeHandler = Handler(Looper.getMainLooper()) + private var isPlaying: Boolean = false + + private class WatchTimeUpdater( + private val context: Context, + private val videoManager: VideoManager + ) : Runnable { + override fun run() { + if (isPlaying) { + val currentTime = System.currentTimeMillis() + watchTime += currentTime - lastUpdateTime + lastUpdateTime = currentTime + checkPrompt(context, videoManager) + watchTimeHandler.postDelayed(this, 1000) + } + } + } + + fun onEpisodeWatched() { + Logger.getLogger(WatchTracker::class.java.name).info("Watcher onEpisodeWatched") + episodeCount++ + } + + fun onUserInteraction() { + Logger.getLogger(WatchTracker::class.java.name).info("Watcher onUserInteraction") + resetWatchTime() + lastInteractionTime = System.currentTimeMillis() + } + + private fun checkPrompt(context: Context, videoManager: VideoManager) { + if (episodeCount == 3 || watchTime >= 90 * 60 * 1000) { + videoManager.pause() + promptUser(context, object : PromptUserCallback { + override fun onResult(result: Boolean) { + if (result) { + videoManager.play() + } else { + context.getActivity()?.finish() + } + } + }) + } + } + + private fun promptUser(context: Context, callback: PromptUserCallback) { + val dialog = AlertDialog.Builder(context) + .setTitle(context.getString(R.string.still_watching_title)) + .setMessage(context.getString(R.string.continue_watching_message)) + .setPositiveButton(R.string.lbl_yes) { dialog, _ -> + dialog.dismiss() + callback.onResult(true) + } + .setNegativeButton(R.string.lbl_no) { dialog, _ -> + dialog.dismiss() + callback.onResult(false) + } + .setCancelable(false) + .create() + + dialog.show() + + val negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE) + val handler = Handler(Looper.getMainLooper()) + val startTime = System.currentTimeMillis() + val countdownTime = 15000L // 15 seconds + + handler.post(object : Runnable { + override fun run() { + val elapsedTime = System.currentTimeMillis() - startTime + val remainingTime = (countdownTime - elapsedTime) / 1000 + if (remainingTime > 0) { + negativeButton.text = context.getString(R.string.no_button_text_with_time, remainingTime) + handler.postDelayed(this, 1000) + } else { + if (dialog.isShowing) { + dialog.dismiss() + callback.onResult(false) + } + } + } + }) + } + + fun startWatchTime(context: Context, videoManager: VideoManager) { + Logger.getLogger(WatchTracker::class.java.name).info("Start tracker") + resetWatchTime() + lastUpdateTime = System.currentTimeMillis() + watchTimeHandler.post(WatchTimeUpdater(context, videoManager)) + } + + fun stopWatchTime() { + isPlaying = false + watchTimeHandler.removeCallbacksAndMessages(null) + } + + private fun resetWatchTime() { + watchTime = 0L + episodeCount = 0 + isPlaying = true + } +} diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0b4224b86f..ca221144f1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -461,6 +461,9 @@ %1$s gelöscht Bildschirmschoner In-App-Bildschirmschoner verwenden + Schaust du noch? + Möchtest du weiter schauen? + Nein (%1$d) %1$s Sekunde %1$s Sekunden @@ -566,4 +569,4 @@ Sprungweite vorwärts %1$dh %2$dm %1$dm - \ No newline at end of file + diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index afe90eb79f..1e773727e3 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -463,6 +463,9 @@ Delete item Recently added in %1$s Start screensaver after + Are you still watching? + Do you want to continue watching? + No (%1$d) %1$s minute %1$s minutes @@ -564,4 +567,4 @@ Ask to skip Skip forward length Enable trickplay in video player - \ No newline at end of file + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 25c0c49735..c3bf9114af 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -467,6 +467,9 @@ Lancer l\'économiseur d\'écran après Économiseur d\'écran Non défini + Regardez-vous toujours ? + Voulez-vous continuer à regarder ? + Non (%1$d) %1$s seconde %1$s secondes @@ -576,4 +579,4 @@ Longueur de l\'avance rapide %1$dh %2$dm %1$dm - \ No newline at end of file + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 14d10e8655..5dd1bf7797 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -464,6 +464,9 @@ Pokaż wygaszacz ekranu Jellyfin podczas uruchomienia aplikacji Użyj wbudowanego wygaszacza ekranu Uruchom wygaszacz ekranu po + Czy nadal oglądasz? + Czy chcesz kontynuować oglądanie? + Nie (%1$d) %1$s sekunda %1$s sekundy @@ -586,4 +589,4 @@ Długość pomijania do przodu %1$dh %2$dm %1$dm - \ No newline at end of file + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a09403f8d9..45fb7ddd89 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -467,6 +467,9 @@ Не задано Заставка Использовать заставку приложения + Вы все еще смотрите? + Вы хотите продолжить просмотр? + Нет (%1$d) %1$s час %1$s часа @@ -584,4 +587,4 @@ Очистить кэш изображений Включить trickplay при просмотре видео Длина перемотки вперед - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84ab1f7be7..9922f3f7b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -526,6 +526,9 @@ Unknown segments Skip forward length Enable trickplay in video player + Are you still watching? + Do you want to continue watching? + No (%1$d) %1$s second %1$s seconds