mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 16:57:39 +00:00
feat(hls): adapter for Nip96/Blossom + production orchestrator wiring
HlsBlobUploaderFactory turns a user-chosen ServerName into the simple file+contentType HlsBlobUploader the pipeline expects by adapting BlossomUploader or Nip96Uploader. Each adapter reuses the existing roleBasedHttpClientBuilder.okHttpClientForUploads and the account's signer helpers (createBlossomUploadAuth / createHTTPAuthorization), matching how UploadOrchestrator wires them today. NIP-95 is rejected explicitly — storing each rendition as an event would blow past relay size limits. createProductionHlsPublishOrchestrator binds the transcode to HlsTranscoder, uploads to HlsBlobUploaderFactory, and signAndPublish to account.signer.sign(...) + account.sendAutomatic(...). The Uri is captured via a lazy provider so the orchestrator can be constructed at VM load time before the user picks a video. NewHlsVideoViewModel.load(account, context) is the new one-call setup the screen will use; the existing load(account, orchestrator) overload stays for unit tests. Milestone 5b of the HLS video sharing plan (2026-04-13). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
c008daee7e
commit
6942314cf8
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.uploads.hls
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||
|
||||
/**
|
||||
* Turns a user-chosen [ServerName] into an [HlsBlobUploader] by adapting the concrete
|
||||
* [Nip96Uploader] / [BlossomUploader] to the simpler file+contentType interface the HLS upload
|
||||
* pipeline uses. Keeps the pipeline free of direct Amethyst/account wiring so it stays
|
||||
* unit-testable.
|
||||
*/
|
||||
object HlsBlobUploaderFactory {
|
||||
fun create(
|
||||
server: ServerName,
|
||||
account: Account,
|
||||
context: Context,
|
||||
): HlsBlobUploader =
|
||||
when (server.type) {
|
||||
ServerType.Blossom -> {
|
||||
blossomAdapter(server.baseUrl, account, context)
|
||||
}
|
||||
|
||||
ServerType.NIP96 -> {
|
||||
nip96Adapter(server.baseUrl, account, context)
|
||||
}
|
||||
|
||||
ServerType.NIP95 -> {
|
||||
throw IllegalArgumentException(
|
||||
"NIP-95 storage stores each blob as an event and is not suitable for HLS renditions",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blossomAdapter(
|
||||
serverBaseUrl: String,
|
||||
account: Account,
|
||||
context: Context,
|
||||
): HlsBlobUploader =
|
||||
HlsBlobUploader { file, contentType ->
|
||||
BlossomUploader().upload(
|
||||
uri = file.toUri(),
|
||||
contentType = contentType,
|
||||
size = file.length(),
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads,
|
||||
httpAuth = account::createBlossomUploadAuth,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
|
||||
private fun nip96Adapter(
|
||||
serverBaseUrl: String,
|
||||
account: Account,
|
||||
context: Context,
|
||||
): HlsBlobUploader =
|
||||
HlsBlobUploader { file, contentType ->
|
||||
Nip96Uploader().upload(
|
||||
uri = file.toUri(),
|
||||
contentType = contentType,
|
||||
size = file.length(),
|
||||
alt = null,
|
||||
sensitiveContent = null,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads,
|
||||
onProgress = { /* pipeline reports progress per-upload; NIP-96 per-request progress is not forwarded */ },
|
||||
httpAuth = account::createHTTPAuthorization,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.uploads.hls.HlsBlobUploaderFactory
|
||||
import com.vitorpamplona.amethyst.service.uploads.hls.HlsTranscoder
|
||||
import com.vitorpamplona.amethyst.service.uploads.hls.HlsVideoEventTemplate
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Production wiring for [HlsPublishOrchestrator]. Binds the transcode to [HlsTranscoder], the
|
||||
* uploader factory to [HlsBlobUploaderFactory], and the signAndPublish closure to the account's
|
||||
* signer + outbox publish path.
|
||||
*
|
||||
* The Uri is read via [uriProvider] on each transcode invocation so the orchestrator can be built
|
||||
* once (at VM load) before the user actually picks a video.
|
||||
*/
|
||||
fun createProductionHlsPublishOrchestrator(
|
||||
account: Account,
|
||||
context: Context,
|
||||
uriProvider: () -> Uri?,
|
||||
): HlsPublishOrchestrator =
|
||||
HlsPublishOrchestrator(
|
||||
runTranscode = { workDir, codec, onProgress ->
|
||||
val uri = uriProvider() ?: error("No video picked")
|
||||
HlsTranscoder.transcode(
|
||||
context = context,
|
||||
uri = uri,
|
||||
workDir = workDir,
|
||||
codec = codec,
|
||||
onRenditionProgress = onProgress,
|
||||
)
|
||||
},
|
||||
buildUploader = { server ->
|
||||
HlsBlobUploaderFactory.create(server, account, context)
|
||||
},
|
||||
signAndPublish = { template ->
|
||||
val signed =
|
||||
when (template) {
|
||||
is HlsVideoEventTemplate.Horizontal -> account.signer.sign(template.template)
|
||||
is HlsVideoEventTemplate.Vertical -> account.signer.sign(template.template)
|
||||
}
|
||||
account.sendAutomatic(signed)
|
||||
signed.id
|
||||
},
|
||||
workDirFactory = {
|
||||
File(context.cacheDir, "hls-${System.currentTimeMillis()}").apply { mkdirs() }
|
||||
},
|
||||
)
|
||||
+12
@@ -78,6 +78,18 @@ open class NewHlsVideoViewModel : ViewModel() {
|
||||
this.selectedServer = this.selectedServer ?: DEFAULT_MEDIA_SERVERS.first()
|
||||
}
|
||||
|
||||
fun load(
|
||||
account: Account,
|
||||
context: Context,
|
||||
) = load(
|
||||
account,
|
||||
createProductionHlsPublishOrchestrator(
|
||||
account = account,
|
||||
context = context,
|
||||
uriProvider = { pickedUri },
|
||||
),
|
||||
)
|
||||
|
||||
fun onVideoPicked(
|
||||
uri: Uri,
|
||||
metadata: HlsSourceMetadata?,
|
||||
|
||||
Reference in New Issue
Block a user