Skip to content

Commit

Permalink
fix crash with vod downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
crackededed committed Feb 25, 2024
1 parent d6f850b commit 030a7ff
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,5 @@ import kotlinx.parcelize.Parcelize
data class VideoDownloadInfo(
val video: Video,
val qualities: Map<String, String>,
val relativeStartTimes: List<Long>,
val durations: List<Long>,
val totalDuration: Long,
val targetDuration: Long,
val currentPosition: Long) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -106,46 +106,10 @@ class VideoDownloadDialog : BaseDownloadDialog() {
timeTo.editText?.error = getString(R.string.to_is_longer)
}
from < to -> {
val fromIndex = if (from == 0L) {
0
} else {
val min = from - targetDuration
val tmpIndex = relativeStartTimes.binarySearch(comparison = { time ->
when {
time > from -> 1
time < min -> -1
else -> 0
}
})
/***
* If the item is not found by the binarySearch method, it will return a
* negative value and the app will crash. On that case, the function
* returns the inverted insertion point (-insertion point - 1).
* */
if (tmpIndex < 0) -tmpIndex else tmpIndex

}
val toIndex = if (to in relativeStartTimes.last()..totalDuration) {
relativeStartTimes.lastIndex
} else {
val max = to + targetDuration
val tmpIndex= relativeStartTimes.binarySearch(comparison = { time ->
when {
time > max -> 1
time < to -> -1
else -> 0
}
})
//Apply the same check to the toIndex result
if (tmpIndex < 0) -tmpIndex else tmpIndex
}
fun startDownload() {
val quality = spinner.editText?.text.toString()
val url = videoInfo.qualities.getValue(quality)
viewModel.download(url, downloadPath, quality, fromIndex, toIndex, Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE || requireContext().prefs().getBoolean(C.DEBUG_WORKMANAGER_DOWNLOADS, false))
dismiss()
}
startDownload()
val quality = spinner.editText?.text.toString()
val url = videoInfo.qualities.getValue(quality)
viewModel.download(url, downloadPath, quality, from, to, Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE || requireContext().prefs().getBoolean(C.DEBUG_WORKMANAGER_DOWNLOADS, false))
dismiss()
}
from >= to -> {
timeFrom.requestFocus()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import javax.inject.Inject

@HiltViewModel
Expand Down Expand Up @@ -78,22 +77,7 @@ class VideoDownloadViewModel @Inject constructor(
put(ContextCompat.getString(getApplication(), R.string.audio_only), it.value)
}
}
val mediaPlaylist = URL(map.values.elementAt(0)).openStream().use {
PlaylistParser(it, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT).parse().mediaPlaylist
}
var totalDuration = 0L
val size = mediaPlaylist.tracks.size
val relativeTimes = ArrayList<Long>(size)
val durations = ArrayList<Long>(size)
var time = 0L
mediaPlaylist.tracks.forEach {
val duration = (it.trackInfo.duration * 1000f).toLong()
durations.add(duration)
totalDuration += duration
relativeTimes.add(time)
time += duration
}
_videoInfo.postValue(VideoDownloadInfo(video, map, relativeTimes, durations, totalDuration, mediaPlaylist.targetDuration * 1000L, 0))
_videoInfo.postValue(VideoDownloadInfo(video, map, video.duration?.let { TwitchApiHelper.getDuration(it)?.times(1000) } ?: 0, 0))
} catch (e: Exception) {
if (e.message == "failed integrity check") {
_integrity.postValue(true)
Expand All @@ -116,53 +100,86 @@ class VideoDownloadViewModel @Inject constructor(
}
}

fun download(url: String, path: String, quality: String, fromIndex: Int, toIndex: Int, useWorkManager: Boolean) {
fun download(url: String, path: String, quality: String, from: Long, to: Long, useWorkManager: Boolean) {
GlobalScope.launch {
with(_videoInfo.value!!) {
val context = getApplication<Application>()

val startPosition = relativeStartTimes[fromIndex]
val duration = (relativeStartTimes[toIndex] + durations[toIndex] - startPosition) - 1000L
val directory = "$path${File.separator}" +
if (!video.id.isNullOrBlank()) {
"${video.id}${if (!quality.contains("Audio", true)) quality else "audio"}"
} else {
System.currentTimeMillis()
} + File.separator

val urlPath = url.substringBeforeLast('/') + "/"
val offlineVideo = DownloadUtils.prepareDownload(context, video, urlPath, directory, duration, startPosition, fromIndex, toIndex)
val playlist = ByteArrayInputStream(playerRepository.getResponse(url = url).toByteArray()).use {
PlaylistParser(it, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT).parse().mediaPlaylist
}
val tracks = ArrayList<TrackData>()
for (i in fromIndex..toIndex) {
val track = playlist.tracks[i]
tracks.add(
track.buildUpon()
.withUri(track.uri.replace("-unmuted", "-muted"))
.build()
)
}
File(directory).mkdir()
FileOutputStream(offlineVideo.url).use {
PlaylistWriter(it, Format.EXT_M3U, Encoding.UTF_8).write(Playlist.Builder().withMediaPlaylist(playlist.buildUpon().withTracks(tracks).build()).build())
}
val videoId = offlineRepository.saveVideo(offlineVideo).toInt()
if (useWorkManager) {
WorkManager.getInstance(context).enqueueUniqueWork(
videoId.toString(),
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(workDataOf(DownloadWorker.KEY_VIDEO_ID to videoId))
.build()
)
} else {
val request = Request(videoId, urlPath, directory)
offlineRepository.saveRequest(request)
val context = getApplication<Application>()
val video = _videoInfo.value!!.video
val playlist = ByteArrayInputStream(playerRepository.getResponse(url = url).toByteArray()).use {
PlaylistParser(it, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT).parse().mediaPlaylist
}
val targetDuration = playlist.targetDuration * 1000L
var totalDuration = 0L
val size = playlist.tracks.size
val relativeStartTimes = ArrayList<Long>(size)
val durations = ArrayList<Long>(size)
var relativeTime = 0L
playlist.tracks.forEach {
val duration = (it.trackInfo.duration * 1000f).toLong()
durations.add(duration)
totalDuration += duration
relativeStartTimes.add(relativeTime)
relativeTime += duration
}
val fromIndex = if (from == 0L) {
0
} else {
val min = from - targetDuration
relativeStartTimes.binarySearch(comparison = { time ->
when {
time > from -> 1
time < min -> -1
else -> 0
}
}).let { if (it < 0) -it else it }
}
val toIndex = if (to in relativeStartTimes.last()..totalDuration) {
relativeStartTimes.lastIndex
} else {
val max = to + targetDuration
relativeStartTimes.binarySearch(comparison = { time ->
when {
time > max -> 1
time < to -> -1
else -> 0
}
}).let { if (it < 0) -it else it }
}
val tracks = ArrayList<TrackData>()
for (i in fromIndex..toIndex) {
val track = playlist.tracks[i]
tracks.add(
track.buildUpon()
.withUri(track.uri.replace("-unmuted", "-muted"))
.build()
)
}
val startPosition = relativeStartTimes[fromIndex]
val duration = (relativeStartTimes[toIndex] + durations[toIndex] - startPosition) - 1000L
val directory = "$path${File.separator}" + if (!video.id.isNullOrBlank()) {
"${video.id}${if (!quality.contains("Audio", true)) quality else "audio"}"
} else {
System.currentTimeMillis()
} + File.separator
val urlPath = url.substringBeforeLast('/') + "/"
val offlineVideo = DownloadUtils.prepareDownload(context, video, urlPath, directory, duration, startPosition, fromIndex, toIndex)
File(directory).mkdir()
FileOutputStream(offlineVideo.url).use {
PlaylistWriter(it, Format.EXT_M3U, Encoding.UTF_8).write(Playlist.Builder().withMediaPlaylist(playlist.buildUpon().withTracks(tracks).build()).build())
}
val videoId = offlineRepository.saveVideo(offlineVideo).toInt()
if (useWorkManager) {
WorkManager.getInstance(context).enqueueUniqueWork(
videoId.toString(),
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(workDataOf(DownloadWorker.KEY_VIDEO_ID to videoId))
.build()
)
} else {
val request = Request(videoId, urlPath, directory)
offlineRepository.saveRequest(request)

DownloadUtils.download(context, request)
}
DownloadUtils.download(context, request)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,22 +626,10 @@ class PlaybackService : MediaSessionService() {
}
GET_VIDEO_DOWNLOAD_INFO -> {
val info = (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.let { playlist ->
val segments = playlist.segments
val size = segments.size
val relativeTimes = ArrayList<Long>(size)
val durations = ArrayList<Long>(size)
for (i in 0 until size) {
val segment = segments[i]
relativeTimes.add(segment.relativeStartTimeUs / 1000L)
durations.add(segment.durationUs / 1000L)
}
VideoDownloadInfo(
video = Video(),
qualities = urls,
relativeStartTimes = relativeTimes,
durations = durations,
totalDuration = playlist.durationUs / 1000L,
targetDuration = playlist.targetDurationUs / 1000L,
currentPosition = session.player.currentPosition
)
}
Expand Down

0 comments on commit 030a7ff

Please sign in to comment.