Skip to content

Commit

Permalink
feat(VideoManager): add "Continue watching" dialog
Browse files Browse the repository at this point in the history
Adding a WatchTracker singleton to track watch time and watched episodes. It resets counts on user interaction
  • Loading branch information
kinhelm committed Jan 7, 2025
1 parent 63d30eb commit 68a4669
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 5 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -165,6 +166,7 @@ class MainActivity : FragmentActivity() {
onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event)

override fun onUserInteraction() {
WatchTracker.onUserInteraction()
super.onUserInteraction()

screensaverViewModel.notifyInteraction(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1168,6 +1169,7 @@ public void onError() {

@Override
public void onCompletion() {
WatchTracker.INSTANCE.onEpisodeWatched();
Timber.d("On Completion fired");
itemComplete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -162,6 +163,8 @@ public void onTracksChanged(Tracks tracks) {
Timber.d("Tracks changed");
}
});

WatchTracker.INSTANCE.startWatchTime(activity, this);
}

public void subscribe(@NonNull PlaybackControllerNotifiable notifier) {
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
120 changes: 120 additions & 0 deletions app/src/main/java/org/jellyfin/androidtv/util/WatchTracker.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
5 changes: 4 additions & 1 deletion app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@
<string name="item_deleted">%1$s gelöscht</string>
<string name="pref_screensaver">Bildschirmschoner</string>
<string name="pref_screensaver_inapp_enabled">In-App-Bildschirmschoner verwenden</string>
<string name="still_watching_title">Schaust du noch?</string>
<string name="continue_watching_message">Möchtest du weiter schauen?</string>
<string name="no_button_text_with_time">Nein (%1$d)</string>
<plurals name="seconds">
<item quantity="one">%1$s Sekunde</item>
<item quantity="other">%1$s Sekunden</item>
Expand Down Expand Up @@ -566,4 +569,4 @@
<string name="skip_forward_length">Sprungweite vorwärts</string>
<string name="runtime_hours_minutes">%1$dh %2$dm</string>
<string name="runtime_minutes">%1$dm</string>
</resources>
</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values-en-rGB/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,9 @@
<string name="item_delete_confirm_title">Delete item</string>
<string name="lbl_latest_in">Recently added in %1$s</string>
<string name="pref_screensaver_inapp_timeout">Start screensaver after</string>
<string name="still_watching_title">Are you still watching?</string>
<string name="continue_watching_message">Do you want to continue watching?</string>
<string name="no_button_text_with_time">No (%1$d)</string>
<plurals name="minutes">
<item quantity="one">%1$s minute</item>
<item quantity="other">%1$s minutes</item>
Expand Down Expand Up @@ -564,4 +567,4 @@
<string name="segment_action_ask_to_skip">Ask to skip</string>
<string name="skip_forward_length">Skip forward length</string>
<string name="preference_enable_trickplay">Enable trickplay in video player</string>
</resources>
</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@
<string name="pref_screensaver_inapp_timeout">Lancer l\'économiseur d\'écran après</string>
<string name="pref_screensaver">Économiseur d\'écran</string>
<string name="not_set">Non défini</string>
<string name="still_watching_title">Regardez-vous toujours ?</string>
<string name="continue_watching_message">Voulez-vous continuer à regarder ?</string>
<string name="no_button_text_with_time">Non (%1$d)</string>
<plurals name="seconds">
<item quantity="one">%1$s seconde</item>
<item quantity="many">%1$s secondes</item>
Expand Down Expand Up @@ -576,4 +579,4 @@
<string name="skip_forward_length">Longueur de l\'avance rapide</string>
<string name="runtime_hours_minutes">%1$dh %2$dm</string>
<string name="runtime_minutes">%1$dm</string>
</resources>
</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@
<string name="pref_screensaver_inapp_enabled_description">Pokaż wygaszacz ekranu Jellyfin podczas uruchomienia aplikacji</string>
<string name="pref_screensaver_inapp_enabled">Użyj wbudowanego wygaszacza ekranu</string>
<string name="pref_screensaver_inapp_timeout">Uruchom wygaszacz ekranu po</string>
<string name="still_watching_title">Czy nadal oglądasz?</string>
<string name="continue_watching_message">Czy chcesz kontynuować oglądanie?</string>
<string name="no_button_text_with_time">Nie (%1$d)</string>
<plurals name="seconds">
<item quantity="one">%1$s sekunda</item>
<item quantity="few">%1$s sekundy</item>
Expand Down Expand Up @@ -586,4 +589,4 @@
<string name="skip_forward_length">Długość pomijania do przodu</string>
<string name="runtime_hours_minutes">%1$dh %2$dm</string>
<string name="runtime_minutes">%1$dm</string>
</resources>
</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@
<string name="not_set">Не задано</string>
<string name="pref_screensaver">Заставка</string>
<string name="pref_screensaver_inapp_enabled">Использовать заставку приложения</string>
<string name="still_watching_title">Вы все еще смотрите?</string>
<string name="continue_watching_message">Вы хотите продолжить просмотр?</string>
<string name="no_button_text_with_time">Нет (%1$d)</string>
<plurals name="hours">
<item quantity="one">%1$s час</item>
<item quantity="few">%1$s часа</item>
Expand Down Expand Up @@ -584,4 +587,4 @@
<string name="clear_image_cache">Очистить кэш изображений</string>
<string name="preference_enable_trickplay">Включить trickplay при просмотре видео</string>
<string name="skip_forward_length">Длина перемотки вперед</string>
</resources>
</resources>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,9 @@
<string name="segment_type_unknown">Unknown segments</string>
<string name="skip_forward_length">Skip forward length</string>
<string name="preference_enable_trickplay">Enable trickplay in video player</string>
<string name="still_watching_title">Are you still watching?</string>
<string name="continue_watching_message">Do you want to continue watching?</string>
<string name="no_button_text_with_time">No (%1$d)</string>
<plurals name="seconds">
<item quantity="one">%1$s second</item>
<item quantity="other">%1$s seconds</item>
Expand Down

0 comments on commit 68a4669

Please sign in to comment.