mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-06-01 06:20:21 +02:00
Permanently stop playlist video download on cancel, Use detailed video download overlay in overviews
This commit is contained in:
parent
8bb1ff87c0
commit
9ffdf39f13
@ -17,6 +17,7 @@ import com.futo.platformplayer.helpers.VideoHelper
|
|||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.Playlist
|
import com.futo.platformplayer.models.Playlist
|
||||||
import com.futo.platformplayer.states.*
|
import com.futo.platformplayer.states.*
|
||||||
|
import com.futo.platformplayer.views.Loader
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
|
||||||
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
|
||||||
@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
class UISlideOverlays {
|
class UISlideOverlays {
|
||||||
companion object {
|
companion object {
|
||||||
@ -43,7 +45,7 @@ class UISlideOverlays {
|
|||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showDownloadVideoOverlay(contentResolver: ContentResolver, video: IPlatformVideoDetails, container: ViewGroup): SlideUpMenuOverlay? {
|
fun showDownloadVideoOverlay(video: IPlatformVideoDetails, container: ViewGroup, contentResolver: ContentResolver? = null): SlideUpMenuOverlay? {
|
||||||
val items = arrayListOf<View>();
|
val items = arrayListOf<View>();
|
||||||
var menu: SlideUpMenuOverlay? = null;
|
var menu: SlideUpMenuOverlay? = null;
|
||||||
|
|
||||||
@ -121,6 +123,8 @@ class UISlideOverlays {
|
|||||||
if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioUrlSource?;
|
if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioUrlSource?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ContentResolver is required for subtitles..
|
||||||
|
if(contentResolver != null) {
|
||||||
items.add(SlideUpMenuGroup(container.context, "Subtitles", subtitleSources, subtitleSources
|
items.add(SlideUpMenuGroup(container.context, "Subtitles", subtitleSources, subtitleSources
|
||||||
.map {
|
.map {
|
||||||
SlideUpMenuItem(container.context, R.drawable.ic_edit, it.name, "", it, {
|
SlideUpMenuItem(container.context, R.drawable.ic_edit, it.name, "", it, {
|
||||||
@ -133,6 +137,7 @@ class UISlideOverlays {
|
|||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
menu = SlideUpMenuOverlay(container.context, container, "Download Video", null, true, items);
|
menu = SlideUpMenuOverlay(container.context, container, "Download Video", null, true, items);
|
||||||
|
|
||||||
@ -157,29 +162,12 @@ class UISlideOverlays {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val subtitleUri = subtitleToDownload.getSubtitlesURI();
|
val subtitleUri = subtitleToDownload.getSubtitlesURI();
|
||||||
if (subtitleUri != null) {
|
//TODO: Remove uri dependency, should be able to work with raw aswell?
|
||||||
var subtitles: String? = null;
|
if (subtitleUri != null && contentResolver != null) {
|
||||||
if ("file" == subtitleUri.scheme) {
|
val subtitlesRaw = StateDownloads.instance.downloadSubtitles(subtitleToDownload, contentResolver);
|
||||||
val inputStream = contentResolver.openInputStream(subtitleUri);
|
|
||||||
inputStream?.use { stream ->
|
|
||||||
val reader = stream.bufferedReader();
|
|
||||||
subtitles = reader.use { it.readText() };
|
|
||||||
}
|
|
||||||
} else if ("http" == subtitleUri.scheme || "https" == subtitleUri.scheme) {
|
|
||||||
val client = ManagedHttpClient();
|
|
||||||
val subtitleResponse = client.get(subtitleUri.toString());
|
|
||||||
if (!subtitleResponse.isOk) {
|
|
||||||
throw Exception("Cannot fetch subtitles from source '${subtitleUri}': ${subtitleResponse.code}");
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitles = subtitleResponse.body?.toString()
|
|
||||||
?: throw Exception("Subtitles are invalid '${subtitleUri}': ${subtitleResponse.code}");
|
|
||||||
} else {
|
|
||||||
throw Exception("Unsuported scheme");
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
StateDownloads.instance.download(video, selectedVideo, selectedAudio, if (subtitles != null) SubtitleRawSource(subtitleToDownload.name, subtitleToDownload.format, subtitles!!) else null);
|
StateDownloads.instance.download(video, selectedVideo, selectedAudio, subtitlesRaw);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -195,10 +183,41 @@ class UISlideOverlays {
|
|||||||
};
|
};
|
||||||
return menu.apply { show() };
|
return menu.apply { show() };
|
||||||
}
|
}
|
||||||
fun showDownloadVideoOverlay(video: IPlatformVideo, container: ViewGroup) {
|
fun showDownloadVideoOverlay(video: IPlatformVideo, container: ViewGroup, useDetails: Boolean = false) {
|
||||||
|
val handleUnknownDownload: ()->Unit = {
|
||||||
showUnknownVideoDownload("Video", container) { px, bitrate ->
|
showUnknownVideoDownload("Video", container) { px, bitrate ->
|
||||||
StateDownloads.instance.download(video, px, bitrate)
|
StateDownloads.instance.download(video, px, bitrate)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
if(!useDetails)
|
||||||
|
handleUnknownDownload();
|
||||||
|
else {
|
||||||
|
val scope = StateApp.instance.scopeOrNull;
|
||||||
|
|
||||||
|
if(scope != null) {
|
||||||
|
val loader = showLoaderOverlay("Fetching video details", container);
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val videoDetails = StatePlatform.instance.getContentDetails(video.url, false).await();
|
||||||
|
if(videoDetails !is IPlatformVideoDetails)
|
||||||
|
throw IllegalStateException("Not a video details");
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if(showDownloadVideoOverlay(videoDetails, container, StateApp.instance.contextOrNull?.contentResolver) == null)
|
||||||
|
loader.hide(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
UIDialogs.toast("Failed to fetch details for download");
|
||||||
|
handleUnknownDownload();
|
||||||
|
loader.hide(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else handleUnknownDownload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fun showDownloadPlaylistOverlay(playlist: Playlist, container: ViewGroup) {
|
fun showDownloadPlaylistOverlay(playlist: Playlist, container: ViewGroup) {
|
||||||
showUnknownVideoDownload("Video", container) { px, bitrate ->
|
showUnknownVideoDownload("Video", container) { px, bitrate ->
|
||||||
@ -273,6 +292,18 @@ class UISlideOverlays {
|
|||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showLoaderOverlay(text: String, container: ViewGroup): SlideUpMenuOverlay {
|
||||||
|
val dp70 = 70.dp(container.context.resources);
|
||||||
|
val dp15 = 15.dp(container.context.resources);
|
||||||
|
val overlay = SlideUpMenuOverlay(container.context, container, text, null, true, listOf(
|
||||||
|
Loader(container.context, true, dp70).apply {
|
||||||
|
this.setPadding(0, dp15, 0, dp15);
|
||||||
|
}
|
||||||
|
), true);
|
||||||
|
overlay.show();
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, onVideoHidden: (()->Unit)? = null): SlideUpMenuOverlay {
|
fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, onVideoHidden: (()->Unit)? = null): SlideUpMenuOverlay {
|
||||||
val items = arrayListOf<View>();
|
val items = arrayListOf<View>();
|
||||||
val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();
|
val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();
|
||||||
@ -295,7 +326,7 @@ class UISlideOverlays {
|
|||||||
SlideUpMenuItem(container.context, R.drawable.ic_visibility_off, "Hide", "Hide from Home", "hide",
|
SlideUpMenuItem(container.context, R.drawable.ic_visibility_off, "Hide", "Hide from Home", "hide",
|
||||||
{ StateMeta.instance.addHiddenVideo(video.url); onVideoHidden?.invoke() }),
|
{ StateMeta.instance.addHiddenVideo(video.url); onVideoHidden?.invoke() }),
|
||||||
SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download",
|
SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download",
|
||||||
{ showDownloadVideoOverlay(video, container); }, false)
|
{ showDownloadVideoOverlay(video, container, true); }, false)
|
||||||
))
|
))
|
||||||
items.add(
|
items.add(
|
||||||
SlideUpMenuGroup(container.context, "Add To", "addto",
|
SlideUpMenuGroup(container.context, "Add To", "addto",
|
||||||
@ -348,7 +379,7 @@ class UISlideOverlays {
|
|||||||
SlideUpMenuItem(container.context, R.drawable.ic_watchlist_add, StatePlayer.TYPE_WATCHLATER, "${watchLater.size} videos", "watch later",
|
SlideUpMenuItem(container.context, R.drawable.ic_watchlist_add, StatePlayer.TYPE_WATCHLATER, "${watchLater.size} videos", "watch later",
|
||||||
{ StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }),
|
{ StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }),
|
||||||
SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download",
|
SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download",
|
||||||
{ showDownloadVideoOverlay(video, container); }, false))
|
{ showDownloadVideoOverlay(video, container, true); }, false))
|
||||||
);
|
);
|
||||||
|
|
||||||
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
||||||
|
@ -22,6 +22,7 @@ import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
|||||||
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
||||||
import com.futo.platformplayer.toHumanBitrate
|
import com.futo.platformplayer.toHumanBitrate
|
||||||
import com.futo.platformplayer.toHumanBytesSpeed
|
import com.futo.platformplayer.toHumanBytesSpeed
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
@ -30,7 +31,6 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.concurrent.CancellationException
|
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.ForkJoinTask
|
import java.util.concurrent.ForkJoinTask
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
@ -371,7 +371,7 @@ class VideoDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCancelled)
|
if (isCancelled)
|
||||||
throw IllegalStateException("Cancelled");
|
throw CancellationException("Cancelled");
|
||||||
} while (read > 0);
|
} while (read > 0);
|
||||||
|
|
||||||
lastSpeed = 0;
|
lastSpeed = 0;
|
||||||
@ -423,7 +423,7 @@ class VideoDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(isCancelled)
|
if(isCancelled)
|
||||||
throw IllegalStateException("Cancelled");
|
throw CancellationException("Cancelled", null);
|
||||||
}
|
}
|
||||||
onProgress(sourceLength, totalRead, 0);
|
onProgress(sourceLength, totalRead, 0);
|
||||||
}
|
}
|
||||||
|
@ -608,7 +608,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
},
|
},
|
||||||
RoundButton(context, R.drawable.ic_download, "Download", TAG_DOWNLOAD) {
|
RoundButton(context, R.drawable.ic_download, "Download", TAG_DOWNLOAD) {
|
||||||
video?.let {
|
video?.let {
|
||||||
_slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(context.contentResolver, it, _overlayContainer);
|
_slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
RoundButton(context, R.drawable.ic_share, "Share", TAG_SHARE) {
|
RoundButton(context, R.drawable.ic_share, "Share", TAG_SHARE) {
|
||||||
|
@ -19,6 +19,7 @@ import com.futo.platformplayer.states.AnnouncementType
|
|||||||
import com.futo.platformplayer.states.StateAnnouncement
|
import com.futo.platformplayer.states.StateAnnouncement
|
||||||
import com.futo.platformplayer.states.StateDownloads
|
import com.futo.platformplayer.states.StateDownloads
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@ -138,18 +139,7 @@ class DownloadService : Service() {
|
|||||||
else if(ex is DownloadException && !ex.isRetryable) {
|
else if(ex is DownloadException && !ex.isRetryable) {
|
||||||
Logger.w(TAG, "Video had exception that should not be retried");
|
Logger.w(TAG, "Video had exception that should not be retried");
|
||||||
StateDownloads.instance.removeDownload(currentVideo);
|
StateDownloads.instance.removeDownload(currentVideo);
|
||||||
//Ensure impossible downloads are not retried for playlists
|
StateDownloads.instance.preventPlaylistDownload(currentVideo);
|
||||||
if(currentVideo.video != null && currentVideo.groupID != null && currentVideo.groupType == VideoDownload.GROUP_PLAYLIST) {
|
|
||||||
StateDownloads.instance.getPlaylistDownload(currentVideo.groupID!!)?.let {
|
|
||||||
synchronized(it.preventDownload) {
|
|
||||||
if(currentVideo?.video?.url != null && !it.preventDownload.contains(currentVideo!!.video!!.url)) {
|
|
||||||
it.preventDownload.add(currentVideo!!.video!!.url);
|
|
||||||
StateDownloads.instance.savePlaylistDownload(it);
|
|
||||||
Logger.w(TAG, "Preventing further download attempts in playlist [${it.id}] for [${currentVideo?.name}]:${currentVideo?.video?.url}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Logger.e(TAG, "Failed download [${currentVideo.name}]: ${ex.message}", ex);
|
Logger.e(TAG, "Failed download [${currentVideo.name}]: ${ex.message}", ex);
|
||||||
@ -157,6 +147,7 @@ class DownloadService : Service() {
|
|||||||
currentVideo.changeState(VideoDownload.State.ERROR);
|
currentVideo.changeState(VideoDownload.State.ERROR);
|
||||||
ignore.add(currentVideo);
|
ignore.add(currentVideo);
|
||||||
|
|
||||||
|
if(ex !is CancellationException)
|
||||||
StateAnnouncement.instance.registerAnnouncement(currentVideo?.id?.value?:"" + currentVideo?.id?.pluginId?:"" + "_FailDownload",
|
StateAnnouncement.instance.registerAnnouncement(currentVideo?.id?.value?:"" + currentVideo?.id?.pluginId?:"" + "_FailDownload",
|
||||||
"Download failed",
|
"Download failed",
|
||||||
"Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", AnnouncementType.SESSION, null, "download");
|
"Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", AnnouncementType.SESSION, null, "download");
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package com.futo.platformplayer.states
|
package com.futo.platformplayer.states
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.net.Uri
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.api.media.PlatformID
|
import com.futo.platformplayer.api.media.PlatformID
|
||||||
import com.futo.platformplayer.api.media.exceptions.AlreadyQueuedException
|
import com.futo.platformplayer.api.media.exceptions.AlreadyQueuedException
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.*
|
import com.futo.platformplayer.api.media.models.streams.sources.*
|
||||||
|
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.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
@ -147,6 +151,19 @@ class StateDownloads {
|
|||||||
_downloading.delete(download);
|
_downloading.delete(download);
|
||||||
onDownloadsChanged.emit();
|
onDownloadsChanged.emit();
|
||||||
}
|
}
|
||||||
|
fun preventPlaylistDownload(download: VideoDownload) {
|
||||||
|
if(download.video != null && download.groupID != null && download.groupType == VideoDownload.GROUP_PLAYLIST) {
|
||||||
|
getPlaylistDownload(download.groupID!!)?.let {
|
||||||
|
synchronized(it.preventDownload) {
|
||||||
|
if(download.video?.url != null && !it.preventDownload.contains(download.video!!.url)) {
|
||||||
|
it.preventDownload.add(download.video!!.url);
|
||||||
|
savePlaylistDownload(it);
|
||||||
|
Logger.w(TAG, "Preventing further download attempts in playlist [${it.id}] for [${download.name}]:${download.video?.url}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun checkForDownloadsTodos() {
|
fun checkForDownloadsTodos() {
|
||||||
val hasPlaylistChanged = checkForOutdatedPlaylists();
|
val hasPlaylistChanged = checkForOutdatedPlaylists();
|
||||||
@ -304,6 +321,32 @@ class StateDownloads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun downloadSubtitles(subtitle: ISubtitleSource, contentResolver: ContentResolver): SubtitleRawSource? {
|
||||||
|
val subtitleUri = subtitle.getSubtitlesURI();
|
||||||
|
if(subtitleUri == null)
|
||||||
|
return null;
|
||||||
|
var subtitles: String? = null;
|
||||||
|
if ("file" == subtitleUri.scheme) {
|
||||||
|
val inputStream = contentResolver.openInputStream(subtitleUri);
|
||||||
|
inputStream?.use { stream ->
|
||||||
|
val reader = stream.bufferedReader();
|
||||||
|
subtitles = reader.use { it.readText() };
|
||||||
|
}
|
||||||
|
} else if ("http" == subtitleUri.scheme || "https" == subtitleUri.scheme) {
|
||||||
|
val client = ManagedHttpClient();
|
||||||
|
val subtitleResponse = client.get(subtitleUri.toString());
|
||||||
|
if (!subtitleResponse.isOk) {
|
||||||
|
throw Exception("Cannot fetch subtitles from source '${subtitleUri}': ${subtitleResponse.code}");
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitles = subtitleResponse.body?.toString()
|
||||||
|
?: throw Exception("Subtitles are invalid '${subtitleUri}': ${subtitleResponse.code}");
|
||||||
|
} else {
|
||||||
|
throw NotImplementedError("Unsuported scheme");
|
||||||
|
}
|
||||||
|
return if (subtitles != null) SubtitleRawSource(subtitle.name, subtitle.format, subtitles!!) else null;
|
||||||
|
}
|
||||||
|
|
||||||
fun cleanupDownloads(): Pair<Int, Long> {
|
fun cleanupDownloads(): Pair<Int, Long> {
|
||||||
val expected = getDownloadedVideos();
|
val expected = getDownloadedVideos();
|
||||||
val validFiles = HashSet(expected.flatMap { it.videoSource.map { it.filePath } + it.audioSource.map { it.filePath } });
|
val validFiles = HashSet(expected.flatMap { it.videoSource.map { it.filePath } + it.audioSource.map { it.filePath } });
|
||||||
|
@ -5,8 +5,10 @@ import android.graphics.drawable.Animatable
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
|
||||||
class Loader : LinearLayout {
|
class Loader : LinearLayout {
|
||||||
@ -15,7 +17,7 @@ class Loader : LinearLayout {
|
|||||||
private val _animatable: Animatable;
|
private val _animatable: Animatable;
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_loader, this, true);
|
inflate(context, R.layout.view_loader, this);
|
||||||
_imageLoader = findViewById(R.id.image_loader);
|
_imageLoader = findViewById(R.id.image_loader);
|
||||||
_animatable = _imageLoader.drawable as Animatable;
|
_animatable = _imageLoader.drawable as Animatable;
|
||||||
|
|
||||||
@ -29,6 +31,18 @@ class Loader : LinearLayout {
|
|||||||
|
|
||||||
visibility = View.GONE;
|
visibility = View.GONE;
|
||||||
}
|
}
|
||||||
|
constructor(context: Context, automatic: Boolean, height: Int = -1) : super(context) {
|
||||||
|
inflate(context, R.layout.view_loader, this);
|
||||||
|
_imageLoader = findViewById(R.id.image_loader);
|
||||||
|
_animatable = _imageLoader.drawable as Animatable;
|
||||||
|
_automatic = automatic;
|
||||||
|
|
||||||
|
if(height > 0) {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
visibility = View.GONE;
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
super.onAttachedToWindow()
|
super.onAttachedToWindow()
|
||||||
|
@ -68,6 +68,7 @@ class ActiveDownloadItem: LinearLayout {
|
|||||||
|
|
||||||
_videoCancel.setOnClickListener {
|
_videoCancel.setOnClickListener {
|
||||||
StateDownloads.instance.removeDownload(_download);
|
StateDownloads.instance.removeDownload(_download);
|
||||||
|
StateDownloads.instance.preventPlaylistDownload(_download);
|
||||||
};
|
};
|
||||||
|
|
||||||
_download.onProgressChanged.subscribe(this) {
|
_download.onProgressChanged.subscribe(this) {
|
||||||
|
@ -40,7 +40,7 @@ class SlideUpMenuOverlay : RelativeLayout {
|
|||||||
_groupItems = listOf();
|
_groupItems = listOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, items: List<View>): super(context){
|
constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, items: List<View>, hideButtons: Boolean = false): super(context){
|
||||||
init(animated, okText);
|
init(animated, okText);
|
||||||
_container = parent;
|
_container = parent;
|
||||||
if(!_container!!.children.contains(this)) {
|
if(!_container!!.children.contains(this)) {
|
||||||
@ -50,6 +50,12 @@ class SlideUpMenuOverlay : RelativeLayout {
|
|||||||
_textTitle.text = titleText;
|
_textTitle.text = titleText;
|
||||||
_groupItems = items;
|
_groupItems = items;
|
||||||
|
|
||||||
|
if(hideButtons) {
|
||||||
|
_textCancel.visibility = GONE;
|
||||||
|
_textOK.visibility = GONE;
|
||||||
|
_textTitle.textAlignment = TextView.TEXT_ALIGNMENT_CENTER;
|
||||||
|
}
|
||||||
|
|
||||||
setItems(items);
|
setItems(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user