tangled
alpha
login
or
join now
stream.place
/
streamplace
Live video on the AT Protocol
74
fork
atom
overview
issues
1
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
v/moderation-actions-tests
v/fix-update-stream-title-location
v/delegated-moderation-frontend
v/delegated-moderation-actions
v/chat-mod-lexicon
revert-567-eli/localhost-dev-is-back
revert-374-eli/fix-type-export
revert-288-eli/expo-bump
restructure
no-await-in-handle
next
natb/workers-docs
natb/widgets-dashboard
natb/widget-dashboard
natb/wider-docs
natb/weird-chat-alignment
natb/web-hooks
natb/vol-tap-targets
natb/verify-sidebar-state
natb/ux-rtmp
natb/uuid-player-id
natb/use-runes
natb/use-navigate
natb/use-mise
natb/use-macos-15-intel
natb/urfave
natb/upload-thrice
natb/update-title
natb/update-ls
natb/update-liquid-glass
natb/update-docs-rtmp
natb/unscope-dropdown-passins
natb/typeahead-in-login
natb/tost
natb/text-content-type-none
natb/temp-error-reporting
natb/teleport-dialog
natb/teleport
natb/system-messages
natb/sync-client-time
natb/stretch-to-contain-image
natb/streamer-agreement-popup
natb/stereo-mobile
natb/stepper-reporter
natb/spinner-while-loading-chat
natb/size-in-seg
natb/signup-modal
natb/sidebar-doesnt-default-home
natb/show-dropdowns-fullscreen
natb/sharesheet
natb/settings-redesign
natb/set-explicit-bg-mobile
natb/sentry
natb/self-host
natb/scrollable-about
natb/scroll-down-kb-dismiss
natb/screen-sharing
natb/s3-ui
natb/rust-testing
natb/rtmp-preferred
natb/rotation
natb/rework-facets
natb/restore-last-ls-title
natb/responsive-player
natb/reporting-interface
natb/reorg-docs
natb/remove-tamagui-fixups-2
natb/remove-tamagui-fixups
natb/remove-stream-viewers
natb/remove-old-chat
natb/remove-lex-reference
natb/relogo-embed
natb/refocus-chat
natb/redirect-logins
natb/readd-bsky-and-follow
natb/proxy-comatprotorepo-correctly
natb/profile-bios
natb/player-respect-top-margin
natb/player-nits
natb/player-components-store
natb/pds-login
natb/patch-rn-webrtc-fork
natb/pass-object-fit
natb/ozone-alarm
natb/og-images
natb/offset-scrolltobottom
natb/offline
natb/no-tamagui
natb/no-redux
natb/no-react-navi
natb/new-popout-chat
natb/mute-words-discord
natb/mute-param
natb/multiple-line-livestream-title
natb/move-login-tr
natb/move-actions-outside-portal
natb/mod-actions
natb/mobile-player-0.7
natb/mobile-backgrounding
natb/misc-chat
natb/max-3-line-title
natb/manual-play-button
natb/make-nav-more-navigable
natb/make-dashboard-scrollable-native
natb/make-app-bsky-unauthed
natb/login-tweak
natb/login-redesign
natb/loading-overlay
natb/loader-under-ui
natb/live-stop
natb/link-to-right-bsky
natb/link-offset
natb/less-sensitive-slides
natb/less-annoying-replies
natb/key-mgr-attempt-2
natb/key-mgmt
natb/jpeg-thumbs
natb/invoke-pip-action
natb/instant-rotation
natb/increase-timeout
natb/increase-player-handle
natb/increase-jpeg-bounds
natb/import-i18n-properly
natb/i18next-cli
natb/i18n
natb/hovers-to-button
natb/hotfix-readd-chat-text
natb/hotfix-ios-cursor
natb/home-menu
natb/hide-sidebar-embeds
natb/hide-msg
natb/hide-login-if-logged-in
natb/hide-hidden-from-front-page
natb/hide-chat-on-new-ingest
natb/handle-redirect
natb/handle-autocomplete
natb/global-volume-store
natb/get-live-working
natb/fullscreen-fixes
natb/format-handle
natb/force-dark-theme
natb/force-dark-chat
natb/force-bar-style
natb/fix-webhook-urls
natb/fix-viewer-misalignment
natb/fix-untrusted-resolution
natb/fix-unmute-button
natb/fix-stream-key-buttons
natb/fix-popout-bg
natb/fix-ogimage-ratio
natb/fix-login-mobile
natb/fix-fullscreen
natb/fix-frame-capture
natb/fix-emoji
natb/fix-docs-get-started
natb/fix-dialogs-forever-questionmark
natb/fix-cyclic-sidebar
natb/fix-crash-when-loading-deletechatmessage
natb/fix-chat-alignment
natb/fix-c2pa-styles
natb/finally-fix-reloading
natb/filter-system-authors
natb/fallback-message-facets
natb/facet-livestream-post-titles
natb/embed-branding-web
natb/dropdown-stuff-2
natb/dropdown-stuff
natb/dont-restore-deleted-messages
natb/dont-replace-routes
natb/document-public-host
natb/docs-table-wrapping
natb/docs-separation
natb/docs-openapi
natb/do-not-login-redirect-mobile
natb/discord-no-lan-ips-with-smokescreen
natb/discord-no-lan-ips
natb/did-real
natb/detect-non-h264-webrtc
natb/danmu-back-in-router
natb/constrain-stream-title
natb/configurable-live-internal-api-url
natb/concerningly-close-chatbox
natb/command-errors
natb/clear-login-error-notif
natb/cleanup-mod-menu
natb/clean-mobile-player
natb/check-repo-discord
natb/check-platform-version
natb/check-perms
natb/check-before-golive
natb/chat-nits
natb/chat-full-height
natb/chat-fixes
natb/chat-bottom-offset
natb/changing-live
natb/changesets-2
natb/changesets
natb/certmagic
natb/censor-facet
natb/cant-log-in-native
natb/canonical-url
natb/buttons
natb/bullet-hell
natb/bug-no-default-fallback-for-bsky-appview
natb/blur-objectionable-content
natb/block-javascript-protocol
natb/backport-cards
natb/auto-resume-video
natb/at-handle
natb/astro-global-favicon
natb/analytics
natb/admonition-to-cws
natb/add-problems-back
natb/a-href-in-sidebar
issue-784
issue-705-2
iroh-streamplace-kv
iroh-stream
iroh-key-in-receive
iroh-c2pa-release
fix/aqtime-datetime-parsing
feature/ci-mise-check
feature/branding-system
feature-user-consent-c2pa
feature-c2pa-metadata-clean
feat/smokesignal-event-form
feat-iroh-replicator
eli/wrapped-peer-connection
eli/windows-selftest-github
eli/who-limits-the-limiter
eli/webrtc-with-pip
eli/webrtc-track-switching
eli/webrtc-ingest-fixes
eli/version-label
eli/upload-fixes
eli/too-many-keyframes
eli/sync-tangled
eli/sync-changes
eli/streamplace-the-labeler
eli/streamplace-package
eli/stream-from-phone
eli/stateful-repo
eli/standalone-oproxy
eli/skip-corepack-prompt
eli/simple-build
eli/server-restart-test
eli/segmentation-fixes
eli/segment-optimization
eli/rust-logging
eli/rust-experimentation
eli/rtmps
eli/rtmprec-2
eli/rtmprec
eli/rtmp-rec
eli/rtmp-push
eli/rtcrec-test-comment
eli/revert-thumbnail
eli/revert-dev-env
eli/reporting
eli/repo-magic-linking
eli/remove-auth-webhook-url
eli/relaxed-oauth-nonces
eli/registry-on-in-dev
eli/red-circles-for-the-people
eli/problem-detection
eli/prettier-curlies
eli/potentially-working-webrtc-patch-2
eli/potentially-working-webrtc-patch
eli/postinstall-bad
eli/postgres
eli/popout-moderation
eli/podman-cache
eli/pnpm
eli/plane-branch
eli/pion-verbose-logging
eli/phone-streaming-spike
eli/pg-locking-fix
eli/packetize-tweak-failing
eli/packetize-tweak
eli/packaging
eli/other-leaky-error-cases
eli/ota-fixes
eli/osxcross-prebuild-clang
eli/osxcross-earlier-sdk
eli/osxcross-different-linker-twice
eli/osxcross-different-linker-pause
eli/osxcross-different-linker-many
eli/osxcross-different-linker
eli/osxcross-apple-clang
eli/osxcross
eli/organize-imports
eli/opusparse-ingest
eli/optional-convergence
eli/oproxy-fix
eli/oproxy
eli/oauth-scopes
eli/oauth-in-dev-stinks
eli/oauth-improvements
eli/oatproxy-panic
eli/oatproxy-http-client
eli/npmignore
eli/npm-publishing
eli/noop-ci
eli/node-22
eli/no-thumbnail-stall
eli/no-selftest-updates
eli/no-crash-on-reload
eli/new-webrtc-style
eli/new-webrtc-fixes-0.7
eli/new-webrtc-fixes
eli/multitesting-github
eli/multitesting-eh-prolly-not
eli/multitesting
eli/multistream-improvements
eli/multistream-fixes
eli/more-azure-archives
eli/mobile-performance
eli/macbuild-fix
eli/lower-case-login
eli/login-chars
eli/localhost-oauth-fixes
eli/localhost-dev-is-back
eli/localdb
eli/local-pds
eli/live-feed-fix
eli/linking-fixes
eli/lexicon-repo-fixes
eli/lexicon-garbage-collection
eli/lex-it-up
eli/less-sketchy-android-signing
eli/leaky-error-cases
eli/labellers
eli/labelers-merged
eli/labelers
eli/labeler-polish
eli/keep-awake-mobile
eli/iroh-streamplace-merged
eli/iroh-replicator-integration
eli/iroh-kv-integration
eli/integrated-iroh-streamplace
eli/infinite-loop-bad
eli/indigo-renames
eli/http-metrics
eli/handle-old-auth
eli/handle-oauth-rejections
eli/handle-changes
eli/gstreamer-leak-ci-dynamic
eli/gstreamer-leak-ci
eli/gstreamer-leak-0.7
eli/gstreamer-1.26.9-bump
eli/golive-improvements
eli/godeps
eli/gitlab-ci-improvements
eli/github-skip-darwin
eli/get-segments-rebased
eli/get-segments
eli/further-gitlab-ci
eli/fix-type-export
eli/fix-thumbnail-explosion
eli/fix-test-flake
eli/fix-stuck-playback-channel-2
eli/fix-stuck-playback-channel
eli/fix-stuck-peerconnections
eli/fix-stuck-packetize
eli/fix-stereo
eli/fix-postgres-locking
eli/fix-intl-ci
eli/fix-gitlab
eli/fix-context-recursion
eli/feed-workaround
eli/feed-error-handling
eli/faster-native-chat
eli/fast-home-page
eli/failing-bframe-test
eli/expo-rotation-dependency
eli/expo-bump
eli/embedded-rtmp-server
eli/embed-go-livepeer
eli/edge-to-edge-fixes
eli/edge-to-edge
eli/dont-tell-handle-lies
eli/dont-panic
eli/docs-url-fix
eli/docker-linting
eli/docker-deployment-fixes
eli/docker-deployment-docs
eli/discord-polish
eli/discord-hook-mvp
eli/director-panic
eli/direct-rtmp-ingest
eli/devenv-again
eli/dev-env
eli/detox-testing-github
eli/detox-testing
eli/deterministic-muxing
eli/determinism-rebooted
eli/determinism-isk
eli/determinism-idk
eli/deep-linking
eli/deb-no-autostart
eli/database-resync
eli/custom-sources
eli/concat-script
eli/components
eli/com-atproto-sync-getRepo
eli/ci-desktop-darwin
eli/chat-mod-fixes
eli/chat-fixes
eli/cbor-troublemakers
eli/cache-break-buidler
eli/bump-xcode
eli/bump-maker-appimage
eli/bump-c2pa-rs
eli/built-for-newer-macos-version
eli/build-ios-before-release
eli/buffer-thumbnails
eli/bring-back-segment-cleaner
eli/bring-back-mist-addon
eli/bit-more-metadata
eli/better-bframe-detection
eli/aws-docker-registry
eli/async-react-native-webrtc
eli/android-image-NONSENSE
eli/add-language-to-posts
eli/actor-status
eli/5-minute-status
c2pa-metadata
c2pa-docs
build-5
build-4
build-3
build-2
build-1
blog-link-in-docs
backport-0.6
b5/rust_add_logging
b5/feat-replicator-swarm
0.7-rc
v0.9.9
v0.9.8
v0.9.7
v0.9.6
v0.9.5
v0.9.4
v0.9.3
v0.9.2
v0.9.1
v0.9.0
v0.8.18
v0.8.17
v0.8.16
v0.8.15
v0.8.14
v0.8.13
v0.8.12
v0.8.11
v0.8.10
v0.8.9
v0.8.8
v0.8.7
v0.8.6
v0.8.5
v0.8.4
v0.8.3
v0.8.2
v0.8.1
v0.8.0
v0.7.35
v0.7.34
v0.7.33
v0.7.32
v0.7.31
v0.7.30
v0.7.29
v0.7.28
v0.7.27
v0.7.26
v0.7.25
v0.7.24
v0.7.23
v0.7.22
v0.7.21
v0.7.20
v0.7.19
v0.7.18
v0.7.17
v0.7.16
v0.7.15
compare:
v/moderation-actions-tests
v/fix-update-stream-title-location
v/delegated-moderation-frontend
v/delegated-moderation-actions
v/chat-mod-lexicon
revert-567-eli/localhost-dev-is-back
revert-374-eli/fix-type-export
revert-288-eli/expo-bump
restructure
no-await-in-handle
next
natb/workers-docs
natb/widgets-dashboard
natb/widget-dashboard
natb/wider-docs
natb/weird-chat-alignment
natb/web-hooks
natb/vol-tap-targets
natb/verify-sidebar-state
natb/ux-rtmp
natb/uuid-player-id
natb/use-runes
natb/use-navigate
natb/use-mise
natb/use-macos-15-intel
natb/urfave
natb/upload-thrice
natb/update-title
natb/update-ls
natb/update-liquid-glass
natb/update-docs-rtmp
natb/unscope-dropdown-passins
natb/typeahead-in-login
natb/tost
natb/text-content-type-none
natb/temp-error-reporting
natb/teleport-dialog
natb/teleport
natb/system-messages
natb/sync-client-time
natb/stretch-to-contain-image
natb/streamer-agreement-popup
natb/stereo-mobile
natb/stepper-reporter
natb/spinner-while-loading-chat
natb/size-in-seg
natb/signup-modal
natb/sidebar-doesnt-default-home
natb/show-dropdowns-fullscreen
natb/sharesheet
natb/settings-redesign
natb/set-explicit-bg-mobile
natb/sentry
natb/self-host
natb/scrollable-about
natb/scroll-down-kb-dismiss
natb/screen-sharing
natb/s3-ui
natb/rust-testing
natb/rtmp-preferred
natb/rotation
natb/rework-facets
natb/restore-last-ls-title
natb/responsive-player
natb/reporting-interface
natb/reorg-docs
natb/remove-tamagui-fixups-2
natb/remove-tamagui-fixups
natb/remove-stream-viewers
natb/remove-old-chat
natb/remove-lex-reference
natb/relogo-embed
natb/refocus-chat
natb/redirect-logins
natb/readd-bsky-and-follow
natb/proxy-comatprotorepo-correctly
natb/profile-bios
natb/player-respect-top-margin
natb/player-nits
natb/player-components-store
natb/pds-login
natb/patch-rn-webrtc-fork
natb/pass-object-fit
natb/ozone-alarm
natb/og-images
natb/offset-scrolltobottom
natb/offline
natb/no-tamagui
natb/no-redux
natb/no-react-navi
natb/new-popout-chat
natb/mute-words-discord
natb/mute-param
natb/multiple-line-livestream-title
natb/move-login-tr
natb/move-actions-outside-portal
natb/mod-actions
natb/mobile-player-0.7
natb/mobile-backgrounding
natb/misc-chat
natb/max-3-line-title
natb/manual-play-button
natb/make-nav-more-navigable
natb/make-dashboard-scrollable-native
natb/make-app-bsky-unauthed
natb/login-tweak
natb/login-redesign
natb/loading-overlay
natb/loader-under-ui
natb/live-stop
natb/link-to-right-bsky
natb/link-offset
natb/less-sensitive-slides
natb/less-annoying-replies
natb/key-mgr-attempt-2
natb/key-mgmt
natb/jpeg-thumbs
natb/invoke-pip-action
natb/instant-rotation
natb/increase-timeout
natb/increase-player-handle
natb/increase-jpeg-bounds
natb/import-i18n-properly
natb/i18next-cli
natb/i18n
natb/hovers-to-button
natb/hotfix-readd-chat-text
natb/hotfix-ios-cursor
natb/home-menu
natb/hide-sidebar-embeds
natb/hide-msg
natb/hide-login-if-logged-in
natb/hide-hidden-from-front-page
natb/hide-chat-on-new-ingest
natb/handle-redirect
natb/handle-autocomplete
natb/global-volume-store
natb/get-live-working
natb/fullscreen-fixes
natb/format-handle
natb/force-dark-theme
natb/force-dark-chat
natb/force-bar-style
natb/fix-webhook-urls
natb/fix-viewer-misalignment
natb/fix-untrusted-resolution
natb/fix-unmute-button
natb/fix-stream-key-buttons
natb/fix-popout-bg
natb/fix-ogimage-ratio
natb/fix-login-mobile
natb/fix-fullscreen
natb/fix-frame-capture
natb/fix-emoji
natb/fix-docs-get-started
natb/fix-dialogs-forever-questionmark
natb/fix-cyclic-sidebar
natb/fix-crash-when-loading-deletechatmessage
natb/fix-chat-alignment
natb/fix-c2pa-styles
natb/finally-fix-reloading
natb/filter-system-authors
natb/fallback-message-facets
natb/facet-livestream-post-titles
natb/embed-branding-web
natb/dropdown-stuff-2
natb/dropdown-stuff
natb/dont-restore-deleted-messages
natb/dont-replace-routes
natb/document-public-host
natb/docs-table-wrapping
natb/docs-separation
natb/docs-openapi
natb/do-not-login-redirect-mobile
natb/discord-no-lan-ips-with-smokescreen
natb/discord-no-lan-ips
natb/did-real
natb/detect-non-h264-webrtc
natb/danmu-back-in-router
natb/constrain-stream-title
natb/configurable-live-internal-api-url
natb/concerningly-close-chatbox
natb/command-errors
natb/clear-login-error-notif
natb/cleanup-mod-menu
natb/clean-mobile-player
natb/check-repo-discord
natb/check-platform-version
natb/check-perms
natb/check-before-golive
natb/chat-nits
natb/chat-full-height
natb/chat-fixes
natb/chat-bottom-offset
natb/changing-live
natb/changesets-2
natb/changesets
natb/certmagic
natb/censor-facet
natb/cant-log-in-native
natb/canonical-url
natb/buttons
natb/bullet-hell
natb/bug-no-default-fallback-for-bsky-appview
natb/blur-objectionable-content
natb/block-javascript-protocol
natb/backport-cards
natb/auto-resume-video
natb/at-handle
natb/astro-global-favicon
natb/analytics
natb/admonition-to-cws
natb/add-problems-back
natb/a-href-in-sidebar
issue-784
issue-705-2
iroh-streamplace-kv
iroh-stream
iroh-key-in-receive
iroh-c2pa-release
fix/aqtime-datetime-parsing
feature/ci-mise-check
feature/branding-system
feature-user-consent-c2pa
feature-c2pa-metadata-clean
feat/smokesignal-event-form
feat-iroh-replicator
eli/wrapped-peer-connection
eli/windows-selftest-github
eli/who-limits-the-limiter
eli/webrtc-with-pip
eli/webrtc-track-switching
eli/webrtc-ingest-fixes
eli/version-label
eli/upload-fixes
eli/too-many-keyframes
eli/sync-tangled
eli/sync-changes
eli/streamplace-the-labeler
eli/streamplace-package
eli/stream-from-phone
eli/stateful-repo
eli/standalone-oproxy
eli/skip-corepack-prompt
eli/simple-build
eli/server-restart-test
eli/segmentation-fixes
eli/segment-optimization
eli/rust-logging
eli/rust-experimentation
eli/rtmps
eli/rtmprec-2
eli/rtmprec
eli/rtmp-rec
eli/rtmp-push
eli/rtcrec-test-comment
eli/revert-thumbnail
eli/revert-dev-env
eli/reporting
eli/repo-magic-linking
eli/remove-auth-webhook-url
eli/relaxed-oauth-nonces
eli/registry-on-in-dev
eli/red-circles-for-the-people
eli/problem-detection
eli/prettier-curlies
eli/potentially-working-webrtc-patch-2
eli/potentially-working-webrtc-patch
eli/postinstall-bad
eli/postgres
eli/popout-moderation
eli/podman-cache
eli/pnpm
eli/plane-branch
eli/pion-verbose-logging
eli/phone-streaming-spike
eli/pg-locking-fix
eli/packetize-tweak-failing
eli/packetize-tweak
eli/packaging
eli/other-leaky-error-cases
eli/ota-fixes
eli/osxcross-prebuild-clang
eli/osxcross-earlier-sdk
eli/osxcross-different-linker-twice
eli/osxcross-different-linker-pause
eli/osxcross-different-linker-many
eli/osxcross-different-linker
eli/osxcross-apple-clang
eli/osxcross
eli/organize-imports
eli/opusparse-ingest
eli/optional-convergence
eli/oproxy-fix
eli/oproxy
eli/oauth-scopes
eli/oauth-in-dev-stinks
eli/oauth-improvements
eli/oatproxy-panic
eli/oatproxy-http-client
eli/npmignore
eli/npm-publishing
eli/noop-ci
eli/node-22
eli/no-thumbnail-stall
eli/no-selftest-updates
eli/no-crash-on-reload
eli/new-webrtc-style
eli/new-webrtc-fixes-0.7
eli/new-webrtc-fixes
eli/multitesting-github
eli/multitesting-eh-prolly-not
eli/multitesting
eli/multistream-improvements
eli/multistream-fixes
eli/more-azure-archives
eli/mobile-performance
eli/macbuild-fix
eli/lower-case-login
eli/login-chars
eli/localhost-oauth-fixes
eli/localhost-dev-is-back
eli/localdb
eli/local-pds
eli/live-feed-fix
eli/linking-fixes
eli/lexicon-repo-fixes
eli/lexicon-garbage-collection
eli/lex-it-up
eli/less-sketchy-android-signing
eli/leaky-error-cases
eli/labellers
eli/labelers-merged
eli/labelers
eli/labeler-polish
eli/keep-awake-mobile
eli/iroh-streamplace-merged
eli/iroh-replicator-integration
eli/iroh-kv-integration
eli/integrated-iroh-streamplace
eli/infinite-loop-bad
eli/indigo-renames
eli/http-metrics
eli/handle-old-auth
eli/handle-oauth-rejections
eli/handle-changes
eli/gstreamer-leak-ci-dynamic
eli/gstreamer-leak-ci
eli/gstreamer-leak-0.7
eli/gstreamer-1.26.9-bump
eli/golive-improvements
eli/godeps
eli/gitlab-ci-improvements
eli/github-skip-darwin
eli/get-segments-rebased
eli/get-segments
eli/further-gitlab-ci
eli/fix-type-export
eli/fix-thumbnail-explosion
eli/fix-test-flake
eli/fix-stuck-playback-channel-2
eli/fix-stuck-playback-channel
eli/fix-stuck-peerconnections
eli/fix-stuck-packetize
eli/fix-stereo
eli/fix-postgres-locking
eli/fix-intl-ci
eli/fix-gitlab
eli/fix-context-recursion
eli/feed-workaround
eli/feed-error-handling
eli/faster-native-chat
eli/fast-home-page
eli/failing-bframe-test
eli/expo-rotation-dependency
eli/expo-bump
eli/embedded-rtmp-server
eli/embed-go-livepeer
eli/edge-to-edge-fixes
eli/edge-to-edge
eli/dont-tell-handle-lies
eli/dont-panic
eli/docs-url-fix
eli/docker-linting
eli/docker-deployment-fixes
eli/docker-deployment-docs
eli/discord-polish
eli/discord-hook-mvp
eli/director-panic
eli/direct-rtmp-ingest
eli/devenv-again
eli/dev-env
eli/detox-testing-github
eli/detox-testing
eli/deterministic-muxing
eli/determinism-rebooted
eli/determinism-isk
eli/determinism-idk
eli/deep-linking
eli/deb-no-autostart
eli/database-resync
eli/custom-sources
eli/concat-script
eli/components
eli/com-atproto-sync-getRepo
eli/ci-desktop-darwin
eli/chat-mod-fixes
eli/chat-fixes
eli/cbor-troublemakers
eli/cache-break-buidler
eli/bump-xcode
eli/bump-maker-appimage
eli/bump-c2pa-rs
eli/built-for-newer-macos-version
eli/build-ios-before-release
eli/buffer-thumbnails
eli/bring-back-segment-cleaner
eli/bring-back-mist-addon
eli/bit-more-metadata
eli/better-bframe-detection
eli/aws-docker-registry
eli/async-react-native-webrtc
eli/android-image-NONSENSE
eli/add-language-to-posts
eli/actor-status
eli/5-minute-status
c2pa-metadata
c2pa-docs
build-5
build-4
build-3
build-2
build-1
blog-link-in-docs
backport-0.6
b5/rust_add_logging
b5/feat-replicator-swarm
0.7-rc
v0.9.9
v0.9.8
v0.9.7
v0.9.6
v0.9.5
v0.9.4
v0.9.3
v0.9.2
v0.9.1
v0.9.0
v0.8.18
v0.8.17
v0.8.16
v0.8.15
v0.8.14
v0.8.13
v0.8.12
v0.8.11
v0.8.10
v0.8.9
v0.8.8
v0.8.7
v0.8.6
v0.8.5
v0.8.4
v0.8.3
v0.8.2
v0.8.1
v0.8.0
v0.7.35
v0.7.34
v0.7.33
v0.7.32
v0.7.31
v0.7.30
v0.7.29
v0.7.28
v0.7.27
v0.7.26
v0.7.25
v0.7.24
v0.7.23
v0.7.22
v0.7.21
v0.7.20
v0.7.19
v0.7.18
v0.7.17
v0.7.16
v0.7.15
go
+425
-3
4 changed files
expand all
collapse all
unified
split
js
components
src
components
chat
chat-box.tsx
teleport-modal.tsx
ui
dialog.tsx
lib
slash-commands
teleport.ts
+39
-3
js/components/src/components/chat/chat-box.tsx
···
7
7
import { ChatMessageViewHydrated } from "streamplace";
8
8
import { Button, Loader, Text, toast, useTheme, View } from "../../";
9
9
import { handleSlashCommand } from "../../lib/slash-commands";
10
10
-
import { registerTeleportCommand } from "../../lib/slash-commands/teleport";
10
10
+
import {
11
11
+
createTeleport,
12
12
+
registerTeleportCommand,
13
13
+
} from "../../lib/slash-commands/teleport";
11
14
import { StreamNotifications } from "../../lib/stream-notifications";
12
15
import { SystemMessages } from "../../lib/system-messages";
13
16
import {
···
36
39
import { RenderChatMessage } from "./chat-message";
37
40
import { EmojiData, EmojiSuggestions } from "./emoji-suggestions";
38
41
import { MentionSuggestions } from "./mention-suggestions";
42
42
+
import { TeleportModal } from "./teleport-modal";
39
43
40
44
const COOL_EMOJI_LIST = [
41
45
// @ts-ignore we can iterate through this just fine it seems
···
68
72
new Map(),
69
73
);
70
74
const [filteredEmojis, setFilteredEmojis] = useState<any[]>([]);
75
75
+
const [showTeleportModal, setShowTeleportModal] = useState(false);
71
76
const isOverLimit = graphemer.countGraphemes(message) > 300;
72
77
73
78
let linfo = useLivestream();
···
88
93
89
94
useEffect(() => {
90
95
if (pdsAgent && userDID) {
91
91
-
registerTeleportCommand(pdsAgent, userDID, setActiveTeleportUri);
96
96
+
registerTeleportCommand(pdsAgent, userDID, setActiveTeleportUri, () =>
97
97
+
setShowTeleportModal(true),
98
98
+
);
92
99
}
93
100
}, [pdsAgent, userDID, setActiveTeleportUri]);
94
101
···
105
112
106
113
useEffect(() => {
107
114
if (pdsAgent && linfo?.author?.did && pdsAgent.did === linfo.author.did) {
108
108
-
registerTeleportCommand(pdsAgent, pdsAgent.did, setActiveTeleportUri);
115
115
+
registerTeleportCommand(
116
116
+
pdsAgent,
117
117
+
pdsAgent.did,
118
118
+
setActiveTeleportUri,
119
119
+
() => setShowTeleportModal(true),
120
120
+
);
109
121
}
110
122
}, [pdsAgent, linfo?.author?.did, setActiveTeleportUri]);
111
123
···
119
131
const beforeColon = message.slice(0, message.lastIndexOf(":"));
120
132
setMessage(`${beforeColon}${emoji.skins[0]?.native} `);
121
133
setShowEmojiSuggestions(false);
134
134
+
};
135
135
+
136
136
+
const handleTeleportSubmit = async (
137
137
+
targetHandle: string,
138
138
+
countdownSeconds: number,
139
139
+
) => {
140
140
+
if (!pdsAgent || !userDID) return;
141
141
+
142
142
+
const result = await createTeleport(
143
143
+
pdsAgent,
144
144
+
userDID,
145
145
+
targetHandle,
146
146
+
countdownSeconds,
147
147
+
setActiveTeleportUri,
148
148
+
);
149
149
+
150
150
+
if (!result.success && result.error) {
151
151
+
SystemMessages.commandError(result.error);
152
152
+
}
122
153
};
123
154
124
155
const updateSuggestions = (text: string) => {
···
321
352
322
353
return (
323
354
<View style={[layout.flex.column, flex.shrink[1], gap.all[2]]}>
355
355
+
<TeleportModal
356
356
+
open={showTeleportModal}
357
357
+
onOpenChange={setShowTeleportModal}
358
358
+
onSubmit={handleTeleportSubmit}
359
359
+
/>
324
360
{replyTo && (
325
361
<View
326
362
style={[
+310
js/components/src/components/chat/teleport-modal.tsx
···
1
1
+
import { Check, X } from "lucide-react-native";
2
2
+
import React, { useEffect, useMemo, useState } from "react";
3
3
+
import { Image, Pressable, ScrollView, View } from "react-native";
4
4
+
import { PlaceStreamLivestream } from "streamplace";
5
5
+
import { useAvatars, zero } from "../..";
6
6
+
import { useStreamplaceStore } from "../../streamplace-store";
7
7
+
import { Button, Input, ResponsiveDialog, Text, useTheme } from "../ui";
8
8
+
9
9
+
interface TeleportModalProps {
10
10
+
open: boolean;
11
11
+
onOpenChange: (open: boolean) => void;
12
12
+
onSubmit: (targetHandle: string, countdownSeconds: number) => void;
13
13
+
}
14
14
+
15
15
+
export const TeleportModal: React.FC<TeleportModalProps> = ({
16
16
+
open,
17
17
+
onOpenChange,
18
18
+
onSubmit,
19
19
+
}) => {
20
20
+
const [searchQuery, setSearchQuery] = useState("");
21
21
+
const [selectedStream, setSelectedStream] =
22
22
+
useState<PlaceStreamLivestream.LivestreamView | null>(null);
23
23
+
const [countdownSeconds, setCountdownSeconds] = useState("10");
24
24
+
25
25
+
const { theme } = useTheme();
26
26
+
27
27
+
const liveUsersCache = useStreamplaceStore((state) => state.liveUsers);
28
28
+
const liveUsersLoading = useStreamplaceStore(
29
29
+
(state) => state.liveUsersLoading,
30
30
+
);
31
31
+
32
32
+
const [liveUsers, setLiveUsers] = useState(liveUsersCache);
33
33
+
34
34
+
useEffect(() => {
35
35
+
setLiveUsers(liveUsersCache);
36
36
+
}, [liveUsersCache]);
37
37
+
38
38
+
const profiles = useAvatars(liveUsers?.map((u) => u.author?.did || "") || []);
39
39
+
40
40
+
const filteredStreams = useMemo(() => {
41
41
+
if (!liveUsers) return [];
42
42
+
if (!searchQuery.trim()) return liveUsers;
43
43
+
44
44
+
const query = searchQuery.toLowerCase();
45
45
+
// filter by handle or stream title
46
46
+
return liveUsers.filter(
47
47
+
(stream) =>
48
48
+
stream.author?.handle?.toLowerCase().includes(query) ||
49
49
+
stream.record.title?.toString().toLowerCase().includes(query),
50
50
+
);
51
51
+
}, [liveUsers, searchQuery]);
52
52
+
53
53
+
const handleCancel = () => {
54
54
+
setSearchQuery("");
55
55
+
setSelectedStream(null);
56
56
+
setCountdownSeconds("10");
57
57
+
onOpenChange(false);
58
58
+
};
59
59
+
60
60
+
const handleSubmit = () => {
61
61
+
if (!selectedStream?.author?.handle) return;
62
62
+
63
63
+
const countdown = parseInt(countdownSeconds, 10);
64
64
+
if (isNaN(countdown) || countdown < 5 || countdown > 300) {
65
65
+
return;
66
66
+
}
67
67
+
68
68
+
onSubmit(selectedStream.author.handle, countdown);
69
69
+
handleCancel();
70
70
+
};
71
71
+
72
72
+
return (
73
73
+
<ResponsiveDialog
74
74
+
open={open}
75
75
+
onOpenChange={onOpenChange}
76
76
+
showCloseButton={false}
77
77
+
variant="default"
78
78
+
size="xl"
79
79
+
dismissible={false}
80
80
+
>
81
81
+
<View style={[zero.py[2]]}>
82
82
+
<View style={[zero.layout.flex.row, zero.layout.flex.justify.between]}>
83
83
+
<View style={[zero.mb[4], zero.gap.all[1], zero.layout.flex.column]}>
84
84
+
<Text size="2xl">Teleport to another live streamer</Text>
85
85
+
<Text color="muted">
86
86
+
Select a streamer to teleport your viewers to their stream.
87
87
+
</Text>
88
88
+
</View>
89
89
+
<Pressable onPress={handleCancel} style={[{ padding: 8 }]}>
90
90
+
<X color={theme.colors.mutedForeground} />
91
91
+
</Pressable>
92
92
+
</View>
93
93
+
<View style={[zero.mb[4]]}>
94
94
+
<Input
95
95
+
value={searchQuery}
96
96
+
onChangeText={setSearchQuery}
97
97
+
placeholder="Search by handle..."
98
98
+
autoCapitalize="none"
99
99
+
autoCorrect={false}
100
100
+
/>
101
101
+
</View>
102
102
+
103
103
+
{liveUsersLoading && !liveUsers ? (
104
104
+
<View style={[zero.py[8], { alignItems: "center" }]}>
105
105
+
<Text color="muted">Loading live users...</Text>
106
106
+
</View>
107
107
+
) : filteredStreams.length === 0 ? (
108
108
+
<View style={[zero.py[8], { alignItems: "center" }]}>
109
109
+
<Text color="muted">
110
110
+
{searchQuery
111
111
+
? "No matching live users found"
112
112
+
: "No live users found"}
113
113
+
</Text>
114
114
+
</View>
115
115
+
) : (
116
116
+
<ScrollView style={[{ maxHeight: 400 }]}>
117
117
+
<View
118
118
+
style={[
119
119
+
{
120
120
+
flexDirection: "row",
121
121
+
flexWrap: "wrap",
122
122
+
gap: 12,
123
123
+
},
124
124
+
]}
125
125
+
>
126
126
+
{filteredStreams.map((stream) => {
127
127
+
const isSelected = selectedStream?.uri === stream.uri;
128
128
+
const profile = profiles[stream.author?.did];
129
129
+
130
130
+
return (
131
131
+
<Pressable
132
132
+
key={stream.uri}
133
133
+
onPress={() => setSelectedStream(stream)}
134
134
+
style={[
135
135
+
{
136
136
+
width: "49.2%",
137
137
+
minWidth: 200,
138
138
+
},
139
139
+
]}
140
140
+
>
141
141
+
<View
142
142
+
style={[
143
143
+
{
144
144
+
backgroundColor: theme.colors.muted,
145
145
+
borderRadius: 12,
146
146
+
overflow: "hidden",
147
147
+
borderWidth: 2,
148
148
+
borderColor: isSelected
149
149
+
? theme.colors.primary
150
150
+
: "transparent",
151
151
+
},
152
152
+
]}
153
153
+
>
154
154
+
<View
155
155
+
style={[
156
156
+
{
157
157
+
width: "100%",
158
158
+
aspectRatio: 16 / 9,
159
159
+
backgroundColor: theme.colors.card,
160
160
+
position: "relative",
161
161
+
},
162
162
+
]}
163
163
+
>
164
164
+
<Image
165
165
+
source={{
166
166
+
uri:
167
167
+
"/api/playback/" +
168
168
+
stream.author.did +
169
169
+
"/stream.jpg",
170
170
+
}}
171
171
+
style={{
172
172
+
width: "100%",
173
173
+
height: "100%",
174
174
+
}}
175
175
+
resizeMode="cover"
176
176
+
/>
177
177
+
{isSelected && (
178
178
+
<View
179
179
+
style={[
180
180
+
{
181
181
+
position: "absolute",
182
182
+
top: 8,
183
183
+
right: 8,
184
184
+
backgroundColor: theme.colors.primary,
185
185
+
borderRadius: 999,
186
186
+
width: 24,
187
187
+
height: 24,
188
188
+
alignItems: "center",
189
189
+
justifyContent: "center",
190
190
+
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.6)",
191
191
+
},
192
192
+
]}
193
193
+
>
194
194
+
<Check size={16} color="white" />
195
195
+
</View>
196
196
+
)}
197
197
+
{stream.viewerCount && (
198
198
+
<View
199
199
+
style={[
200
200
+
{
201
201
+
position: "absolute",
202
202
+
top: 8,
203
203
+
left: 8,
204
204
+
backgroundColor: "rgba(0, 0, 0, 0.75)",
205
205
+
borderRadius: 999,
206
206
+
paddingHorizontal: 8,
207
207
+
paddingVertical: 4,
208
208
+
},
209
209
+
]}
210
210
+
>
211
211
+
<Text style={[{ fontSize: 12, color: "white" }]}>
212
212
+
{stream.viewerCount.count} viewer
213
213
+
{stream.viewerCount.count !== 1 ? "s" : ""}
214
214
+
</Text>
215
215
+
</View>
216
216
+
)}
217
217
+
</View>
218
218
+
<View
219
219
+
style={[
220
220
+
{
221
221
+
padding: 12,
222
222
+
flexDirection: "row",
223
223
+
gap: 8,
224
224
+
alignItems: "center",
225
225
+
},
226
226
+
]}
227
227
+
>
228
228
+
<View
229
229
+
style={[
230
230
+
{
231
231
+
width: 40,
232
232
+
height: 40,
233
233
+
borderRadius: 20,
234
234
+
overflow: "hidden",
235
235
+
backgroundColor: theme.colors.card,
236
236
+
flexShrink: 0,
237
237
+
},
238
238
+
]}
239
239
+
>
240
240
+
{profile?.avatar ? (
241
241
+
<Image
242
242
+
source={{
243
243
+
uri: profile.avatar,
244
244
+
}}
245
245
+
style={{ width: "100%", height: "100%" }}
246
246
+
resizeMode="cover"
247
247
+
/>
248
248
+
) : (
249
249
+
<View
250
250
+
style={{
251
251
+
width: "100%",
252
252
+
height: "100%",
253
253
+
backgroundColor: theme.colors.muted,
254
254
+
}}
255
255
+
/>
256
256
+
)}
257
257
+
</View>
258
258
+
259
259
+
{/* Text */}
260
260
+
<View style={[{ flex: 1, minWidth: 0 }]}>
261
261
+
<Text numberOfLines={1} ellipsizeMode="tail">
262
262
+
{stream.author?.handle}
263
263
+
</Text>
264
264
+
{stream.record.title ? (
265
265
+
<Text
266
266
+
numberOfLines={1}
267
267
+
ellipsizeMode="tail"
268
268
+
style={[
269
269
+
{
270
270
+
fontSize: 12,
271
271
+
color: theme.colors.textMuted,
272
272
+
},
273
273
+
]}
274
274
+
>
275
275
+
{stream.record.title as any}
276
276
+
</Text>
277
277
+
) : null}
278
278
+
</View>
279
279
+
</View>
280
280
+
</View>
281
281
+
</Pressable>
282
282
+
);
283
283
+
})}
284
284
+
</View>
285
285
+
</ScrollView>
286
286
+
)}
287
287
+
</View>
288
288
+
<View
289
289
+
style={[
290
290
+
zero.mt[8],
291
291
+
zero.layout.flex.row,
292
292
+
zero.layout.flex.justify.end,
293
293
+
zero.gap.all[2],
294
294
+
]}
295
295
+
>
296
296
+
<Button width="min" variant="secondary" onPress={handleCancel}>
297
297
+
<Text>Cancel</Text>
298
298
+
</Button>
299
299
+
<Button
300
300
+
width="min"
301
301
+
variant="primary"
302
302
+
onPress={handleSubmit}
303
303
+
disabled={!selectedStream}
304
304
+
>
305
305
+
<Text>Teleport</Text>
306
306
+
</Button>
307
307
+
</View>
308
308
+
</ResponsiveDialog>
309
309
+
);
310
310
+
};
+8
js/components/src/components/ui/dialog.tsx
···
477
477
478
478
// Size styles
479
479
smContent: {
480
480
+
width: 400,
480
481
minWidth: 300,
482
482
+
maxWidth: 500,
481
483
minHeight: 200,
482
484
},
483
485
484
486
mdContent: {
487
487
+
width: 500,
485
488
minWidth: 400,
489
489
+
maxWidth: 600,
486
490
minHeight: 300,
487
491
},
488
492
489
493
lgContent: {
494
494
+
width: 600,
490
495
minWidth: 500,
496
496
+
maxWidth: 800,
491
497
minHeight: 400,
492
498
},
493
499
494
500
xlContent: {
501
501
+
width: 800,
495
502
minWidth: 600,
503
503
+
maxWidth: 1000,
496
504
minHeight: 500,
497
505
},
498
506
+68
js/components/src/lib/slash-commands/teleport.ts
···
21
21
});
22
22
}
23
23
24
24
+
export async function createTeleport(
25
25
+
pdsAgent: StreamplaceAgent,
26
26
+
userDID: string,
27
27
+
targetHandle: string,
28
28
+
countdownSeconds: number,
29
29
+
setActiveTeleportUri?: (uri: string | null) => void,
30
30
+
): Promise<{ success: boolean; error?: string }> {
31
31
+
if (countdownSeconds < 5 || countdownSeconds > 300) {
32
32
+
return {
33
33
+
success: false,
34
34
+
error: "Countdown must be between 5 seconds and 5 minutes",
35
35
+
};
36
36
+
}
37
37
+
38
38
+
let targetDID: string;
39
39
+
try {
40
40
+
const resolution = await pdsAgent.resolveHandle({
41
41
+
handle: targetHandle,
42
42
+
});
43
43
+
targetDID = resolution.data.did;
44
44
+
} catch (err) {
45
45
+
return {
46
46
+
success: false,
47
47
+
error: `Could not resolve handle: ${targetHandle}`,
48
48
+
};
49
49
+
}
50
50
+
51
51
+
if (targetDID === userDID) {
52
52
+
return {
53
53
+
success: false,
54
54
+
error: "You cannot teleport to yourself",
55
55
+
};
56
56
+
}
57
57
+
58
58
+
const startsAt = new Date(Date.now() + countdownSeconds * 1000).toISOString();
59
59
+
60
60
+
const record: PlaceStreamLiveTeleport.Record = {
61
61
+
$type: "place.stream.live.teleport",
62
62
+
streamer: targetDID,
63
63
+
startsAt,
64
64
+
countdownSeconds,
65
65
+
};
66
66
+
67
67
+
try {
68
68
+
const result = await pdsAgent.com.atproto.repo.createRecord({
69
69
+
repo: userDID,
70
70
+
collection: "place.stream.live.teleport",
71
71
+
record,
72
72
+
});
73
73
+
74
74
+
if (setActiveTeleportUri) {
75
75
+
setActiveTeleportUri(result.data.uri);
76
76
+
}
77
77
+
78
78
+
return { success: true };
79
79
+
} catch (err) {
80
80
+
return {
81
81
+
success: false,
82
82
+
error: err instanceof Error ? err.message : "Failed to create teleport",
83
83
+
};
84
84
+
}
85
85
+
}
86
86
+
24
87
export function registerTeleportCommand(
25
88
pdsAgent: StreamplaceAgent,
26
89
userDID: string,
27
90
setActiveTeleportUri?: (uri: string | null) => void,
91
91
+
onOpenModal?: () => void,
28
92
) {
29
93
const teleportHandler: SlashCommandHandler = async (
30
94
args,
31
95
rawInput,
32
96
): Promise<SlashCommandResult> => {
33
97
if (args.length === 0) {
98
98
+
if (onOpenModal) {
99
99
+
onOpenModal();
100
100
+
return { handled: true };
101
101
+
}
34
102
return {
35
103
handled: true,
36
104
error: "Usage: /teleport @handle.bsky.social [duration_seconds]",