Skip to content

Commit 99ad911

Browse files
committed
Add still watching feature
1 parent e9d69fa commit 99ad911

15 files changed

+398
-41
lines changed

app/src/main/java/org/jellyfin/androidtv/di/AppModule.kt

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
3434
import org.jellyfin.androidtv.ui.playback.nextup.NextUpViewModel
3535
import org.jellyfin.androidtv.ui.playback.segment.MediaSegmentRepository
3636
import org.jellyfin.androidtv.ui.playback.segment.MediaSegmentRepositoryImpl
37+
import org.jellyfin.androidtv.ui.playback.stillwatching.StillWatchingViewModel
3738
import org.jellyfin.androidtv.ui.search.SearchFragmentDelegate
3839
import org.jellyfin.androidtv.ui.search.SearchRepository
3940
import org.jellyfin.androidtv.ui.search.SearchRepositoryImpl
@@ -97,6 +98,7 @@ val appModule = module {
9798
single { DataRefreshService() }
9899
single { PlaybackControllerContainer() }
99100

101+
100102
single<UserRepository> { UserRepositoryImpl() }
101103
single<UserViewsRepository> { UserViewsRepositoryImpl(get()) }
102104
single<NotificationsRepository> { NotificationsRepositoryImpl(get(), get()) }
@@ -110,6 +112,7 @@ val appModule = module {
110112
viewModel { UserLoginViewModel(get(), get(), get(), get(defaultDeviceInfo)) }
111113
viewModel { ServerAddViewModel(get()) }
112114
viewModel { NextUpViewModel(get(), get(), get(), get()) }
115+
viewModel { StillWatchingViewModel() }
113116
viewModel { PictureViewerViewModel(get()) }
114117
viewModel { ScreensaverViewModel(get()) }
115118
viewModel { SearchViewModel(get()) }

app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destinations.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jellyfin.androidtv.ui.navigation
22

33
import androidx.core.os.bundleOf
4-
import kotlinx.serialization.encodeToString
54
import kotlinx.serialization.json.Json
65
import org.jellyfin.androidtv.constant.Extras
76
import org.jellyfin.androidtv.ui.browsing.BrowseGridFragment
@@ -27,6 +26,7 @@ import org.jellyfin.androidtv.ui.playback.CustomPlaybackOverlayFragment
2726
import org.jellyfin.androidtv.ui.playback.ExternalPlayerActivity
2827
import org.jellyfin.androidtv.ui.playback.nextup.NextUpFragment
2928
import org.jellyfin.androidtv.ui.playback.rewrite.PlaybackRewriteFragment
29+
import org.jellyfin.androidtv.ui.playback.stillwatching.StillWatchingFragment
3030
import org.jellyfin.androidtv.ui.preference.PreferencesActivity
3131
import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment
3232
import org.jellyfin.androidtv.ui.preference.screen.UserPreferencesScreen
@@ -147,6 +147,7 @@ object Destinations {
147147

148148
// Playback
149149
val nowPlaying = fragmentDestination<AudioNowPlayingFragment>()
150+
val stillWatching = fragmentDestination<StillWatchingFragment>()
150151

151152
fun pictureViewer(item: UUID, autoPlay: Boolean, albumSortBy: ItemSortBy?, albumSortOrder: SortOrder?) =
152153
fragmentDestination<PictureViewerFragment>(

app/src/main/java/org/jellyfin/androidtv/ui/playback/CustomPlaybackOverlayFragment.java

+40-34
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
138138

139139
private final PlaybackOverlayFragmentHelper helper = new PlaybackOverlayFragmentHelper(this);
140140

141+
private PlaybackController playbackController;
142+
141143
@Override
142144
public void onCreate(Bundle savedInstanceState) {
143145
super.onCreate(savedInstanceState);
@@ -161,6 +163,8 @@ public void onCreate(Bundle savedInstanceState) {
161163

162164
playbackControllerContainer.getValue().setPlaybackController(new PlaybackController(mItemsToPlay, this, mediaPosition));
163165

166+
playbackController = playbackControllerContainer.getValue().getPlaybackController();
167+
164168
// setup fade task
165169
mHideTask = () -> {
166170
if (mIsVisible) {
@@ -218,8 +222,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
218222
return;
219223
}
220224

221-
if (playbackControllerContainer.getValue().getPlaybackController() != null) {
222-
playbackControllerContainer.getValue().getPlaybackController().init(new VideoManager((requireActivity()), view, helper), this);
225+
if (playbackController != null) {
226+
playbackController.init(new VideoManager((requireActivity()), view, helper), this);
223227
}
224228
}
225229

@@ -294,7 +298,7 @@ public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, in
294298
int startPos = getArguments().getInt("Position", 0);
295299

296300
// start playing
297-
playbackControllerContainer.getValue().getPlaybackController().play(startPos);
301+
playbackController.play(startPos);
298302
leanbackOverlayFragment.updatePlayState();
299303

300304
// Set initial skip overlay state
@@ -356,7 +360,7 @@ public void onAnimationRepeat(Animation animation) {
356360
public void onAudioFocusChange(int focusChange) {
357361
switch (focusChange) {
358362
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
359-
playbackControllerContainer.getValue().getPlaybackController().pause();
363+
playbackController.pause();
360364
break;
361365
case AudioManager.AUDIOFOCUS_LOSS:
362366
// We don't do anything here on purpose
@@ -375,7 +379,7 @@ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
375379
if (item instanceof ChapterItemInfoBaseRowItem) {
376380
ChapterItemInfoBaseRowItem rowItem = (ChapterItemInfoBaseRowItem) item;
377381
Long start = rowItem.getChapterInfo().getStartPositionTicks() / 10000;
378-
playbackControllerContainer.getValue().getPlaybackController().seek(start);
382+
playbackController.seek(start);
379383
hidePopupPanel();
380384
} else if (item instanceof BaseItemDto) {
381385
hidePopupPanel();
@@ -393,7 +397,7 @@ public void handleOnBackPressed() {
393397
leanbackOverlayFragment.hideOverlay();
394398

395399
// also close this if live tv
396-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv()) hide();
400+
if (playbackController.isLiveTv()) hide();
397401
} else if (mGuideVisible) {
398402
hideGuide();
399403
} else {
@@ -436,20 +440,23 @@ else if (mSelectedProgramView instanceof GuideChannelHeader)
436440
return false;
437441
}
438442

443+
playbackControllerContainer.getValue().setEpisodeWasInterrupted(true);
444+
playbackControllerContainer.getValue().resetEpisodesPlayedWithoutInterruption();
445+
439446
if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
440-
playbackControllerContainer.getValue().getPlaybackController().play(0);
447+
playbackController.play(0);
441448
return true;
442449
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
443-
playbackControllerContainer.getValue().getPlaybackController().pause();
450+
playbackController.pause();
444451
return true;
445452
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
446-
playbackControllerContainer.getValue().getPlaybackController().playPause();
453+
playbackController.playPause();
447454
return true;
448455
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || keyCode == KeyEvent.KEYCODE_BUTTON_R1 || keyCode == KeyEvent.KEYCODE_BUTTON_R2) {
449-
playbackControllerContainer.getValue().getPlaybackController().fastForward();
456+
playbackController.fastForward();
450457
return true;
451458
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND || keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_L2) {
452-
playbackControllerContainer.getValue().getPlaybackController().rewind();
459+
playbackController.rewind();
453460
return true;
454461
}
455462
}
@@ -485,7 +492,7 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
485492

486493
// Hide with seek
487494
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
488-
playbackControllerContainer.getValue().getPlaybackController().seek(binding.skipOverlay.getTargetPositionMs(), true);
495+
playbackController.seek(binding.skipOverlay.getTargetPositionMs(), true);
489496
leanbackOverlayFragment.setShouldShowOverlay(false);
490497
if (binding != null) binding.skipOverlay.setTargetPositionMs(null);
491498
return true;
@@ -504,15 +511,15 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
504511
leanbackOverlayFragment.hideOverlay();
505512

506513
// also close this if live tv
507-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv()) hide();
514+
if (playbackController.isLiveTv()) hide();
508515
return true;
509516
} else if (mGuideVisible) {
510517
hideGuide();
511518
return true;
512519
}
513520
}
514521

515-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv() && !mPopupPanelVisible && !mGuideVisible && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
522+
if (playbackController.isLiveTv() && !mPopupPanelVisible && !mGuideVisible && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
516523
if (!leanbackOverlayFragment.isControlsOverlayVisible()) {
517524
leanbackOverlayFragment.setShouldShowOverlay(false);
518525
leanbackOverlayFragment.hideOverlay();
@@ -541,7 +548,7 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
541548
}
542549
}
543550

544-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv() && keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BUTTON_Y) {
551+
if (playbackController.isLiveTv() && keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BUTTON_Y) {
545552
showGuide();
546553
return true;
547554
}
@@ -557,7 +564,7 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
557564
// up or down should close panel
558565
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
559566
hidePopupPanel();
560-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv())
567+
if (playbackController.isLiveTv())
561568
hide(); //also close this if live tv
562569
return true;
563570
} else {
@@ -566,22 +573,22 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
566573
}
567574

568575
// Control fast forward and rewind if overlay hidden and not showing live TV
569-
if (!playbackControllerContainer.getValue().getPlaybackController().isLiveTv()) {
576+
if (!playbackController.isLiveTv()) {
570577
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || keyCode == KeyEvent.KEYCODE_BUTTON_R1 || keyCode == KeyEvent.KEYCODE_BUTTON_R2) {
571-
playbackControllerContainer.getValue().getPlaybackController().fastForward();
578+
playbackController.fastForward();
572579
setFadingEnabled(true);
573580
return true;
574581
}
575582

576583
if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND || keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_L2) {
577-
playbackControllerContainer.getValue().getPlaybackController().rewind();
584+
playbackController.rewind();
578585
setFadingEnabled(true);
579586
return true;
580587
}
581588
}
582589

583590
if (!mIsVisible) {
584-
if (!playbackControllerContainer.getValue().getPlaybackController().isLiveTv()) {
591+
if (!playbackController.isLiveTv()) {
585592
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
586593
setFadingEnabled(true);
587594
return true;
@@ -594,10 +601,10 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
594601
}
595602

596603
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
597-
&& playbackControllerContainer.getValue().getPlaybackController().canSeek()) {
604+
&& playbackController.canSeek()) {
598605
// if the player is playing and the overlay is hidden, this will pause
599606
// if the player is paused and then 'back' is pressed to hide the overlay, this will play
600-
playbackControllerContainer.getValue().getPlaybackController().playPause();
607+
playbackController.playPause();
601608
return true;
602609
}
603610
}
@@ -630,12 +637,12 @@ public void switchChannel(UUID id) {
630637
}
631638

632639
public void switchChannel(UUID id, boolean hideGuide) {
633-
if (playbackControllerContainer.getValue().getPlaybackController().getCurrentlyPlayingItem().getId().equals(id)) {
640+
if (playbackController.getCurrentlyPlayingItem().getId().equals(id)) {
634641
// same channel, just dismiss overlay
635642
if (hideGuide)
636643
hideGuide();
637644
} else {
638-
playbackControllerContainer.getValue().getPlaybackController().stop();
645+
playbackController.stop();
639646
if (hideGuide)
640647
hideGuide();
641648

@@ -654,7 +661,6 @@ public void onResume() {
654661
super.onResume();
655662

656663
// Close player when resuming without a valid playback controller
657-
PlaybackController playbackController = playbackControllerContainer.getValue().getPlaybackController();
658664
if (playbackController == null || !playbackController.hasFragment()) {
659665
closePlayer();
660666

@@ -693,9 +699,9 @@ public void onStop() {
693699

694700
// end playback from here if this fragment belongs to the current session.
695701
// if it doesn't, playback has already been stopped elsewhere, and the references to this have been replaced
696-
if (playbackControllerContainer.getValue().getPlaybackController() != null && playbackControllerContainer.getValue().getPlaybackController().getFragment() == this) {
702+
if (playbackController != null && playbackController.getFragment() == this) {
697703
Timber.d("this fragment belongs to the current session, ending it");
698-
playbackControllerContainer.getValue().getPlaybackController().endPlayback();
704+
playbackController.endPlayback();
699705
}
700706

701707
closePlayer();
@@ -736,7 +742,7 @@ public void showGuide() {
736742
hide();
737743
leanbackOverlayFragment.setShouldShowOverlay(false);
738744
leanbackOverlayFragment.hideOverlay();
739-
playbackControllerContainer.getValue().getPlaybackController().mVideoManager.contractVideo(Utils.convertDpToPixel(requireContext(), 300));
745+
playbackController.mVideoManager.contractVideo(Utils.convertDpToPixel(requireContext(), 300));
740746
tvGuideBinding.getRoot().setVisibility(View.VISIBLE);
741747
mGuideVisible = true;
742748
LocalDateTime now = LocalDateTime.now();
@@ -755,7 +761,7 @@ public void showGuide() {
755761

756762
private void hideGuide() {
757763
tvGuideBinding.getRoot().setVisibility(View.GONE);
758-
playbackControllerContainer.getValue().getPlaybackController().mVideoManager.setVideoFullSize(true);
764+
playbackController.mVideoManager.setVideoFullSize(true);
759765
mGuideVisible = false;
760766
binding.skipOverlay.setSkipUiEnabled(!mIsVisible && !mGuideVisible && !mPopupPanelVisible);
761767
}
@@ -825,7 +831,7 @@ protected void onPreExecute() {
825831
Timber.d("*** Display programs pre-execute");
826832
tvGuideBinding.channels.removeAllViews();
827833
tvGuideBinding.programRows.removeAllViews();
828-
mFirstFocusChannelId = playbackControllerContainer.getValue().getPlaybackController().getCurrentlyPlayingItem().getId();
834+
mFirstFocusChannelId = playbackController.getCurrentlyPlayingItem().getId();
829835

830836
if (mCurrentDisplayChannelStartNdx > 0) {
831837
// Show a paging row for channels above
@@ -1134,7 +1140,7 @@ public void showChapterSelector() {
11341140
mHandler.postDelayed(() -> {
11351141
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) return;
11361142

1137-
int ndx = getCurrentChapterIndex(playbackControllerContainer.getValue().getPlaybackController().getCurrentlyPlayingItem(), playbackControllerContainer.getValue().getPlaybackController().getCurrentPosition() * 10000);
1143+
int ndx = getCurrentChapterIndex(playbackController.getCurrentlyPlayingItem(), playbackController.getCurrentPosition() * 10000);
11381144
if (ndx > 0) {
11391145
mPopupRowPresenter.setPosition(ndx);
11401146
}
@@ -1223,7 +1229,7 @@ public void setPlayPauseActionState(final int state) {
12231229
}
12241230

12251231
public void updateDisplay() {
1226-
BaseItemDto current = playbackControllerContainer.getValue().getPlaybackController().getCurrentlyPlayingItem();
1232+
BaseItemDto current = playbackController.getCurrentlyPlayingItem();
12271233
if (current != null && getContext() != null) {
12281234
leanbackOverlayFragment.mediaInfoChanged();
12291235
leanbackOverlayFragment.onFullyInitialized();
@@ -1251,7 +1257,7 @@ public void updateDisplay() {
12511257
binding.itemTitle.setVisibility(View.VISIBLE);
12521258
}
12531259

1254-
if (playbackControllerContainer.getValue().getPlaybackController().isLiveTv()) {
1260+
if (playbackController.isLiveTv()) {
12551261
prepareChannelAdapter();
12561262
} else {
12571263
prepareChapterAdapter();
@@ -1260,7 +1266,7 @@ public void updateDisplay() {
12601266
}
12611267

12621268
private void prepareChapterAdapter() {
1263-
BaseItemDto item = playbackControllerContainer.getValue().getPlaybackController().getCurrentlyPlayingItem();
1269+
BaseItemDto item = playbackController.getCurrentlyPlayingItem();
12641270
List<ChapterInfo> chapters = item.getChapters();
12651271

12661272
if (chapters != null && !chapters.isEmpty()) {

0 commit comments

Comments
 (0)