mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
resize images for lower mem footprint
This commit is contained in:
parent
2f2bbd7d88
commit
bb6a17e23c
3 changed files with 189 additions and 49 deletions
|
@ -37,6 +37,7 @@ import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode
|
||||||
import com.lagradost.cloudstream3.CommonActivity.isInPIPMode
|
import com.lagradost.cloudstream3.CommonActivity.isInPIPMode
|
||||||
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
||||||
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
|
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.screenWidth
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -79,12 +80,12 @@ abstract class AbstractPlayerFragment(
|
||||||
var isBuffering = true
|
var isBuffering = true
|
||||||
protected open var hasPipModeSupport = true
|
protected open var hasPipModeSupport = true
|
||||||
|
|
||||||
var playerPausePlayHolderHolder : FrameLayout? = null
|
var playerPausePlayHolderHolder: FrameLayout? = null
|
||||||
var playerPausePlay : ImageView? = null
|
var playerPausePlay: ImageView? = null
|
||||||
var playerBuffering : ProgressBar? = null
|
var playerBuffering: ProgressBar? = null
|
||||||
var playerView : PlayerView? = null
|
var playerView: PlayerView? = null
|
||||||
var piphide : FrameLayout? = null
|
var piphide: FrameLayout? = null
|
||||||
var subtitleHolder : FrameLayout? = null
|
var subtitleHolder: FrameLayout? = null
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
protected open var layout: Int = R.layout.fragment_player
|
protected open var layout: Int = R.layout.fragment_player
|
||||||
|
@ -97,11 +98,11 @@ abstract class AbstractPlayerFragment(
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun playerPositionChanged(position: Long, duration : Long) {
|
open fun playerPositionChanged(position: Long, duration: Long) {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun playerDimensionsLoaded(width: Int, height : Int) {
|
open fun playerDimensionsLoaded(width: Int, height: Int) {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +138,10 @@ abstract class AbstractPlayerFragment(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIsPlaying(wasPlaying : CSPlayerLoading,
|
private fun updateIsPlaying(
|
||||||
isPlaying : CSPlayerLoading) {
|
wasPlaying: CSPlayerLoading,
|
||||||
|
isPlaying: CSPlayerLoading
|
||||||
|
) {
|
||||||
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
|
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
|
||||||
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
|
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
|
||||||
|
|
||||||
|
@ -186,7 +189,11 @@ abstract class AbstractPlayerFragment(
|
||||||
canEnterPipMode = isPlayingRightNow && hasPipModeSupport
|
canEnterPipMode = isPlayingRightNow && hasPipModeSupport
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
activity?.let { act ->
|
activity?.let { act ->
|
||||||
PlayerPipHelper.updatePIPModeActions(act, isPlayingRightNow, player.getAspectRatio())
|
PlayerPipHelper.updatePIPModeActions(
|
||||||
|
act,
|
||||||
|
isPlayingRightNow,
|
||||||
|
player.getAspectRatio()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,49 +382,61 @@ abstract class AbstractPlayerFragment(
|
||||||
/** This receives the events from the player, if you want to append functionality you do it here,
|
/** This receives the events from the player, if you want to append functionality you do it here,
|
||||||
* do note that this only receives events for UI changes,
|
* do note that this only receives events for UI changes,
|
||||||
* and returning early WONT stop it from changing in eg the player time or pause status */
|
* and returning early WONT stop it from changing in eg the player time or pause status */
|
||||||
open fun mainCallback(event : PlayerEvent) {
|
open fun mainCallback(event: PlayerEvent) {
|
||||||
Log.i(TAG, "Handle event: $event")
|
Log.i(TAG, "Handle event: $event")
|
||||||
when(event) {
|
when (event) {
|
||||||
is ResizedEvent -> {
|
is ResizedEvent -> {
|
||||||
playerDimensionsLoaded(event.width, event.height)
|
playerDimensionsLoaded(event.width, event.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
is PlayerAttachedEvent -> {
|
is PlayerAttachedEvent -> {
|
||||||
playerUpdated(event.player)
|
playerUpdated(event.player)
|
||||||
}
|
}
|
||||||
|
|
||||||
is SubtitlesUpdatedEvent -> {
|
is SubtitlesUpdatedEvent -> {
|
||||||
subtitlesChanged()
|
subtitlesChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
is TimestampSkippedEvent -> {
|
is TimestampSkippedEvent -> {
|
||||||
onTimestampSkipped(event.timestamp)
|
onTimestampSkipped(event.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
is TimestampInvokedEvent -> {
|
is TimestampInvokedEvent -> {
|
||||||
onTimestamp(event.timestamp)
|
onTimestamp(event.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
is TracksChangedEvent -> {
|
is TracksChangedEvent -> {
|
||||||
onTracksInfoChanged()
|
onTracksInfoChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
is EmbeddedSubtitlesFetchedEvent -> {
|
is EmbeddedSubtitlesFetchedEvent -> {
|
||||||
embeddedSubtitlesFetched(event.tracks)
|
embeddedSubtitlesFetched(event.tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ErrorEvent -> {
|
is ErrorEvent -> {
|
||||||
playerError(event.error)
|
playerError(event.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
is RequestAudioFocusEvent -> {
|
is RequestAudioFocusEvent -> {
|
||||||
requestAudioFocus()
|
requestAudioFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
is EpisodeSeekEvent -> {
|
is EpisodeSeekEvent -> {
|
||||||
when(event.offset) {
|
when (event.offset) {
|
||||||
-1 -> prevEpisode()
|
-1 -> prevEpisode()
|
||||||
1 -> nextEpisode()
|
1 -> nextEpisode()
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is StatusEvent -> {
|
is StatusEvent -> {
|
||||||
updateIsPlaying(wasPlaying = event.wasPlaying, isPlaying = event.isPlaying)
|
updateIsPlaying(wasPlaying = event.wasPlaying, isPlaying = event.isPlaying)
|
||||||
}
|
}
|
||||||
|
|
||||||
is PositionEvent -> {
|
is PositionEvent -> {
|
||||||
playerPositionChanged(position = event.toMs, duration = event.durationMs)
|
playerPositionChanged(position = event.toMs, duration = event.durationMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
is VideoEndedEvent -> {
|
is VideoEndedEvent -> {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
// Only play next episode if autoplay is on (default)
|
// Only play next episode if autoplay is on (default)
|
||||||
|
@ -434,6 +453,7 @@ abstract class AbstractPlayerFragment(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PauseEvent -> Unit
|
is PauseEvent -> Unit
|
||||||
is PlayEvent -> Unit
|
is PlayEvent -> Unit
|
||||||
}
|
}
|
||||||
|
@ -457,10 +477,10 @@ abstract class AbstractPlayerFragment(
|
||||||
|
|
||||||
if (player is CS3IPlayer) {
|
if (player is CS3IPlayer) {
|
||||||
// preview bar
|
// preview bar
|
||||||
val progressBar : PreviewTimeBar? = playerView?.findViewById(R.id.exo_progress)
|
val progressBar: PreviewTimeBar? = playerView?.findViewById(R.id.exo_progress)
|
||||||
val previewImageView : ImageView? = playerView?.findViewById(R.id.previewImageView)
|
val previewImageView: ImageView? = playerView?.findViewById(R.id.previewImageView)
|
||||||
val previewFrameLayout : FrameLayout? = playerView?.findViewById(R.id.previewFrameLayout)
|
val previewFrameLayout: FrameLayout? = playerView?.findViewById(R.id.previewFrameLayout)
|
||||||
if(progressBar != null && previewImageView != null && previewFrameLayout != null) {
|
if (progressBar != null && previewImageView != null && previewFrameLayout != null) {
|
||||||
var resume = false
|
var resume = false
|
||||||
progressBar.addOnScrubListener(object : PreviewBar.OnScrubListener {
|
progressBar.addOnScrubListener(object : PreviewBar.OnScrubListener {
|
||||||
override fun onScrubStart(previewBar: PreviewBar?) {
|
override fun onScrubStart(previewBar: PreviewBar?) {
|
||||||
|
@ -495,17 +515,32 @@ abstract class AbstractPlayerFragment(
|
||||||
subView = playerView?.findViewById(R.id.exo_subtitles)
|
subView = playerView?.findViewById(R.id.exo_subtitles)
|
||||||
subStyle = SubtitlesFragment.getCurrentSavedStyle()
|
subStyle = SubtitlesFragment.getCurrentSavedStyle()
|
||||||
player.initSubtitles(subView, subtitleHolder, subStyle)
|
player.initSubtitles(subView, subtitleHolder, subStyle)
|
||||||
|
(player.imageGenerator as? PreviewGenerator)?.params = ImageParams.new16by9(screenWidth)
|
||||||
|
|
||||||
|
/*previewImageView?.doOnLayout {
|
||||||
|
(player.imageGenerator as? PreviewGenerator)?.params = ImageParams(
|
||||||
|
it.measuredWidth,
|
||||||
|
it.measuredHeight
|
||||||
|
)
|
||||||
|
}*/
|
||||||
/** this might seam a bit fucky and that is because it is, the seek event is captured twice, once by the player
|
/** this might seam a bit fucky and that is because it is, the seek event is captured twice, once by the player
|
||||||
* and once by the UI even if it should only be registered once by the UI */
|
* and once by the UI even if it should only be registered once by the UI */
|
||||||
playerView?.findViewById<DefaultTimeBar>(R.id.exo_progress)?.addListener(object : TimeBar.OnScrubListener {
|
playerView?.findViewById<DefaultTimeBar>(R.id.exo_progress)
|
||||||
|
?.addListener(object : TimeBar.OnScrubListener {
|
||||||
override fun onScrubStart(timeBar: TimeBar, position: Long) = Unit
|
override fun onScrubStart(timeBar: TimeBar, position: Long) = Unit
|
||||||
override fun onScrubMove(timeBar: TimeBar, position: Long) = Unit
|
override fun onScrubMove(timeBar: TimeBar, position: Long) = Unit
|
||||||
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
|
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
|
||||||
if (canceled) return
|
if (canceled) return
|
||||||
val playerDuration = player.getDuration() ?: return
|
val playerDuration = player.getDuration() ?: return
|
||||||
val playerPosition = player.getPosition() ?: return
|
val playerPosition = player.getPosition() ?: return
|
||||||
mainCallback(PositionEvent(source = PlayerEventSource.UI, durationMs = playerDuration, fromMs = playerPosition, toMs = position))
|
mainCallback(
|
||||||
|
PositionEvent(
|
||||||
|
source = PlayerEventSource.UI,
|
||||||
|
durationMs = playerDuration,
|
||||||
|
fromMs = playerPosition,
|
||||||
|
toMs = position
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ class CS3IPlayer : IPlayer {
|
||||||
var simpleCacheSize = 0L
|
var simpleCacheSize = 0L
|
||||||
var videoBufferMs = 0L
|
var videoBufferMs = 0L
|
||||||
|
|
||||||
private val imageGenerator = PreviewGenerator()
|
val imageGenerator = IPreviewGenerator.new()
|
||||||
|
|
||||||
private val seekActionTime = 30000L
|
private val seekActionTime = 30000L
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ class CS3IPlayer : IPlayer {
|
||||||
subtitles: Set<SubtitleData>,
|
subtitles: Set<SubtitleData>,
|
||||||
subtitle: SubtitleData?,
|
subtitle: SubtitleData?,
|
||||||
autoPlay: Boolean?,
|
autoPlay: Boolean?,
|
||||||
preview : Boolean,
|
preview: Boolean,
|
||||||
) {
|
) {
|
||||||
Log.i(TAG, "loadPlayer")
|
Log.i(TAG, "loadPlayer")
|
||||||
if (sameEpisode) {
|
if (sameEpisode) {
|
||||||
|
@ -224,24 +224,30 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
// release the current exoplayer and cache
|
// release the current exoplayer and cache
|
||||||
releasePlayer()
|
releasePlayer()
|
||||||
|
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
// only video support atm
|
// only video support atm
|
||||||
|
(imageGenerator as? PreviewGenerator)?.let { gen ->
|
||||||
if (preview) {
|
if (preview) {
|
||||||
imageGenerator.load(link, sameEpisode)
|
gen.load(link, sameEpisode)
|
||||||
} else {
|
} else {
|
||||||
imageGenerator.clear(sameEpisode)
|
gen.clear(sameEpisode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadOnlinePlayer(context, link)
|
loadOnlinePlayer(context, link)
|
||||||
} else if (data != null) {
|
} else if (data != null) {
|
||||||
|
(imageGenerator as? PreviewGenerator)?.let { gen ->
|
||||||
if (preview) {
|
if (preview) {
|
||||||
imageGenerator.load(context, data, sameEpisode)
|
gen.load(context, data, sameEpisode)
|
||||||
} else {
|
} else {
|
||||||
imageGenerator.clear(sameEpisode)
|
gen.clear(sameEpisode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadOfflinePlayer(context, data)
|
loadOfflinePlayer(context, data)
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Requires link or uri")
|
throw IllegalArgumentException("Requires link or uri")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setActiveSubtitles(subtitles: Set<SubtitleData>) {
|
override fun setActiveSubtitles(subtitles: Set<SubtitleData>) {
|
||||||
|
@ -537,7 +543,10 @@ class CS3IPlayer : IPlayer {
|
||||||
**/
|
**/
|
||||||
var preferredAudioTrackLanguage: String? = null
|
var preferredAudioTrackLanguage: String? = null
|
||||||
get() {
|
get() {
|
||||||
return field ?: getKey("$currentAccount/$PREFERRED_AUDIO_LANGUAGE_KEY", field)?.also {
|
return field ?: getKey(
|
||||||
|
"$currentAccount/$PREFERRED_AUDIO_LANGUAGE_KEY",
|
||||||
|
field
|
||||||
|
)?.also {
|
||||||
field = it
|
field = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@ import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.core.graphics.scale
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||||
|
@ -25,21 +28,98 @@ import kotlin.math.log2
|
||||||
const val MAX_LOD = 6
|
const val MAX_LOD = 6
|
||||||
const val MIN_LOD = 3
|
const val MIN_LOD = 3
|
||||||
|
|
||||||
|
data class ImageParams(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = ImageParams(200, 320)
|
||||||
|
fun new16by9(width: Int): ImageParams {
|
||||||
|
if (width < 100) {
|
||||||
|
return DEFAULT
|
||||||
|
}
|
||||||
|
return ImageParams(
|
||||||
|
width / 4,
|
||||||
|
(width * 9) / (4 * 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
assert(width > 0 && height > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface IPreviewGenerator {
|
interface IPreviewGenerator {
|
||||||
fun hasPreview(): Boolean
|
fun hasPreview(): Boolean
|
||||||
fun getPreviewImage(fraction: Float): Bitmap?
|
fun getPreviewImage(fraction: Float): Bitmap?
|
||||||
fun release()
|
fun release()
|
||||||
|
|
||||||
|
var params: ImageParams
|
||||||
|
|
||||||
var durationMs: Long
|
var durationMs: Long
|
||||||
var loadedImages: Int
|
var loadedImages: Int
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun new(): IPreviewGenerator {
|
||||||
|
/** because TV has low ram + not show we disable this for now */
|
||||||
|
return if (SettingsFragment.isTrueTvSettings()) {
|
||||||
|
empty()
|
||||||
|
} else {
|
||||||
|
PreviewGenerator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun empty(): IPreviewGenerator {
|
||||||
|
return NoPreviewGenerator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rescale(image: Bitmap, params: ImageParams): Bitmap {
|
||||||
|
if (image.width <= params.width && image.height <= params.height) return image
|
||||||
|
val new = image.scale(params.width, params.height)
|
||||||
|
// throw away the old image
|
||||||
|
if (new != image) {
|
||||||
|
image.recycle()
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
/** rescale to not take up as much memory */
|
||||||
|
private fun MediaMetadataRetriever.image(timeUs: Long, params: ImageParams): Bitmap? {
|
||||||
|
/*if (timeUs <= 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
try {
|
||||||
|
val primary = this.primaryImage
|
||||||
|
if (primary != null) {
|
||||||
|
return rescale(primary, params)
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
this.getScaledFrameAtTime(
|
||||||
|
timeUs,
|
||||||
|
MediaMetadataRetriever.OPTION_CLOSEST_SYNC,
|
||||||
|
params.width,
|
||||||
|
params.height
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return rescale(this.getFrameAtTime(timeUs) ?: return null, params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** PreviewGenerator that hides the implementation details of the sub generators that is used, used for source switch cache */
|
/** PreviewGenerator that hides the implementation details of the sub generators that is used, used for source switch cache */
|
||||||
class PreviewGenerator : IPreviewGenerator {
|
class PreviewGenerator : IPreviewGenerator {
|
||||||
|
|
||||||
/** the most up to date generator, will always mirror the actual source in the player */
|
/** the most up to date generator, will always mirror the actual source in the player */
|
||||||
private var currentGenerator: IPreviewGenerator = NoPreviewGenerator()
|
private var currentGenerator: IPreviewGenerator = NoPreviewGenerator()
|
||||||
|
|
||||||
/** the longest generated preview of the same episode */
|
/** the longest generated preview of the same episode */
|
||||||
private var lastGenerator: IPreviewGenerator = NoPreviewGenerator()
|
private var lastGenerator: IPreviewGenerator = NoPreviewGenerator()
|
||||||
|
|
||||||
/** always NoPreviewGenerator, used as a cache for nothing */
|
/** always NoPreviewGenerator, used as a cache for nothing */
|
||||||
private val dummy: IPreviewGenerator = NoPreviewGenerator()
|
private val dummy: IPreviewGenerator = NoPreviewGenerator()
|
||||||
|
|
||||||
|
@ -76,6 +156,14 @@ class PreviewGenerator : IPreviewGenerator {
|
||||||
currentGenerator = NoPreviewGenerator()
|
currentGenerator = NoPreviewGenerator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var params: ImageParams = ImageParams.DEFAULT
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
lastGenerator.params = value
|
||||||
|
backupGenerator.params = value
|
||||||
|
currentGenerator.params = value
|
||||||
|
}
|
||||||
|
|
||||||
override var durationMs: Long
|
override var durationMs: Long
|
||||||
get() = currentGenerator.durationMs
|
get() = currentGenerator.durationMs
|
||||||
set(_) {}
|
set(_) {}
|
||||||
|
@ -110,13 +198,13 @@ class PreviewGenerator : IPreviewGenerator {
|
||||||
|
|
||||||
when (link.type) {
|
when (link.type) {
|
||||||
ExtractorLinkType.M3U8 -> {
|
ExtractorLinkType.M3U8 -> {
|
||||||
currentGenerator = M3u8PreviewGenerator().apply {
|
currentGenerator = M3u8PreviewGenerator(params).apply {
|
||||||
load(url = link.url, headers = link.getAllHeaders())
|
load(url = link.url, headers = link.getAllHeaders())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtractorLinkType.VIDEO -> {
|
ExtractorLinkType.VIDEO -> {
|
||||||
currentGenerator = Mp4PreviewGenerator().apply {
|
currentGenerator = Mp4PreviewGenerator(params).apply {
|
||||||
load(url = link.url, headers = link.getAllHeaders())
|
load(url = link.url, headers = link.getAllHeaders())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,21 +217,25 @@ class PreviewGenerator : IPreviewGenerator {
|
||||||
|
|
||||||
fun load(context: Context, link: ExtractorUri, keepCache: Boolean) {
|
fun load(context: Context, link: ExtractorUri, keepCache: Boolean) {
|
||||||
clear(keepCache)
|
clear(keepCache)
|
||||||
currentGenerator = Mp4PreviewGenerator().apply {
|
currentGenerator = Mp4PreviewGenerator(params).apply {
|
||||||
load(keepCache = keepCache, context = context, uri = link.uri)
|
load(keepCache = keepCache, context = context, uri = link.uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private class NoPreviewGenerator : IPreviewGenerator {
|
private class NoPreviewGenerator : IPreviewGenerator {
|
||||||
override fun hasPreview(): Boolean = false
|
override fun hasPreview(): Boolean = false
|
||||||
override fun getPreviewImage(fraction: Float): Bitmap? = null
|
override fun getPreviewImage(fraction: Float): Bitmap? = null
|
||||||
override fun release() = Unit
|
override fun release() = Unit
|
||||||
|
override var params: ImageParams
|
||||||
|
get() = ImageParams(0, 0)
|
||||||
|
set(value) {}
|
||||||
override var durationMs: Long = 0L
|
override var durationMs: Long = 0L
|
||||||
override var loadedImages: Int = 0
|
override var loadedImages: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private class M3u8PreviewGenerator : IPreviewGenerator {
|
private class M3u8PreviewGenerator(override var params: ImageParams) : IPreviewGenerator {
|
||||||
// generated images 1:1 to idx of hsl
|
// generated images 1:1 to idx of hsl
|
||||||
private var images: Array<Bitmap?> = arrayOf()
|
private var images: Array<Bitmap?> = arrayOf()
|
||||||
|
|
||||||
|
@ -194,6 +286,9 @@ private class M3u8PreviewGenerator : IPreviewGenerator {
|
||||||
private fun clear() {
|
private fun clear() {
|
||||||
synchronized(images) {
|
synchronized(images) {
|
||||||
currentJob?.cancel()
|
currentJob?.cancel()
|
||||||
|
// for (i in images.indices) {
|
||||||
|
// images[i]?.recycle()
|
||||||
|
// }
|
||||||
images = arrayOf()
|
images = arrayOf()
|
||||||
prefixSum = arrayOf()
|
prefixSum = arrayOf()
|
||||||
loadedImages = 0
|
loadedImages = 0
|
||||||
|
@ -280,7 +375,7 @@ private class M3u8PreviewGenerator : IPreviewGenerator {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
val img = retriever.getFrameAtTime(0)
|
val img = retriever.image(0, params)
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
@ -308,7 +403,7 @@ private class M3u8PreviewGenerator : IPreviewGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Mp4PreviewGenerator : IPreviewGenerator {
|
private class Mp4PreviewGenerator(override var params: ImageParams) : IPreviewGenerator {
|
||||||
// lod = level of detail where the number indicates how many ones there is
|
// lod = level of detail where the number indicates how many ones there is
|
||||||
// 2^(lod-1) = images
|
// 2^(lod-1) = images
|
||||||
private var loadedLod = 0
|
private var loadedLod = 0
|
||||||
|
@ -369,6 +464,10 @@ private class Mp4PreviewGenerator : IPreviewGenerator {
|
||||||
synchronized(images) {
|
synchronized(images) {
|
||||||
loadedLod = 0
|
loadedLod = 0
|
||||||
loadedImages = 0
|
loadedImages = 0
|
||||||
|
// for (i in images.indices) {
|
||||||
|
// images[i]?.recycle()
|
||||||
|
// images[i] = null
|
||||||
|
//}
|
||||||
images.fill(null)
|
images.fill(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,10 +524,7 @@ private class Mp4PreviewGenerator : IPreviewGenerator {
|
||||||
val fraction = (1.0f.div((1 shl l).toFloat()) + i * 1.0f.div(items.toFloat()))
|
val fraction = (1.0f.div((1 shl l).toFloat()) + i * 1.0f.div(items.toFloat()))
|
||||||
Log.i(TAG, "Generating preview for ${fraction * 100}%")
|
Log.i(TAG, "Generating preview for ${fraction * 100}%")
|
||||||
val frame = durationUs * fraction
|
val frame = durationUs * fraction
|
||||||
val img = retriever.getFrameAtTime(
|
val img = retriever.image(frame.toLong(), params);
|
||||||
frame.toLong(),
|
|
||||||
MediaMetadataRetriever.OPTION_CLOSEST_SYNC
|
|
||||||
)
|
|
||||||
if (!scope.isActive) return
|
if (!scope.isActive) return
|
||||||
if (img == null || img.width <= 1 || img.height <= 1) continue
|
if (img == null || img.width <= 1 || img.height <= 1) continue
|
||||||
synchronized(images) {
|
synchronized(images) {
|
||||||
|
|
Loading…
Reference in a new issue