Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Koen J 2025-02-11 10:32:24 +01:00
commit 4826b40136
13 changed files with 167 additions and 50 deletions

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,7 @@ import com.futo.platformplayer.engine.internal.V8Converter
import com.futo.platformplayer.engine.packages.PackageBridge import com.futo.platformplayer.engine.packages.PackageBridge
import com.futo.platformplayer.engine.packages.PackageDOMParser import com.futo.platformplayer.engine.packages.PackageDOMParser
import com.futo.platformplayer.engine.packages.PackageHttp import com.futo.platformplayer.engine.packages.PackageHttp
import com.futo.platformplayer.engine.packages.PackageJSDOM
import com.futo.platformplayer.engine.packages.PackageUtilities import com.futo.platformplayer.engine.packages.PackageUtilities
import com.futo.platformplayer.engine.packages.V8Package import com.futo.platformplayer.engine.packages.V8Package
import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrow
@ -264,6 +265,7 @@ class V8Plugin {
"DOMParser" -> PackageDOMParser(this) "DOMParser" -> PackageDOMParser(this)
"Http" -> PackageHttp(this, config) "Http" -> PackageHttp(this, config)
"Utilities" -> PackageUtilities(this, config) "Utilities" -> PackageUtilities(this, config)
"JSDOM" -> PackageJSDOM(this, config)
else -> if(allowNull) null else throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}"); else -> if(allowNull) null else throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}");
}; };
} }

View File

@ -0,0 +1,20 @@
package com.futo.platformplayer.engine.packages
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.states.StateApp
class PackageJSDOM : V8Package {
@Transient
private val _config: IV8PluginConfig;
override val name: String get() = "JSDOM";
override val variableName: String get() = "packageJSDOM";
constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) {
_config = config;
plugin.withDependency(StateApp.instance.contextOrNull ?: return, "scripts/JSDOM.js");
}
}

View File

