Skip to content

Commit

Permalink
Merge pull request #18 from kuylar/dev
Browse files Browse the repository at this point in the history
Release v2023.09.30
  • Loading branch information
kuylar authored Sep 30, 2023
2 parents 61c129b + 112e93e commit 7e31cba
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 385 deletions.
15 changes: 9 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ android {
}
buildFeatures {
viewBinding true
buildConfig true
}
}

Expand Down Expand Up @@ -75,14 +76,16 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.15.1'

// https://maven.google.com/web/index.html#com.google.android.exoplayer
implementation 'com.google.android.exoplayer:exoplayer-core:2.18.7'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.18.7'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.7'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.18.7'
implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'

// https://github.com/vkay94/YouTubeTimeBar
implementation 'com.github.vkay94:YouTubeTimeBar:0.2.2'
// Forked version to support androidx.media3
implementation 'com.github.kuylar:YouTubeTimeBar:0.2.3'

// https://github.com/vkay94/DoubleTapPlayerView
implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.4'
// Forked version to support androidx.media3
implementation 'com.github.kuylar:DoubleTapPlayerView:1.0.5'
}
59 changes: 54 additions & 5 deletions app/src/main/java/dev/kuylar/lighttube/SponsorBlockSegment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,63 @@ import kotlin.math.roundToLong
class SponsorBlockSegment(
val category: String,
val actionType: String,
segment: List<Double>,
val uuid: String,
val segment: List<Double>,
val uuid: String?,
val videoDuration: Double,
val locked: Long,
val votes: Long,
val description: String
) : YouTubeSegment {
override var color = 0xFFFFFF
override val endTimeMs = (segment[1] * 1000).roundToLong()
override val startTimeMs = (segment[0] * 1000).roundToLong()
override var color = 0x00000000
override var endTimeMs: Long = 0
override var startTimeMs: Long = 0

// we have to do this cus gson doesnt call init{}
constructor(
segment: SponsorBlockSegment
) : this(
segment.category,
segment.actionType,
segment.segment,
segment.uuid,
segment.videoDuration,
segment.locked,
segment.votes,
segment.description
) {
this.color = when (category) {
"sponsor" -> DEFAULT_SPONSOR_COLOR
"selfpromo" -> DEFAULT_SELFPROMO_COLOR
"interaction" -> DEFAULT_INTERACTION_COLOR
"intro" -> DEFAULT_INTRO_COLOR
"outro" -> DEFAULT_OUTRO_COLOR
"preview" -> DEFAULT_PREVIEW_COLOR
"music_offtopic" -> DEFAULT_MUSIC_OFFTOPIC_COLOR
else -> DEFAULT_SELFPROMO_COLOR
}
this.endTimeMs = (segment.segment[1] * 1000).roundToLong()
this.startTimeMs = (segment.segment[0] * 1000).roundToLong()
}
fun getCategoryTextId(): Int {
return when (category) {
"sponsor" -> R.string.sponsorblock_category_sponsor
"selfpromo" -> R.string.sponsorblock_category_selfpromo
"interaction" -> R.string.sponsorblock_category_interaction
"intro" -> R.string.sponsorblock_category_intro
"outro" -> R.string.sponsorblock_category_outro
"preview" -> R.string.sponsorblock_category_preview
"music_offtopic" -> R.string.sponsorblock_category_music_offtopic
else -> R.string.sponsorblock_category_unknown
}
}

companion object {
private const val DEFAULT_SPONSOR_COLOR = 0xFF00D400.toInt()
private const val DEFAULT_SELFPROMO_COLOR = 0xFFFFFF00.toInt()
private const val DEFAULT_INTERACTION_COLOR = 0xFFCC00FF.toInt()
private const val DEFAULT_INTRO_COLOR = 0xFF00FFFF.toInt()
private const val DEFAULT_OUTRO_COLOR = 0xFF0202ED.toInt()
private const val DEFAULT_PREVIEW_COLOR = 0xFF008FD6.toInt()
private const val DEFAULT_MUSIC_OFFTOPIC_COLOR = 0xFFFF9900.toInt()
}
}
80 changes: 48 additions & 32 deletions app/src/main/java/dev/kuylar/lighttube/StoryboardInfo.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package dev.kuylar.lighttube

