Added autoplay feature.

This commit is contained in:
Koen J 2024-09-09 14:58:08 +02:00
parent e39d862ef3
commit ec370dd94b
5 changed files with 154 additions and 35 deletions

View file

@ -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<IPlatformContent>?, 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<IPlatformVideo>?, 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<VideoDetailFragment>(video).maximizeVideoDetail()
}
onChannelClicked.subscribe {
fragment.navigate<ChannelFragment>(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<VideoDetailFragment>(video).maximizeVideoDetail()
}
onChannelClicked.subscribe {
fragment.navigate<ChannelFragment>(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<String, IPager<IPlatformContent>?>(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<Throwable> {
setRecommendations(null, it.message)
Logger.w(TAG, "Failed to load recommendations.", it);

View file

@ -45,6 +45,33 @@ class StatePlayer {
onRotationLockChanged.emit(value)
}
val onRotationLockChanged = Event1<Boolean>()
var autoplay: Boolean = false
get() = field
set(value) {
if (field != value)
_autoplayed.clear()
field = value
autoplayChanged.emit(value)
}
private val _autoplayed = hashSetOf<String>()
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<Boolean>()
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;
}

View file

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

View file

@ -133,6 +133,20 @@
android:scaleType="fitCenter"
android:layout_marginBottom="18dp" />
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="55dp"
android:layout_height="40dp"
android:clickable="true"
app:srcCompat="@drawable/autoplay_24px"
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
android:paddingStart="5dp"
android:paddingTop="15dp"
android:paddingEnd="5dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/text_position"
android:layout_width="wrap_content"

View file

@ -147,6 +147,20 @@
app:layout_constraintLeft_toRightOf="@id/layout_play_pause"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:id="@+id/button_autoplay"
android:layout_width="55dp"
android:layout_height="40dp"
android:clickable="true"
app:srcCompat="@drawable/autoplay_24px"
app:layout_constraintRight_toLeftOf="@id/button_fullscreen"
app:layout_constraintBottom_toBottomOf="@id/button_fullscreen"
app:layout_constraintTop_toTopOf="@id/button_fullscreen"
android:paddingStart="5dp"
android:paddingTop="15dp"
android:paddingEnd="5dp"
android:scaleType="fitCenter"/>
<ImageButton
android:id="@+id/button_fullscreen"
android:layout_width="55dp"