Skip to content

Commit

Permalink
Add basic support for intro-skipper button
Browse files Browse the repository at this point in the history
  • Loading branch information
brewkunz committed Oct 2, 2024
1 parent bb42fd4 commit f850a71
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 2 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/custom-app-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Custom App / Build

on:
push:
tags:
- 'custom-v*'

jobs:
build:
name: Build
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Set JELLYFIN_VERSION
run: echo "JELLYFIN_VERSION=$(echo ${GITHUB_REF#refs/tags/custom-v} | tr / -)" >> $GITHUB_ENV

- name: Assemble release file
run: ./gradlew assembleRelease

- name: Sign APK
id: signApk
uses: r0adkll/sign-android-release@v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}

- name: Prepare release archive
run: |
mkdir -p build/jellyfin-publish
mv ${{ steps.signApk.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-androidtv-v${{ env.JELLYFIN_VERSION }}-release.apk
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
files: build/jellyfin-publish/*.apk
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ android {
targetSdk = libs.versions.android.targetSdk.get().toInt()

// Release version
applicationId = namespace
applicationId = "com.brewkunz.jellyfin"
versionName = project.getVersionName()
versionCode = getVersionCode(versionName!!)
setProperty("archivesBaseName", "jellyfin-androidtv-v$versionName")
Expand Down Expand Up @@ -110,6 +110,7 @@ dependencies {
// Kotlin
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core)

// Android(x)
implementation(libs.androidx.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.leanback.app.RowsSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
Expand Down Expand Up @@ -65,6 +66,7 @@
import org.jellyfin.androidtv.ui.navigation.Destinations;
import org.jellyfin.androidtv.ui.navigation.NavigationRepository;
import org.jellyfin.androidtv.ui.playback.overlay.LeanbackOverlayFragment;
import org.jellyfin.androidtv.ui.playback.segmentskip.SegmentSkipFragment;
import org.jellyfin.androidtv.ui.presentation.CardPresenter;
import org.jellyfin.androidtv.ui.presentation.ChannelCardPresenter;
import org.jellyfin.androidtv.ui.presentation.MutableObjectAdapter;
Expand Down Expand Up @@ -92,6 +94,9 @@
import java.util.UUID;

import kotlin.Lazy;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.Dispatchers;
import timber.log.Timber;

public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGuide, View.OnKeyListener {
Expand Down Expand Up @@ -132,6 +137,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
private boolean mFadeEnabled = false;
private boolean mIsVisible = false;
private boolean mPopupPanelVisible = false;
private SegmentSkipFragment mSegmentSkipFragment;

private LeanbackOverlayFragment leanbackOverlayFragment;

Expand Down Expand Up @@ -243,6 +249,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
if (playbackControllerContainer.getValue().getPlaybackController() != null) {
playbackControllerContainer.getValue().getPlaybackController().init(new VideoManager((requireActivity()), view, helper), this);
}

initSegmentSkip();
}

@Override
Expand Down Expand Up @@ -1305,7 +1313,43 @@ private void prepareChannelAdapter() {
});
}

private void initSegmentSkip() {
mSegmentSkipFragment = new SegmentSkipFragment();

FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
transaction.add(R.id.container, mSegmentSkipFragment, null);
transaction.commit();
}

private void destroySegmentSkip() {
if (mSegmentSkipFragment == null) return;

FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
transaction.remove(mSegmentSkipFragment);
transaction.commit();
}

public void handleProgress(long currentPosition) {
mSegmentSkipFragment.handleProgress(currentPosition);
}

public void onStartItem(BaseItemDto item) {
mSegmentSkipFragment.onStartItem(item, new Continuation<Object>() {
@Override
@NonNull
public CoroutineContext getContext() {
return Dispatchers.getDefault();
}

@Override
public void resumeWith(@NonNull Object o) {
}
});
}

public void closePlayer() {
destroySegmentSkip();

if (navigationRepository.getValue().getCanGoBack()) {
navigationRepository.getValue().goBack();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ private void startItem(BaseItemDto item, long position, StreamInfo response) {
mVideoManager.setPlaybackSpeed(isLiveTv() ? 1.0f : mRequestedPlaybackSpeed);

if (mFragment != null) mFragment.updateDisplay();
if (mFragment != null) mFragment.onStartItem(item);

if (mVideoManager != null) {
mVideoManager.setVideoPath(response.getMediaUrl());
Expand Down Expand Up @@ -1282,8 +1283,10 @@ public void onProgress() {
if (mFragment != null && finishedInitialSeek)
mFragment.updateSubtitles(mCurrentPosition);
}
if (mFragment != null)
if (mFragment != null) {
mFragment.handleProgress(mCurrentPosition);
mFragment.setCurrentTime(mCurrentPosition);
}
}

public long getDuration() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jellyfin.androidtv.ui.playback.segmentskip

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

enum class SegmentType {
INTRO, CREDITS, UNKNOWN
}

@Serializable
data class SegmentModel (
var type: SegmentType = SegmentType.UNKNOWN,
@SerialName("IntroStart")
val startTime: Double,
@SerialName("IntroEnd")
val endTime: Double,
@SerialName("ShowSkipPromptAt")
val showAt: Double,
@SerialName("HideSkipPromptAt")
val hideAt: Double,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.jellyfin.androidtv.ui.playback.segmentskip

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.get
import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
import org.koin.android.ext.android.inject

class SegmentSkipFragment() : Fragment() {

private val api: ApiClient by inject()
private val playbackControllerContainer: PlaybackControllerContainer by inject()

private lateinit var button: Button
private var segments: List<SegmentModel>? = null
private var lastSegment: SegmentModel? = null

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_segment_skip, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

button = view.findViewById(R.id.skip_segment_button)
button.setOnClickListener {
buttonClicked()
}
}

private fun buttonClicked() {
lastSegment?.let { segment ->
playbackControllerContainer.playbackController?.seek((segment.endTime * 1000).toLong())
}
}

fun handleProgress(currentPosition: Long) {
val currentSegment = getCurrentSegment(currentPosition) ?: lastSegment ?: return
lastSegment = currentSegment

if (currentPosition >= currentSegment.showAt * 1000 && currentPosition < currentSegment.hideAt * 1000 &&
button.visibility != View.VISIBLE
) {
button.visibility = View.VISIBLE
button.requestFocus()
}

if ((currentPosition < currentSegment.showAt * 1000 || currentPosition >= currentSegment.hideAt * 1000) &&
button.visibility == View.VISIBLE
) {
button.visibility = View.GONE
}
}

suspend fun onStartItem(item: BaseItemDto) {
button.visibility = View.GONE
segments = getSegments(item.id)
}

private suspend fun getSegments(itemId: UUID): List<SegmentModel> {
val segmentsMap = api.get<Map<String, SegmentModel>>(
pathTemplate = "/Episode/{itemId}/IntroSkipperSegments",
pathParameters = mapOf("itemId" to itemId),
).content

for ((type, segment) in segmentsMap) {
segment.type = when (type) {
"Introduction" -> SegmentType.INTRO
"Credits" -> SegmentType.CREDITS
else -> SegmentType.UNKNOWN
}
}

return segmentsMap.values.toList()
}

private fun getCurrentSegment(currentPosition: Long): SegmentModel? {
return segments?.firstOrNull {
currentPosition >= it.startTime * 1000 && currentPosition < it.endTime * 1000
}
}
}
21 changes: 21 additions & 0 deletions app/src/main/res/layout/fragment_segment_skip.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.playback.segmentskip.SegmentSkipFragment">

<Button
android:id="@+id/skip_segment_button"
style="@style/Button.Default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="56dp"
android:layout_marginBottom="32.5dp"
android:elevation="0dp"
android:text="Skip"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ markwon = "4.6.2"
mockk = "1.13.12"
slf4j-timber = "0.0.4"
timber = "5.0.1"
kotlinxCoroutinesCore = "1.9.0"

[plugins]
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
Expand All @@ -56,6 +57,7 @@ kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }

# Android(x)
android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
Expand Down

0 comments on commit f850a71

Please sign in to comment.