mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-22 10:19:27 +00:00
Fix Android ANR in SwapSources.
This commit is contained in:
parent
1fb55dca0a
commit
4df227147c
19 changed files with 219 additions and 120 deletions
|
@ -209,7 +209,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
|
||||||
_searchView = searchView
|
_searchView = searchView
|
||||||
updateSearchViewVisibility()
|
updateSearchViewVisibility()
|
||||||
|
|
||||||
_adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar, viewsToPrepend = arrayListOf(searchView)).apply {
|
_adapterResults = PreviewContentListAdapter(lifecycleScope, view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar, viewsToPrepend = arrayListOf(searchView)).apply {
|
||||||
this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit);
|
this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit);
|
||||||
this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit);
|
this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit);
|
||||||
this.onContentClicked.subscribe { content, num ->
|
this.onContentClicked.subscribe { content, num ->
|
||||||
|
|
|
@ -148,7 +148,7 @@ class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
|
||||||
_recyclerResults = view.findViewById(R.id.recycler_videos)
|
_recyclerResults = view.findViewById(R.id.recycler_videos)
|
||||||
|
|
||||||
_adapterResults = PreviewContentListAdapter(
|
_adapterResults = PreviewContentListAdapter(
|
||||||
view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar
|
lifecycleScope, view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar
|
||||||
).apply {
|
).apply {
|
||||||
this.onContentUrlClicked.subscribe(this@ChannelPlaylistsFragment.onContentUrlClicked::emit)
|
this.onContentUrlClicked.subscribe(this@ChannelPlaylistsFragment.onContentUrlClicked::emit)
|
||||||
this.onUrlClicked.subscribe(this@ChannelPlaylistsFragment.onUrlClicked::emit)
|
this.onUrlClicked.subscribe(this@ChannelPlaylistsFragment.onUrlClicked::emit)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
@ -19,6 +20,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.JSWeb
|
import com.futo.platformplayer.api.media.platforms.js.models.JSWeb
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.ShortView.Companion
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateMeta
|
import com.futo.platformplayer.states.StateMeta
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
|
@ -34,6 +36,9 @@ import com.futo.platformplayer.views.adapters.feedtypes.PreviewVideoViewHolder
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||||
import com.futo.platformplayer.withTimestamp
|
import com.futo.platformplayer.withTimestamp
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@ -59,7 +64,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
|
player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
|
||||||
_exoPlayer = player;
|
_exoPlayer = player;
|
||||||
|
|
||||||
return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
|
return PreviewContentListAdapter(fragment.lifecycleScope, context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
|
||||||
attachAdapterEvents(this);
|
attachAdapterEvents(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,8 +251,14 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Is this still necessary?
|
//TODO: Is this still necessary?
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
if(viewHolder.childViewHolder is ContentPreviewViewHolder)
|
if(viewHolder.childViewHolder is ContentPreviewViewHolder)
|
||||||
(recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder)
|
(recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "playPreview failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopVideo() {
|
private fun stopVideo() {
|
||||||
|
|
|
@ -54,6 +54,8 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
|
import com.futo.platformplayer.casting.CastConnectionState
|
||||||
|
import com.futo.platformplayer.casting.StateCasting
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.constructs.Event3
|
import com.futo.platformplayer.constructs.Event3
|
||||||
|
@ -563,7 +565,13 @@ class ShortView : FrameLayout {
|
||||||
var toSet: ISubtitleSource? = subtitleSource
|
var toSet: ISubtitleSource? = subtitleSource
|
||||||
if (_lastSubtitleSource == subtitleSource) toSet = null
|
if (_lastSubtitleSource == subtitleSource) toSet = null
|
||||||
|
|
||||||
player.swapSubtitles(mainFragment.lifecycleScope, toSet)
|
mainFragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
player.swapSubtitles(toSet)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "handleSelectSubtitleTrack failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_lastSubtitleSource = toSet
|
_lastSubtitleSource = toSet
|
||||||
}
|
}
|
||||||
|
@ -852,9 +860,16 @@ class ShortView : FrameLayout {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
else player.setArtwork(null)
|
else player.setArtwork(null)
|
||||||
|
|
||||||
|
mainFragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
player.setSource(videoSource, audioSource, play = true, keepSubtitles = false, resume = resumePositionMs > 0)
|
player.setSource(videoSource, audioSource, play = true, keepSubtitles = false, resume = resumePositionMs > 0)
|
||||||
if (subtitleSource != null) player.swapSubtitles(mainFragment.lifecycleScope, subtitleSource)
|
if (subtitleSource != null) player.swapSubtitles(subtitleSource)
|
||||||
player.seekTo(resumePositionMs)
|
player.seekTo(resumePositionMs)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "playVideo failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_lastVideoSource = videoSource
|
_lastVideoSource = videoSource
|
||||||
_lastAudioSource = audioSource
|
_lastAudioSource = audioSource
|
||||||
|
|
|
@ -1925,13 +1925,28 @@ class VideoDetailView : ConstraintLayout {
|
||||||
else
|
else
|
||||||
_player.setArtwork(null);
|
_player.setArtwork(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
_player.setSource(videoSource, audioSource, _playWhenReady && playWhenReady, false, resume = resumePositionMs > 0);
|
_player.setSource(videoSource, audioSource, _playWhenReady && playWhenReady, false, resume = resumePositionMs > 0);
|
||||||
if(subtitleSource != null)
|
if(subtitleSource != null)
|
||||||
_player.swapSubtitles(fragment.lifecycleScope, subtitleSource);
|
_player.swapSubtitles(subtitleSource);
|
||||||
_player.seekTo(resumePositionMs);
|
_player.seekTo(resumePositionMs);
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "loadCurrentVideo failed", e)
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
loadCurrentVideoCast(video, videoSource, audioSource, subtitleSource, resumePositionMs, Settings.instance.playback.getDefaultPlaybackSpeed().toDouble());
|
loadCurrentVideoCast(video, videoSource, audioSource, subtitleSource, resumePositionMs, Settings.instance.playback.getDefaultPlaybackSpeed().toDouble());
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "loadCurrentVideo failed (casting)", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_lastVideoSource = videoSource;
|
_lastVideoSource = videoSource;
|
||||||
_lastAudioSource = audioSource;
|
_lastAudioSource = audioSource;
|
||||||
|
@ -1946,13 +1961,12 @@ class VideoDetailView : ConstraintLayout {
|
||||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_load_media), ex);
|
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_load_media), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun loadCurrentVideoCast(video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) {
|
private suspend fun loadCurrentVideoCast(video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) {
|
||||||
Logger.i(TAG, "loadCurrentVideoCast(video=$video, videoSource=$videoSource, audioSource=$audioSource, resumePositionMs=$resumePositionMs)")
|
Logger.i(TAG, "loadCurrentVideoCast(video=$video, videoSource=$videoSource, audioSource=$audioSource, resumePositionMs=$resumePositionMs)")
|
||||||
castIfAvailable(context.contentResolver, video, videoSource, audioSource, subtitleSource, resumePositionMs, speed)
|
castIfAvailable(context.contentResolver, video, videoSource, audioSource, subtitleSource, resumePositionMs, speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) {
|
private suspend fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) {
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
try {
|
||||||
val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin()
|
val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin()
|
||||||
else if (audioSource is JSSource) audioSource.getUnderlyingPlugin()
|
else if (audioSource is JSSource) audioSource.getUnderlyingPlugin()
|
||||||
|
@ -1988,7 +2002,6 @@ class VideoDetailView : ConstraintLayout {
|
||||||
Logger.e(TAG, "loadCurrentVideoCast", e)
|
Logger.e(TAG, "loadCurrentVideoCast", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
@androidx.annotation.OptIn(UnstableApi::class)
|
@androidx.annotation.OptIn(UnstableApi::class)
|
||||||
|
@ -2501,11 +2514,17 @@ class VideoDetailView : ConstraintLayout {
|
||||||
if(_lastVideoSource == videoSource)
|
if(_lastVideoSource == videoSource)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
val d = StateCasting.instance.activeDevice;
|
val d = StateCasting.instance.activeDevice;
|
||||||
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
||||||
castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
|
castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
|
||||||
else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true))
|
else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true))
|
||||||
_player.hideControls(false); //TODO: Disable player?
|
_player.hideControls(false); //TODO: Disable player?
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "handleSelectVideoTrack failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_lastVideoSource = videoSource;
|
_lastVideoSource = videoSource;
|
||||||
}
|
}
|
||||||
|
@ -2516,11 +2535,17 @@ class VideoDetailView : ConstraintLayout {
|
||||||
if(_lastAudioSource == audioSource)
|
if(_lastAudioSource == audioSource)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
val d = StateCasting.instance.activeDevice;
|
val d = StateCasting.instance.activeDevice;
|
||||||
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
||||||
castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed)
|
castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed)
|
||||||
else(!_player.swapSources(_lastVideoSource, audioSource, true, true, true))
|
else if (!_player.swapSources(_lastVideoSource, audioSource, true, true, true))
|
||||||
_player.hideControls(false); //TODO: Disable player?
|
_player.hideControls(false); //TODO: Disable player?
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "handleSelectAudioTrack failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_lastAudioSource = audioSource;
|
_lastAudioSource = audioSource;
|
||||||
}
|
}
|
||||||
|
@ -2532,12 +2557,20 @@ class VideoDetailView : ConstraintLayout {
|
||||||
if(_lastSubtitleSource == subtitleSource)
|
if(_lastSubtitleSource == subtitleSource)
|
||||||
toSet = null;
|
toSet = null;
|
||||||
|
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
val d = StateCasting.instance.activeDevice;
|
val d = StateCasting.instance.activeDevice;
|
||||||
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
if (d != null && d.connectionState == CastConnectionState.CONNECTED)
|
||||||
castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
|
castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
|
||||||
else
|
else {
|
||||||
_player.swapSubtitles(fragment.lifecycleScope, toSet);
|
withContext(Dispatchers.Main) {
|
||||||
|
_player.swapSubtitles(toSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "handleSelectAudioTrack failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
_lastSubtitleSource = toSet;
|
_lastSubtitleSource = toSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ abstract class ContentPreviewViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||||
|
|
||||||
abstract fun bind(content: IPlatformContent);
|
abstract fun bind(content: IPlatformContent);
|
||||||
|
|
||||||
abstract fun preview(details: IPlatformContentDetails?, paused: Boolean);
|
abstract suspend fun preview(details: IPlatformContentDetails?, paused: Boolean);
|
||||||
abstract fun stopPreview();
|
abstract fun stopPreview();
|
||||||
abstract fun pausePreview();
|
abstract fun pausePreview();
|
||||||
abstract fun resumePreview();
|
abstract fun resumePreview();
|
||||||
|
|
|
@ -11,7 +11,7 @@ class EmptyPreviewViewHolder(viewGroup: ViewGroup) : ContentPreviewViewHolder(Vi
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) {}
|
override fun bind(content: IPlatformContent) {}
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) {}
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) {}
|
||||||
|
|
||||||
override fun stopPreview() {}
|
override fun stopPreview() {}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ class PreviewChannelViewHolder : ContentPreviewViewHolder {
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) = view.bind(content);
|
override fun bind(content: IPlatformContent) = view.bind(content);
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) = Unit;
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) = Unit;
|
||||||
override fun stopPreview() = Unit;
|
override fun stopPreview() = Unit;
|
||||||
override fun pausePreview() = Unit;
|
override fun pausePreview() = Unit;
|
||||||
override fun resumePreview() = Unit;
|
override fun resumePreview() = Unit;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
|
@ -15,6 +16,8 @@ import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.constructs.Event2
|
import com.futo.platformplayer.constructs.Event2
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.debug.Stopwatch
|
import com.futo.platformplayer.debug.Stopwatch
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.ShortView
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.ShortView.Companion
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
|
@ -23,6 +26,9 @@ import com.futo.platformplayer.views.FeedStyle
|
||||||
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.EmptyPreviewViewHolder
|
import com.futo.platformplayer.views.adapters.EmptyPreviewViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.internal.platform.Platform
|
import okhttp3.internal.platform.Platform
|
||||||
|
|
||||||
class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewViewHolder> {
|
class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewViewHolder> {
|
||||||
|
@ -33,6 +39,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
private val _feedStyle : FeedStyle;
|
private val _feedStyle : FeedStyle;
|
||||||
private var _paused: Boolean = false;
|
private var _paused: Boolean = false;
|
||||||
private val _shouldShowTimeBar: Boolean
|
private val _shouldShowTimeBar: Boolean
|
||||||
|
private val _scope: CoroutineScope
|
||||||
|
|
||||||
val onUrlClicked = Event1<String>();
|
val onUrlClicked = Event1<String>();
|
||||||
val onContentUrlClicked = Event2<String, ContentType>();
|
val onContentUrlClicked = Event2<String, ContentType>();
|
||||||
|
@ -43,15 +50,9 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
||||||
val onLongPress = Event1<IPlatformContent>();
|
val onLongPress = Event1<IPlatformContent>();
|
||||||
|
|
||||||
private var _taskLoadContent = TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>(
|
private var _taskLoadContent: TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>
|
||||||
StateApp.instance.scopeGetter, { (viewHolder, video) ->
|
|
||||||
val stopwatch = Stopwatch()
|
|
||||||
val contentDetails = StatePlatform.instance.getContentDetails(video.url).await();
|
|
||||||
stopwatch.logAndNext(TAG, "Retrieving video detail (IO thread)")
|
|
||||||
return@TaskHandler Pair(viewHolder, contentDetails)
|
|
||||||
}).exception<Throwable> { Logger.e(TAG, "Failed to retrieve preview content.", it) }.success { previewContentDetails(it.first, it.second) }
|
|
||||||
|
|
||||||
constructor(context: Context, feedStyle : FeedStyle, dataSet: ArrayList<IPlatformContent>, exoPlayer: PlayerManager? = null,
|
constructor(scope: CoroutineScope, context: Context, feedStyle : FeedStyle, dataSet: ArrayList<IPlatformContent>, exoPlayer: PlayerManager? = null,
|
||||||
initialPlay: Boolean = false, viewsToPrepend: ArrayList<View> = arrayListOf(),
|
initialPlay: Boolean = false, viewsToPrepend: ArrayList<View> = arrayListOf(),
|
||||||
viewsToAppend: ArrayList<View> = arrayListOf(), shouldShowTimeBar: Boolean = true) : super(context, viewsToPrepend, viewsToAppend) {
|
viewsToAppend: ArrayList<View> = arrayListOf(), shouldShowTimeBar: Boolean = true) : super(context, viewsToPrepend, viewsToAppend) {
|
||||||
|
|
||||||
|
@ -60,6 +61,24 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
this._initialPlay = initialPlay;
|
this._initialPlay = initialPlay;
|
||||||
this._exoPlayer = exoPlayer;
|
this._exoPlayer = exoPlayer;
|
||||||
this._shouldShowTimeBar = shouldShowTimeBar
|
this._shouldShowTimeBar = shouldShowTimeBar
|
||||||
|
this._scope = scope
|
||||||
|
|
||||||
|
_taskLoadContent = TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>(
|
||||||
|
{ scope }, { (viewHolder, video) ->
|
||||||
|
val stopwatch = Stopwatch()
|
||||||
|
val contentDetails = StatePlatform.instance.getContentDetails(video.url).await();
|
||||||
|
stopwatch.logAndNext(TAG, "Retrieving video detail (IO thread)")
|
||||||
|
return@TaskHandler Pair(viewHolder, contentDetails)
|
||||||
|
}).exception<Throwable> { Logger.e(TAG, "Failed to retrieve preview content.", it) }.success {
|
||||||
|
|
||||||
|
_scope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
previewContentDetails(it.first, it.second)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "bindChild preview failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChildCount(): Int = _dataSet.size;
|
override fun getChildCount(): Int = _dataSet.size;
|
||||||
|
@ -132,12 +151,18 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
_initialPlay = false;
|
_initialPlay = false;
|
||||||
|
|
||||||
if (_feedStyle != FeedStyle.THUMBNAIL) {
|
if (_feedStyle != FeedStyle.THUMBNAIL) {
|
||||||
preview(holder);
|
_scope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
preview(holder)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "bindChild preview failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preview(viewHolder: ContentPreviewViewHolder) {
|
suspend fun preview(viewHolder: ContentPreviewViewHolder) {
|
||||||
Log.v(TAG, "previewing content");
|
Log.v(TAG, "previewing content");
|
||||||
if (viewHolder == _previewingViewHolder)
|
if (viewHolder == _previewingViewHolder)
|
||||||
return
|
return
|
||||||
|
@ -175,7 +200,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
onAddToWatchLaterClicked.clear();
|
onAddToWatchLaterClicked.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun previewContentDetails(viewHolder: ContentPreviewViewHolder, videoDetails: IPlatformContentDetails?) {
|
private suspend fun previewContentDetails(viewHolder: ContentPreviewViewHolder, videoDetails: IPlatformContentDetails?) {
|
||||||
_previewingViewHolder?.stopPreview();
|
_previewingViewHolder?.stopPreview();
|
||||||
viewHolder.preview(videoDetails, _paused);
|
viewHolder.preview(videoDetails, _paused);
|
||||||
_previewingViewHolder = viewHolder;
|
_previewingViewHolder = viewHolder;
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PreviewLockedViewHolder : ContentPreviewViewHolder {
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) = view.bind(content);
|
override fun bind(content: IPlatformContent) = view.bind(content);
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) { }
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) { }
|
||||||
override fun stopPreview() { }
|
override fun stopPreview() { }
|
||||||
override fun pausePreview() { }
|
override fun pausePreview() { }
|
||||||
override fun resumePreview() { }
|
override fun resumePreview() { }
|
||||||
|
|
|
@ -185,7 +185,7 @@ class PreviewNestedVideoView : PreviewVideoView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preview(video: IPlatformContentDetails?, paused: Boolean) {
|
override suspend fun preview(video: IPlatformContentDetails?, paused: Boolean) {
|
||||||
if(video != null) {
|
if(video != null) {
|
||||||
super.preview(video, paused);
|
super.preview(video, paused);
|
||||||
} else if(_content is IPlatformVideoDetails) _contentNested?.let {
|
} else if(_content is IPlatformVideoDetails) _contentNested?.let {
|
||||||
|
|
|
@ -40,7 +40,7 @@ class PreviewNestedVideoViewHolder : ContentPreviewViewHolder {
|
||||||
view.bind(content);
|
view.bind(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) {
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) {
|
||||||
view.preview(details, paused);
|
view.preview(details, paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ class PreviewPlaceholderViewHolder : ContentPreviewViewHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) { }
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) { }
|
||||||
override fun stopPreview() { }
|
override fun stopPreview() { }
|
||||||
override fun pausePreview() { }
|
override fun pausePreview() { }
|
||||||
override fun resumePreview() { }
|
override fun resumePreview() { }
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PreviewPlaylistViewHolder : ContentPreviewViewHolder {
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) = view.bind(content);
|
override fun bind(content: IPlatformContent) = view.bind(content);
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) = Unit;
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) = Unit;
|
||||||
override fun stopPreview() = Unit;
|
override fun stopPreview() = Unit;
|
||||||
override fun pausePreview() = Unit;
|
override fun pausePreview() = Unit;
|
||||||
override fun resumePreview() = Unit;
|
override fun resumePreview() = Unit;
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PreviewPostViewHolder : ContentPreviewViewHolder {
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) = view.bind(content);
|
override fun bind(content: IPlatformContent) = view.bind(content);
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) {};
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) {};
|
||||||
override fun stopPreview() {};
|
override fun stopPreview() {};
|
||||||
override fun pausePreview() {};
|
override fun pausePreview() {};
|
||||||
override fun resumePreview() {};
|
override fun resumePreview() {};
|
||||||
|
|
|
@ -248,7 +248,7 @@ open class PreviewVideoView : LinearLayout {
|
||||||
_textVideoMetadata.text = metadata + timeMeta;
|
_textVideoMetadata.text = metadata + timeMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun preview(video: IPlatformContentDetails?, paused: Boolean) {
|
open suspend fun preview(video: IPlatformContentDetails?, paused: Boolean) {
|
||||||
if(video == null)
|
if(video == null)
|
||||||
return;
|
return;
|
||||||
Logger.i(TAG, "Previewing");
|
Logger.i(TAG, "Previewing");
|
||||||
|
|
|
@ -42,7 +42,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
|
||||||
|
|
||||||
override fun bind(content: IPlatformContent) = view.bind(content);
|
override fun bind(content: IPlatformContent) = view.bind(content);
|
||||||
|
|
||||||
override fun preview(details: IPlatformContentDetails?, paused: Boolean) = view.preview(details, paused);
|
override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) = view.preview(details, paused);
|
||||||
override fun stopPreview() = view.stopPreview();
|
override fun stopPreview() = view.stopPreview();
|
||||||
override fun pausePreview() = view.pausePreview();
|
override fun pausePreview() = view.pausePreview();
|
||||||
override fun resumePreview() = view.resumePreview();
|
override fun resumePreview() = view.resumePreview();
|
||||||
|
|
|
@ -126,7 +126,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase {
|
||||||
_evMuteChanged.add(callback);
|
_evMuteChanged.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPreview(video: IPlatformVideoDetails) {
|
suspend fun setPreview(video: IPlatformVideoDetails) {
|
||||||
if (video.live != null) {
|
if (video.live != null) {
|
||||||
setSource(video.live, null,true, false);
|
setSource(video.live, null,true, false);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -365,10 +365,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
return _chapters?.let { chaps -> chaps.find { pos.toDouble() / 1000 > it.timeStart && pos.toDouble() / 1000 < it.timeEnd && (toIgnore.isEmpty() || !toIgnore.contains(it)) } };
|
return _chapters?.let { chaps -> chaps.find { pos.toDouble() / 1000 > it.timeStart && pos.toDouble() / 1000 < it.timeEnd && (toIgnore.isEmpty() || !toIgnore.contains(it)) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false, resume: Boolean = false) {
|
suspend fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false, resume: Boolean = false) {
|
||||||
swapSources(videoSource, audioSource,resume, play, keepSubtitles);
|
swapSources(videoSource, audioSource,resume, play, keepSubtitles);
|
||||||
}
|
}
|
||||||
fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
suspend fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
||||||
|
val didSet = withContext(Dispatchers.IO) {
|
||||||
var videoSourceUsed = videoSource;
|
var videoSourceUsed = videoSource;
|
||||||
var audioSourceUsed = audioSource;
|
var audioSourceUsed = audioSource;
|
||||||
if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){
|
if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){
|
||||||
|
@ -382,31 +383,45 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
val didSetAudio = swapSourceInternal(audioSourceUsed, play, resume);
|
val didSetAudio = swapSourceInternal(audioSourceUsed, play, resume);
|
||||||
if(!keepSubtitles)
|
if(!keepSubtitles)
|
||||||
_lastSubtitleMediaSource = null;
|
_lastSubtitleMediaSource = null;
|
||||||
if(didSetVideo && didSetAudio)
|
|
||||||
return loadSelectedSources(play, resume);
|
return@withContext didSetVideo && didSetAudio
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
|
||||||
|
return withContext(Dispatchers.Main) {
|
||||||
|
if (didSet)
|
||||||
|
return@withContext loadSelectedSources(play, resume)
|
||||||
|
else
|
||||||
|
return@withContext true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suspend fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
||||||
|
val didSet = withContext(Dispatchers.IO) {
|
||||||
var videoSourceUsed = videoSource;
|
var videoSourceUsed = videoSource;
|
||||||
if(videoSource is JSDashManifestRawSource && lastVideoSource is JSDashManifestMergingRawSource)
|
if (videoSource is JSDashManifestRawSource && lastVideoSource is JSDashManifestMergingRawSource)
|
||||||
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, (lastVideoSource as JSDashManifestMergingRawSource).audio);
|
videoSourceUsed = JSDashManifestMergingRawSource(videoSource, (lastVideoSource as JSDashManifestMergingRawSource).audio);
|
||||||
val didSet = swapSourceInternal(videoSourceUsed, play, resume);
|
return@withContext swapSourceInternal(videoSourceUsed, play, resume);
|
||||||
if(didSet)
|
|
||||||
return loadSelectedSources(play, resume);
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
return withContext(Dispatchers.Main) {
|
||||||
if(audioSource is JSDashManifestRawAudioSource && lastVideoSource is JSDashManifestMergingRawSource)
|
if (didSet)
|
||||||
|
return@withContext loadSelectedSources(play, resume);
|
||||||
|
else
|
||||||
|
return@withContext true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suspend fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (audioSource is JSDashManifestRawAudioSource && lastVideoSource is JSDashManifestMergingRawSource)
|
||||||
swapSourceInternal(JSDashManifestMergingRawSource((lastVideoSource as JSDashManifestMergingRawSource).video, audioSource), play, resume);
|
swapSourceInternal(JSDashManifestMergingRawSource((lastVideoSource as JSDashManifestMergingRawSource).video, audioSource), play, resume);
|
||||||
else
|
else
|
||||||
swapSourceInternal(audioSource, play, resume);
|
swapSourceInternal(audioSource, play, resume);
|
||||||
return loadSelectedSources(play, resume);
|
}
|
||||||
|
return withContext(Dispatchers.Main) {
|
||||||
|
return@withContext loadSelectedSources(play, resume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) {
|
suspend fun swapSubtitles(subtitles: ISubtitleSource?) {
|
||||||
if(subtitles == null)
|
if(subtitles == null)
|
||||||
clearSubtitles();
|
clearSubtitles();
|
||||||
else {
|
else {
|
||||||
|
@ -420,9 +435,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
C.TIME_UNSET);
|
C.TIME_UNSET);
|
||||||
loadSelectedSources(true, true);
|
loadSelectedSources(true, true);
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val subUri = subtitles.getSubtitlesURI() ?: return@launch;
|
val subUri = subtitles.getSubtitlesURI() ?: return@withContext;
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
_lastSubtitleMediaSource = SingleSampleMediaSource.Factory(DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)))
|
_lastSubtitleMediaSource = SingleSampleMediaSource.Factory(DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue