2023-01-31 21:13:39 +05:30
|
|
|
package com.github.libretube.helpers
|
2022-07-17 01:01:15 +05:30
|
|
|
|
2023-01-09 21:38:05 +05:30
|
|
|
import android.app.Activity
|
|
|
|
import android.app.PendingIntent
|
2022-07-17 01:01:15 +05:30
|
|
|
import android.content.Context
|
2023-01-09 21:38:05 +05:30
|
|
|
import android.content.Intent
|
2022-10-07 22:48:04 +05:30
|
|
|
import android.content.pm.ActivityInfo
|
2023-06-01 21:58:44 +05:30
|
|
|
import android.net.Uri
|
2023-06-24 22:27:33 +05:30
|
|
|
import android.os.Handler
|
|
|
|
import android.os.Looper
|
2023-06-19 16:26:53 +05:30
|
|
|
import android.text.format.DateUtils
|
2023-06-01 21:58:44 +05:30
|
|
|
import android.util.Base64
|
2022-07-17 01:01:15 +05:30
|
|
|
import android.view.accessibility.CaptioningManager
|
2023-02-12 17:47:44 +05:30
|
|
|
import android.widget.Toast
|
2023-01-09 21:38:05 +05:30
|
|
|
import androidx.annotation.StringRes
|
2023-04-09 17:36:28 +05:30
|
|
|
import androidx.core.app.PendingIntentCompat
|
2023-02-03 17:09:08 +05:30
|
|
|
import androidx.core.app.RemoteActionCompat
|
2023-03-31 06:09:38 +05:30
|
|
|
import androidx.core.content.getSystemService
|
2023-02-03 17:09:08 +05:30
|
|
|
import androidx.core.graphics.drawable.IconCompat
|
2023-06-01 21:58:44 +05:30
|
|
|
import androidx.core.net.toUri
|
2023-06-24 22:27:33 +05:30
|
|
|
import androidx.core.view.children
|
2023-05-15 20:51:49 +05:30
|
|
|
import androidx.media3.common.AudioAttributes
|
|
|
|
import androidx.media3.common.C
|
|
|
|
import androidx.media3.common.PlaybackParameters
|
|
|
|
import androidx.media3.exoplayer.DefaultLoadControl
|
|
|
|
import androidx.media3.exoplayer.ExoPlayer
|
|
|
|
import androidx.media3.exoplayer.LoadControl
|
|
|
|
import androidx.media3.ui.CaptionStyleCompat
|
2023-01-09 21:38:05 +05:30
|
|
|
import com.github.libretube.R
|
2023-06-19 16:26:53 +05:30
|
|
|
import com.github.libretube.api.obj.ChapterSegment
|
2023-06-24 23:08:39 +05:30
|
|
|
import com.github.libretube.api.obj.PreviewFrames
|
2023-02-12 17:47:44 +05:30
|
|
|
import com.github.libretube.api.obj.Segment
|
2023-06-01 21:58:44 +05:30
|
|
|
import com.github.libretube.api.obj.Streams
|
2022-09-20 23:30:51 +05:30
|
|
|
import com.github.libretube.constants.PreferenceKeys
|
2023-01-09 21:38:05 +05:30
|
|
|
import com.github.libretube.enums.PlayerEvent
|
2023-06-19 16:10:47 +05:30
|
|
|
import com.github.libretube.enums.SbSkipOptions
|
2023-06-24 23:08:39 +05:30
|
|
|
import com.github.libretube.obj.PreviewFrame
|
2023-06-19 16:26:53 +05:30
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
2023-02-12 17:57:35 +05:30
|
|
|
import kotlin.math.absoluteValue
|
2022-10-07 23:09:41 +05:30
|
|
|
import kotlin.math.roundToInt
|
2022-07-17 01:01:15 +05:30
|
|
|
|
|
|
|
object PlayerHelper {
|
2023-01-09 21:38:05 +05:30
|
|
|
private const val ACTION_MEDIA_CONTROL = "media_control"
|
|
|
|
const val CONTROL_TYPE = "control_type"
|
2023-06-24 23:08:39 +05:30
|
|
|
private val SPONSOR_CATEGORIES =
|
2023-06-19 16:10:47 +05:30
|
|
|
arrayOf(
|
|
|
|
"intro",
|
|
|
|
"selfpromo",
|
|
|
|
"interaction",
|
|
|
|
"sponsor",
|
|
|
|
"outro",
|
|
|
|
"filler",
|
|
|
|
"music_offtopic",
|
2023-06-24 22:27:33 +05:30
|
|
|
"preview"
|
|
|
|
)
|
2023-06-24 23:08:39 +05:30
|
|
|
const val SPONSOR_HIGHLIGHT_CATEGORY = "poi_highlight"
|
2023-01-09 21:38:05 +05:30
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
/**
|
2023-06-01 21:58:44 +05:30
|
|
|
* Create a base64 encoded DASH stream manifest
|
2023-01-08 20:36:29 +05:30
|
|
|
*/
|
2023-06-19 23:41:12 +05:30
|
|
|
fun createDashSource(
|
|
|
|
streams: Streams,
|
|
|
|
context: Context,
|
|
|
|
audioOnly: Boolean = false,
|
|
|
|
disableProxy: Boolean
|
|
|
|
): Uri {
|
|
|
|
val manifest = DashHelper.createManifest(
|
|
|
|
streams,
|
|
|
|
DisplayHelper.supportsHdr(context),
|
|
|
|
audioOnly,
|
|
|
|
disableProxy
|
|
|
|
)
|
2023-06-01 21:58:44 +05:30
|
|
|
|
|
|
|
// encode to base64
|
|
|
|
val encoded = Base64.encodeToString(manifest.toByteArray(), Base64.DEFAULT)
|
|
|
|
return "data:application/dash+xml;charset=utf-8;base64,$encoded".toUri()
|
2022-07-17 01:01:15 +05:30
|
|
|
}
|
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
/**
|
2023-06-01 21:58:44 +05:30
|
|
|
* Get the system's default captions style
|
2023-01-08 20:36:29 +05:30
|
|
|
*/
|
2023-05-15 20:51:49 +05:30
|
|
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
2022-07-17 01:01:15 +05:30
|
|
|
fun getCaptionStyle(context: Context): CaptionStyleCompat {
|
2023-03-31 06:09:38 +05:30
|
|
|
val captioningManager = context.getSystemService<CaptioningManager>()!!
|
2022-07-17 01:01:15 +05:30
|
|
|
return if (!captioningManager.isEnabled) {
|
|
|
|
// system captions are disabled, using android default captions style
|
|
|
|
CaptionStyleCompat.DEFAULT
|
|
|
|
} else {
|
|
|
|
// system captions are enabled
|
|
|
|
CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle)
|
|
|
|
}
|
|
|
|
}
|
2022-08-02 14:47:15 +05:30
|
|
|
|
2023-03-24 20:32:56 +05:30
|
|
|
fun getOrientation(videoWidth: Int, videoHeight: Int): Int {
|
2022-10-07 22:48:04 +05:30
|
|
|
val fullscreenOrientationPref = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.FULLSCREEN_ORIENTATION,
|
2023-06-24 23:27:00 +05:30
|
|
|
"ratio"
|
2022-10-07 22:48:04 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
return when (fullscreenOrientationPref) {
|
|
|
|
"ratio" -> {
|
|
|
|
// probably a youtube shorts video
|
2023-03-24 20:32:56 +05:30
|
|
|
if (videoHeight > videoWidth) {
|
2022-10-07 22:48:04 +05:30
|
|
|
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
|
|
} // a video with normal aspect ratio
|
|
|
|
else {
|
|
|
|
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
|
|
|
}
|
|
|
|
}
|
2023-06-24 22:27:33 +05:30
|
|
|
|
2022-10-07 22:48:04 +05:30
|
|
|
"auto" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
|
|
|
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
|
|
|
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
|
|
else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
|
|
|
}
|
|
|
|
}
|
2022-10-07 23:00:59 +05:30
|
|
|
|
|
|
|
val autoRotationEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.AUTO_FULLSCREEN,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
val relatedStreamsEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.RELATED_STREAMS,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
val pausePlayerOnScreenOffEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PAUSE_ON_SCREEN_OFF,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2023-02-12 17:25:31 +05:30
|
|
|
private val watchPositionsPref: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.WATCH_POSITIONS,
|
2023-06-24 23:27:00 +05:30
|
|
|
"always"
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2023-02-12 17:25:31 +05:30
|
|
|
val watchPositionsVideo: Boolean
|
|
|
|
get() = watchPositionsPref in listOf("always", "videos")
|
|
|
|
|
|
|
|
val watchPositionsAudio: Boolean
|
|
|
|
get() = watchPositionsPref == "always"
|
|
|
|
|
2022-10-07 23:00:59 +05:30
|
|
|
val watchHistoryEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.WATCH_HISTORY_TOGGLE,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
val useSystemCaptionStyle: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SYSTEM_CAPTION_STYLE,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2023-01-19 22:18:35 +05:30
|
|
|
private val bufferingGoal: Int
|
2022-10-07 23:00:59 +05:30
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.BUFFERING_GOAL,
|
2023-06-24 23:27:00 +05:30
|
|
|
"50"
|
2022-10-07 23:00:59 +05:30
|
|
|
).toInt() * 1000
|
|
|
|
|
|
|
|
val sponsorBlockEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
"sb_enabled_key",
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2023-02-12 17:47:44 +05:30
|
|
|
private val sponsorBlockNotifications: Boolean
|
2022-10-07 23:00:59 +05:30
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
"sb_notifications_key",
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2023-06-24 23:08:39 +05:30
|
|
|
private val sponsorBlockHighlights: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SB_HIGHLIGHTS,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
2022-12-22 15:47:34 +05:30
|
|
|
val defaultSubtitleCode: String?
|
2022-10-07 23:00:59 +05:30
|
|
|
get() {
|
|
|
|
val code = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.DEFAULT_SUBTITLE,
|
2023-06-24 23:27:00 +05:30
|
|
|
""
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2022-12-22 15:47:34 +05:30
|
|
|
if (code == "") return null
|
|
|
|
|
2022-10-07 23:00:59 +05:30
|
|
|
if (code.contains("-")) {
|
|
|
|
return code.split("-")[0]
|
|
|
|
}
|
|
|
|
return code
|
|
|
|
}
|
|
|
|
|
|
|
|
val skipButtonsEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SKIP_BUTTONS,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2022-10-07 23:00:59 +05:30
|
|
|
)
|
|
|
|
|
2022-10-19 23:23:18 +05:30
|
|
|
val pipEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PICTURE_IN_PICTURE,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-19 23:23:18 +05:30
|
|
|
)
|
2022-10-07 23:00:59 +05:30
|
|
|
|
2023-06-25 13:50:06 +05:30
|
|
|
var autoPlayEnabled: Boolean
|
2022-10-07 23:09:41 +05:30
|
|
|
get() = PreferenceHelper.getBoolean(
|
2023-06-25 13:50:06 +05:30
|
|
|
PreferenceKeys.AUTOPLAY,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-10-07 23:09:41 +05:30
|
|
|
)
|
2023-06-25 13:50:06 +05:30
|
|
|
set(value) {
|
|
|
|
PreferenceHelper.putBoolean(PreferenceKeys.AUTOPLAY, value)
|
|
|
|
}
|
2022-10-07 23:09:41 +05:30
|
|
|
|
2023-02-22 16:57:03 +05:30
|
|
|
val autoPlayCountdown: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.AUTOPLAY_COUNTDOWN,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2023-02-22 16:57:03 +05:30
|
|
|
)
|
|
|
|
|
2022-10-07 23:09:41 +05:30
|
|
|
val seekIncrement: Long
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.SEEK_INCREMENT,
|
2023-06-24 23:27:00 +05:30
|
|
|
"10.0"
|
2022-10-07 23:09:41 +05:30
|
|
|
).toFloat()
|
|
|
|
.roundToInt()
|
|
|
|
.toLong() * 1000
|
|
|
|
|
2023-06-26 13:00:05 +05:30
|
|
|
val playbackSpeed: Float
|
2022-10-07 23:09:41 +05:30
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.PLAYBACK_SPEED,
|
2023-06-24 23:27:00 +05:30
|
|
|
"1"
|
2023-02-17 00:53:46 +05:30
|
|
|
).replace("F", "").toFloat()
|
|
|
|
|
|
|
|
private val backgroundSpeed: Float
|
|
|
|
get() = when (PreferenceHelper.getBoolean(PreferenceKeys.CUSTOM_PLAYBACK_SPEED, false)) {
|
2023-06-24 22:27:33 +05:30
|
|
|
true -> PreferenceHelper.getString(PreferenceKeys.BACKGROUND_PLAYBACK_SPEED, "1")
|
|
|
|
.toFloat()
|
|
|
|
|
2023-02-17 00:53:46 +05:30
|
|
|
else -> playbackSpeed
|
|
|
|
}
|
2022-10-07 23:09:41 +05:30
|
|
|
|
|
|
|
val resizeModePref: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.PLAYER_RESIZE_MODE,
|
2023-06-24 23:27:00 +05:30
|
|
|
"fit"
|
2022-10-07 23:09:41 +05:30
|
|
|
)
|
|
|
|
|
2022-11-12 23:34:40 +05:30
|
|
|
val alternativeVideoLayout: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.ALTERNATIVE_PLAYER_LAYOUT,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2022-11-12 23:34:40 +05:30
|
|
|
)
|
|
|
|
|
2022-11-22 21:29:24 +05:30
|
|
|
val autoInsertRelatedVideos: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.QUEUE_AUTO_INSERT_RELATED,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-11-22 21:29:24 +05:30
|
|
|
)
|
|
|
|
|
2022-11-25 16:48:37 +05:30
|
|
|
val swipeGestureEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
2022-11-25 20:23:41 +05:30
|
|
|
PreferenceKeys.PLAYER_SWIPE_CONTROLS,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-11-25 16:48:37 +05:30
|
|
|
)
|
|
|
|
|
2023-04-10 18:23:28 +05:30
|
|
|
val fullscreenGesturesEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.FULLSCREEN_GESTURES,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2023-04-10 18:23:28 +05:30
|
|
|
)
|
|
|
|
|
2022-12-01 23:03:30 +05:30
|
|
|
val pinchGestureEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PLAYER_PINCH_CONTROL,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-12-01 23:03:30 +05:30
|
|
|
)
|
|
|
|
|
2022-11-27 22:20:09 +05:30
|
|
|
val captionsTextSize: Float
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.CAPTIONS_SIZE,
|
2023-06-24 23:27:00 +05:30
|
|
|
"18"
|
2022-11-27 22:20:09 +05:30
|
|
|
).toFloat()
|
|
|
|
|
2022-11-27 23:11:34 +05:30
|
|
|
val doubleTapToSeek: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.DOUBLE_TAP_TO_SEEK,
|
2023-06-24 23:27:00 +05:30
|
|
|
true
|
2022-11-27 23:11:34 +05:30
|
|
|
)
|
|
|
|
|
2022-12-27 23:18:09 +05:30
|
|
|
val pauseOnQuit: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PAUSE_ON_QUIT,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2022-12-27 23:18:09 +05:30
|
|
|
)
|
|
|
|
|
2023-01-19 22:18:35 +05:30
|
|
|
private val alternativePiPControls: Boolean
|
2023-01-09 22:08:17 +05:30
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.ALTERNATIVE_PIP_CONTROLS,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2023-01-09 22:08:17 +05:30
|
|
|
)
|
|
|
|
|
2023-01-21 16:37:28 +05:30
|
|
|
private val skipSilence: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SKIP_SILENCE,
|
2023-06-24 23:27:00 +05:30
|
|
|
false
|
2023-01-21 16:37:28 +05:30
|
|
|
)
|
|
|
|
|
2023-02-08 14:11:59 +05:30
|
|
|
val enabledVideoCodecs: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.ENABLED_VIDEO_CODECS,
|
2023-06-24 23:27:00 +05:30
|
|
|
"all"
|
2023-02-08 14:11:59 +05:30
|
|
|
)
|
|
|
|
|
2022-10-07 23:00:59 +05:30
|
|
|
fun getDefaultResolution(context: Context): String {
|
2023-01-21 17:44:06 +05:30
|
|
|
val prefKey = if (NetworkHelper.isNetworkMetered(context)) {
|
|
|
|
PreferenceKeys.DEFAULT_RESOLUTION_MOBILE
|
2022-10-07 23:00:59 +05:30
|
|
|
} else {
|
2023-01-21 17:44:06 +05:30
|
|
|
PreferenceKeys.DEFAULT_RESOLUTION
|
2022-10-07 23:00:59 +05:30
|
|
|
}
|
2023-01-21 17:44:06 +05:30
|
|
|
return PreferenceHelper.getString(prefKey, "")
|
2022-10-07 23:00:59 +05:30
|
|
|
}
|
2023-01-09 21:38:05 +05:30
|
|
|
|
|
|
|
fun getIntentActon(context: Context): String {
|
|
|
|
return context.packageName + "." + ACTION_MEDIA_CONTROL
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getPendingIntent(activity: Activity, code: Int): PendingIntent {
|
2023-04-09 17:36:28 +05:30
|
|
|
val intent = Intent(getIntentActon(activity)).putExtra(CONTROL_TYPE, code)
|
|
|
|
return PendingIntentCompat.getBroadcast(activity, code, intent, 0, false)
|
2023-01-09 21:38:05 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
private fun getRemoteAction(
|
|
|
|
activity: Activity,
|
|
|
|
id: Int,
|
|
|
|
@StringRes title: Int,
|
2023-06-24 23:27:00 +05:30
|
|
|
event: PlayerEvent
|
2023-02-03 17:09:08 +05:30
|
|
|
): RemoteActionCompat {
|
2023-01-09 21:38:05 +05:30
|
|
|
val text = activity.getString(title)
|
2023-02-03 17:09:08 +05:30
|
|
|
return RemoteActionCompat(
|
|
|
|
IconCompat.createWithResource(activity, id),
|
2023-01-09 21:38:05 +05:30
|
|
|
text,
|
|
|
|
text,
|
2023-06-24 23:27:00 +05:30
|
|
|
getPendingIntent(activity, event.value)
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-09 22:08:17 +05:30
|
|
|
/**
|
|
|
|
* Create controls to use in the PiP window
|
|
|
|
*/
|
2023-02-03 17:09:08 +05:30
|
|
|
fun getPiPModeActions(activity: Activity, isPlaying: Boolean): List<RemoteActionCompat> {
|
2023-01-15 17:56:16 +05:30
|
|
|
val audioModeAction = getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_headphones,
|
|
|
|
R.string.background_mode,
|
2023-06-24 23:27:00 +05:30
|
|
|
PlayerEvent.Background
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
|
|
|
|
2023-01-15 17:56:16 +05:30
|
|
|
val rewindAction = getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_rewind,
|
|
|
|
R.string.rewind,
|
2023-06-24 23:27:00 +05:30
|
|
|
PlayerEvent.Rewind
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
|
|
|
|
2023-01-15 17:56:16 +05:30
|
|
|
val playPauseAction = getRemoteAction(
|
|
|
|
activity,
|
|
|
|
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
|
|
|
|
R.string.pause,
|
2023-06-24 23:27:00 +05:30
|
|
|
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
|
2023-01-15 17:56:16 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
val skipNextAction = getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_next,
|
|
|
|
R.string.play_next,
|
2023-06-24 23:27:00 +05:30
|
|
|
PlayerEvent.Next
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
2023-01-15 17:56:16 +05:30
|
|
|
|
|
|
|
val forwardAction = getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_forward,
|
|
|
|
R.string.forward,
|
2023-06-24 23:27:00 +05:30
|
|
|
PlayerEvent.Forward
|
2023-01-15 17:56:16 +05:30
|
|
|
)
|
2023-02-03 17:09:08 +05:30
|
|
|
return if (alternativePiPControls) {
|
|
|
|
listOf(audioModeAction, playPauseAction, skipNextAction)
|
2023-01-15 17:56:16 +05:30
|
|
|
} else {
|
2023-02-03 17:09:08 +05:30
|
|
|
listOf(rewindAction, playPauseAction, forwardAction)
|
2023-01-15 17:56:16 +05:30
|
|
|
}
|
2023-01-09 21:38:05 +05:30
|
|
|
}
|
2023-01-19 22:18:35 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the audio attributes to use for the player
|
|
|
|
*/
|
|
|
|
fun getAudioAttributes(): AudioAttributes {
|
|
|
|
return AudioAttributes.Builder()
|
|
|
|
.setUsage(C.USAGE_MEDIA)
|
|
|
|
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
|
|
|
.build()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the load controls for the player (buffering, etc)
|
|
|
|
*/
|
2023-05-15 20:51:49 +05:30
|
|
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
2023-01-19 22:18:35 +05:30
|
|
|
fun getLoadControl(): LoadControl {
|
|
|
|
return DefaultLoadControl.Builder()
|
|
|
|
// cache the last three minutes
|
|
|
|
.setBackBuffer(1000 * 60 * 3, true)
|
|
|
|
.setBufferDurationsMs(
|
|
|
|
1000 * 10, // exo default is 50s
|
|
|
|
bufferingGoal,
|
|
|
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
2023-06-24 23:27:00 +05:30
|
|
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
2023-01-19 22:18:35 +05:30
|
|
|
)
|
|
|
|
.build()
|
|
|
|
}
|
2023-01-21 16:37:28 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Load playback parameters such as speed and skip silence
|
|
|
|
*/
|
2023-05-15 20:51:49 +05:30
|
|
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
2023-02-17 00:53:46 +05:30
|
|
|
fun ExoPlayer.loadPlaybackParams(isBackgroundMode: Boolean = false): ExoPlayer {
|
2023-01-21 16:37:28 +05:30
|
|
|
skipSilenceEnabled = skipSilence
|
2023-02-17 00:53:46 +05:30
|
|
|
val speed = if (isBackgroundMode) backgroundSpeed else playbackSpeed
|
|
|
|
playbackParameters = PlaybackParameters(speed, 1.0f)
|
2023-01-21 16:37:28 +05:30
|
|
|
return this
|
|
|
|
}
|
2023-02-12 17:47:44 +05:30
|
|
|
|
2023-06-24 23:08:39 +05:30
|
|
|
/**
|
|
|
|
* Get the preview frame according to the current position
|
|
|
|
*/
|
|
|
|
fun getPreviewFrame(previewFrames: List<PreviewFrames>, position: Long): PreviewFrame? {
|
|
|
|
var startPosition: Long = 0
|
|
|
|
// get the frames with the best quality
|
|
|
|
val frames = previewFrames.maxByOrNull { it.frameHeight }
|
|
|
|
frames?.urls?.forEach { url ->
|
|
|
|
// iterate over all available positions and find the one matching the current position
|
|
|
|
for (y in 0 until frames.framesPerPageY) {
|
|
|
|
for (x in 0 until frames.framesPerPageX) {
|
|
|
|
val endPosition = startPosition + frames.durationPerFrame
|
|
|
|
if (position in startPosition until endPosition) {
|
2023-06-24 23:22:03 +05:30
|
|
|
return PreviewFrame(url, x, y, frames.frameWidth, frames.frameHeight)
|
2023-06-24 23:08:39 +05:30
|
|
|
}
|
|
|
|
startPosition = endPosition
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get the categories for sponsorBlock
|
|
|
|
*/
|
|
|
|
fun getSponsorBlockCategories(): MutableMap<String, SbSkipOptions> {
|
|
|
|
val categories: MutableMap<String, SbSkipOptions> = mutableMapOf()
|
|
|
|
|
|
|
|
for (category in SPONSOR_CATEGORIES) {
|
|
|
|
val state = PreferenceHelper.getString(category + "_category", "off").uppercase()
|
|
|
|
if (SbSkipOptions.valueOf(state) != SbSkipOptions.OFF) {
|
|
|
|
categories[category] = SbSkipOptions.valueOf(state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add the highlights category to display in the chapters
|
|
|
|
if (sponsorBlockHighlights) categories[SPONSOR_HIGHLIGHT_CATEGORY] = SbSkipOptions.OFF
|
|
|
|
return categories
|
|
|
|
}
|
|
|
|
|
2023-02-12 17:47:44 +05:30
|
|
|
/**
|
|
|
|
* Check for SponsorBlock segments matching the current player position
|
|
|
|
* @param context A main dispatcher context
|
|
|
|
* @param segments List of the SponsorBlock segments
|
2023-06-24 22:27:33 +05:30
|
|
|
* @return If segment found and should skip manually, the end position of the segment in ms, otherwise null
|
2023-02-12 17:47:44 +05:30
|
|
|
*/
|
2023-02-12 17:57:35 +05:30
|
|
|
fun ExoPlayer.checkForSegments(
|
|
|
|
context: Context,
|
|
|
|
segments: List<Segment>,
|
2023-06-24 23:27:00 +05:30
|
|
|
sponsorBlockConfig: MutableMap<String, SbSkipOptions>
|
2023-02-12 17:57:35 +05:30
|
|
|
): Long? {
|
2023-06-24 23:08:39 +05:30
|
|
|
for (segment in segments.filter { it.category != SPONSOR_HIGHLIGHT_CATEGORY }) {
|
2023-07-04 06:47:50 +05:30
|
|
|
val (start, end) = segment.segmentStartAndEnd
|
|
|
|
val (segmentStart, segmentEnd) = (start * 1000f).toLong() to (end * 1000f).toLong()
|
2023-02-12 17:57:35 +05:30
|
|
|
|
|
|
|
// avoid seeking to the same segment multiple times, e.g. when the SB segment is at the end of the video
|
|
|
|
if ((duration - currentPosition).absoluteValue < 500) continue
|
|
|
|
|
2023-02-12 17:47:44 +05:30
|
|
|
if (currentPosition in segmentStart until segmentEnd) {
|
2023-06-19 16:26:53 +05:30
|
|
|
if (sponsorBlockConfig[segment.category] == SbSkipOptions.AUTOMATIC) {
|
2023-02-12 17:47:44 +05:30
|
|
|
if (sponsorBlockNotifications) {
|
|
|
|
runCatching {
|
|
|
|
Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT)
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seekTo(segmentEnd)
|
2023-06-22 19:01:41 +05:30
|
|
|
} else if (sponsorBlockConfig[segment.category] == SbSkipOptions.MANUAL) {
|
2023-02-12 17:47:44 +05:30
|
|
|
return segmentEnd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
2023-06-19 16:26:53 +05:30
|
|
|
|
2023-07-04 06:47:50 +05:30
|
|
|
fun ExoPlayer.isInSegment(segments: List<Segment>): Boolean {
|
|
|
|
return segments.any {
|
|
|
|
val (start, end) = it.segmentStartAndEnd
|
|
|
|
val (segmentStart, segmentEnd) = (start * 1000f).toLong() to (end * 1000f).toLong()
|
|
|
|
currentPosition in segmentStart..segmentEnd
|
2023-06-23 17:34:58 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-24 22:27:33 +05:30
|
|
|
/**
|
|
|
|
* Get the name of the currently played chapter
|
|
|
|
*/
|
|
|
|
fun getCurrentChapterIndex(exoPlayer: ExoPlayer, chapters: List<ChapterSegment>): Int? {
|
|
|
|
val currentPosition = exoPlayer.currentPosition / 1000
|
|
|
|
return chapters.indexOfLast { currentPosition >= it.start }.takeIf { it >= 0 }
|
|
|
|
}
|
|
|
|
|
2023-06-19 16:26:53 +05:30
|
|
|
/**
|
|
|
|
* Show a dialog with the chapters provided, even if the list is empty
|
|
|
|
*/
|
|
|
|
fun showChaptersDialog(context: Context, chapters: List<ChapterSegment>, player: ExoPlayer) {
|
|
|
|
val titles = chapters.map { chapter ->
|
|
|
|
"(${DateUtils.formatElapsedTime(chapter.start)}) ${chapter.title}"
|
|
|
|
}
|
2023-06-24 22:27:33 +05:30
|
|
|
val dialog = MaterialAlertDialogBuilder(context)
|
2023-06-19 16:26:53 +05:30
|
|
|
.setTitle(R.string.chapters)
|
|
|
|
.setItems(titles.toTypedArray()) { _, index ->
|
|
|
|
player.seekTo(chapters[index].start * 1000)
|
|
|
|
}
|
2023-06-24 22:27:33 +05:30
|
|
|
.create()
|
|
|
|
val handler = Handler(Looper.getMainLooper())
|
|
|
|
|
|
|
|
val updatePosition = Runnable {
|
|
|
|
// scroll to the current playing index in the chapter
|
|
|
|
val currentPosition =
|
|
|
|
getCurrentChapterIndex(player, chapters) ?: return@Runnable
|
|
|
|
dialog.listView.smoothScrollToPosition(currentPosition)
|
|
|
|
val current = dialog.listView.children.toList()[currentPosition]
|
|
|
|
val highlightColor =
|
|
|
|
ThemeHelper.getThemeColor(context, android.R.attr.colorControlHighlight)
|
|
|
|
current.setBackgroundColor(highlightColor)
|
|
|
|
}
|
|
|
|
|
|
|
|
dialog.setOnShowListener {
|
|
|
|
updatePosition.run()
|
|
|
|
// update the position after a short delay
|
|
|
|
if (dialog.isShowing) handler.postDelayed(updatePosition, 200)
|
|
|
|
}
|
|
|
|
|
|
|
|
dialog.show()
|
2023-06-19 16:26:53 +05:30
|
|
|
}
|
2022-07-17 02:19:32 +05:30
|
|
|
}
|