import android.util.Log
import dev.kuylar.lighttube.ui.StoryboardTransformation
import java.lang.Exception
import kotlin.math.ceil
import java.net.URLDecoder
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.truncate

class StoryboardInfo(
levelsString: String,
val recommendedLevel: String,
var recommendedLevel: String,
videoLength: Long
) {
val levels: HashMap<String, String> = HashMap()
val storyboardCount: Double
val secondsPerIndex: Double
val secondsPerFrame: Double
var lastPosition = -1L
private val levels: HashMap<String, String> = HashMap()
private val storyboardCount: Double
private val secondsPerIndex: Double
private val secondsPerFrame: Double
val msPerFrame: Double
private var indexes = ArrayList<Pair<Int, Int>>()

init {
levelsString.split("&").forEach {
Expand All @@ -30,44 +28,62 @@ class StoryboardInfo(
}
storyboardCount = if (videoLength < 250) {
ceil((videoLength / 2) / 25.0)
} else if (videoLength in 250..1000) {
} else if (videoLength in 250 ..1000) {
ceil((videoLength / 4) / 25.0)
} else {
ceil((videoLength / 10) / 25.0)
} - 1

for (i in 0..(storyboardCount - 1).toInt()) {
Log.i("VideoPlayerManager", levels["2"]!!.replace("M0", "M$i"))
}
if (recommendedLevel == "2") {
secondsPerIndex = 125.0
secondsPerFrame = 5.0
msPerFrame = 5000.0

val frameCount = floor(videoLength / 5f).toInt() + 1
val indexCount = ceil(frameCount / 25f).toInt()

val lastIndexFrameCount = if (frameCount % 25 == 0) 25 else frameCount % 25

val lastIndexRows = ceil(lastIndexFrameCount / 5f).toInt()
val lastIndexColumns = if (lastIndexRows > 1) 5 else frameCount % 5

secondsPerIndex = videoLength / storyboardCount
secondsPerFrame = secondsPerIndex
for (i in 1 until indexCount) {
indexes.add(Pair(5, 5))
}
indexes.add(Pair(lastIndexRows, lastIndexColumns))
} else {
secondsPerIndex = videoLength.toDouble()
secondsPerFrame = secondsPerIndex / 100
msPerFrame = secondsPerFrame * 1000

indexes.add(Pair(10, 10))
}
}

fun getImageUrl(position: Long): String {
return levels["2"]!!.replace("M0", "M${getStoryboardIndex(position)}")
return if (recommendedLevel == "2")
levels["2"]?.replace("M0", "M${getStoryboardIndex(position)}") ?: ""
else
levels["0"] ?: ""
}

private fun getStoryboardIndex(position: Long): Int {
return floor((position / 1000) / secondsPerIndex).toInt()
return if (recommendedLevel == "2")
floor((position / 1000) / secondsPerIndex).toInt()
else
0
}

fun getTransformation(position: Long): StoryboardTransformation {
val index = indexes[getStoryboardIndex(position)]
val n = position / 1000 / secondsPerIndex
val positionInFrame = (n - truncate(n)) * 25
val x = floor(positionInFrame % 5).toInt()
val y = floor(positionInFrame / 5).toInt()
Log.i("Storyboard", "pif: $positionInFrame x: $x, y: $y")
return StoryboardTransformation(x, y, 100, 50)
}

fun throttle(position: Long) {
Log.i("Storyboard", "Difference: ${abs(lastPosition - position)}")
if (lastPosition == -1L) {
lastPosition = position
return
val xy = if (recommendedLevel == "2") {
val positionInFrame = (n - truncate(n)) * 25
Pair(floor(positionInFrame % 5).toInt(), floor(positionInFrame / 5).toInt())
} else {
val positionInFrame = (n - truncate(n)) * 100
Pair(floor(positionInFrame % 10).toInt(), floor(positionInFrame / 10).toInt())
}
if (abs(lastPosition - position) < secondsPerFrame * 40) throw Exception("throttle")
lastPosition = position
return StoryboardTransformation(xy.first, xy.second, index.first, index.second)
}
}
35 changes: 13 additions & 22 deletions app/src/main/java/dev/kuylar/lighttube/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dev.kuylar.lighttube

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
Expand Down Expand Up @@ -62,14 +61,14 @@ class Utils {
).asString!!
}

private fun getUserAgent(): String =
"LightTube-Android/${BuildConfig.VERSION_NAME} (https://github.com/kuylar/lighttube-android)"

fun getDislikeCount(videoId: String): Long {
try {
val req = Request.Builder().apply {
url("https://returnyoutubedislikeapi.com/votes?videoId=$videoId")
header(
"User-Agent",
"LightTube-Android/1.0 (https://github.com/kuylar/lighttube-android)"
)
header("User-Agent", getUserAgent())
}.build()

http.newCall(req).execute().use { response ->
Expand All @@ -93,12 +92,9 @@ class Utils {
0,
4
)
}?category=sponsor&category=selfpromo&category=interaction&category=intro&category=outro&category=preview&category=music_offtopic&category=filler"
)
header(
"User-Agent",
"LightTube-Android/1.0 (https://github.com/kuylar/lighttube-android)"
}?category=sponsor&category=selfpromo&category=interaction&category=intro&category=outro&category=preview&category=music_offtopic"
)
header("User-Agent", getUserAgent())
}.build()

