diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 5dad5907a..977b027bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -1044,13 +1044,13 @@ object LocalCache : ILocalCache { event: VideoNormalEvent, relay: NormalizedRelayUrl?, wasVerified: Boolean, - ) = consumeBaseReplaceable(event, relay, wasVerified) + ) = consumeRegularEvent(event, relay, wasVerified) private fun consume( event: VideoShortEvent, relay: NormalizedRelayUrl?, wasVerified: Boolean, - ) = consumeBaseReplaceable(event, relay, wasVerified) + ) = consumeRegularEvent(event, relay, wasVerified) fun consume( event: StatusEvent, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt index 0ae502b83..f49d1b10c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt @@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.imageModifier +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists @@ -75,18 +76,20 @@ fun VideoDisplay( accountViewModel: AccountViewModel, nav: INav, ) { - val event = (note.event as? VideoEvent) ?: return - val imeta = event.imetaTags().firstOrNull() ?: return + val videoEvent = (note.event as? VideoEvent) ?: return + val event = (videoEvent as? Event) ?: return - val title = event.title() - val summary = event.content.ifBlank { null }?.takeIf { title != it } + val imeta = videoEvent.imetaTags().firstOrNull() ?: return + + val title = videoEvent.title() + val summary = videoEvent.content.ifBlank { null }?.takeIf { title != it } val image = imeta.image.firstOrNull() val isYouTube = imeta.url.contains("youtube.com") || imeta.url.contains("youtu.be") val tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList } val content by remember(note) { - val description = event.content.ifBlank { null } ?: event.alt() + val description = videoEvent.content.ifBlank { null } ?: event.alt() val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url) val uri = note.toNostrUri() @@ -187,11 +190,11 @@ fun VideoDisplay( nav = nav, ) - if (event.hasHashtags()) { + if (videoEvent.hasHashtags()) { Row( Modifier.fillMaxWidth(), ) { - DisplayUncitedHashtags(event, summary, callbackUri, accountViewModel, nav) + DisplayUncitedHashtags(videoEvent, summary, callbackUri, accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt index c9cc553e8..ab73587d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VideoDisplay.kt @@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip71Video.VideoEvent @@ -43,8 +44,10 @@ fun JustVideoDisplay( contentScale: ContentScale, accountViewModel: AccountViewModel, ) { - val event = (note.event as? VideoEvent) ?: return - val imeta = event.imetaTags().getOrNull(0) ?: return + val videoEvent = (note.event as? VideoEvent) ?: return + val event = (videoEvent as? Event) ?: return + + val imeta = videoEvent.imetaTags().getOrNull(0) ?: return val content by remember(note) { diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt index 54c1f7e5e..d9318f5da 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt @@ -237,6 +237,9 @@ open class BasicRelayClient( text: String, onConnected: () -> Unit, ) { + if (text.contains("2e9244f75b36d47116b8c8bd5c4ea4d29fb1a3d64688d2524156af34c6124dc3")) { + Log.d(logTag, "AABBCC Receiving: $text") + } // Log.d(logTag, "Receiving: $text") stats.addBytesReceived(text.bytesUsedInMemory()) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/RegularVideoEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/RegularVideoEvent.kt new file mode 100644 index 000000000..0007b48c1 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/RegularVideoEvent.kt @@ -0,0 +1,73 @@ +/** + * 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.quartz.nip71Video + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.tags.events.ETag +import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags +import com.vitorpamplona.quartz.nip01Core.tags.people.PTag +import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider +import com.vitorpamplona.quartz.nip22Comments.RootScope +import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag +import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag +import com.vitorpamplona.quartz.nip71Video.tags.DurationTag +import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag +import com.vitorpamplona.quartz.nip92IMeta.imetas +import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag + +@Immutable +abstract class RegularVideoEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, kind, tags, content, sig), + PublishedAtProvider, + VideoEvent, + RootScope { + @Transient var iMetas: List? = null + + override fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) + + override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse) + + override fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse) + + override fun textTrack() = tags.mapNotNull(ETag::parse) + + override fun segments() = tags.mapNotNull(SegmentTag::parse) + + override fun participants() = tags.mapNotNull(PTag::parse) + + override fun hashtags() = tags.hashtags() + + override fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) + + override fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) + + override fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/ReplaceableVideoEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/ReplaceableVideoEvent.kt new file mode 100644 index 000000000..f57e4cc43 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/ReplaceableVideoEvent.kt @@ -0,0 +1,73 @@ +/** + * 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.quartz.nip71Video + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.tags.events.ETag +import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags +import com.vitorpamplona.quartz.nip01Core.tags.people.PTag +import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider +import com.vitorpamplona.quartz.nip22Comments.RootScope +import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag +import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag +import com.vitorpamplona.quartz.nip71Video.tags.DurationTag +import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag +import com.vitorpamplona.quartz.nip92IMeta.imetas +import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag + +@Immutable +abstract class ReplaceableVideoEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + sig: HexKey, +) : BaseReplaceableEvent(id, pubKey, createdAt, kind, tags, content, sig), + PublishedAtProvider, + VideoEvent, + RootScope { + @Transient var iMetas: List? = null + + override fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) + + override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse) + + override fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse) + + override fun textTrack() = tags.mapNotNull(ETag::parse) + + override fun segments() = tags.mapNotNull(SegmentTag::parse) + + override fun participants() = tags.mapNotNull(PTag::parse) + + override fun hashtags() = tags.hashtags() + + override fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) + + override fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) + + override fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt index 71ec35c8c..d4d773c04 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt @@ -21,52 +21,30 @@ package com.vitorpamplona.quartz.nip71Video import androidx.compose.runtime.Immutable -import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent -import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.IEvent import com.vitorpamplona.quartz.nip01Core.tags.events.ETag -import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.PTag -import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider -import com.vitorpamplona.quartz.nip22Comments.RootScope -import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag -import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag -import com.vitorpamplona.quartz.nip71Video.tags.DurationTag import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag -import com.vitorpamplona.quartz.nip92IMeta.imetas -import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag @Immutable -abstract class VideoEvent( - id: HexKey, - pubKey: HexKey, - createdAt: Long, - kind: Int, - tags: Array>, - content: String, - sig: HexKey, -) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig), - PublishedAtProvider, - RootScope { - @Transient var iMetas: List? = null +interface VideoEvent : IEvent { + fun title(): String? - fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) + fun publishedAt(): Long? - override fun publishedAt() = tags.firstNotNullOfOrNull(PublishedAtTag::parse) + fun duration(): Int? - fun duration() = tags.firstNotNullOfOrNull(DurationTag::parse) + fun textTrack(): List - fun textTrack() = tags.mapNotNull(ETag::parse) + fun segments(): List - fun segments() = tags.mapNotNull(SegmentTag::parse) + fun participants(): List - fun participants() = tags.mapNotNull(PTag::parse) + fun hashtags(): List - fun hashtags() = tags.hashtags() + fun mimeType(): String? - private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) + fun hash(): String? - private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) - - fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it } + fun imetaTags(): List } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoHorizontalEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoHorizontalEvent.kt index 308b1b6e4..39738bb91 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoHorizontalEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoHorizontalEvent.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip71Video.videoIMetas import com.vitorpamplona.quartz.utils.TimeUtils import java.util.UUID @@ -38,7 +39,7 @@ class VideoHorizontalEvent( tags: Array>, content: String, sig: HexKey, -) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), +) : ReplaceableVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), RootScope { companion object { const val KIND = 34235 diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt index bdb3fabb6..73fbd8b01 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt @@ -24,11 +24,9 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate -import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.utils.TimeUtils -import java.util.UUID @Immutable class VideoNormalEvent( @@ -38,7 +36,7 @@ class VideoNormalEvent( tags: Array>, content: String, sig: HexKey, -) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), +) : RegularVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), RootScope { companion object { const val KIND = 21 @@ -47,10 +45,9 @@ class VideoNormalEvent( fun build( video: VideoMeta, description: String, - dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, - ) = build(description, dTag, createdAt) { + ) = build(description, createdAt) { videoIMeta(video) initializer() } @@ -58,21 +55,18 @@ class VideoNormalEvent( fun build( video: List, description: String, - dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, - ) = build(description, dTag, createdAt) { + ) = build(description, createdAt) { videoIMetas(video) initializer() } fun build( description: String, - dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate(KIND, description, createdAt) { - dTag(dTag) alt(ALT_DESCRIPTION) initializer() } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt index 4be29bde8..64fe9de6b 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt @@ -24,11 +24,9 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate -import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.utils.TimeUtils -import java.util.UUID @Immutable class VideoShortEvent( @@ -38,7 +36,7 @@ class VideoShortEvent( tags: Array>, content: String, sig: HexKey, -) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), +) : RegularVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), RootScope { companion object { const val KIND = 22 @@ -47,21 +45,18 @@ class VideoShortEvent( fun build( video: VideoMeta, description: String, - dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, - ) = build(description, dTag, createdAt) { + ) = build(description, createdAt) { videoIMeta(video) initializer() } fun build( description: String, - dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate(KIND, description, createdAt) { - dTag(dTag) alt(ALT_DESCRIPTION) initializer() } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoVerticalEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoVerticalEvent.kt index b3ae883fe..0751775fe 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoVerticalEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip71Video/VideoVerticalEvent.kt @@ -38,7 +38,7 @@ class VideoVerticalEvent( tags: Array>, content: String, sig: HexKey, -) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), +) : ReplaceableVideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), RootScope { companion object { const val KIND = 34236