Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ abstract class BasePlayerGestureListener(
protected val binding: PlayerBinding = playerUi.binding

override fun onTouch(v: View, event: MotionEvent): Boolean {
playerUi.gestureDetector.onTouchEvent(event)
playerUi.gestureDetector?.onTouchEvent(event)
return false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public void setupAfterIntent() {
}

@Override
BasePlayerGestureListener buildGestureListener() {
protected BasePlayerGestureListener buildGestureListener() {
return new MainPlayerGestureListener(this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void setupAfterIntent() {
}

@Override
BasePlayerGestureListener buildGestureListener() {
protected BasePlayerGestureListener buildGestureListener() {
return new PopupPlayerGestureListener(this);
}

Expand Down
1,629 changes: 0 additions & 1,629 deletions app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java

This file was deleted.

1,670 changes: 1,670 additions & 0 deletions app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.kt

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions app/src/main/java/org/schabi/newpipe/views/ChaptersSeekBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe contributors <https://newpipe.net>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.views

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import org.schabi.newpipe.extractor.stream.StreamSegment

/**
* A [FocusAwareSeekBar] that renders narrow transparent gaps at chapter boundaries,
* giving the seekbar a segmented "chopped" appearance.
* Call [setChapters] whenever a new stream loads.
*/
class ChaptersSeekBar : FocusAwareSeekBar {

private val gapPaint = Paint().apply {
style = Paint.Style.FILL
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}

private var chapters: List<StreamSegment> = emptyList()
private var durationSeconds: Long = 0

constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)

/**
* Stores chapter data for rendering segment gaps.
*
* @param newChapters list of [StreamSegment]s; may be empty but never null
* @param newDurationSecs total duration in seconds; used to compute fractional positions
*/
fun setChapters(newChapters: List<StreamSegment>, newDurationSecs: Long) {
chapters = newChapters
durationSeconds = newDurationSecs
invalidate()
}

override fun onDraw(canvas: Canvas) {
if (chapters.isEmpty() || durationSeconds <= 0) {
super.onDraw(canvas)
return
}

// Draw the seekbar into an offscreen layer so CLEAR mode can punch transparent gaps
val sc = canvas.saveLayer(null, null)
super.onDraw(canvas)

val density = resources.displayMetrics.density
val gapHalfWidth = (GAP_WIDTH_DP * density) / 2f
val left = paddingLeft
val trackWidth = (width - left - paddingRight).toFloat()

if (trackWidth > 0) {
for (seg in chapters) {
val startSec = seg.startTimeSeconds
// Skip the very first position and anything at or past the end
if (startSec <= 0 || startSec.toLong() >= durationSeconds) {
continue
}
val x = left + (startSec.toFloat() / durationSeconds.toFloat()) * trackWidth
canvas.drawRect(x - gapHalfWidth, 0f, x + gapHalfWidth, height.toFloat(), gapPaint)
}
}

canvas.restoreToCount(sc)

// Redraw the thumb on top so it visually overlaps the gaps
val t = thumb
if (t != null) {
val thumbSave = canvas.save()
canvas.translate((paddingLeft - thumbOffset).toFloat(), paddingTop.toFloat())
t.draw(canvas)
canvas.restoreToCount(thumbSave)
}
}

companion object {
private const val GAP_WIDTH_DP = 2f
}
}
147 changes: 0 additions & 147 deletions app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java

This file was deleted.

101 changes: 101 additions & 0 deletions app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe contributors <https://newpipe.net>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.views

import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.ViewTreeObserver
import android.widget.SeekBar
import androidx.appcompat.widget.AppCompatSeekBar
import org.schabi.newpipe.util.DeviceUtils

/**
* SeekBar, adapted for directional navigation. It emulates touch-related callbacks
* (onStartTrackingTouch/onStopTrackingTouch), so existing code does not need to be changed to
* work with it.
*/
open class FocusAwareSeekBar : AppCompatSeekBar {

private var listener: NestedListener? = null
private var treeObserver: ViewTreeObserver? = null

constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)

override fun setOnSeekBarChangeListener(l: OnSeekBarChangeListener?) {
listener = if (l == null) null else NestedListener(l)
super.setOnSeekBarChangeListener(listener)
}

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (!isInTouchMode && DeviceUtils.isConfirmKey(keyCode)) {
releaseTrack()
}
return super.onKeyDown(keyCode, event)
}

override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
if (!isInTouchMode && !gainFocus) {
releaseTrack()
}
}

private val touchModeListener = ViewTreeObserver.OnTouchModeChangeListener { inTouchMode ->
if (inTouchMode) {
releaseTrack()
}
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()
treeObserver = viewTreeObserver
treeObserver?.addOnTouchModeChangeListener(touchModeListener)
}

override fun onDetachedFromWindow() {
if (treeObserver?.isAlive != true) {
treeObserver = viewTreeObserver
}
treeObserver?.removeOnTouchModeChangeListener(touchModeListener)
treeObserver = null
super.onDetachedFromWindow()
}

private fun releaseTrack() {
val l = listener
if (l != null && l.isSeeking) {
l.onStopTrackingTouch(this)
}
}

private class NestedListener(private val delegate: OnSeekBarChangeListener) :
OnSeekBarChangeListener {

var isSeeking = false

override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!seekBar.isInTouchMode && !isSeeking && fromUser) {
isSeeking = true
onStartTrackingTouch(seekBar)
}
delegate.onProgressChanged(seekBar, progress, fromUser)
}

override fun onStartTrackingTouch(seekBar: SeekBar) {
isSeeking = true
delegate.onStartTrackingTouch(seekBar)
}

override fun onStopTrackingTouch(seekBar: SeekBar) {
isSeeking = false
delegate.onStopTrackingTouch(seekBar)
}
}
}
19 changes: 18 additions & 1 deletion app/src/main/res/layout/player.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,23 @@
android:orientation="vertical"
android:paddingBottom="12dp">

<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/currentChapterTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#60000000"
android:ellipsize="end"
android:maxLines="1"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="2dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="Introduction"
tools:visibility="visible" />

<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/currentDisplaySeek"
android:layout_width="wrap_content"
Expand Down Expand Up @@ -467,7 +484,7 @@
tools:text="1:06:29" />


<org.schabi.newpipe.views.FocusAwareSeekBar
<org.schabi.newpipe.views.ChaptersSeekBar
android:id="@+id/playbackSeekBar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="0dp"
Expand Down
Loading