Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin K
2025-11-26 09:38:03 -06:00
10 changed files with 159 additions and 150 deletions
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea10d3c5562c9f449a4e89e9c3dfcf881ed79a952f3409bc005bcc62c2cf4b81
size 65512557
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22c06ca0d1a5808b2fc0a12227d5915b3126bc0b9b1305cf6bab855f2ec6fcbb
size 36133152
@@ -1,6 +1,9 @@
package com.futo.platformplayer.downloads
import android.content.Context
import android.media.MediaCodec
import android.media.MediaExtractor
import android.media.MediaMuxer
import android.util.Log
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
@@ -585,55 +588,44 @@ class VideoDownload {
return cipher.doFinal(encryptedSegment)
}
private fun remuxWithFfmpegInPlace(inputFile: File): Boolean {
val inputPath = inputFile.absolutePath
if (!inputFile.exists()) {
Logger.w(TAG, "remuxWithFfmpegInPlace: input does not exist: $inputPath")
return false
private suspend fun combineSegments(context: Context, segmentFiles: List<File>, targetFile: File) = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { continuation ->
val fileList = File(context.cacheDir, "fileList-${UUID.randomUUID()}.txt")
fileList.writeText(segmentFiles.joinToString("\n") { "file '${it.absolutePath}'" })
val cmd = "-f concat -safe 0 -i \"${fileList.absolutePath}\" -c copy \"${targetFile.absolutePath}\""
val statisticsCallback = StatisticsCallback { _ ->
//TODO: Show progress?
}
val parent = inputFile.parentFile
if (parent == null) {
Logger.w(TAG, "remuxWithFfmpegInPlace: input has no parent: $inputPath")
return false
}
val tmpFile = File(parent, inputFile.nameWithoutExtension + "_fixed." + inputFile.extension)
val cmd = buildString {
append("-y ")
append("-i \"").append(inputFile.absolutePath).append("\" ")
append("-c copy ")
append("-movflags +faststart ")
append("\"").append(tmpFile.absolutePath).append("\"")
}
Logger.i(TAG, "FFmpeg remux command: $cmd")
val session = FFmpegKit.execute(cmd)
val returnCode = session.returnCode
if (ReturnCode.isSuccess(returnCode)) {
val newLen = tmpFile.length()
if (!inputFile.delete()) {
Logger.w(TAG, "remuxWithFfmpegInPlace: failed to delete original: ${inputFile.absolutePath}")
}
if (!tmpFile.renameTo(inputFile)) {
Logger.w(TAG, "remuxWithFfmpegInPlace: failed to move tmp: ${tmpFile.absolutePath}")
val executorService = Executors.newSingleThreadExecutor()
val session = FFmpegKit.executeAsync(cmd,
{ session ->
if (ReturnCode.isSuccess(session.returnCode)) {
fileList.delete()
continuation.resumeWith(Result.success(Unit))
} else {
Logger.i(TAG, "remuxWithFfmpegInPlace: success for $inputPath (size=$newLen bytes)")
}
return true
val errorMessage = if (ReturnCode.isCancel(session.returnCode)) {
"Command cancelled"
} else {
Logger.e(TAG, "FFmpeg remux failed for $inputPath. rc=$returnCode, logs=${session.allLogsAsString}")
tmpFile.delete()
return false
"Command failed with state '${session.state}' and return code ${session.returnCode}, stack trace ${session.failStackTrace}"
}
fileList.delete()
continuation.resumeWithException(RuntimeException(errorMessage))
}
},
{ Logger.v(TAG, it.message) },
statisticsCallback,
executorService
)
continuation.invokeOnCancellation {
session.cancel()
}
}
}
private fun downloadHlsSource(context: Context, name: String, client: ManagedHttpClient, source: JSSource?, hlsUrl: String, targetFile: File, onProgress: (Long, Long, Long) -> Unit): Long {
private suspend fun downloadHlsSource(context: Context, name: String, client: ManagedHttpClient, source: JSSource?, hlsUrl: String, targetFile: File, onProgress: (Long, Long, Long) -> Unit): Long {
if (targetFile.exists())
targetFile.delete()
@@ -678,6 +670,7 @@ class VideoDownload {
.array()
}
val segmentFiles = arrayListOf<File>()
try {
val playlistHeaders = mutableMapOf<String, String>()
val modifiedPlaylistReq = modifier?.modifyRequest(hlsUrl, playlistHeaders)
@@ -713,7 +706,6 @@ class VideoDownload {
val mediaSequence = variantPlaylist.mediaSequence ?: 0L
val rangeOffsets = mutableMapOf<String, Long>()
targetFile.outputStream().use { outStr ->
if (!variantPlaylist.mapUrl.isNullOrEmpty()) {
if (isCancelled) throw CancellationException("Cancelled")
@@ -725,7 +717,7 @@ class VideoDownload {
if (variantPlaylist.mapBytesLength > 0) {
mapRangeLength = variantPlaylist.mapBytesLength
val mapUrl = variantPlaylist.mapUrl!!
val mapUrl = variantPlaylist.mapUrl
if (variantPlaylist.mapBytesStart >= 0) {
mapRangeStart = variantPlaylist.mapBytesStart
rangeOffsets[mapUrl] =
@@ -750,8 +742,15 @@ class VideoDownload {
throw IllegalStateException("HLS MAP segment too large to handle.")
}
val segmentFile = File(context.cacheDir, "segment-${UUID.randomUUID()}")
val outStr = segmentFile.outputStream()
try {
segmentFiles.add(segmentFile)
outStr.write(mapBytes)
outStr.flush()
} finally {
outStr.close()
}
downloadedTotalLength += mapBytes.size
}
@@ -790,7 +789,7 @@ class VideoDownload {
if (useDecryption) {
val kb = keyBytes ?: throw IllegalStateException("Decryption key bytes are missing.")
val ivBytes = if (staticIvBytes != null) {
staticIvBytes!!
staticIvBytes
} else {
val sequenceNumber = mediaSequence + mediaSegmentIndex
buildSequenceIv(sequenceNumber)
@@ -811,7 +810,14 @@ class VideoDownload {
}
val expectedTotal = avgLen * (totalSegments - 1) + segmentLength
val segmentFile = File(context.cacheDir, "segment-${UUID.randomUUID()}")
val outStr = segmentFile.outputStream()
try {
segmentFiles.add(segmentFile)
outStr.write(segmentBytes)
} finally {
outStr.close()
}
downloadedTotalLength += segmentLength
bytesSinceLastSpeedUpdate += segmentLength
@@ -826,10 +832,8 @@ class VideoDownload {
onProgress(expectedTotal, downloadedTotalLength, lastSpeed)
mediaSegmentIndex++
}
}
remuxWithFfmpegInPlace(targetFile)
combineSegments(context, segmentFiles, targetFile)
Logger.i(TAG, "Finished HLS Source for $name")
} catch (ioex: IOException) {
if (targetFile.exists())
@@ -843,6 +847,11 @@ class VideoDownload {
targetFile.delete()
throw ex
}
finally {
for (segmentFile in segmentFiles) {
segmentFile.delete()
}
}
return downloadedTotalLength
}
@@ -436,9 +436,9 @@ class StateApp {
try {
val caFile = AppCaUpdater.ensureCaBundle(context)
Libcurl.setDefaultCAPath(caFile.absolutePath)
Logger.i(TAG, "Libcurl initialized")
} catch (t: Throwable) {
val fallback = File(context.noBackupFilesDir, "curl-ca-bundle.pem")
if (fallback.exists()) Libcurl.setDefaultCAPath(fallback.absolutePath)
Logger.e(TAG, "Failed to initialize Libcurl", t);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.