diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt index b99e211e..8bfc80e0 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -209,7 +209,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), _searchView = searchView 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.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit); this.onContentClicked.subscribe { content, num -> diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelPlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelPlaylistsFragment.kt index cd88e610..c9509144 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelPlaylistsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelPlaylistsFragment.kt @@ -148,7 +148,7 @@ class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment { _recyclerResults = view.findViewById(R.id.recycler_videos) _adapterResults = PreviewContentListAdapter( - view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar + lifecycleScope, view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar ).apply { this.onContentUrlClicked.subscribe(this@ChannelPlaylistsFragment.onContentUrlClicked::emit) this.onUrlClicked.subscribe(this@ChannelPlaylistsFragment.onUrlClicked::emit) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt index fbb85dac..6aea0189 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import android.view.LayoutInflater import android.widget.LinearLayout +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView 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.platforms.js.models.JSWeb 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.states.StateMeta 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.SlideUpMenuOverlay import com.futo.platformplayer.withTimestamp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlin.math.floor import kotlin.math.max @@ -59,7 +64,7 @@ abstract class ContentFeedView : FeedView state.muted = true }; _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); } } @@ -246,8 +251,14 @@ abstract class ContentFeedView : FeedView 0) - if (subtitleSource != null) player.swapSubtitles(mainFragment.lifecycleScope, subtitleSource) - player.seekTo(resumePositionMs) + + mainFragment.lifecycleScope.launch(Dispatchers.Main) { + try { + player.setSource(videoSource, audioSource, play = true, keepSubtitles = false, resume = resumePositionMs > 0) + if (subtitleSource != null) player.swapSubtitles(subtitleSource) + player.seekTo(resumePositionMs) + } catch (e: Throwable) { + Logger.e(TAG, "playVideo failed", e) + } + } _lastVideoSource = videoSource _lastAudioSource = audioSource 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 3dbde51b..06de4362 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 @@ -1925,13 +1925,28 @@ class VideoDetailView : ConstraintLayout { else _player.setArtwork(null); } - _player.setSource(videoSource, audioSource, _playWhenReady && playWhenReady, false, resume = resumePositionMs > 0); - if(subtitleSource != null) - _player.swapSubtitles(fragment.lifecycleScope, subtitleSource); - _player.seekTo(resumePositionMs); + + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + _player.setSource(videoSource, audioSource, _playWhenReady && playWhenReady, false, resume = resumePositionMs > 0); + if(subtitleSource != null) + _player.swapSubtitles(subtitleSource); + _player.seekTo(resumePositionMs); + } catch (e: Throwable) { + Logger.e(TAG, "loadCurrentVideo failed", e) + } + } } - else - loadCurrentVideoCast(video, videoSource, audioSource, subtitleSource, resumePositionMs, Settings.instance.playback.getDefaultPlaybackSpeed().toDouble()); + else { + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + loadCurrentVideoCast(video, videoSource, audioSource, subtitleSource, resumePositionMs, Settings.instance.playback.getDefaultPlaybackSpeed().toDouble()); + } catch (e: Throwable) { + Logger.e(TAG, "loadCurrentVideo failed (casting)", e) + } + } + } + _lastVideoSource = videoSource; _lastAudioSource = audioSource; @@ -1946,47 +1961,45 @@ class VideoDetailView : ConstraintLayout { 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)") 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?) { - fragment.lifecycleScope.launch(Dispatchers.IO) { + private suspend fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) { + try { + val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin() + else if (audioSource is JSSource) audioSource.getUnderlyingPlugin() + else null + + val startId = plugin?.getUnderlyingPlugin()?.runtimeId try { - val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin() - else if (audioSource is JSSource) audioSource.getUnderlyingPlugin() - else null + val castingSucceeded = StateCasting.instance.castIfAvailable(contentResolver, video, videoSource, audioSource, subtitleSource, resumePositionMs, speed, onLoading = { + _cast.setLoading(it) + }, onLoadingEstimate = { + _cast.setLoading(it) + }) - val startId = plugin?.getUnderlyingPlugin()?.runtimeId - try { - val castingSucceeded = StateCasting.instance.castIfAvailable(contentResolver, video, videoSource, audioSource, subtitleSource, resumePositionMs, speed, onLoading = { - _cast.setLoading(it) - }, onLoadingEstimate = { - _cast.setLoading(it) - }) - - if (castingSucceeded) { - withContext(Dispatchers.Main) { - _cast.setVideoDetails(video, resumePositionMs / 1000); - setCastEnabled(true); - } + if (castingSucceeded) { + withContext(Dispatchers.Main) { + _cast.setVideoDetails(video, resumePositionMs / 1000); + setCastEnabled(true); } - } catch (e: ScriptReloadRequiredException) { - Log.i(TAG, "Reload required exception", e) - if (plugin == null) - throw e - - if (startId != -1 && plugin.getUnderlyingPlugin().runtimeId != startId) - throw e - - StatePlatform.instance.handleReloadRequired(e, { - fetchVideo() - }); } - } catch (e: Throwable) { - Logger.e(TAG, "loadCurrentVideoCast", e) + } catch (e: ScriptReloadRequiredException) { + Log.i(TAG, "Reload required exception", e) + if (plugin == null) + throw e + + if (startId != -1 && plugin.getUnderlyingPlugin().runtimeId != startId) + throw e + + StatePlatform.instance.handleReloadRequired(e, { + fetchVideo() + }); } + } catch (e: Throwable) { + Logger.e(TAG, "loadCurrentVideoCast", e) } } @@ -2501,11 +2514,17 @@ class VideoDetailView : ConstraintLayout { if(_lastVideoSource == videoSource) return; - val d = StateCasting.instance.activeDevice; - if (d != null && d.connectionState == CastConnectionState.CONNECTED) - castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed); - else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true)) - _player.hideControls(false); //TODO: Disable player? + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed); + else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true)) + _player.hideControls(false); //TODO: Disable player? + } catch (e: Throwable) { + Logger.e(TAG, "handleSelectVideoTrack failed", e) + } + } _lastVideoSource = videoSource; } @@ -2516,11 +2535,17 @@ class VideoDetailView : ConstraintLayout { if(_lastAudioSource == audioSource) return; - val d = StateCasting.instance.activeDevice; - if (d != null && d.connectionState == CastConnectionState.CONNECTED) - castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed) - else(!_player.swapSources(_lastVideoSource, audioSource, true, true, true)) - _player.hideControls(false); //TODO: Disable player? + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed) + else if (!_player.swapSources(_lastVideoSource, audioSource, true, true, true)) + _player.hideControls(false); //TODO: Disable player? + } catch (e: Throwable) { + Logger.e(TAG, "handleSelectAudioTrack failed", e) + } + } _lastAudioSource = audioSource; } @@ -2532,12 +2557,20 @@ class VideoDetailView : ConstraintLayout { if(_lastSubtitleSource == subtitleSource) toSet = null; - val d = StateCasting.instance.activeDevice; - if (d != null && d.connectionState == CastConnectionState.CONNECTED) - castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong(), d.speed); - else - _player.swapSubtitles(fragment.lifecycleScope, toSet); - + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong(), d.speed); + else { + withContext(Dispatchers.Main) { + _player.swapSubtitles(toSet); + } + } + } catch (e: Throwable) { + Logger.e(TAG, "handleSelectAudioTrack failed", e) + } + } _lastSubtitleSource = toSet; } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt index 67e946e0..c9558e4c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt @@ -10,7 +10,7 @@ abstract class ContentPreviewViewHolder(itemView: View) : ViewHolder(itemView) { 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 pausePreview(); abstract fun resumePreview(); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt index 05b33db2..6830036a 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt @@ -11,7 +11,7 @@ class EmptyPreviewViewHolder(viewGroup: ViewGroup) : ContentPreviewViewHolder(Vi override fun bind(content: IPlatformContent) {} - override fun preview(details: IPlatformContentDetails?, paused: Boolean) {} + override suspend fun preview(details: IPlatformContentDetails?, paused: Boolean) {} override fun stopPreview() {} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewChannelViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewChannelViewHolder.kt index 17754984..ad09e0e9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewChannelViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewChannelViewHolder.kt @@ -29,7 +29,7 @@ class PreviewChannelViewHolder : ContentPreviewViewHolder { 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 pausePreview() = Unit; override fun resumePreview() = Unit; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt index 327497fb..20327904 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope 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.IPlatformContent @@ -15,6 +16,8 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.TaskHandler 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.states.StateApp 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.EmptyPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.internal.platform.Platform class PreviewContentListAdapter : InsertedViewAdapterWithLoader { @@ -33,6 +39,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader(); val onContentUrlClicked = Event2(); @@ -43,15 +50,9 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader(); val onLongPress = Event1(); - private var _taskLoadContent = TaskHandler, Pair>( - 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 { Logger.e(TAG, "Failed to retrieve preview content.", it) }.success { previewContentDetails(it.first, it.second) } + private var _taskLoadContent: TaskHandler, Pair> - constructor(context: Context, feedStyle : FeedStyle, dataSet: ArrayList, exoPlayer: PlayerManager? = null, + constructor(scope: CoroutineScope, context: Context, feedStyle : FeedStyle, dataSet: ArrayList, exoPlayer: PlayerManager? = null, initialPlay: Boolean = false, viewsToPrepend: ArrayList = arrayListOf(), viewsToAppend: ArrayList = arrayListOf(), shouldShowTimeBar: Boolean = true) : super(context, viewsToPrepend, viewsToAppend) { @@ -60,6 +61,24 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader, Pair>( + { 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 { 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; @@ -132,12 +151,18 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader 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); } - fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean { - var videoSourceUsed = videoSource; - var audioSourceUsed = audioSource; - if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){ - videoSource.getUnderlyingPlugin()?.busy { - videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource); - audioSourceUsed = null; + 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 audioSourceUsed = audioSource; + if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){ + videoSource.getUnderlyingPlugin()?.busy { + videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource); + audioSourceUsed = null; + } } + + val didSetVideo = swapSourceInternal(videoSourceUsed, play, resume); + val didSetAudio = swapSourceInternal(audioSourceUsed, play, resume); + if(!keepSubtitles) + _lastSubtitleMediaSource = null; + + return@withContext didSetVideo && didSetAudio } - val didSetVideo = swapSourceInternal(videoSourceUsed, play, resume); - val didSetAudio = swapSourceInternal(audioSourceUsed, play, resume); - if(!keepSubtitles) - _lastSubtitleMediaSource = null; - if(didSetVideo && didSetAudio) - return loadSelectedSources(play, resume); - else - return true; + return withContext(Dispatchers.Main) { + if (didSet) + return@withContext loadSelectedSources(play, resume) + else + return@withContext true + } } - fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean { - var videoSourceUsed = videoSource; - if(videoSource is JSDashManifestRawSource && lastVideoSource is JSDashManifestMergingRawSource) - videoSourceUsed = JSDashManifestMergingRawSource(videoSource, (lastVideoSource as JSDashManifestMergingRawSource).audio); - val didSet = swapSourceInternal(videoSourceUsed, play, resume); - if(didSet) - return loadSelectedSources(play, resume); - else - return true; + suspend fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean { + val didSet = withContext(Dispatchers.IO) { + var videoSourceUsed = videoSource; + if (videoSource is JSDashManifestRawSource && lastVideoSource is JSDashManifestMergingRawSource) + videoSourceUsed = JSDashManifestMergingRawSource(videoSource, (lastVideoSource as JSDashManifestMergingRawSource).audio); + return@withContext swapSourceInternal(videoSourceUsed, play, resume); + } + return withContext(Dispatchers.Main) { + if (didSet) + return@withContext loadSelectedSources(play, resume); + else + return@withContext true; + } } - fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean { - if(audioSource is JSDashManifestRawAudioSource && lastVideoSource is JSDashManifestMergingRawSource) - swapSourceInternal(JSDashManifestMergingRawSource((lastVideoSource as JSDashManifestMergingRawSource).video, audioSource), play, resume); - else - swapSourceInternal(audioSource, play, resume); - return loadSelectedSources(play, resume); + 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); + else + swapSourceInternal(audioSource, play, resume); + } + return withContext(Dispatchers.Main) { + return@withContext loadSelectedSources(play, resume); + } } @OptIn(UnstableApi::class) - fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) { + suspend fun swapSubtitles(subtitles: ISubtitleSource?) { if(subtitles == null) clearSubtitles(); else { @@ -420,9 +435,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { C.TIME_UNSET); loadSelectedSources(true, true); } else { - scope.launch(Dispatchers.IO) { + withContext(Dispatchers.IO) { try { - val subUri = subtitles.getSubtitlesURI() ?: return@launch; + val subUri = subtitles.getSubtitlesURI() ?: return@withContext; withContext(Dispatchers.Main) { try { _lastSubtitleMediaSource = SingleSampleMediaSource.Factory(DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)))