mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Export playlist support
This commit is contained in:
parent
65ae8610fd
commit
4dce8d6a80
6 changed files with 92 additions and 6 deletions
|
@ -375,8 +375,8 @@ class VideoDownload {
|
|||
else throw DownloadException("Could not find a valid video or audio source for download")
|
||||
|
||||
if(asource is JSSource) {
|
||||
this.hasVideoRequestExecutor = this.hasVideoRequestExecutor || asource.hasRequestExecutor;
|
||||
this.requiresLiveVideoSource = this.hasVideoRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate);
|
||||
this.hasAudioRequestExecutor = this.hasAudioRequestExecutor || asource.hasRequestExecutor;
|
||||
this.requiresLiveAudioSource = this.hasAudioRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate);
|
||||
}
|
||||
|
||||
if(asource == null) {
|
||||
|
|
|
@ -39,7 +39,7 @@ class VideoExport {
|
|||
this.subtitleSource = subtitleSource;
|
||||
}
|
||||
|
||||
suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null): DocumentFile = coroutineScope {
|
||||
suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null, documentRoot: DocumentFile? = null): DocumentFile = coroutineScope {
|
||||
val v = videoSource;
|
||||
val a = audioSource;
|
||||
val s = subtitleSource;
|
||||
|
@ -50,7 +50,7 @@ class VideoExport {
|
|||
if (s != null) sourceCount++;
|
||||
|
||||
val outputFile: DocumentFile?;
|
||||
val downloadRoot = StateApp.instance.getExternalDownloadDirectory(context) ?: throw Exception("External download directory is not set");
|
||||
val downloadRoot = documentRoot ?: StateApp.instance.getExternalDownloadDirectory(context) ?: throw Exception("External download directory is not set");
|
||||
if (sourceCount > 1) {
|
||||
val outputFileName = videoLocal.name.sanitizeFileName(true) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container);
|
||||
val f = downloadRoot.createFile("video/mp4", outputFileName)
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||
import androidx.core.app.ShareCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.activities.IWithResultLauncher
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
|
@ -78,6 +79,14 @@ class PlaylistFragment : MainFragment() {
|
|||
val nameInput = SlideUpMenuTextInput(context, context.getString(R.string.name));
|
||||
val editPlaylistOverlay = SlideUpMenuOverlay(context, overlayContainer, context.getString(R.string.edit_playlist), context.getString(R.string.ok), false, nameInput);
|
||||
|
||||
_buttonExport.setOnClickListener {
|
||||
_playlist?.let {
|
||||
val context = StateApp.instance.contextOrNull ?: return@let;
|
||||
if(context is IWithResultLauncher)
|
||||
StateDownloads.instance.exportPlaylist(context, it.id);
|
||||
}
|
||||
};
|
||||
|
||||
_buttonDownload.visibility = View.VISIBLE;
|
||||
editPlaylistOverlay.onOK.subscribe {
|
||||
val text = nameInput.text;
|
||||
|
@ -176,6 +185,7 @@ class PlaylistFragment : MainFragment() {
|
|||
setVideos(parameter.videos, true)
|
||||
setMetadata(parameter.videos.size, parameter.videos.sumOf { it.duration })
|
||||
setButtonDownloadVisible(true)
|
||||
setButtonExportVisible(false)
|
||||
setButtonEditVisible(true)
|
||||
|
||||
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.id }) {
|
||||
|
|
|
@ -34,6 +34,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
protected var overlayContainer: FrameLayout
|
||||
private set;
|
||||
protected var _buttonDownload: ImageButton;
|
||||
protected var _buttonExport: ImageButton;
|
||||
private var _buttonShare: ImageButton;
|
||||
private var _buttonEdit: ImageButton;
|
||||
|
||||
|
@ -54,6 +55,8 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
_buttonEdit = findViewById(R.id.button_edit);
|
||||
_buttonDownload = findViewById(R.id.button_download);
|
||||
_buttonDownload.visibility = View.GONE;
|
||||
_buttonExport = findViewById(R.id.button_export);
|
||||
_buttonExport.visibility = View.GONE;
|
||||
|
||||
_buttonShare = findViewById(R.id.button_share);
|
||||
val onShare = _onShare;
|
||||
|
@ -68,6 +71,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
buttonShuffle.setOnClickListener { onShuffleClick(); };
|
||||
|
||||
_buttonEdit.setOnClickListener { onEditClick(); };
|
||||
setButtonExportVisible(false);
|
||||
setButtonDownloadVisible(canEdit());
|
||||
|
||||
videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged);
|
||||
|
@ -108,6 +112,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round);
|
||||
|
||||
if(isDownloading) {
|
||||
setButtonExportVisible(false);
|
||||
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
||||
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
||||
_buttonDownload.setOnClickListener {
|
||||
|
@ -117,6 +122,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
}
|
||||
}
|
||||
else if(isDownloaded) {
|
||||
setButtonExportVisible(true)
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
||||
_buttonDownload.setOnClickListener {
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
|
@ -125,6 +131,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
}
|
||||
}
|
||||
else {
|
||||
setButtonExportVisible(false);
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download);
|
||||
_buttonDownload.setOnClickListener {
|
||||
onDownload();
|
||||
|
@ -171,6 +178,9 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
protected fun setButtonDownloadVisible(isVisible: Boolean) {
|
||||
_buttonDownload.visibility = if (isVisible) View.VISIBLE else View.GONE;
|
||||
}
|
||||
protected fun setButtonExportVisible(isVisible: Boolean) {
|
||||
_buttonExport.visibility = if (isVisible) View.VISIBLE else View.GONE;
|
||||
}
|
||||
|
||||
protected fun setButtonEditVisible(isVisible: Boolean) {
|
||||
_buttonEdit.visibility = if (isVisible) View.VISIBLE else View.GONE;
|
||||
|
|
|
@ -3,9 +3,11 @@ package com.futo.platformplayer.states
|
|||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.os.StatFs
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.activities.IWithResultLauncher
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.media.PlatformID
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||
|
@ -466,6 +468,54 @@ class StateDownloads {
|
|||
return _downloadsDirectory;
|
||||
}
|
||||
|
||||
fun exportPlaylist(context: Context, playlistId: String) {
|
||||
if(context is IWithResultLauncher)
|
||||
StateApp.instance.requestDirectoryAccess(context, "Export Playlist", "To export playlist to directory", null) {
|
||||
if (it == null)
|
||||
return@requestDirectoryAccess;
|
||||
|
||||
val root = DocumentFile.fromTreeUri(context, it!!);
|
||||
|
||||
val localVideos = StateDownloads.instance.getDownloadedVideosPlaylist(playlistId)
|
||||
|
||||
var lastNotifyTime = -1L;
|
||||
|
||||
UIDialogs.showDialogProgress(context) {
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
it.setText("Exporting videos..");
|
||||
var i = 0;
|
||||
for (video in localVideos) {
|
||||
withContext(Dispatchers.Main) {
|
||||
it.setText("Exporting videos...(${i}/${localVideos.size})");
|
||||
//it.setProgress(i.toDouble() / localVideos.size);
|
||||
}
|
||||
|
||||
try {
|
||||
val export = VideoExport(video, video.videoSource.firstOrNull(), video.audioSource.firstOrNull(), video.subtitlesSources.firstOrNull());
|
||||
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
||||
|
||||
val file = export.export(context, { progress ->
|
||||
val now = System.currentTimeMillis();
|
||||
if (lastNotifyTime == -1L || now - lastNotifyTime > 100) {
|
||||
it.setProgress(progress);
|
||||
lastNotifyTime = now;
|
||||
}
|
||||
}, root);
|
||||
} catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed export [${video.name}]: ${ex.message}", ex);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
it.setProgress(1f);
|
||||
it.dismiss();
|
||||
UIDialogs.appToast("Finished exporting playlist");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun export(context: Context, videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) {
|
||||
var lastNotifyTime = -1L;
|
||||
|
||||
|
@ -477,13 +527,13 @@ class StateDownloads {
|
|||
try {
|
||||
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
||||
|
||||
val file = export.export(context) { progress ->
|
||||
val file = export.export(context, { progress ->
|
||||
val now = System.currentTimeMillis();
|
||||
if (lastNotifyTime == -1L || now - lastNotifyTime > 100) {
|
||||
it.setProgress(progress);
|
||||
lastNotifyTime = now;
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
it.setProgress(100.0f)
|
||||
|
|
|
@ -54,6 +54,22 @@
|
|||
|
||||
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_export"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/cd_button_download"
|
||||
android:background="@drawable/background_button_round"
|
||||
android:gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
android:orientation="horizontal"
|
||||
app:srcCompat="@drawable/ic_export"
|
||||
app:layout_constraintRight_toLeftOf="@id/button_share"
|
||||
app:layout_constraintTop_toTopOf="@id/button_share"
|
||||
app:tint="@color/white"
|
||||
android:padding="8dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:scaleType="fitCenter" />
|
||||
<ImageButton
|
||||
android:id="@+id/button_share"
|
||||
android:layout_width="40dp"
|
||||
|
|
Loading…
Add table
Reference in a new issue