diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index cd7d7952..33333af2 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -290,6 +290,7 @@ class VideoDetailView : ConstraintLayout { private var _commentsCount = 0; private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null; private var _slideUpOverlay: SlideUpMenuOverlay? = null; + private var _autoplayVideo: IPlatformVideo? = null //Events val onMinimize = Event0(); @@ -720,6 +721,17 @@ class VideoDetailView : ConstraintLayout { fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); }; + StatePlayer.instance.autoplayChanged.subscribe(this) { + if (it) { + val url = _url + val autoPlayVideo = _autoplayVideo + if (url != null && autoPlayVideo == null) { + _taskLoadRecommendations.cancel() + _taskLoadRecommendations.run(url) + } + } + } + _layoutResume.setOnClickListener { handleSeek(_historicalPosition * 1000); @@ -1006,6 +1018,7 @@ class VideoDetailView : ConstraintLayout { _container_content_queue.cleanup(); _container_content_description.cleanup(); _container_content_support.cleanup(); + StatePlayer.instance.autoplayChanged.remove(this) StateCasting.instance.onActiveDevicePlayChanged.remove(this); StateCasting.instance.onActiveDeviceTimeChanged.remove(this); StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); @@ -1102,6 +1115,8 @@ class VideoDetailView : ConstraintLayout { this.video = null; cleanupPlaybackTracker(); _searchVideo = video; + _autoplayVideo = null + Logger.i(TAG, "Autoplay video cleared (setVideoOverview)") _videoResumePositionMilliseconds = resumeSeconds * 1000; setLastPositionMilliseconds(_videoResumePositionMilliseconds, false); _addCommentView.setContext(null, null); @@ -1191,6 +1206,8 @@ class VideoDetailView : ConstraintLayout { Logger.i(TAG, "setVideoDetails (${videoDetail.name})") _didTriggerDatasourceErrroCount = 0; _didTriggerDatasourceError = false; + _autoplayVideo = null + Logger.i(TAG, "Autoplay video cleared (setVideoDetails)") if(newVideo && this.video?.url == videoDetail.url) return; @@ -1511,6 +1528,11 @@ class VideoDetailView : ConstraintLayout { _layoutRating.visibility = View.VISIBLE _layoutChangeBottomSection.visibility = View.VISIBLE } + + if (StatePlayer.instance.autoplay) { + _taskLoadRecommendations.cancel() + _taskLoadRecommendations.run(videoDetail.url) + } } fun loadLiveChat(video: IPlatformVideoDetails) { _liveChat?.stop(); @@ -1779,6 +1801,14 @@ class VideoDetailView : ConstraintLayout { fun nextVideo(forceLoop: Boolean = false, withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false): Boolean { Logger.i(TAG, "nextVideo") var next = StatePlayer.instance.nextQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9, bypassVideoLoop); + val autoplayVideo = _autoplayVideo + if (next == null && autoplayVideo != null && StatePlayer.instance.autoplay) { + Logger.i(TAG, "Found autoplay video!") + StatePlayer.instance.setAutoplayed(autoplayVideo.url) + next = autoplayVideo + } + _autoplayVideo = null + Logger.i(TAG, "Autoplay video cleared (nextVideo)") if(next == null && forceLoop) next = StatePlayer.instance.restartQueue(); if(next != null) { @@ -2347,43 +2377,49 @@ class VideoDetailView : ConstraintLayout { } } - private fun setRecommendations(pager: IPager?, message: String? = null) { - _layoutRecommended.removeAllViews() - if (pager == null) { - _layoutRecommended.addView(TextView(context).apply { - layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { - setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources)) - } - textAlignment = TEXT_ALIGNMENT_CENTER - textSize = 14.0f - text = message - }) - return + private fun setRecommendations(results: List?, message: String? = null) { + if (results != null && StatePlayer.instance.autoplay) { + _autoplayVideo = results.firstOrNull { !StatePlayer.instance.wasAutoplayed(it.url) } + Logger.i(TAG, "Autoplay video set (url = ${_autoplayVideo?.url})") } - val results = pager.getResults().filter { it is IPlatformVideo } - for (result in results) { - _layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply { - layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) - bind(result) - - hideAddTo() - - onVideoClicked.subscribe { video, _ -> - fragment.navigate(video).maximizeVideoDetail() - } - - onChannelClicked.subscribe { - fragment.navigate(it) - } - - onAddToWatchLaterClicked.subscribe(this) { - if(it is IPlatformVideo) { - StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it)); - UIDialogs.toast("Added to watch later\n[${it.name}]"); + if (_tabIndex == 2) { + _layoutRecommended.removeAllViews() + if (results == null) { + _layoutRecommended.addView(TextView(context).apply { + layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources)) } - } - }) + textAlignment = TEXT_ALIGNMENT_CENTER + textSize = 14.0f + text = message + }) + return + } + + for (result in results) { + _layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply { + layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + bind(result) + + hideAddTo() + + onVideoClicked.subscribe { video, _ -> + fragment.navigate(video).maximizeVideoDetail() + } + + onChannelClicked.subscribe { + fragment.navigate(it) + } + + onAddToWatchLaterClicked.subscribe(this) { + if(it is IPlatformVideo) { + StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it)); + UIDialogs.toast("Added to watch later\n[${it.name}]"); + } + } + }) + } } } @@ -2732,7 +2768,7 @@ class VideoDetailView : ConstraintLayout { } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); private val _taskLoadRecommendations = TaskHandler?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) }) - .success { setRecommendations(it, "No recommendations found") } + .success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") } .exception { setRecommendations(null, it.message) Logger.w(TAG, "Failed to load recommendations.", it); diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt index bcdd122d..8dde0b66 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -45,6 +45,33 @@ class StatePlayer { onRotationLockChanged.emit(value) } val onRotationLockChanged = Event1() + var autoplay: Boolean = false + get() = field + set(value) { + if (field != value) + _autoplayed.clear() + field = value + autoplayChanged.emit(value) + } + private val _autoplayed = hashSetOf() + fun wasAutoplayed(url: String?): Boolean { + if (url == null) { + return false + } + synchronized(_autoplayed) { + return _autoplayed.contains(url) + } + } + fun setAutoplayed(url: String?) { + if (url == null) { + return + } + synchronized(_autoplayed) { + _autoplayed.add(url) + } + } + + val autoplayChanged = Event1() var loopVideo : Boolean = false; val isPlaying: Boolean get() = _exoplayer?.player?.playWhenReady ?: false; @@ -138,6 +165,12 @@ class StatePlayer { } } + fun isUrlInQueue(url : String) : Boolean { + synchronized(_queue) { + return _queue.any { it.url == url }; + } + } + fun getQueueType() : String { return _queueType; } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 26da95f1..3f465269 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -18,6 +18,7 @@ import android.widget.ImageButton import android.widget.TextView import androidx.annotation.OptIn import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat import androidx.core.view.setMargins import androidx.media3.common.C import androidx.media3.common.PlaybackParameters @@ -74,6 +75,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { //Custom buttons private val _control_fullscreen: ImageButton; + private val _control_autoplay: ImageButton; private val _control_videosettings: ImageButton; private val _control_minimize: ImageButton; private val _control_rotate_lock: ImageButton; @@ -92,6 +94,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { private val _control_videosettings_fullscreen: ImageButton; private val _control_minimize_fullscreen: ImageButton; private val _control_rotate_lock_fullscreen: ImageButton; + private val _control_autoplay_fullscreen: ImageButton; private val _control_loop_fullscreen: ImageButton; private val _control_cast_fullscreen: ImageButton; private val _control_play_fullscreen: ImageButton; @@ -149,6 +152,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { videoControls = findViewById(R.id.video_player_controller); _control_fullscreen = videoControls.findViewById(R.id.button_fullscreen); + _control_autoplay = videoControls.findViewById(R.id.button_autoplay); _control_videosettings = videoControls.findViewById(R.id.button_settings); _control_minimize = videoControls.findViewById(R.id.button_minimize); _control_rotate_lock = videoControls.findViewById(R.id.button_rotate_lock); @@ -164,6 +168,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _control_duration = videoControls.findViewById(R.id.text_duration); _videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen); + _control_autoplay_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_autoplay); _control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_fullscreen); _control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_minimize); _control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.button_settings); @@ -386,6 +391,18 @@ class FutoVideoPlayer : FutoVideoPlayerBase { UIDialogs.showCastingDialog(context); }; + _control_autoplay.setOnClickListener { + StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay; + updateAutoplayButton() + } + updateAutoplayButton() + + _control_autoplay_fullscreen.setOnClickListener { + StatePlayer.instance.autoplay = !StatePlayer.instance.autoplay; + updateAutoplayButton() + } + updateAutoplayButton() + val progressUpdateListener = { position: Long, bufferedPosition: Long -> val currentTime = position.formatDuration() val currentDuration = duration.formatDuration() @@ -433,6 +450,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } } + private fun updateAutoplayButton() { + _control_autoplay.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white)) + _control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white)) + } + private fun setSystemBrightness(brightness: Float) { Log.i(TAG, "setSystemBrightness $brightness") if (android.provider.Settings.System.canWrite(context)) { diff --git a/app/src/main/res/layout/video_player_ui.xml b/app/src/main/res/layout/video_player_ui.xml index cf352edd..01d15752 100644 --- a/app/src/main/res/layout/video_player_ui.xml +++ b/app/src/main/res/layout/video_player_ui.xml @@ -133,6 +133,20 @@ android:scaleType="fitCenter" android:layout_marginBottom="18dp" /> + + + +