2022-07-17 01:01:15 +05:30
|
|
|
package com.github.libretube.util
|
|
|
|
|
2023-01-09 21:38:05 +05:30
|
|
|
import android.app.Activity
|
|
|
|
import android.app.PendingIntent
|
|
|
|
import android.app.RemoteAction
|
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-01-09 21:38:05 +05:30
|
|
|
import android.graphics.drawable.Icon
|
|
|
|
import android.os.Build
|
2022-07-17 01:01:15 +05:30
|
|
|
import android.view.accessibility.CaptioningManager
|
2023-01-09 21:38:05 +05:30
|
|
|
import androidx.annotation.RequiresApi
|
|
|
|
import androidx.annotation.StringRes
|
|
|
|
import com.github.libretube.R
|
2022-11-16 15:30:39 +05:30
|
|
|
import com.github.libretube.api.obj.PipedStream
|
2022-09-20 23:30:51 +05:30
|
|
|
import com.github.libretube.constants.PreferenceKeys
|
2023-01-08 20:36:29 +05:30
|
|
|
import com.github.libretube.enums.AudioQuality
|
2023-01-09 21:38:05 +05:30
|
|
|
import com.github.libretube.enums.PlayerEvent
|
2023-01-15 00:32:01 +05:30
|
|
|
import com.google.android.exoplayer2.text.Cue
|
2022-07-17 01:01:15 +05:30
|
|
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
2023-01-15 00:32:01 +05:30
|
|
|
import com.google.android.exoplayer2.ui.SubtitleView
|
2022-10-07 22:48:04 +05:30
|
|
|
import com.google.android.exoplayer2.video.VideoSize
|
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-01-08 20:36:29 +05:30
|
|
|
/**
|
|
|
|
* Get the audio source following the users preferences
|
|
|
|
*/
|
2022-09-22 21:29:15 +05:30
|
|
|
fun getAudioSource(
|
|
|
|
context: Context,
|
2022-11-16 15:30:39 +05:30
|
|
|
audios: List<PipedStream>
|
2022-09-22 21:29:15 +05:30
|
|
|
): String {
|
2022-07-24 16:29:15 +05:30
|
|
|
val audioFormat = PreferenceHelper.getString(PreferenceKeys.PLAYER_AUDIO_FORMAT, "all")
|
2023-01-08 20:36:29 +05:30
|
|
|
val audioPrefKey = if (
|
2022-09-11 21:24:04 +05:30
|
|
|
NetworkHelper.isNetworkMobile(context)
|
|
|
|
) {
|
2023-01-08 20:36:29 +05:30
|
|
|
PreferenceKeys.PLAYER_AUDIO_QUALITY_MOBILE
|
2022-09-11 21:24:04 +05:30
|
|
|
} else {
|
2023-01-08 20:36:29 +05:30
|
|
|
PreferenceKeys.PLAYER_AUDIO_QUALITY
|
2022-09-11 21:24:04 +05:30
|
|
|
}
|
2022-07-24 16:29:15 +05:30
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
val audioQuality = PreferenceHelper.getString(audioPrefKey, "best")
|
2022-07-24 16:29:15 +05:30
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
val filteredAudios = audios.filter {
|
|
|
|
val audioMimeType = "audio/$audioFormat"
|
|
|
|
it.mimeType != audioMimeType || audioFormat == "all"
|
2022-07-24 16:29:15 +05:30
|
|
|
}
|
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
return getBitRate(
|
|
|
|
filteredAudios,
|
|
|
|
if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST
|
|
|
|
)
|
2022-07-17 01:01:15 +05:30
|
|
|
}
|
|
|
|
|
2023-01-08 20:36:29 +05:30
|
|
|
/**
|
|
|
|
* Get the best or worst bitrate from a list of audio streams
|
|
|
|
* @param audios list of the audio streams
|
2023-01-09 21:38:05 +05:30
|
|
|
* @param quality Whether to use the best or worst quality available
|
2023-01-08 20:36:29 +05:30
|
|
|
* @return Url of the audio source
|
|
|
|
*/
|
|
|
|
private fun getBitRate(audios: List<PipedStream>, quality: AudioQuality): String {
|
|
|
|
val filteredAudios = audios.filter {
|
|
|
|
it.bitrate != null
|
|
|
|
}.sortedBy {
|
|
|
|
it.bitrate
|
2022-07-24 16:29:15 +05:30
|
|
|
}
|
2023-01-08 20:36:29 +05:30
|
|
|
return when (quality) {
|
|
|
|
AudioQuality.BEST -> filteredAudios.last()
|
|
|
|
AudioQuality.WORST -> filteredAudios.first()
|
|
|
|
}.url!!
|
2022-07-24 16:29:15 +05:30
|
|
|
}
|
|
|
|
|
2022-07-17 01:01:15 +05:30
|
|
|
// get the system default caption style
|
|
|
|
fun getCaptionStyle(context: Context): CaptionStyleCompat {
|
2022-07-18 23:06:21 +05:30
|
|
|
val captioningManager =
|
|
|
|
context.getSystemService(Context.CAPTIONING_SERVICE) as 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
|
|
|
|
|
|
|
/**
|
|
|
|
* get the categories for sponsorBlock
|
|
|
|
*/
|
|
|
|
fun getSponsorBlockCategories(): ArrayList<String> {
|
|
|
|
val categories: ArrayList<String> = arrayListOf()
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"intro_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("intro")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"selfpromo_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("selfpromo")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"interaction_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("interaction")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"sponsors_category_key",
|
|
|
|
true
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("sponsor")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"outro_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("outro")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"filler_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("filler")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"music_offtopic_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("music_offtopic")
|
|
|
|
}
|
|
|
|
if (PreferenceHelper.getBoolean(
|
|
|
|
"preview_category_key",
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
categories.add("preview")
|
|
|
|
}
|
|
|
|
return categories
|
|
|
|
}
|
2022-10-07 22:48:04 +05:30
|
|
|
|
|
|
|
fun getOrientation(videoSize: VideoSize): Int {
|
|
|
|
val fullscreenOrientationPref = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.FULLSCREEN_ORIENTATION,
|
|
|
|
"ratio"
|
|
|
|
)
|
|
|
|
|
|
|
|
return when (fullscreenOrientationPref) {
|
|
|
|
"ratio" -> {
|
|
|
|
// probably a youtube shorts video
|
|
|
|
if (videoSize.height > videoSize.width) {
|
|
|
|
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
|
|
} // a video with normal aspect ratio
|
|
|
|
else {
|
|
|
|
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"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,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
|
|
|
val relatedStreamsEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.RELATED_STREAMS,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val pausePlayerOnScreenOffEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PAUSE_ON_SCREEN_OFF,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
|
|
|
val watchPositionsEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.WATCH_POSITION_TOGGLE,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val watchHistoryEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.WATCH_HISTORY_TOGGLE,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val useSystemCaptionStyle: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SYSTEM_CAPTION_STYLE,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val videoFormatPreference: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.PLAYER_VIDEO_FORMAT,
|
|
|
|
"webm"
|
|
|
|
)
|
|
|
|
|
|
|
|
val bufferingGoal: Int
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.BUFFERING_GOAL,
|
|
|
|
"50"
|
|
|
|
).toInt() * 1000
|
|
|
|
|
|
|
|
val sponsorBlockEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
"sb_enabled_key",
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val sponsorBlockNotifications: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
"sb_notifications_key",
|
|
|
|
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,
|
|
|
|
""
|
|
|
|
)
|
|
|
|
|
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,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2022-10-19 23:23:18 +05:30
|
|
|
val pipEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PICTURE_IN_PICTURE,
|
|
|
|
true
|
|
|
|
)
|
2022-10-07 23:00:59 +05:30
|
|
|
|
|
|
|
val skipSegmentsManually: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.SB_SKIP_MANUALLY,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2022-10-07 23:09:41 +05:30
|
|
|
val autoPlayEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.AUTO_PLAY,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
|
|
|
val seekIncrement: Long
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.SEEK_INCREMENT,
|
|
|
|
"10.0"
|
|
|
|
).toFloat()
|
|
|
|
.roundToInt()
|
|
|
|
.toLong() * 1000
|
|
|
|
|
|
|
|
val playbackSpeed: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.PLAYBACK_SPEED,
|
|
|
|
"1"
|
|
|
|
).replace("F", "")
|
|
|
|
|
|
|
|
val resizeModePref: String
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.PLAYER_RESIZE_MODE,
|
|
|
|
"fit"
|
|
|
|
)
|
|
|
|
|
2022-11-12 23:34:40 +05:30
|
|
|
val alternativeVideoLayout: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.ALTERNATIVE_PLAYER_LAYOUT,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2022-11-22 21:29:24 +05:30
|
|
|
val autoInsertRelatedVideos: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.QUEUE_AUTO_INSERT_RELATED,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
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,
|
2022-11-25 16:48:37 +05:30
|
|
|
true
|
|
|
|
)
|
|
|
|
|
2022-12-01 23:03:30 +05:30
|
|
|
val pinchGestureEnabled: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PLAYER_PINCH_CONTROL,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
2022-11-27 22:20:09 +05:30
|
|
|
val captionsTextSize: Float
|
|
|
|
get() = PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.CAPTIONS_SIZE,
|
|
|
|
"18"
|
|
|
|
).toFloat()
|
|
|
|
|
2022-11-27 23:11:34 +05:30
|
|
|
val doubleTapToSeek: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.DOUBLE_TAP_TO_SEEK,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
|
2022-12-27 23:18:09 +05:30
|
|
|
val pauseOnQuit: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.PAUSE_ON_QUIT,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2023-01-09 22:08:17 +05:30
|
|
|
val alternativePiPControls: Boolean
|
|
|
|
get() = PreferenceHelper.getBoolean(
|
|
|
|
PreferenceKeys.ALTERNATIVE_PIP_CONTROLS,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2022-10-07 23:00:59 +05:30
|
|
|
fun getDefaultResolution(context: Context): String {
|
|
|
|
return if (NetworkHelper.isNetworkMobile(context)) {
|
|
|
|
PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.DEFAULT_RESOLUTION_MOBILE,
|
|
|
|
""
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
PreferenceHelper.getString(
|
|
|
|
PreferenceKeys.DEFAULT_RESOLUTION,
|
|
|
|
""
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
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 {
|
|
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
PendingIntent.getBroadcast(
|
|
|
|
activity,
|
|
|
|
code,
|
|
|
|
Intent(getIntentActon(activity)).putExtra(CONTROL_TYPE, code),
|
|
|
|
PendingIntent.FLAG_IMMUTABLE
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
PendingIntent.getBroadcast(
|
|
|
|
activity,
|
|
|
|
code,
|
|
|
|
Intent(getIntentActon(activity)).putExtra(CONTROL_TYPE, code),
|
|
|
|
0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
|
|
private fun getRemoteAction(
|
|
|
|
activity: Activity,
|
|
|
|
id: Int,
|
|
|
|
@StringRes title: Int,
|
|
|
|
event: PlayerEvent
|
|
|
|
): RemoteAction {
|
|
|
|
val text = activity.getString(title)
|
|
|
|
return RemoteAction(
|
|
|
|
Icon.createWithResource(activity, id),
|
|
|
|
text,
|
|
|
|
text,
|
|
|
|
getPendingIntent(activity, event.value)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-09 22:08:17 +05:30
|
|
|
/**
|
|
|
|
* Create controls to use in the PiP window
|
|
|
|
*/
|
2023-01-09 21:38:05 +05:30
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
2023-01-09 22:08:17 +05:30
|
|
|
fun getPiPModeActions(activity: Activity, isPlaying: Boolean, isOfflinePlayer: Boolean = false): ArrayList<RemoteAction> {
|
2023-01-09 21:38:05 +05:30
|
|
|
val actions: ArrayList<RemoteAction> = ArrayList()
|
|
|
|
actions.add(
|
2023-01-09 22:08:17 +05:30
|
|
|
if (!isOfflinePlayer && alternativePiPControls) {
|
|
|
|
getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_headphones,
|
|
|
|
R.string.background_mode,
|
|
|
|
PlayerEvent.Background
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_rewind,
|
|
|
|
R.string.rewind,
|
|
|
|
PlayerEvent.Rewind
|
|
|
|
)
|
|
|
|
}
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
actions.add(
|
|
|
|
getRemoteAction(
|
|
|
|
activity,
|
|
|
|
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
|
|
|
|
R.string.pause,
|
|
|
|
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
actions.add(
|
2023-01-09 22:08:17 +05:30
|
|
|
if (!isOfflinePlayer && alternativePiPControls) {
|
|
|
|
getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_next,
|
|
|
|
R.string.play_next,
|
|
|
|
PlayerEvent.Next
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
getRemoteAction(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_forward,
|
|
|
|
R.string.forward,
|
|
|
|
PlayerEvent.Forward
|
|
|
|
)
|
|
|
|
}
|
2023-01-09 21:38:05 +05:30
|
|
|
)
|
|
|
|
return actions
|
|
|
|
}
|
2023-01-15 00:32:01 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Load the captions style according to the users preferences
|
|
|
|
*/
|
|
|
|
fun applyCaptionsStyle(context: Context, subtitleView: SubtitleView?) {
|
|
|
|
val captionStyle = getCaptionStyle(context)
|
|
|
|
subtitleView?.apply {
|
|
|
|
setApplyEmbeddedFontSizes(false)
|
|
|
|
setFixedTextSize(Cue.TEXT_SIZE_TYPE_ABSOLUTE, captionsTextSize)
|
|
|
|
if (!useSystemCaptionStyle) return
|
|
|
|
setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT)
|
|
|
|
setStyle(captionStyle)
|
|
|
|
}
|
|
|
|
}
|
2022-07-17 02:19:32 +05:30
|
|
|
}
|