mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Basic chapter system working
This commit is contained in:
parent
1d18c13817
commit
d8aecd325b
14 changed files with 202 additions and 2 deletions
|
@ -31,6 +31,12 @@ let Type = {
|
|||
RAW: 0,
|
||||
HTML: 1,
|
||||
MARKUP: 2
|
||||
},
|
||||
Chapter: {
|
||||
NORMAL: 0,
|
||||
|
||||
SKIPPABLE: 5,
|
||||
SKIP: 6
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media
|
|||
import androidx.collection.LruCache
|
||||
import com.futo.platformplayer.api.media.models.ResultCapabilities
|
||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
|
@ -49,6 +50,7 @@ class CachedPlatformClient : IPlatformClient {
|
|||
return result;
|
||||
}
|
||||
|
||||
override fun getContentChapters(url: String): List<IChapter> = _client.getContentChapters(url);
|
||||
override fun getPlaybackTracker(url: String): IPlaybackTracker? = _client.getPlaybackTracker(url);
|
||||
|
||||
override fun isChannelUrl(url: String): Boolean = _client.isChannelUrl(url);
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media
|
|||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.ResultCapabilities
|
||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
|
@ -100,6 +101,8 @@ interface IPlatformClient {
|
|||
*/
|
||||
fun getContentDetails(url: String): IPlatformContentDetails;
|
||||
|
||||
fun getContentChapters(url: String): List<IChapter>;
|
||||
|
||||
/**
|
||||
* Gets the playback tracker for a piece of content
|
||||
*/
|
||||
|
|
|
@ -15,7 +15,8 @@ data class PlatformClientCapabilities(
|
|||
val hasGetSearchCapabilities: Boolean = false,
|
||||
val hasGetChannelCapabilities: Boolean = false,
|
||||
val hasGetLiveEvents: Boolean = false,
|
||||
val hasGetLiveChatWindow: Boolean = false
|
||||
val hasGetLiveChatWindow: Boolean = false,
|
||||
val hasGetContentChapters: Boolean = false
|
||||
) {
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.futo.platformplayer.api.media.models.chapters
|
||||
|
||||
import com.futo.platformplayer.api.media.exceptions.UnknownPlatformException
|
||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
|
||||
interface IChapter {
|
||||
val name: String;
|
||||
val type: ChapterType;
|
||||
val timeStart: Int;
|
||||
val timeEnd: Int;
|
||||
}
|
||||
|
||||
enum class ChapterType(val value: Int) {
|
||||
NORMAL(0),
|
||||
|
||||
SKIPPABLE(5),
|
||||
SKIP(6);
|
||||
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): ChapterType
|
||||
{
|
||||
val result = ChapterType.values().firstOrNull { it.value == value };
|
||||
if(result == null)
|
||||
throw UnknownPlatformException(value.toString());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import com.futo.platformplayer.api.media.PlatformClientCapabilities
|
|||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.ResultCapabilities
|
||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
|
@ -181,6 +182,7 @@ open class JSClient : IPlatformClient {
|
|||
hasGetChannelCapabilities = plugin.executeBoolean("!!source.getChannelCapabilities") ?: false,
|
||||
hasGetLiveEvents = plugin.executeBoolean("!!source.getLiveEvents") ?: false,
|
||||
hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false,
|
||||
hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false,
|
||||
);
|
||||
|
||||
try {
|
||||
|
@ -414,6 +416,17 @@ open class JSClient : IPlatformClient {
|
|||
plugin.executeTyped("source.getContentDetails(${Json.encodeToString(url)})"));
|
||||
}
|
||||
|
||||
@JSOptional //getContentChapters = function(url, initialData)
|
||||
@JSDocs(15, "source.getContentChapters(url)", "Gets chapters for content details")
|
||||
@JSDocsParameter("url", "A content url (this platform)")
|
||||
override fun getContentChapters(url: String): List<IChapter> = isBusyWith {
|
||||
if(!capabilities.hasGetContentChapters)
|
||||
return@isBusyWith listOf();
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSChapter.fromV8(config,
|
||||
plugin.executeTyped("source.getContentChapters(${Json.encodeToString(url)})"));
|
||||
}
|
||||
|
||||
@JSOptional
|
||||
@JSDocs(15, "source.getPlaybackTracker(url)", "Gets a playback tracker for given content url")
|
||||
@JSDocsParameter("url", "A content url (this platform)")
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueArray
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.chapters.ChapterType
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSChapter : IChapter {
|
||||
override val name: String;
|
||||
override val type: ChapterType;
|
||||
override val timeStart: Int;
|
||||
override val timeEnd: Int;
|
||||
|
||||
constructor(name: String, timeStart: Int, timeEnd: Int, type: ChapterType = ChapterType.NORMAL) {
|
||||
this.name = name;
|
||||
this.timeStart = timeStart;
|
||||
this.timeEnd = timeEnd;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject): IChapter {
|
||||
val context = "Chapter";
|
||||
|
||||
val name = obj.getOrThrow<String>(config,"name", context);
|
||||
val type = ChapterType.fromInt(obj.getOrDefault<Int>(config, "type", context, ChapterType.NORMAL.value) ?: ChapterType.NORMAL.value);
|
||||
val timeStart = obj.getOrThrow<Int>(config, "timeStart", context);
|
||||
val timeEnd = obj.getOrThrow<Int>(config, "timeEnd", context);
|
||||
|
||||
return JSChapter(name, timeStart, timeEnd, type);
|
||||
}
|
||||
|
||||
fun fromV8(config: IV8PluginConfig, arr: V8ValueArray): List<IChapter> {
|
||||
return arr.keys.mapNotNull {
|
||||
val obj = arr.get<V8ValueObject>(it);
|
||||
return@mapNotNull fromV8(config, obj);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -949,6 +949,17 @@ class VideoDetailView : ConstraintLayout {
|
|||
if(video is JSVideoDetails) {
|
||||
val me = this;
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
//TODO: Implement video.getContentChapters()
|
||||
val chapters = null ?: StatePlatform.instance.getContentChapters(video.url);
|
||||
_player.setChapters(chapters);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to get chapters", ex);
|
||||
withContext(Dispatchers.Main) {
|
||||
UIDialogs.toast(context, "Failed to get chapters\n" + ex.message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
val stopwatch = com.futo.platformplayer.debug.Stopwatch()
|
||||
var tracker = video.getPlaybackTracker()
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.futo.platformplayer.api.media.models.FilterGroup
|
|||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.ResultCapabilities
|
||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
|
@ -615,6 +616,14 @@ class StatePlatform {
|
|||
}
|
||||
}
|
||||
|
||||
fun getContentChapters(url: String): List<IChapter>? {
|
||||
val baseClient = getContentClientOrNull(url) ?: return null;
|
||||
if (baseClient !is JSClient) {
|
||||
return baseClient.getContentChapters(url);
|
||||
}
|
||||
val client = _trackerClientPool.getClientPooled(baseClient, 1);
|
||||
return client.getContentChapters(url);
|
||||
}
|
||||
fun getPlaybackTracker(url: String): IPlaybackTracker? {
|
||||
val baseClient = getContentClientOrNull(url) ?: return null;
|
||||
if (baseClient !is JSClient) {
|
||||
|
|
|
@ -17,6 +17,7 @@ import android.widget.TextView
|
|||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.setMargins
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
|
@ -63,6 +64,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
private val _control_rotate_lock: ImageButton;
|
||||
private val _control_cast: ImageButton;
|
||||
private val _control_play: ImageButton;
|
||||
private val _control_chapter: TextView;
|
||||
private val _time_bar: TimeBar;
|
||||
|
||||
private val _control_fullscreen_fullscreen: ImageButton;
|
||||
|
@ -72,6 +74,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
private val _control_play_fullscreen: ImageButton;
|
||||
private val _time_bar_fullscreen: TimeBar;
|
||||
private val _overlay_brightness: FrameLayout;
|
||||
private val _control_chapter_fullscreen: TextView;
|
||||
|
||||
private val _title_fullscreen: TextView;
|
||||
private val _author_fullscreen: TextView;
|
||||
|
@ -87,6 +90,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
var isFitMode : Boolean = false
|
||||
private set;
|
||||
|
||||
private var _currentChapter: IChapter? = null;
|
||||
|
||||
|
||||
//Events
|
||||
val onMinimize = Event1<FutoVideoPlayer>();
|
||||
val onVideoSettings = Event1<FutoVideoPlayer>();
|
||||
|
@ -112,6 +118,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
_control_cast = videoControls.findViewById(R.id.exo_cast);
|
||||
_control_play = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
|
||||
_time_bar = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
|
||||
_control_chapter = videoControls.findViewById(R.id.text_chapter_current);
|
||||
|
||||
_videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen);
|
||||
_control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_fullscreen);
|
||||
|
@ -119,6 +126,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
_control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_settings);
|
||||
_control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock);
|
||||
_control_play_fullscreen = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
|
||||
_control_chapter_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_chapter_current);
|
||||
_time_bar_fullscreen = _videoControls_fullscreen.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
|
||||
|
||||
_overlay_brightness = findViewById(R.id.overlay_brightness);
|
||||
|
@ -218,8 +226,25 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
updateRotateLock();
|
||||
};
|
||||
|
||||
var lastPos = 0L;
|
||||
videoControls.setProgressUpdateListener { position, bufferedPosition ->
|
||||
onTimeBarChanged.emit(position, bufferedPosition);
|
||||
|
||||
val delta = position - lastPos;
|
||||
if(delta > 1000 || delta < 0) {
|
||||
lastPos = position;
|
||||
val currentChapter = getCurrentChapter(position)
|
||||
if(_currentChapter != currentChapter) {
|
||||
_currentChapter = currentChapter;
|
||||
if (currentChapter != null) {
|
||||
_control_chapter.text = " • " + currentChapter.name;
|
||||
_control_chapter_fullscreen.text = " • " + currentChapter.name;
|
||||
} else {
|
||||
_control_chapter.text = "";
|
||||
_control_chapter_fullscreen.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!isInEditMode) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||
import com.futo.platformplayer.helpers.VideoHelper
|
||||
|
@ -53,6 +54,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
private var _shouldPlaybackRestartOnConnectivity: Boolean = false;
|
||||
private val _referenceObject = Object();
|
||||
|
||||
private var _chapters: List<IChapter>? = null;
|
||||
|
||||
var exoPlayer: PlayerManager? = null
|
||||
private set;
|
||||
val exoPlayerStateName: String;
|
||||
|
@ -208,6 +211,16 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
}
|
||||
}
|
||||
|
||||
fun setChapters(chapters: List<IChapter>?) {
|
||||
_chapters = chapters;
|
||||
}
|
||||
fun getChapters(): List<IChapter> {
|
||||
return _chapters?.let { it.toList() } ?: listOf();
|
||||
}
|
||||
fun getCurrentChapter(pos: Long): IChapter? {
|
||||
return _chapters?.let { chaps -> chaps.find { pos / 1000 > it.timeStart && pos / 1000 < it.timeEnd } };
|
||||
}
|
||||
|
||||
fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false) {
|
||||
swapSources(videoSource, audioSource,false, play, keepSubtitles);
|
||||
}
|
||||
|
|
|
@ -137,6 +137,27 @@
|
|||
app:layout_constraintLeft_toRightOf="@id/text_divider"
|
||||
app:layout_constraintTop_toTopOf="@id/exo_position"
|
||||
app:layout_constraintBottom_toBottomOf="@id/exo_position"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_chapter_current"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:paddingRight="10dp"
|
||||
android:textSize="11sp"
|
||||
android:gravity="left"
|
||||
app:layout_constraintLeft_toRightOf="@id/exo_duration"
|
||||
app:layout_constraintTop_toTopOf="@id/exo_duration"
|
||||
app:layout_constraintBottom_toBottomOf="@id/exo_duration"
|
||||
app:layout_constraintRight_toLeftOf="@id/exo_fullscreen"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="">
|
||||
|
||||
</TextView>
|
||||
|
||||
|
||||
<com.google.android.exoplayer2.ui.SubtitleView
|
||||
android:id="@id/exo_subtitles"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -159,6 +159,26 @@
|
|||
app:layout_constraintTop_toTopOf="@id/exo_position"
|
||||
app:layout_constraintBottom_toBottomOf="@id/exo_position"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_chapter_current"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:paddingRight="10dp"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:textSize="11sp"
|
||||
android:gravity="left"
|
||||
app:layout_constraintLeft_toRightOf="@id/exo_duration"
|
||||
app:layout_constraintTop_toTopOf="@id/exo_duration"
|
||||
app:layout_constraintBottom_toBottomOf="@id/exo_duration"
|
||||
app:layout_constraintRight_toLeftOf="@id/exo_fullscreen"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="">
|
||||
|
||||
</TextView>
|
||||
|
||||
<com.google.android.exoplayer2.ui.DefaultTimeBar
|
||||
android:id="@id/exo_progress"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a1d432865ef87cc4860be998a02ba95f60ccfcd8
|
||||
Subproject commit 5011bfcddb084007b938e6276b11f63e940006eb
|
Loading…
Add table
Reference in a new issue