http.newCall(req).execute().use { response ->
Expand All @@ -112,15 +108,12 @@ class Utils {
}
}

fun checkForUpdates(context: Context): UpdateInfo? {
fun checkForUpdates(): UpdateInfo? {
var updateInfo: UpdateInfo? = null
try {
val req = Request.Builder().apply {
url("https://api.github.com/repos/kuylar/lighttube-android/releases")
header(
"User-Agent",
"LightTube-Android/1.0 (https://github.com/kuylar/lighttube-android)"
)
url("https://api.github.com/repos/kuylar/lighttube-android/releases/latest")
header("User-Agent", getUserAgent())
}.build()

http.newCall(req).execute().use { response ->
Expand All @@ -129,19 +122,17 @@ class Utils {
.create()
.fromJson(
response.body!!.string(),
object : TypeToken<List<GithubRelease>>() {})
val latestVer = res.first().tagName.substring(1)
@Suppress("DEPRECATION")
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val version = pInfo.versionName.split(" ").first()
GithubRelease::class.java)
val latestVer = res.tagName.substring(1)
val version = BuildConfig.VERSION_NAME.split(" ").first()
val latestVersionCode = latestVer.replace(".", "").toInt()
val currentVersionCode = version.replace(".", "").toInt()
if (latestVersionCode > currentVersionCode) {
Log.i("UpdateChecker", "Update available! (${version} -> ${latestVer})")
updateInfo = UpdateInfo(
version,
latestVer,
res.first().assets.first().browserDownloadUrl
res.assets.first().browserDownloadUrl
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dev.kuylar.lighttube.api.models

import android.os.Bundle
import com.google.android.exoplayer2.MediaMetadata
import androidx.media3.common.MediaMetadata
import java.net.URLEncoder

class VideoDetails(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import java.security.MessageDigest
class StoryboardTransformation(
private val x: Int,
private val y: Int,
private val w: Int,
private val h: Int
private val r: Int,
private val c: Int
) :
BitmapTransformation() {
private val ID = "dev.kuylar.lighttube.ui.StoryboardTransformation"
private val ID_BYTES = ID.toByteArray(Charset.forName("UTF-8"))
private fun getCacheKey(): String =
"dev.kuylar.lighttube.ui.StoryboardTransformation($x,$y,$r,$c)"

override fun transform(
pool: BitmapPool,
Expand All @@ -24,17 +24,24 @@ class StoryboardTransformation(
outHeight: Int
): Bitmap = Bitmap.createBitmap(
toTransform,
x * (toTransform.width / 5),
y * 90, // hardcoded cus youtube sometimes doesnt has < 5 rows
w,
h
x * toTransform.width / c,
y * toTransform.height / r,
toTransform.width / c,
toTransform.height / r
)


override fun hashCode(): Int {
return ID.hashCode()
return getCacheKey().hashCode()
}

override fun equals(other: Any?): Boolean {
return if (other is StoryboardTransformation) {
other.x == x && other.y == y && other.r == r && other.c == c
} else false
}

override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
messageDigest.update(getCacheKey().toByteArray(Charset.forName("UTF-8")))
}
}
Loading

0 comments on commit 7e31cba

Please sign in to comment.