mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-01 07:04:35 +02:00
Export playlist support
This commit is contained in:
parent
65ae8610fd
commit
4dce8d6a80
@ -375,8 +375,8 @@ class VideoDownload {
|
|||||||
else throw DownloadException("Could not find a valid video or audio source for download")
|
else throw DownloadException("Could not find a valid video or audio source for download")
|
||||||
|
|
||||||
if(asource is JSSource) {
|
if(asource is JSSource) {
|
||||||
this.hasVideoRequestExecutor = this.hasVideoRequestExecutor || asource.hasRequestExecutor;
|
this.hasAudioRequestExecutor = this.hasAudioRequestExecutor || asource.hasRequestExecutor;
|
||||||
this.requiresLiveVideoSource = this.hasVideoRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate);
|
this.requiresLiveAudioSource = this.hasAudioRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(asource == null) {
|
if(asource == null) {
|
||||||
|
@ -39,7 +39,7 @@ class VideoExport {
|
|||||||
this.subtitleSource = subtitleSource;
|
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 v = videoSource;
|
||||||
val a = audioSource;
|
val a = audioSource;
|
||||||
val s = subtitleSource;
|
val s = subtitleSource;
|
||||||
@ -50,7 +50,7 @@ class VideoExport {
|
|||||||
if (s != null) sourceCount++;
|
if (s != null) sourceCount++;
|
||||||
|
|
||||||
val outputFile: DocumentFile?;
|
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) {
|
if (sourceCount > 1) {
|
||||||
val outputFileName = videoLocal.name.sanitizeFileName(true) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container);
|
val outputFileName = videoLocal.name.sanitizeFileName(true) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container);
|
||||||
val f = downloadRoot.createFile("video/mp4", outputFileName)
|
val f = downloadRoot.createFile("video/mp4", outputFileName)
|
||||||
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.app.ShareCompat
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.*
|
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.IPlatformPlaylist
|
||||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
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 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);
|
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;
|
_buttonDownload.visibility = View.VISIBLE;
|
||||||
editPlaylistOverlay.onOK.subscribe {
|
editPlaylistOverlay.onOK.subscribe {
|
||||||
val text = nameInput.text;
|
val text = nameInput.text;
|
||||||
@ -176,6 +185,7 @@ class PlaylistFragment : MainFragment() {
|
|||||||
setVideos(parameter.videos, true)
|
setVideos(parameter.videos, true)
|
||||||
setMetadata(parameter.videos.size, parameter.videos.sumOf { it.duration })
|
setMetadata(parameter.videos.size, parameter.videos.sumOf { it.duration })
|
||||||
setButtonDownloadVisible(true)
|
setButtonDownloadVisible(true)
|
||||||
|
setButtonExportVisible(false)
|
||||||
setButtonEditVisible(true)
|
setButtonEditVisible(true)
|
||||||
|
|
||||||
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.id }) {
|
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.id }) {
|
||||||
|
@ -34,6 +34,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
protected var overlayContainer: FrameLayout
|
protected var overlayContainer: FrameLayout
|
||||||
private set;
|
private set;
|
||||||
protected var _buttonDownload: ImageButton;
|
protected var _buttonDownload: ImageButton;
|
||||||
|
protected var _buttonExport: ImageButton;
|
||||||
private var _buttonShare: ImageButton;
|
private var _buttonShare: ImageButton;
|
||||||
private var _buttonEdit: ImageButton;
|
private var _buttonEdit: ImageButton;
|
||||||
|
|
||||||
@ -54,6 +55,8 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
_buttonEdit = findViewById(R.id.button_edit);
|
_buttonEdit = findViewById(R.id.button_edit);
|
||||||
_buttonDownload = findViewById(R.id.button_download);
|
_buttonDownload = findViewById(R.id.button_download);
|
||||||
_buttonDownload.visibility = View.GONE;
|
_buttonDownload.visibility = View.GONE;
|
||||||
|
_buttonExport = findViewById(R.id.button_export);
|
||||||
|
_buttonExport.visibility = View.GONE;
|
||||||
|
|
||||||
_buttonShare = findViewById(R.id.button_share);
|
_buttonShare = findViewById(R.id.button_share);
|
||||||
val onShare = _onShare;
|
val onShare = _onShare;
|
||||||
@ -68,6 +71,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
buttonShuffle.setOnClickListener { onShuffleClick(); };
|
buttonShuffle.setOnClickListener { onShuffleClick(); };
|
||||||
|
|
||||||
_buttonEdit.setOnClickListener { onEditClick(); };
|
_buttonEdit.setOnClickListener { onEditClick(); };
|
||||||
|
setButtonExportVisible(false);
|
||||||
setButtonDownloadVisible(canEdit());
|
setButtonDownloadVisible(canEdit());
|
||||||
|
|
||||||
videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged);
|
videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged);
|
||||||
@ -108,6 +112,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round);
|
_buttonDownload.setBackgroundResource(R.drawable.background_button_round);
|
||||||
|
|
||||||
if(isDownloading) {
|
if(isDownloading) {
|
||||||
|
setButtonExportVisible(false);
|
||||||
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
||||||
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
||||||
_buttonDownload.setOnClickListener {
|
_buttonDownload.setOnClickListener {
|
||||||
@ -117,6 +122,7 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(isDownloaded) {
|
else if(isDownloaded) {
|
||||||
|
setButtonExportVisible(true)
|
||||||
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
||||||
_buttonDownload.setOnClickListener {
|
_buttonDownload.setOnClickListener {
|
||||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
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 {
|
else {
|
||||||
|
setButtonExportVisible(false);
|
||||||
_buttonDownload.setImageResource(R.drawable.ic_download);
|
_buttonDownload.setImageResource(R.drawable.ic_download);
|
||||||
_buttonDownload.setOnClickListener {
|
_buttonDownload.setOnClickListener {
|
||||||
onDownload();
|
onDownload();
|
||||||
@ -171,6 +178,9 @@ abstract class VideoListEditorView : LinearLayout {
|
|||||||
protected fun setButtonDownloadVisible(isVisible: Boolean) {
|
protected fun setButtonDownloadVisible(isVisible: Boolean) {
|
||||||
_buttonDownload.visibility = if (isVisible) View.VISIBLE else View.GONE;
|
_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) {
|
protected fun setButtonEditVisible(isVisible: Boolean) {
|
||||||
_buttonEdit.visibility = if (isVisible) View.VISIBLE else View.GONE;
|
_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.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
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.activities.IWithResultLauncher
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
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.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
@ -466,6 +468,54 @@ class StateDownloads {
|
|||||||
return _downloadsDirectory;
|
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?) {
|
fun export(context: Context, videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) {
|
||||||
var lastNotifyTime = -1L;
|
var lastNotifyTime = -1L;
|
||||||
|
|
||||||
@ -477,13 +527,13 @@ class StateDownloads {
|
|||||||
try {
|
try {
|
||||||
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
||||||
|
|
||||||
val file = export.export(context) { progress ->
|
val file = export.export(context, { progress ->
|
||||||
val now = System.currentTimeMillis();
|
val now = System.currentTimeMillis();
|
||||||
if (lastNotifyTime == -1L || now - lastNotifyTime > 100) {
|
if (lastNotifyTime == -1L || now - lastNotifyTime > 100) {
|
||||||
it.setProgress(progress);
|
it.setProgress(progress);
|
||||||
lastNotifyTime = now;
|
lastNotifyTime = now;
|
||||||
}
|
}
|
||||||
}
|
}, null);
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
it.setProgress(100.0f)
|
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
|
<ImageButton
|
||||||
android:id="@+id/button_share"
|
android:id="@+id/button_share"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user