@ -476,8 +476,13 @@ class ChannelFragment : MainFragment() {
R.string.subscribers R.string.subscribers
).lowercase() else "" ).lowercase() else ""
val supportsPlaylists = var supportsPlaylists = false;
StatePlatform.instance.getChannelClient(channel.url).capabilities.hasGetChannelPlaylists try {
supportsPlaylists = StatePlatform.instance.getChannelClient(channel.url).capabilities.hasGetChannelPlaylists
} catch (ex: Throwable) {
//Ignore error
Logger.e(TAG, "Failed to check if supports playlists", ex);
}
val playlistPosition = 1 val playlistPosition = 1
if (supportsPlaylists && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem( if (supportsPlaylists && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
ChannelTab.PLAYLISTS.ordinal.toLong() ChannelTab.PLAYLISTS.ordinal.toLong()

View File

@ -233,8 +233,8 @@ class DownloadsFragment : MainFragment() {
vidsToReturn = when(ordering){ vidsToReturn = when(ordering){
"downloadDateAsc" -> vidsToReturn.sortedBy { it.downloadDate ?: OffsetDateTime.MAX }; "downloadDateAsc" -> vidsToReturn.sortedBy { it.downloadDate ?: OffsetDateTime.MAX };
"downloadDateDesc" -> vidsToReturn.sortedByDescending { it.downloadDate ?: OffsetDateTime.MIN }; "downloadDateDesc" -> vidsToReturn.sortedByDescending { it.downloadDate ?: OffsetDateTime.MIN };
"nameAsc" -> vidsToReturn.sortedBy { it.name } "nameAsc" -> vidsToReturn.sortedBy { it.name.lowercase() }
"nameDesc" -> vidsToReturn.sortedByDescending { it.name } "nameDesc" -> vidsToReturn.sortedByDescending { it.name.lowercase() }
"releasedAsc" -> vidsToReturn.sortedBy { it.datetime ?: OffsetDateTime.MAX } "releasedAsc" -> vidsToReturn.sortedBy { it.datetime ?: OffsetDateTime.MAX }
"releasedDesc" -> vidsToReturn.sortedByDescending { it.datetime ?: OffsetDateTime.MIN } "releasedDesc" -> vidsToReturn.sortedByDescending { it.datetime ?: OffsetDateTime.MIN }
else -> vidsToReturn else -> vidsToReturn

View File

@ -204,8 +204,10 @@ class SubscriptionsFeedFragment : MainFragment() {
val feed = StateSubscriptions.instance.getFeed(group?.id); val feed = StateSubscriptions.instance.getFeed(group?.id);
val currentExs = feed?.exceptions ?: listOf(); val currentExs = feed?.exceptions ?: listOf();
if(currentExs != _lastExceptions && currentExs.any()) if(currentExs != _lastExceptions && currentExs.any()) {
handleExceptions(currentExs); handleExceptions(currentExs)
feed?.exceptions = listOf()
}
return@TaskHandler resp; return@TaskHandler resp;
}) })

View File

@ -11,12 +11,14 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.OrientationEventListener import android.view.OrientationEventListener
import android.view.Surface
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowInsetsController import android.view.WindowInsetsController
import android.view.WindowManager import android.view.WindowManager
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.ViewCompat.getDisplay
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.futo.platformplayer.R import com.futo.platformplayer.R
@ -37,7 +39,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.min
//region Fragment //region Fragment
@UnstableApi @UnstableApi
@ -205,7 +207,37 @@ class VideoDetailFragment() : MainFragment() {
} else if (rotationLock) { } else if (rotationLock) {
_portraitOrientationListener?.disableListener() _portraitOrientationListener?.disableListener()
_landscapeOrientationListener?.disableListener() _landscapeOrientationListener?.disableListener()
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED val display = getDisplay(_viewDetail!!)
val rotation = display!!.rotation
val orientation = resources.configuration.orientation
a.requestedOrientation = when (orientation) {
Configuration.ORIENTATION_PORTRAIT -> {
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
if (rotation == Surface.ROTATION_0) {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
}
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
Configuration.ORIENTATION_LANDSCAPE -> {
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
if (rotation == Surface.ROTATION_90) {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
}
} else {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
} else { } else {
_portraitOrientationListener?.disableListener() _portraitOrientationListener?.disableListener()
_landscapeOrientationListener?.disableListener() _landscapeOrientationListener?.disableListener()

View File

@ -171,7 +171,6 @@ import kotlinx.coroutines.withContext
import userpackage.Protocol import userpackage.Protocol
import java.time.OffsetDateTime import java.time.OffsetDateTime
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToLong import kotlin.math.roundToLong
@UnstableApi @UnstableApi
@ -870,14 +869,12 @@ class VideoDetailView : ConstraintLayout {
} }
_slideUpOverlay?.hide(); _slideUpOverlay?.hide();
} else null, } else null,
if(!isLimitedVersion) if (!isLimitedVersion) RoundButton(context, R.drawable.ic_screen_share, if (allowBackground) context.getString(R.string.background_revert) else context.getString(R.string.background), TAG_BACKGROUND) {
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.background), TAG_BACKGROUND) { if (!allowBackground) {
if(!allowBackground) {
_player.switchToAudioMode(); _player.switchToAudioMode();
allowBackground = true; allowBackground = true;
it.text.text = resources.getString(R.string.background_revert); it.text.text = resources.getString(R.string.background_revert);
} } else {
else {
_player.switchToVideoMode(); _player.switchToVideoMode();
allowBackground = false; allowBackground = false;
it.text.text = resources.getString(R.string.background); it.text.text = resources.getString(R.string.background);
@ -1901,13 +1898,45 @@ class VideoDetailView : ConstraintLayout {
return super.onInterceptTouchEvent(ev); return super.onInterceptTouchEvent(ev);
} }
//Actions //Actions
private fun showVideoSettings() { private fun showVideoSettings() {
Logger.i(TAG, "showVideoSettings") Logger.i(TAG, "showVideoSettings")
_overlay_quality_selector?.selectOption("video", _lastVideoSource); _overlay_quality_selector?.selectOption("video", _lastVideoSource);
_overlay_quality_selector?.selectOption("audio", _lastAudioSource); _overlay_quality_selector?.selectOption("audio", _lastAudioSource);
_overlay_quality_selector?.selectOption("subtitles", _lastSubtitleSource); _overlay_quality_selector?.selectOption("subtitles", _lastSubtitleSource);
if (_lastVideoSource is IDashManifestSource || _lastVideoSource is IHLSManifestSource) {
val videoTracks =
_player.exoPlayer?.player?.currentTracks?.groups?.firstOrNull { it.mediaTrackGroup.type == C.TRACK_TYPE_VIDEO }
var selectedQuality: Format? = null
if (videoTracks != null) {
for (i in 0 until videoTracks.mediaTrackGroup.length) {
if (videoTracks.mediaTrackGroup.getFormat(i).height == _player.targetTrackVideoHeight) {
selectedQuality = videoTracks.mediaTrackGroup.getFormat(i)
}
}
}
var videoMenuGroup: SlideUpMenuGroup? = null
for (view in _overlay_quality_selector!!.groupItems) {
if (view is SlideUpMenuGroup && view.groupTag == "video") {
videoMenuGroup = view
}
}
if (selectedQuality != null) {
videoMenuGroup?.getItem("auto")?.setSubText("")
_overlay_quality_selector?.selectOption("video", selectedQuality)
} else {
videoMenuGroup?.getItem("auto")
?.setSubText("${_player.exoPlayer?.player?.videoFormat?.width}x${_player.exoPlayer?.player?.videoFormat?.height}")
_overlay_quality_selector?.selectOption("video", "auto")
}
}
val currentPlaybackRate = (if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate()) ?: 1.0 val currentPlaybackRate = (if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate()) ?: 1.0
_overlay_quality_selector?.groupItems?.firstOrNull { it is SlideUpMenuButtonList && it.id == "playback_rate" }?.let { _overlay_quality_selector?.groupItems?.firstOrNull { it is SlideUpMenuButtonList && it.id == "playback_rate" }?.let {
(it as SlideUpMenuButtonList).setSelected(currentPlaybackRate.toString()) (it as SlideUpMenuButtonList).setSelected(currentPlaybackRate.toString())
@ -2081,17 +2110,15 @@ class VideoDetailView : ConstraintLayout {
call = { handleSelectSubtitleTrack(it) }) call = { handleSelectSubtitleTrack(it) })
}.toList().toTypedArray()) }.toList().toTypedArray())
else null, else null,
if(liveStreamVideoFormats?.isEmpty() == false) if (liveStreamVideoFormats?.isEmpty() == false) SlideUpMenuGroup(
SlideUpMenuGroup(this.context, context.getString(R.string.stream_video), "video", this.context, context.getString(R.string.stream_video), "video", (listOf(
*liveStreamVideoFormats SlideUpMenuItem(this.context, R.drawable.ic_movie, "Auto", tag = "auto", call = { _player.selectVideoTrack(-1) })
.map { ) + (liveStreamVideoFormats.map {
SlideUpMenuItem(this.context, SlideUpMenuItem(this.context, R.drawable.ic_movie, it.label
R.drawable.ic_movie, ?: it.containerMimeType
it.label ?: it.containerMimeType ?: it.bitrate.toString(), ?: it.bitrate.toString(), "${it.width}x${it.height}", tag = it, call = { _player.selectVideoTrack(it.height) });
"${it.width}x${it.height}", }))
tag = it, )
call = { _player.selectVideoTrack(it.height) });
}.toList().toTypedArray())
else null, else null,
if(liveStreamAudioFormats?.isEmpty() == false) if(liveStreamAudioFormats?.isEmpty() == false)
SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio", SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio",

View File

@ -755,10 +755,6 @@ class GestureControlView : LinearLayout {
} }
} }
if (!Settings.instance.gestureControls.useSystemBrightness) {
_brightnessFactor = 1.0f;
}
if (Settings.instance.gestureControls.useSystemVolume) { if (Settings.instance.gestureControls.useSystemVolume) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)

View File

@ -61,6 +61,15 @@ class SlideUpMenuGroup : LinearLayout {
return didSelect; return didSelect;
} }
fun getItem(tag: Any?): SlideUpMenuItem? {
for(item in items) {
if(item.itemTag == tag){
return item
}
}
return null
}
private fun addItems(items: List<SlideUpMenuItem>) { private fun addItems(items: List<SlideUpMenuItem>) {
for (item in items) { for (item in items) {
item.setParentClickListener { parentClickListener?.invoke() } item.setParentClickListener { parentClickListener?.invoke() }

View File

@ -82,6 +82,10 @@ class SlideUpMenuItem : ConstraintLayout {
return isSelected; return isSelected;
} }
fun setSubText(subText: String) {
_subtext.text = subText
}
fun setParentClickListener(listener: (()->Unit)?) { fun setParentClickListener(listener: (()->Unit)?) {
_parentClickListener = listener; _parentClickListener = listener;
} }

View File

@ -110,8 +110,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
private var _didCallSourceChange = false; private var _didCallSourceChange = false;
private var _lastState: Int = -1; private var _lastState: Int = -1;
private var _targetTrackVideoHeight = -1;
private var _targetTrackAudioBitrate = -1; var targetTrackVideoHeight = -1
private set
private var _targetTrackAudioBitrate = -1
private var _toResume = false; private var _toResume = false;
@ -278,7 +280,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
//TODO: Temporary solution, Implement custom track selector without using constraints //TODO: Temporary solution, Implement custom track selector without using constraints
fun selectVideoTrack(height: Int) { fun selectVideoTrack(height: Int) {
_targetTrackVideoHeight = height; targetTrackVideoHeight = height;
updateTrackSelector(); updateTrackSelector();
} }
fun selectAudioTrack(bitrate: Int) { fun selectAudioTrack(bitrate: Int) {
@ -288,16 +290,22 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun updateTrackSelector() { private fun updateTrackSelector() {
var builder = DefaultTrackSelector.Parameters.Builder(context); var builder = DefaultTrackSelector.Parameters.Builder(context);
if(_targetTrackVideoHeight > 0) { if(targetTrackVideoHeight > 0) {
builder = builder builder = builder
.setMinVideoSize(0, _targetTrackVideoHeight - 10) .setMinVideoSize(0, targetTrackVideoHeight - 10)
.setMaxVideoSize(9999, _targetTrackVideoHeight + 10); .setMaxVideoSize(9999, targetTrackVideoHeight + 10);
} }
if(_targetTrackAudioBitrate > 0) { if(_targetTrackAudioBitrate > 0) {
builder = builder.setMaxAudioBitrate(_targetTrackAudioBitrate); builder = builder.setMaxAudioBitrate(_targetTrackAudioBitrate);
} }
builder = if (isAudioMode) {
builder.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, true)
} else {
builder.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, false)
}
val trackSelector = exoPlayer?.player?.trackSelector; val trackSelector = exoPlayer?.player?.trackSelector;
if(trackSelector != null) { if(trackSelector != null) {
trackSelector.parameters = builder.build(); trackSelector.parameters = builder.build();
@ -737,6 +745,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
val sourceAudio = _lastAudioMediaSource; val sourceAudio = _lastAudioMediaSource;
val sourceSubs = _lastSubtitleMediaSource; val sourceSubs = _lastSubtitleMediaSource;
updateTrackSelector()
beforeSourceChanged(); beforeSourceChanged();

View File

@ -959,12 +959,12 @@
<item>Watchtime Descending</item> <item>Watchtime Descending</item>
</string-array> </string-array>
<string-array name="downloads_sortby_array"> <string-array name="downloads_sortby_array">
<item>Name Ascending</item> <item>Name (Ascending)</item>
<item>Name Descending</item> <item>Name (Descending)</item>
<item>Downloaded Ascending</item> <item>Download Date (Oldest)</item>
<item>Downloaded Descending</item> <item>Download Date (Newest)</item>
<item>Released Ascending</item> <item>Release Date (Oldest)</item>
<item>Released Descending</item> <item>Release Date (Newest)</item>
</string-array> </string-array>
<string-array name="feed_style"> <string-array name="feed_style">
<item>Preview</item> <item>Preview</item>