mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Async fixes, local file playback support
This commit is contained in:
@@ -67,6 +67,7 @@ class ScriptException extends Error {
|
|||||||
super(arguments[0]);
|
super(arguments[0]);
|
||||||
this.plugin_type = "ScriptException";
|
this.plugin_type = "ScriptException";
|
||||||
this.message = arguments[0];
|
this.message = arguments[0];
|
||||||
|
this.msg = arguments[0];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
super(msg);
|
super(msg);
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ fun V8ObjectToHashMap(obj: V8ValueObject?): HashMap<String, String> {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
||||||
val latch = CountDownLatch(1);
|
val latch = CountDownLatch(1);
|
||||||
var promiseResult: T? = null;
|
var promiseResult: T? = null;
|
||||||
@@ -204,16 +203,19 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
override fun onFulfilled(p0: V8Value?) {
|
override fun onFulfilled(p0: V8Value?) {
|
||||||
if(p0 is V8ValueError)
|
if(p0 is V8ValueError)
|
||||||
promiseException = ScriptExecutionException(plugin.config, p0.message);
|
promiseException = ScriptExecutionException(plugin.config, p0.message);
|
||||||
else
|
else {
|
||||||
|
if(p0 is V8ValueObject)
|
||||||
|
p0.setWeak();
|
||||||
promiseResult = p0 as T;
|
promiseResult = p0 as T;
|
||||||
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
override fun onRejected(p0: V8Value?) {
|
override fun onRejected(p0: V8Value?) {
|
||||||
promiseException = (NotImplementedError("onRejected promise not implemented.."));
|
promiseException = p0?.toException(plugin.config);
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
override fun onCatch(p0: V8Value?) {
|
override fun onCatch(p0: V8Value?) {
|
||||||
promiseException = (NotImplementedError("onCatch promise not implemented.."));
|
promiseException = p0?.toException(plugin.config);
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -223,8 +225,25 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
promiseException = CancellationException("Cancelled by system");
|
promiseException = CancellationException("Cancelled by system");
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
plugin.unbusy {
|
//Logger.i("V8", "V8ValueBlocking started (Busy) [" + blockCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()+ ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString());
|
||||||
latch.await();
|
|
||||||
|
|
||||||
|
if(!promise.isPending) {
|
||||||
|
try {
|
||||||
|
Logger.i("V8", "V8Promise resolved synchronously");
|
||||||
|
if(promise.isFulfilled)
|
||||||
|
promiseResult = promise.getResult<T>();
|
||||||
|
else
|
||||||
|
promiseException = promise.getResult<V8Value>().toException(plugin.config);
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
promiseException = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
plugin.unbusy {
|
||||||
|
latch.await();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(promiseException != null)
|
if(promiseException != null)
|
||||||
throw promiseException!!;
|
throw promiseException!!;
|
||||||
@@ -250,11 +269,11 @@ fun <T: V8Value> V8ValuePromise.toV8ValueAsync(plugin: V8Plugin): V8Deferred<T>
|
|||||||
}
|
}
|
||||||
override fun onRejected(p0: V8Value?) {
|
override fun onRejected(p0: V8Value?) {
|
||||||
plugin.resolvePromise(promise);
|
plugin.resolvePromise(promise);
|
||||||
underlyingDef.completeExceptionally(NotImplementedError("onRejected promise not implemented.."));
|
underlyingDef.completeExceptionally(p0?.toException(plugin.config) ?: NotImplementedError("onRejected promise not implemented.."));
|
||||||
}
|
}
|
||||||
override fun onCatch(p0: V8Value?) {
|
override fun onCatch(p0: V8Value?) {
|
||||||
plugin.resolvePromise(promise);
|
plugin.resolvePromise(promise);
|
||||||
underlyingDef.completeExceptionally(NotImplementedError("onCatch promise not implemented.."));
|
underlyingDef.completeExceptionally(p0?.toException(plugin.config) ?: NotImplementedError("onCatch promise not implemented.."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -265,6 +284,20 @@ fun <T: V8Value> V8ValuePromise.toV8ValueAsync(plugin: V8Plugin): V8Deferred<T>
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun V8Value.toException(config: IV8PluginConfig): Throwable {
|
||||||
|
val p0 = this;
|
||||||
|
if(p0 is V8ValueObject) {
|
||||||
|
val pluginType = p0.getOrDefault(config, "plugin_type", "Promise Exception", "")?.let { if(!it.isNullOrBlank()) it + "" else "" }
|
||||||
|
val msg = p0.getOrDefault<String?>(config, "msg", "Promise Exception", null)
|
||||||
|
?: p0.getOrDefault(config, "message", "Promise Exception", "");
|
||||||
|
return Exception("Promise Failed: " + pluginType + msg);
|
||||||
|
}
|
||||||
|
else if(p0 is V8ValueString)
|
||||||
|
return Exception("Promise Failed:" + p0.value);
|
||||||
|
else
|
||||||
|
return NotImplementedError("onCatch promise not implemented..");
|
||||||
|
}
|
||||||
|
|
||||||
class V8Deferred<T>(val deferred: Deferred<T>, val estDuration: Int = -1): Deferred<T> by deferred {
|
class V8Deferred<T>(val deferred: Deferred<T>, val estDuration: Int = -1): Deferred<T> by deferred {
|
||||||
|
|
||||||
fun <R> convert(conversion: (result: T)->R): V8Deferred<R>{
|
fun <R> convert(conversion: (result: T)->R): V8Deferred<R>{
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ 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.models.video.LocalVideoDetails
|
||||||
import com.futo.platformplayer.casting.StateCasting
|
import com.futo.platformplayer.casting.StateCasting
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.dp
|
import com.futo.platformplayer.dp
|
||||||
@@ -768,7 +769,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
if (targetData != null) {
|
if (targetData != null) {
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
handleUrlAll(targetData)
|
handleUrlAll(targetData, intent)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.e(TAG, "Unhandled exception in handleUrlAll", e)
|
Logger.e(TAG, "Unhandled exception in handleUrlAll", e)
|
||||||
}
|
}
|
||||||
@@ -779,8 +780,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handleUrlAll(url: String) {
|
suspend fun handleUrlAll(url: String, openIntent: Intent? = null) {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
|
val intent = openIntent ?: this.intent;
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
"grayjay" -> {
|
"grayjay" -> {
|
||||||
if (url.startsWith("grayjay://license/")) {
|
if (url.startsWith("grayjay://license/")) {
|
||||||
@@ -807,11 +809,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"content" -> {
|
"content" -> {
|
||||||
if (!handleContent(url, intent.type)) {
|
if (!handleContent(url, intent?.type)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
this,
|
this,
|
||||||
R.drawable.ic_play,
|
R.drawable.ic_play,
|
||||||
getString(R.string.unknown_content_format) + " [${url}]\n[${intent.type}]",
|
getString(R.string.unknown_content_format) + " [${url}]\n[${intent?.type}]",
|
||||||
"Ok",
|
"Ok",
|
||||||
{ });
|
{ });
|
||||||
}
|
}
|
||||||
@@ -932,6 +934,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
} else if (file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
} else if (file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
||||||
return handleUnknownText(String(data));
|
return handleUnknownText(String(data));
|
||||||
}
|
}
|
||||||
|
else if (mime?.let { it.startsWith("video/") || it.startsWith("audio/") } ?: false) {
|
||||||
|
val mediaItem = LocalVideoDetails.fromContent(file, mime);
|
||||||
|
navigateWhenReady(_fragVideoDetail, mediaItem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-3
@@ -2,10 +2,24 @@ package com.futo.platformplayer.api.media.models.streams
|
|||||||
|
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalAudioContentSource
|
||||||
import com.futo.platformplayer.downloads.VideoLocal
|
import com.futo.platformplayer.downloads.VideoLocal
|
||||||
|
|
||||||
|
|
||||||
class LocalVideoUnMuxedSourceDescriptor(private val video: VideoLocal) : VideoUnMuxedSourceDescriptor() {
|
class LocalVideoUnMuxedSourceDescriptor : VideoUnMuxedSourceDescriptor {
|
||||||
override val videoSources: Array<IVideoSource> get() = video.videoSource.toTypedArray();
|
override val videoSources: Array<IVideoSource>;
|
||||||
override val audioSources: Array<IAudioSource> get() = video.audioSource.toTypedArray();
|
override val audioSources: Array<IAudioSource>;
|
||||||
|
|
||||||
|
constructor(video: VideoLocal) {
|
||||||
|
videoSources = video.videoSource.toTypedArray();
|
||||||
|
audioSources = video.audioSource.toTypedArray();
|
||||||
|
}
|
||||||
|
constructor(audio: LocalAudioContentSource) {
|
||||||
|
videoSources = arrayOf()
|
||||||
|
audioSources = arrayOf(audio);
|
||||||
|
}
|
||||||
|
constructor(videoSources: Array<IVideoSource>, audioSources: Array<IAudioSource>) {
|
||||||
|
this.videoSources = videoSources;
|
||||||
|
this.audioSources = audioSources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -14,7 +14,8 @@ class AudioUrlSource(
|
|||||||
override val language: String = Language.UNKNOWN,
|
override val language: String = Language.UNKNOWN,
|
||||||
override val duration: Long? = null,
|
override val duration: Long? = null,
|
||||||
override var priority: Boolean = false,
|
override var priority: Boolean = false,
|
||||||
override var original: Boolean = false
|
override var original: Boolean = false,
|
||||||
|
var isLocal: Boolean = false
|
||||||
) : IAudioUrlSource, IStreamMetaDataSource{
|
) : IAudioUrlSource, IStreamMetaDataSource{
|
||||||
override var streamMetaData: StreamMetaData? = null;
|
override var streamMetaData: StreamMetaData? = null;
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -14,7 +14,8 @@ open class VideoUrlSource(
|
|||||||
override val codec : String = "",
|
override val codec : String = "",
|
||||||
override val bitrate : Int? = 0,
|
override val bitrate : Int? = 0,
|
||||||
|
|
||||||
override var priority: Boolean = false
|
override var priority: Boolean = false,
|
||||||
|
var isLocal: Boolean = false
|
||||||
) : IVideoUrlSource, IStreamMetaDataSource {
|
) : IVideoUrlSource, IStreamMetaDataSource {
|
||||||
override var streamMetaData: StreamMetaData? = null;
|
override var streamMetaData: StreamMetaData? = null;
|
||||||
|
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
package com.futo.platformplayer.api.media.models.video
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.futo.platformplayer.api.media.IPlatformClient
|
||||||
|
import com.futo.platformplayer.api.media.PlatformID
|
||||||
|
import com.futo.platformplayer.api.media.Serializer
|
||||||
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
|
import com.futo.platformplayer.api.media.models.Thumbnails
|
||||||
|
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||||
|
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||||
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
|
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
|
||||||
|
import com.futo.platformplayer.api.media.models.ratings.IRating
|
||||||
|
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.LocalVideoUnMuxedSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.LocalVideoMuxedSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalAudioContentSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoContentSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
||||||
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
|
import com.futo.platformplayer.others.Language
|
||||||
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
@kotlinx.serialization.Serializable
|
||||||
|
open class LocalVideoDetails(
|
||||||
|
override val id: PlatformID,
|
||||||
|
override val name: String,
|
||||||
|
override val thumbnails: Thumbnails,
|
||||||
|
override val author: PlatformAuthorLink,
|
||||||
|
override val url: String,
|
||||||
|
override val duration: Long,
|
||||||
|
|
||||||
|
val mimeType: String? = null,
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override val datetime: OffsetDateTime?
|
||||||
|
) : IPlatformVideo, IPlatformVideoDetails {
|
||||||
|
final override val contentType: ContentType get() = ContentType.MEDIA;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
|
override val isLive: Boolean get() = false;
|
||||||
|
|
||||||
|
override val dash: IDashManifestSource? get() = null;
|
||||||
|
override val hls: IHLSManifestSource? get() = null;
|
||||||
|
override val live: IVideoSource? get() = null;
|
||||||
|
|
||||||
|
|
||||||
|
override val shareUrl: String = ""
|
||||||
|
override val viewCount: Long = -1
|
||||||
|
override val rating: IRating = RatingLikes(0)
|
||||||
|
override val description: String = "";
|
||||||
|
override val video: IVideoSourceDescriptor = (if(mimeType?.startsWith("audio/") ?: false)
|
||||||
|
(LocalVideoUnMuxedSourceDescriptor(
|
||||||
|
arrayOf(),
|
||||||
|
arrayOf(LocalAudioContentSource(url, mimeType ?: "", name))
|
||||||
|
))
|
||||||
|
else (LocalVideoMuxedSourceDescriptor(
|
||||||
|
LocalVideoContentSource(url, mimeType ?: "", name)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
override val preview: ISerializedVideoSourceDescriptor? = null;
|
||||||
|
|
||||||
|
override val subtitles: List<SubtitleRawSource> = listOf()
|
||||||
|
override val isShort: Boolean = false
|
||||||
|
|
||||||
|
fun toJson() : String {
|
||||||
|
return Json.encodeToString(this);
|
||||||
|
}
|
||||||
|
fun fromJson(str : String) : SerializedPlatformVideoDetails {
|
||||||
|
return Serializer.json.decodeFromString<SerializedPlatformVideoDetails>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? = null;
|
||||||
|
override fun getPlaybackTracker(): IPlaybackTracker? = null;
|
||||||
|
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? = null;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromFile(name: String, filePath: String, mimeType: String? = null) : LocalVideoDetails {
|
||||||
|
if(filePath.startsWith("content://"))
|
||||||
|
return fromContent(filePath, mimeType);
|
||||||
|
|
||||||
|
return LocalVideoDetails(PlatformID("FILE", filePath, null, 0, -1),
|
||||||
|
name, Thumbnails(), PlatformAuthorLink.UNKNOWN, filePath, -1, mimeType, null);
|
||||||
|
}
|
||||||
|
fun fromContent(contentUrl: String, mimeType: String? = null) : LocalVideoDetails {
|
||||||
|
var nameToUse = getFileNameFromContentUrl(contentUrl) ?: "File";
|
||||||
|
|
||||||
|
return LocalVideoDetails(PlatformID("FILE", contentUrl, null, 0, -1),
|
||||||
|
nameToUse, Thumbnails(), PlatformAuthorLink.UNKNOWN, contentUrl, -1, mimeType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
private fun getFileNameFromContentUrl(url: String): String? {
|
||||||
|
val cursor = StateApp.instance.context.contentResolver.query(url.toUri(), null, null, null, null);
|
||||||
|
cursor?.moveToFirst();
|
||||||
|
val fileName = cursor?.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||||
|
cursor?.close();
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-4
@@ -1,13 +1,23 @@
|
|||||||
package com.futo.platformplayer.api.media.platforms.local.models
|
package com.futo.platformplayer.api.media.platforms.local.models
|
||||||
|
|
||||||
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoContentSource
|
||||||
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
||||||
import com.futo.platformplayer.downloads.VideoLocal
|
import com.futo.platformplayer.downloads.VideoLocal
|
||||||
|
|
||||||
class LocalVideoMuxedSourceDescriptor(
|
class LocalVideoMuxedSourceDescriptor: VideoMuxedSourceDescriptor {
|
||||||
private val video: LocalVideoFileSource
|
override val videoSources: Array<IVideoSource>;
|
||||||
) : VideoMuxedSourceDescriptor() {
|
|
||||||
override val videoSources: Array<IVideoSource> get() = arrayOf(video);
|
constructor(video: LocalVideoFileSource) {
|
||||||
|
videoSources = arrayOf(video);
|
||||||
|
}
|
||||||
|
constructor(video: LocalVideoContentSource) {
|
||||||
|
videoSources = arrayOf(video);
|
||||||
|
}
|
||||||
|
constructor(videoSources: Array<IVideoSource>) {
|
||||||
|
this.videoSources = videoSources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.futo.platformplayer.api.media.platforms.local.models.sources
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.MediaStore.Video
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||||
|
import com.futo.platformplayer.helpers.VideoHelper
|
||||||
|
import com.futo.platformplayer.others.Language
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class LocalAudioContentSource : IAudioSource {
|
||||||
|
|
||||||
|
override val name: String;
|
||||||
|
override val container: String;
|
||||||
|
override val codec: String = ""
|
||||||
|
override val bitrate: Int = 0
|
||||||
|
override val duration: Long;
|
||||||
|
override val priority: Boolean = false;
|
||||||
|
override val language: String = Language.UNKNOWN
|
||||||
|
override val original: Boolean = false;
|
||||||
|
|
||||||
|
var contentUrl: String;
|
||||||
|
|
||||||
|
constructor(contentUrl: String, mime: String, name: String? = null) {
|
||||||
|
this.name = name ?: "File";
|
||||||
|
container = mime;
|
||||||
|
duration = 0;
|
||||||
|
|
||||||
|
this.contentUrl = contentUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package com.futo.platformplayer.api.media.platforms.local.models.sources
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.MediaStore.Video
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||||
|
import com.futo.platformplayer.helpers.VideoHelper
|
||||||
|
import com.futo.platformplayer.others.Language
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class LocalAudioFileSource: IAudioSource {
|
||||||
|
|
||||||
|
|
||||||
|
override val name: String;
|
||||||
|
override val container: String;
|
||||||
|
override val codec: String = ""
|
||||||
|
override val bitrate: Int = 0
|
||||||
|
override val duration: Long;
|
||||||
|
override val priority: Boolean = false;
|
||||||
|
override val language: String = Language.UNKNOWN;
|
||||||
|
override val original: Boolean = false;
|
||||||
|
|
||||||
|
var file: File;
|
||||||
|
|
||||||
|
constructor(file: File) {
|
||||||
|
this.file = file;
|
||||||
|
name = file.name;
|
||||||
|
container = VideoHelper.videoExtensionToMimetype(file.extension) ?: "";
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.futo.platformplayer.api.media.platforms.local.models.sources
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.MediaStore.Video
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||||
|
import com.futo.platformplayer.helpers.VideoHelper
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class LocalVideoContentSource: IVideoSource {
|
||||||
|
|
||||||
|
|
||||||
|
override val name: String;
|
||||||
|
override val width: Int;
|
||||||
|
override val height: Int;
|
||||||
|
override val container: String;
|
||||||
|
override val codec: String = ""
|
||||||
|
override val bitrate: Int = 0
|
||||||
|
override val duration: Long;
|
||||||
|
override val priority: Boolean = false;
|
||||||
|
|
||||||
|
var contentUrl: String;
|
||||||
|
|
||||||
|
constructor(contentUrl: String, mime: String, name: String? = null) {
|
||||||
|
this.name = name ?: "File";
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
container = mime;
|
||||||
|
duration = 0;
|
||||||
|
this.contentUrl = contentUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -20,7 +20,10 @@ class LocalVideoFileSource: IVideoSource {
|
|||||||
override val duration: Long;
|
override val duration: Long;
|
||||||
override val priority: Boolean = false;
|
override val priority: Boolean = false;
|
||||||
|
|
||||||
|
var file: File;
|
||||||
|
|
||||||
constructor(file: File) {
|
constructor(file: File) {
|
||||||
|
this.file = file;
|
||||||
name = file.name;
|
name = file.name;
|
||||||
width = 0;
|
width = 0;
|
||||||
height = 0;
|
height = 0;
|
||||||
|
|||||||
@@ -242,10 +242,12 @@ class V8Plugin {
|
|||||||
}
|
}
|
||||||
fun <T> busy(handle: ()->T): T {
|
fun <T> busy(handle: ()->T): T {
|
||||||
_busyLock.lock();
|
_busyLock.lock();
|
||||||
|
//Logger.i(TAG, "Busy Enter [" + _busyLock.holdCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString())
|
||||||
try {
|
try {
|
||||||
return handle();
|
return handle();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
//Logger.i(TAG, "Busy Leave [" + _busyLock.holdCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()+ ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString())
|
||||||
_busyLock.unlock();
|
_busyLock.unlock();
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManif
|
|||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalAudioContentSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalAudioFileSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoContentSource
|
||||||
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
@@ -480,6 +484,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
is IHLSManifestSource -> { swapVideoSourceHLS(videoSource); true; }
|
is IHLSManifestSource -> { swapVideoSourceHLS(videoSource); true; }
|
||||||
is IVideoUrlWidevineSource -> { swapVideoSourceUrlWidevine(videoSource); true; }
|
is IVideoUrlWidevineSource -> { swapVideoSourceUrlWidevine(videoSource); true; }
|
||||||
is IVideoUrlSource -> { swapVideoSourceUrl(videoSource); true; }
|
is IVideoUrlSource -> { swapVideoSourceUrl(videoSource); true; }
|
||||||
|
is LocalVideoFileSource -> { swapVideoSourceLocalFile(videoSource); true; }
|
||||||
|
is LocalVideoContentSource -> { swapVideoSourceLocalContent(videoSource); true; }
|
||||||
null -> { _lastVideoMediaSource = null; true;}
|
null -> { _lastVideoMediaSource = null; true;}
|
||||||
else -> throw IllegalArgumentException("Unsupported video source [${videoSource.javaClass.simpleName}]");
|
else -> throw IllegalArgumentException("Unsupported video source [${videoSource.javaClass.simpleName}]");
|
||||||
}
|
}
|
||||||
@@ -496,6 +502,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
is JSDashManifestRawAudioSource -> swapAudioSourceDashRaw(audioSource, play, resume, swapId);
|
is JSDashManifestRawAudioSource -> swapAudioSourceDashRaw(audioSource, play, resume, swapId);
|
||||||
is IAudioUrlWidevineSource -> { swapAudioSourceUrlWidevine(audioSource); true; }
|
is IAudioUrlWidevineSource -> { swapAudioSourceUrlWidevine(audioSource); true; }
|
||||||
is IAudioUrlSource -> { swapAudioSourceUrl(audioSource); true; }
|
is IAudioUrlSource -> { swapAudioSourceUrl(audioSource); true; }
|
||||||
|
is LocalAudioFileSource -> { swapAudioSourceLocalFile(audioSource); true; }
|
||||||
|
is LocalAudioContentSource -> { swapAudioSourceLocalContent(audioSource); true; }
|
||||||
null -> { _lastAudioMediaSource = null; true; }
|
null -> { _lastAudioMediaSource = null; true; }
|
||||||
else -> throw IllegalArgumentException("Unsupported video source [${audioSource.javaClass.simpleName}]");
|
else -> throw IllegalArgumentException("Unsupported video source [${audioSource.javaClass.simpleName}]");
|
||||||
}
|
}
|
||||||
@@ -514,6 +522,23 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
}
|
}
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapVideoSourceLocalFile(videoSource: LocalVideoFileSource) {
|
||||||
|
Logger.i(TAG, "Loading VideoSource [Local]");
|
||||||
|
val file = videoSource.file;
|
||||||
|
if(!file.exists())
|
||||||
|
throw IllegalArgumentException("File for this video does not exist");
|
||||||
|
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapVideoSourceLocalContent(videoSource: LocalVideoContentSource) {
|
||||||
|
Logger.i(TAG, "Loading VideoSource [Local]");
|
||||||
|
if(!videoSource.contentUrl.startsWith("content://"))
|
||||||
|
throw IllegalArgumentException("Not a content uri");
|
||||||
|
_lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
|
.createMediaSource(MediaItem.fromUri(videoSource.contentUrl));
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) {
|
private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) {
|
||||||
Logger.i(TAG, "Loading JSVideoUrlRangeSource");
|
Logger.i(TAG, "Loading JSVideoUrlRangeSource");
|
||||||
if(videoSource.hasItag) {
|
if(videoSource.hasItag) {
|
||||||
@@ -707,6 +732,23 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
}
|
}
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapAudioSourceLocalFile(audioSource: LocalAudioFileSource) {
|
||||||
|
Logger.i(TAG, "Loading VideoSource [Local]");
|
||||||
|
val file = audioSource.file;
|
||||||
|
if(!file.exists())
|
||||||
|
throw IllegalArgumentException("File for this video does not exist");
|
||||||
|
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun swapAudioSourceLocalContent(audioSource: LocalAudioContentSource) {
|
||||||
|
Logger.i(TAG, "Loading VideoSource [Local]");
|
||||||
|
if(!audioSource.contentUrl.startsWith("content://"))
|
||||||
|
throw IllegalArgumentException("Not a content uri");
|
||||||
|
_lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context))
|
||||||
|
.createMediaSource(MediaItem.fromUri(audioSource.contentUrl));
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) {
|
private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) {
|
||||||
Logger.i(TAG, "Loading JSAudioUrlRangeSource");
|
Logger.i(TAG, "Loading JSAudioUrlRangeSource");
|
||||||
if(audioSource.hasItag) {
|
if(audioSource.hasItag) {
|
||||||
|
|||||||
Submodule app/src/stable/assets/sources/odysee updated: 736c6b953a...89ad7e9a4b
Submodule app/src/stable/assets/sources/rumble updated: 3368dfaa2c...3a7087ccb0
Submodule app/src/stable/assets/sources/soundcloud updated: 048acef152...49db9e3e15
Submodule app/src/stable/assets/sources/youtube updated: f1465628ec...72c0cf181d
@@ -8,6 +8,16 @@
|
|||||||
<receiver android:name=".receivers.InstallReceiver" />
|
<receiver android:name=".receivers.InstallReceiver" />
|
||||||
|
|
||||||
<activity android:name=".activities.MainActivity" android:launchMode="singleInstance">
|
<activity android:name=".activities.MainActivity" android:launchMode="singleInstance">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
<data android:mimeType="audio/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||||
|
|||||||
Submodule app/src/unstable/assets/sources/odysee updated: 736c6b953a...89ad7e9a4b
Submodule app/src/unstable/assets/sources/rumble updated: 3368dfaa2c...3a7087ccb0
Submodule app/src/unstable/assets/sources/soundcloud updated: 048acef152...49db9e3e15
Submodule app/src/unstable/assets/sources/youtube updated: f1465628ec...72c0cf181d
Reference in New Issue
Block a user