mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-04 08:34:28 +02:00
Removed exporting service.
This commit is contained in:
parent
c76ef7f19b
commit
bc550ae8f5
@ -41,9 +41,6 @@
|
|||||||
<service android:name=".services.DownloadService"
|
<service android:name=".services.DownloadService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
<service android:name=".services.ExportingService"
|
|
||||||
android:enabled="true"
|
|
||||||
android:foregroundServiceType="dataSync" />
|
|
||||||
|
|
||||||
<receiver android:name=".receivers.MediaControlReceiver" />
|
<receiver android:name=".receivers.MediaControlReceiver" />
|
||||||
<receiver android:name=".receivers.AudioNoisyReceiver" />
|
<receiver android:name=".receivers.AudioNoisyReceiver" />
|
||||||
|
@ -1,47 +1,37 @@
|
|||||||
package com.futo.platformplayer.downloads
|
package com.futo.platformplayer.downloads
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.arthenica.ffmpegkit.*
|
import com.arthenica.ffmpegkit.FFmpegKit
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.*
|
import com.arthenica.ffmpegkit.LogCallback
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.arthenica.ffmpegkit.ReturnCode
|
||||||
|
import com.arthenica.ffmpegkit.StatisticsCallback
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
||||||
import com.futo.platformplayer.helpers.FileHelper.Companion.sanitizeFileName
|
import com.futo.platformplayer.helpers.FileHelper.Companion.sanitizeFileName
|
||||||
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.toHumanBitrate
|
import com.futo.platformplayer.toHumanBitrate
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
import java.io.*
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CancellationException
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
class VideoExport {
|
class VideoExport {
|
||||||
var state: State = State.QUEUED;
|
|
||||||
|
|
||||||
var videoLocal: VideoLocal;
|
var videoLocal: VideoLocal;
|
||||||
var videoSource: LocalVideoSource?;
|
var videoSource: LocalVideoSource?;
|
||||||
var audioSource: LocalAudioSource?;
|
var audioSource: LocalAudioSource?;
|
||||||
var subtitleSource: LocalSubtitleSource?;
|
var subtitleSource: LocalSubtitleSource?;
|
||||||
|
|
||||||
var progress: Double = 0.0;
|
|
||||||
var isCancelled = false;
|
|
||||||
|
|
||||||
var error: String? = null;
|
|
||||||
|
|
||||||
@kotlinx.serialization.Transient
|
|
||||||
val onStateChanged = Event1<State>();
|
|
||||||
@kotlinx.serialization.Transient
|
|
||||||
val onProgressChanged = Event1<Double>();
|
|
||||||
|
|
||||||
fun changeState(newState: State) {
|
|
||||||
state = newState;
|
|
||||||
onStateChanged.emit(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) {
|
constructor(videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) {
|
||||||
this.videoLocal = videoLocal;
|
this.videoLocal = videoLocal;
|
||||||
this.videoSource = videoSource;
|
this.videoSource = videoSource;
|
||||||
@ -50,8 +40,6 @@ class VideoExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null): DocumentFile = coroutineScope {
|
suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null): DocumentFile = coroutineScope {
|
||||||
if(isCancelled) throw CancellationException("Export got cancelled");
|
|
||||||
|
|
||||||
val v = videoSource;
|
val v = videoSource;
|
||||||
val a = audioSource;
|
val a = audioSource;
|
||||||
val s = subtitleSource;
|
val s = subtitleSource;
|
||||||
@ -107,7 +95,6 @@ class VideoExport {
|
|||||||
throw Exception("Cannot export when no audio or video source is set.");
|
throw Exception("Cannot export when no audio or video source is set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgressChanged.emit(100.0);
|
|
||||||
return@coroutineScope outputFile;
|
return@coroutineScope outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.*
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.downloads.VideoDownload
|
import com.futo.platformplayer.downloads.VideoDownload
|
||||||
import com.futo.platformplayer.downloads.VideoLocal
|
import com.futo.platformplayer.downloads.VideoLocal
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
@ -16,12 +16,13 @@ import com.futo.platformplayer.models.Playlist
|
|||||||
import com.futo.platformplayer.states.StateDownloads
|
import com.futo.platformplayer.states.StateDownloads
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.states.StatePlaylists
|
import com.futo.platformplayer.states.StatePlaylists
|
||||||
|
import com.futo.platformplayer.toHumanBytesSize
|
||||||
import com.futo.platformplayer.views.AnyInsertedAdapterView
|
import com.futo.platformplayer.views.AnyInsertedAdapterView
|
||||||
import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop
|
import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop
|
||||||
import com.futo.platformplayer.views.others.ProgressBar
|
|
||||||
import com.futo.platformplayer.views.adapters.viewholders.VideoDownloadViewHolder
|
import com.futo.platformplayer.views.adapters.viewholders.VideoDownloadViewHolder
|
||||||
import com.futo.platformplayer.views.items.ActiveDownloadItem
|
import com.futo.platformplayer.views.items.ActiveDownloadItem
|
||||||
import com.futo.platformplayer.views.items.PlaylistDownloadItem
|
import com.futo.platformplayer.views.items.PlaylistDownloadItem
|
||||||
|
import com.futo.platformplayer.views.others.ProgressBar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -64,16 +65,6 @@ class DownloadsFragment : MainFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
StateDownloads.instance.onExportsChanged.subscribe(this) {
|
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
|
||||||
try {
|
|
||||||
Logger.i(TAG, "Reloading UI for exports");
|
|
||||||
_view?.reloadUI()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed to reload UI for exports", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@ -81,7 +72,6 @@ class DownloadsFragment : MainFragment() {
|
|||||||
|
|
||||||
StateDownloads.instance.onDownloadsChanged.remove(this);
|
StateDownloads.instance.onDownloadsChanged.remove(this);
|
||||||
StateDownloads.instance.onDownloadedChanged.remove(this);
|
StateDownloads.instance.onDownloadedChanged.remove(this);
|
||||||
StateDownloads.instance.onExportsChanged.remove(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DownloadsView : LinearLayout {
|
private class DownloadsView : LinearLayout {
|
||||||
|
@ -1,236 +0,0 @@
|
|||||||
package com.futo.platformplayer.services
|
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ServiceInfo
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import com.futo.platformplayer.R
|
|
||||||
import com.futo.platformplayer.activities.MainActivity
|
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
|
||||||
import com.futo.platformplayer.downloads.VideoExport
|
|
||||||
import com.futo.platformplayer.logging.Logger
|
|
||||||
import com.futo.platformplayer.share
|
|
||||||
import com.futo.platformplayer.states.Announcement
|
|
||||||
import com.futo.platformplayer.states.AnnouncementType
|
|
||||||
import com.futo.platformplayer.states.StateAnnouncement
|
|
||||||
import com.futo.platformplayer.states.StateDownloads
|
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
|
|
||||||
class ExportingService : Service() {
|
|
||||||
private val TAG = "ExportingService";
|
|
||||||
|
|
||||||
private val EXPORT_NOTIF_ID = 4;
|
|
||||||
private val EXPORT_NOTIF_TAG = "export";
|
|
||||||
private val EXPORT_NOTIF_CHANNEL_ID = "exportChannel";
|
|
||||||
private val EXPORT_NOTIF_CHANNEL_NAME = "Export";
|
|
||||||
|
|
||||||
//Context
|
|
||||||
private val _scope: CoroutineScope = CoroutineScope(Dispatchers.Default);
|
|
||||||
private var _notificationManager: NotificationManager? = null;
|
|
||||||
private var _notificationChannel: NotificationChannel? = null;
|
|
||||||
|
|
||||||
private val _client = ManagedHttpClient();
|
|
||||||
|
|
||||||
private var _started = false;
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
Logger.i(TAG, "onStartCommand");
|
|
||||||
|
|
||||||
synchronized(this) {
|
|
||||||
if(_started)
|
|
||||||
return START_STICKY;
|
|
||||||
|
|
||||||
if(!FragmentedStorage.isInitialized) {
|
|
||||||
closeExportSession();
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
_started = true;
|
|
||||||
}
|
|
||||||
setupNotificationRequirements();
|
|
||||||
|
|
||||||
_callOnStarted?.invoke(this);
|
|
||||||
_instance = this;
|
|
||||||
|
|
||||||
_scope.launch {
|
|
||||||
try {
|
|
||||||
doExporting();
|
|
||||||
}
|
|
||||||
catch(ex: Throwable) {
|
|
||||||
try {
|
|
||||||
StateAnnouncement.instance.registerAnnouncementSession(
|
|
||||||
Announcement(
|
|
||||||
"rootExportException",
|
|
||||||
"An root export service exception happened",
|
|
||||||
ex.message ?: "",
|
|
||||||
AnnouncementType.SESSION,
|
|
||||||
OffsetDateTime.now()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch(_: Throwable){}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
fun setupNotificationRequirements() {
|
|
||||||
_notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
|
|
||||||
_notificationChannel = NotificationChannel(EXPORT_NOTIF_CHANNEL_ID, EXPORT_NOTIF_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).apply {
|
|
||||||
this.enableVibration(false);
|
|
||||||
this.setSound(null, null);
|
|
||||||
};
|
|
||||||
_notificationManager!!.createNotificationChannel(_notificationChannel!!);
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
Logger.i(TAG, "onCreate");
|
|
||||||
super.onCreate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(p0: Intent?): IBinder? {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun doExporting() {
|
|
||||||
Logger.i(TAG, "doExporting - Starting Exports");
|
|
||||||
val ignore = mutableListOf<VideoExport>();
|
|
||||||
var currentExport: VideoExport? = StateDownloads.instance.getExporting().firstOrNull();
|
|
||||||
while (currentExport != null)
|
|
||||||
{
|
|
||||||
try{
|
|
||||||
notifyExport(currentExport);
|
|
||||||
doExport(applicationContext, currentExport);
|
|
||||||
}
|
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed export [${currentExport.videoLocal.name}]: ${ex.message}", ex);
|
|
||||||
currentExport.error = ex.message;
|
|
||||||
currentExport.changeState(VideoExport.State.ERROR);
|
|
||||||
ignore.add(currentExport);
|
|
||||||
|
|
||||||
//Give it a sec
|
|
||||||
Thread.sleep(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentExport = StateDownloads.instance.getExporting().filter { !ignore.contains(it) }.firstOrNull();
|
|
||||||
}
|
|
||||||
Logger.i(TAG, "doExporting - Ending Exports");
|
|
||||||
stopService(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun doExport(context: Context, export: VideoExport) {
|
|
||||||
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
|
||||||
|
|
||||||
export.changeState(VideoExport.State.EXPORTING);
|
|
||||||
|
|
||||||
var lastNotifyTime: Long = 0L;
|
|
||||||
val file = export.export(context) { progress ->
|
|
||||||
export.progress = progress;
|
|
||||||
|
|
||||||
val currentTime = System.currentTimeMillis();
|
|
||||||
if (currentTime - lastNotifyTime > 500) {
|
|
||||||
notifyExport(export);
|
|
||||||
lastNotifyTime = currentTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export.changeState(VideoExport.State.COMPLETED);
|
|
||||||
Logger.i(TAG, "Export [${export.videoLocal.name}] finished");
|
|
||||||
StateDownloads.instance.removeExport(export);
|
|
||||||
notifyExport(export);
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "File exported", "Exported [${file.uri}]", AnnouncementType.SESSION, time = null, category = "download", actionButton = "Open") {
|
|
||||||
file.share(this@ExportingService);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notifyExport(export: VideoExport) {
|
|
||||||
val channel = _notificationChannel ?: return;
|
|
||||||
|
|
||||||
val bringUpIntent = Intent(this, MainActivity::class.java);
|
|
||||||
bringUpIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
||||||
bringUpIntent.action = "TAB";
|
|
||||||
bringUpIntent.putExtra("TAB", "Exports");
|
|
||||||
|
|
||||||
var builder = NotificationCompat.Builder(this, EXPORT_NOTIF_TAG)
|
|
||||||
.setSmallIcon(R.drawable.ic_export)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setSilent(true)
|
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE))
|
|
||||||
.setContentTitle("${export.state}: ${export.videoLocal.name}")
|
|
||||||
.setContentText(export.getExportInfo())
|
|
||||||
.setProgress(100, (export.progress * 100).toInt(), export.progress == 0.0)
|
|
||||||
.setChannelId(channel.id)
|
|
||||||
|
|
||||||
val notif = builder.build();
|
|
||||||
notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR;
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
startForeground(EXPORT_NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
|
||||||
} else {
|
|
||||||
startForeground(EXPORT_NOTIF_ID, notif);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeExportSession() {
|
|
||||||
Logger.i(TAG, "closeExportSession");
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
|
||||||
_notificationManager?.cancel(EXPORT_NOTIF_ID);
|
|
||||||
stopService();
|
|
||||||
_started = false;
|
|
||||||
super.stopSelf();
|
|
||||||
}
|
|
||||||
override fun onDestroy() {
|
|
||||||
Logger.i(TAG, "onDestroy");
|
|
||||||
_instance = null;
|
|
||||||
_scope.cancel("onDestroy");
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var _instance: ExportingService? = null;
|
|
||||||
private var _callOnStarted: ((ExportingService)->Unit)? = null;
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun getOrCreateService(context: Context, handle: ((ExportingService)->Unit)? = null) {
|
|
||||||
if(!FragmentedStorage.isInitialized)
|
|
||||||
return;
|
|
||||||
if(_instance == null) {
|
|
||||||
_callOnStarted = handle;
|
|
||||||
val intent = Intent(context, ExportingService::class.java);
|
|
||||||
context.startForegroundService(intent);
|
|
||||||
}
|
|
||||||
else _instance?.let {
|
|
||||||
if(handle != null)
|
|
||||||
handle(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Synchronized
|
|
||||||
fun getService() : ExportingService? {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun stopService(service: ExportingService? = null) {
|
|
||||||
(service ?: _instance)?.let {
|
|
||||||
if(_instance == it)
|
|
||||||
_instance = null;
|
|
||||||
it.closeExportSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -445,9 +445,6 @@ class StateApp {
|
|||||||
DownloadService.getOrCreateService(context);
|
DownloadService.getOrCreateService(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.i(TAG, "MainApp Started: Check [Exports]");
|
|
||||||
StateDownloads.instance.checkForExportTodos();
|
|
||||||
|
|
||||||
Logger.i(TAG, "MainApp Started: Initialize [AutoUpdate]");
|
Logger.i(TAG, "MainApp Started: Initialize [AutoUpdate]");
|
||||||
val autoUpdateEnabled = Settings.instance.autoUpdate.isAutoUpdateEnabled();
|
val autoUpdateEnabled = Settings.instance.autoUpdate.isAutoUpdateEnabled();
|
||||||
val shouldDownload = Settings.instance.autoUpdate.shouldDownload();
|
val shouldDownload = Settings.instance.autoUpdate.shouldDownload();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package com.futo.platformplayer.states
|
package com.futo.platformplayer.states
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
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.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.models.streams.sources.IAudioUrlSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
|
||||||
@ -27,10 +27,14 @@ import com.futo.platformplayer.models.DiskUsage
|
|||||||
import com.futo.platformplayer.models.Playlist
|
import com.futo.platformplayer.models.Playlist
|
||||||
import com.futo.platformplayer.models.PlaylistDownloaded
|
import com.futo.platformplayer.models.PlaylistDownloaded
|
||||||
import com.futo.platformplayer.services.DownloadService
|
import com.futo.platformplayer.services.DownloadService
|
||||||
import com.futo.platformplayer.services.ExportingService
|
import com.futo.platformplayer.share
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Used to maintain downloads
|
* Used to maintain downloads
|
||||||
@ -50,12 +54,8 @@ class StateDownloads {
|
|||||||
private val _downloadPlaylists = FragmentedStorage.storeJson<PlaylistDownloadDescriptor>("playlistDownloads")
|
private val _downloadPlaylists = FragmentedStorage.storeJson<PlaylistDownloadDescriptor>("playlistDownloads")
|
||||||
.load();
|
.load();
|
||||||
|
|
||||||
private val _exporting = FragmentedStorage.storeJson<VideoExport>("exporting")
|
|
||||||
.load();
|
|
||||||
|
|
||||||
private lateinit var _downloadedSet: HashSet<PlatformID>;
|
private lateinit var _downloadedSet: HashSet<PlatformID>;
|
||||||
|
|
||||||
val onExportsChanged = Event0();
|
|
||||||
val onDownloadsChanged = Event0();
|
val onDownloadsChanged = Event0();
|
||||||
val onDownloadedChanged = Event0();
|
val onDownloadedChanged = Event0();
|
||||||
|
|
||||||
@ -457,17 +457,6 @@ class StateDownloads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
val currentDownloads = _downloaded.getItems().map { it.url }.toHashSet();
|
|
||||||
val exporting = _exporting.findItems { !currentDownloads.contains(it.videoLocal.url) };
|
|
||||||
for (export in exporting)
|
|
||||||
_exporting.delete(export);
|
|
||||||
}
|
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed to delete dangling export:", ex);
|
|
||||||
UIDialogs.toast("Failed to delete dangling export:\n" + ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair(totalDeletedCount, totalDeleted);
|
return Pair(totalDeletedCount, totalDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,64 +464,39 @@ class StateDownloads {
|
|||||||
return _downloadsDirectory;
|
return _downloadsDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun export(context: Context, videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) {
|
||||||
|
var lastNotifyTime = -1L;
|
||||||
|
|
||||||
|
UIDialogs.showDialogProgress(context) {
|
||||||
//Export
|
it.setText("Exporting content..");
|
||||||
fun getExporting(): List<VideoExport> {
|
it.setProgress(0f);
|
||||||
return _exporting.getItems();
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
}
|
val export = VideoExport(videoLocal, videoSource, audioSource, subtitleSource);
|
||||||
fun checkForExportTodos() {
|
|
||||||
if(_exporting.hasItems()) {
|
|
||||||
StateApp.withContext {
|
|
||||||
ExportingService.getOrCreateService(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateExport(export: VideoExport) {
|
|
||||||
if(_exporting.hasItem { it.videoLocal.url == export.videoLocal.url })
|
|
||||||
throw AlreadyQueuedException("Video [${export.videoLocal.name}] is already queued for export");
|
|
||||||
}
|
|
||||||
fun export(videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, notify: Boolean = true) {
|
|
||||||
val shortName = if(videoLocal.name.length > 23)
|
|
||||||
videoLocal.name.substring(0, 20) + "...";
|
|
||||||
else
|
|
||||||
videoLocal.name;
|
|
||||||
|
|
||||||
val videoExport = VideoExport(videoLocal, videoSource, audioSource, subtitleSource);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
validateExport(videoExport);
|
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
|
||||||
_exporting.save(videoExport);
|
|
||||||
|
|
||||||
if(notify) {
|
val file = export.export(context) { progress ->
|
||||||
UIDialogs.toast("Exporting [${shortName}]");
|
val now = System.currentTimeMillis();
|
||||||
StateApp.withContext { ExportingService.getOrCreateService(it) };
|
if (lastNotifyTime == -1L || now - lastNotifyTime > 100) {
|
||||||
onExportsChanged.emit();
|
it.setProgress(progress);
|
||||||
}
|
lastNotifyTime = now;
|
||||||
}
|
|
||||||
catch (ex: AlreadyQueuedException) {
|
|
||||||
Logger.e(TAG, "File is already queued for export.", ex);
|
|
||||||
StateApp.withContext { ExportingService.getOrCreateService(it) };
|
|
||||||
}
|
|
||||||
catch(ex: Throwable) {
|
|
||||||
StateApp.withContext {
|
|
||||||
UIDialogs.showDialog(
|
|
||||||
it,
|
|
||||||
R.drawable.ic_error,
|
|
||||||
"Failed to start export due to:\n${ex.message}", null, null,
|
|
||||||
0,
|
|
||||||
UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
it.setProgress(100.0f)
|
||||||
|
it.dismiss()
|
||||||
|
|
||||||
fun removeExport(export: VideoExport) {
|
StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "File exported", "Exported [${file.uri}]", AnnouncementType.SESSION, time = null, category = "download", actionButton = "Open") {
|
||||||
_exporting.delete(export);
|
file.share(context);
|
||||||
export.isCancelled = true;
|
};
|
||||||
onExportsChanged.emit();
|
}
|
||||||
|
} catch(ex: Throwable) {
|
||||||
|
Logger.e(TAG, "Failed export [${export.videoLocal.name}]: ${ex.message}", ex);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.futo.platformplayer.views
|
package com.futo.platformplayer.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -49,6 +48,7 @@ class MonetizationView : LinearLayout {
|
|||||||
|
|
||||||
private val _taskLoadMerchandise = TaskHandler<String, List<StoreItem>>(StateApp.instance.scopeGetter, { url ->
|
private val _taskLoadMerchandise = TaskHandler<String, List<StoreItem>>(StateApp.instance.scopeGetter, { url ->
|
||||||
val client = ManagedHttpClient();
|
val client = ManagedHttpClient();
|
||||||
|
Logger.i(TAG, "Loading https://storecache.grayjay.app/StoreData?url=$url")
|
||||||
val result = client.get("https://storecache.grayjay.app/StoreData?url=$url")
|
val result = client.get("https://storecache.grayjay.app/StoreData?url=$url")
|
||||||
if (!result.isOk) {
|
if (!result.isOk) {
|
||||||
throw Exception("Failed to retrieve store data.");
|
throw Exception("Failed to retrieve store data.");
|
||||||
|
@ -16,6 +16,8 @@ import com.futo.platformplayer.states.StateApp
|
|||||||
import com.futo.platformplayer.states.StateDownloads
|
import com.futo.platformplayer.states.StateDownloads
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.views.adapters.AnyAdapter
|
import com.futo.platformplayer.views.adapters.AnyAdapter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<VideoLocal>(
|
class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<VideoLocal>(
|
||||||
@ -57,10 +59,14 @@ class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<
|
|||||||
return@changeExternalDownloadDirectory;
|
return@changeExternalDownloadDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||||
|
StateDownloads.instance.export(_viewGroup.context, v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||||
|
StateDownloads.instance.export(_viewGroup.context, v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user