모바일 버전에서는 방을 생성할 수는 있지만, 이미 생성된 방을 직접 클릭해도 들어가지 못합니다. /resenha/r/topic을 직접 입력하면 정상적으로 열리는데, 이것이 버그인가요?
제 폰에서는 정상적으로 작동합니다. 이번 주에 Discourse 인스턴스를 업데이트하셨나요?
실험용 플러그인은 빈번한 업데이트가 필요합니다
이번 달에 업데이트가 많았지만, 주요 사항은 LiveKit를 통한 SFU의 선택적 및 룸별 지원입니다.
완전 P2P WebRTC가 여전히 주요 및 기본 지원 전송 프로토콜이지만, 이제 단일 룸에서 수백 명의 참여자를 지원해야 하는 사용자를 위해 SFU도 지원됩니다.
또한 룸 및 위젯 컨트롤에 오디오 및 비디오 장치 선택기를 추가하고, 채팅과 게시물에서 # 자동완성을 통해 Resenha 룸을 언급할 수 있도록 했으며, 룸 레이아웃 옵션 등 다양한 개선 사항도 반영했습니다.
감사합니다. SFU가 실제로 빠져 있던 핵심 부분이었습니다. 처음에 별도 포크를 유지하게 된 계기도 바로 이 때문이었죠. 이제 업스트림에서 지원이 가능해졌으니, 포크를 종료하고 resenha를 직접 추적할 수 있게 되었습니다.
LiveKit에 대해 여쭤볼까 하다가, 그 방향으로 진행할 의사가 있는지 몰라서 차마 물어보지 못했습니다. 이제 진행하기로 하셨으니, 기능 추가와 작은 개선 사항에 대한 PR을 받아주실 수 있으신가요?
@Falco 안녕하세요, 업데이트된 resenha를 테스트하는 과정에서 재현 가능한 몇 가지 문제를 발견했습니다. 유용할 수 있기를 바라며, 조사 결과와 로컬에서 실행 중인 패치를 공유합니다.
1. 관리자 UI가 room_type을 조용히 무시하여 stage 룸이 영원히 유지되지 않음
app/controllers/resenha/admin_rooms_controller.rb:65 (room_params)에서 허용 목록에 :room_type이 빠져 있고, admin_room_serializer.rb에서는 이를 직렬화하지 않습니다.
관리자 UI를 통해 수행한 모든 stage 할당이 삭제되며, 룸은 open 상태로 생성되고 이후 관리자 편집 시 open 상태로 "복원"됩니다. 프로덕션 로그를 통해 확인했습니다. 사용자용 컨트롤러에서는 허용되고 있으며, 관리자 경로에서만 누락되었습니다.
# admin_rooms_controller.rb — 허용 목록에 :room_type을 추가한 다음:
if permitted.key?(:room_type)
value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
raise Discourse::InvalidParameters.new(:room_type) if value.nil?
permitted[:room_type] = value
end
# admin_room_serializer.rb — room_type을 직렬화하여 양식이 올바르게 초기화되도록 함
2. 잘못된 room_type이 조용히 open으로 폴백됨
app/controllers/resenha/rooms_controller.rb:575 — ROOM_TYPES[...] || ROOM_TYPE_OPEN.
그 외 유효한 룸 편집에서 잘못된/오래된 room_type은 stage 룸을 open으로 조용히 전환시킵니다. 400 오류를 반환하면 룸이 손상되는 대신 잘못된 동작을 하는 호출자를 확인할 수 있습니다.
value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
raise Discourse::InvalidParameters.new(:room_type) if value.nil?
permitted[:room_type] = value
3. 하트비트가 방금 떠난 사용자를 부활시킴 (고유 존재감)
rooms_controller.rb:234 (heartbeat)는 무조건적으로 존재감을 다시 추가하므로, leave(:220)가 처리되는 시점에 전송 중인 하트비트가 두 번째로 도착하여 떠난 사용자를 다시 생성합니다.
이 고유의 존재감은 TTL 수거(reap)까지 지속되며, 이는 브로드캐스트되지 않습니다 — 클라이언트는 최대 1분 동안 해당 사용자가 "룸에 있음"으로 표시합니다.
기기에서 재현했습니다. kick도 동일한 취약점이 있습니다.
# ParticipantTracker: 15초 톰스톤(tombstone)
def mark_left(room_id, user_id) = redis.setex(left_key(room_id, user_id), 15, "1")
def recently_left?(room_id, user_id) = redis.exists?(left_key(room_id, user_id))
# leave/kick → mark_left; join/livekit_token → clear_left; heartbeat:
return head :no_content if Resenha::ParticipantTracker.recently_left?(@room.id, current_user.id)
4. 오래된 participant_left 웹훅이 새로운 세션을 유령처럼 강퇴함
livekit_webhooks_controller.rb:37/57 — 이탈은 사용자 신원만으로 매칭됩니다.
빠른 연결 해제/재연결 시, 대체된 세션의 participant_left가 늦게 도착하여 새로운 세션의 존재감을 만료시킵니다(사용자가 재연결 후 약 3초后被 "강퇴"되고, 약 15초 후에 다시 나타남). 기기에서 재현했습니다. 재연결이 이전 세션의 연결 해제보다 먼저 발생한 경우, gone_at 휴리스틱은 세션을 구별할 수 없습니다.
# participant_joined → 활성 SID 기록
Resenha::ParticipantTracker.set_livekit_sid(room.id, user_id, event.dig("participant", "sid"))
# expire_participant → 대체된 세션의 이탈 무시
known = Resenha::ParticipantTracker.livekit_sid(room.id, user_id)
return if sid.present? && known.present? && sid != known
5. 마지막 이탈 후 DeleteRoom 404가 로그를 범람시킴
lib/resenha/livekit/room_service_client.rb:84는 200이 아닌 모든 응답에 대해 경고를 기록합니다.
SFU는 룸이 비어지는 순간 자동으로 룸을 닫으므로, 마지막 이탈의 DeleteRoom은 이를 정기적으로 경쟁(race)합니다 — "요청한 룸이 존재하지 않습니다"는 결함이 아니라 원하시는 최종 상태입니다.
이는 마지막 이탈 시마다 Logster에 기록됩니다.
elsif method == "DeleteRoom" && response.status == 404
Rails.logger.debug("[resenha-livekit] DeleteRoom no-op for room #{room.id}: already gone")
true
원하시면 풀 리퀘스트를 보내겠습니다.
이전에 말했듯이, 님의 전체 방향성을 정확히 알지는 못하지만, 제가 여기에 게시물을 올렸으므로 제 것을 참고해 보실 수 있습니다 → https://meta.discourse.org/t/discourse-desktop-mac-app/406912/10?u=nicolsdennis
이것은 정말 훌륭할 것입니다. 라이브스트림 메커니즘과 연동하여 관련 채팅 채널을 함께 제공하면, 아름답게 통합된 경험을 만들어낼 수 있을 것입니다.
안녕하세요, 방 이름을 변경해도 슬러그가 업데이트되지 않습니다. 또한 방을 삭제하려고 하면 서버 오류가 발생합니다.
방의 순서와 표시 방식을 시각적으로 변경할 수 있으면 좋겠습니다.
자세한 보고 감사합니다. 해당 사항에 대한 PR을 방금 병합했습니다:
개인 음성 방과 관련된 사소한 UI/UX 사항이 몇 가지 있습니다:
1. 개인/잠금 마이크로 아이콘이 너무 작습니다.
방의 개인 설정 상태를 나타내는 아이콘이 있는 것은 좋지만, 불행히도 너무 작고 다른 아이콘과 많이 겹칩니다. 또한 사이드바의 나머지 부분과 일관성이 없습니다:
![]()
2. 참가자 목록을 찾기 어렵습니다
개인 음성 방의 참가자 목록에는 통화 자체 안에서만 접근할 수 있습니다.
이것은 매우 직관적이지 않습니다. 음성 방이 비활성 상태일 때에도 설정에서 참가자/초대 대상을 수정할 수 있을 것이라고 기대합니다. 비활성 상태일 때 실제로 사용 가능한 설정이 꽤 많습니다(공개 vs 개인, 최대 참가자 수 등)만, 실제 참가자 목록은 없습니다.
두 가지 모두 수정했습니다. 보고해 주셔서 감사합니다, @nathank!
href 링크에서 특정 방을 링크할 수 있는지 궁금합니다. UX를 단순화하기 위해 단일 테스트 룸으로 바로 이동할 수 있는 단축키를 추가하고 싶습니다.
네, 이제 이 형식으로 가능합니다
최근 개선 사항 중 주목할 만한 부분도 함께 소개합니다:
coturn 인증을 통한 개인용 STUN/TURN 지원
이를 통해 coturn 인스턴스를 본인만 사용하도록 제한할 수 있으며, Discourse와 Coturn 사이에 공유 시크릿을 설정하면 매우 쉽게 구성할 수 있습니다.
LiveKit 사용 시 세션 녹화 지원
모두 통화 UI를 통해 이루어지며, 녹화를 시작하거나 중지하려면 각각 한 번만 클릭하면 됩니다.
통화 참가자용 국기 표시 지원
국기 자체에는 맥락이 부족할 수 있지만, 사용자는 텍스트 영역을 통해 이를 제공할 수 있습니다.
비디오 타일 우클릭 메뉴
다른 참가자의 볼륨을 쉽게 조절하거나, 내보내기 등을 수행할 수 있습니다.
음성, 비디오 및 화면 공유의 고화질 옵션(선택적)
이번 주 제가 가장 좋아하는 기능입니다. 이를 통해 사용자는 훨씬 더 높은 비트레이트 한도를 선택하여 훨씬 더 좋은 음질과 화질을 즐길 수 있습니다.
내 인스턴스 문제일까요, 아니면 확인해봐야 할 다른 문제가 있을까요? 관리자 패널에서 음성 방을 삭제하려고 하면 오류 500이 발생합니다:
ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR: update or delete on table "resenha_rooms" violates foreign key constraint "fk_rails_ID" on table "resenha_sessions" DETAIL: Key (id)=(1) is still referenced from table "resenha_sessions".
리포트 감사합니다. 수정이 병합되었습니다
이렇게 빠른 수정을 해주셔서 감사합니다. 예상대로 잘 작동했습니다.
다만, 후속으로 한 가지 참고할 점이 있습니다. 기본 방 이름을 변경해도 /resenha/r/watercooler 경로는 업데이트되지 않았습니다. 하지만 이전 기본 방을 삭제하고 새로운 방을 추가하는 것까지는 성공했습니다.
슬러그를 이제 명시적으로 편집 가능하도록 변경했으니, 카테고리처럼 동작합니다.
특정 방식으로 열기 위해 URL 파라미터를 추가할 수도 있습니다:
https://discourse-on-a-pi5.falco.dev/resenha/r/watercooler?join — 참가 버튼 클릭 없이 바로 방에 입장합니다
https://discourse-on-a-pi5.falco.dev/resenha/r/watercooler?join&widget (이번 주早些 추가됨) — 열리고 위젯 모드로 전환됩니다
https://discourse-on-a-pi5.falco.dev/resenha/r/watercooler?join&chat — 채팅 모드로 열립니다
우리는 이 플러그인을 내부 회의에 사용해 왔으며, 이를 통해 피드백을 수집하고 실제 사용 사례에 기반하여 플러그인을 발전시켜 왔습니다. 지난 2주간의 주요 업데이트 사항은 다음과 같습니다:
AI 노이즈 서프레션
노이즈 서프레션 기능이 여러 가지 변경 사항과 함께 완전히 재구축되었습니다. 이제 표준 WebRTC 기능을 제어할 수 있는 옵션을 제공합니다:
- 노이즈 서프레션 (Noise Suppression)
- 자동 이득 제어 (Auto Gain Control)
- 에코 캔슬레이션 (Echo Cancellation)
또한, 더 나은 노이즈 서프레션이 필요한 사용자를 위해 저음역에서 고음역 순서로 세 가지 다른 AI 모델을 제공합니다:
- RNNoise
- DTLN
- DeepFilter3
다이렉트 통화
사용자는 이제 사용자 카드의 버튼을 통해 1:1 또는 1:N 다이렉트 통화를 시작할 수 있습니다. 이는 우리가 구축한 에페메럴 룸(일시적인 방) 인프라를 사용하며, 향후 이벤트 플러그인과의 통합 처리에도 사용될 예정입니다.
초대
룸을 우클릭하면 초대를 생성할 수 있으며, 해당 룸의 대화 이력을 기반으로 사용자를 추천해 줍니다.
사운드 팩
Resenha 사운드는 이제 새로운 채팅 알림 사운드 팩에서 사용자가 선택한 사운드 팩을 따릅니다.
룸 UX
룸 링크 복사(우클릭), 위젠타일 자동 크기 조정 및 접기, 사이트 헤더 위쪽의 위젠타일 위치 지정, Shift+클릭 룸 지원, 음성 활동 지표의 정확도 및 성능 개선 등이 적용되었습니다.
Android 카메라 전환
이제 통화 중에도 Android 휴대폰의 전면 및 후면 카메라를 부드럽게 전환할 수 있습니다.
메시 연결 경고
사용자가 WebRTC 메시를 사용하는 룸에 연결을 시도하는 첫 번째 시점에서, 잠재적인 IP 노출에 대해 설명하는 경고가 표시됩니다.
그리고 마지막으로, 두 가지 실험적 기능 ![]()
라이브 자막
기기에서 실행되는 최첨단 AI 모델로 구동되는 선택적 라이브 자막 기능을 추가했습니다.
활성화하면 WebGPU를 사용하여 실행되는 2.5GB 크기의 AI 모델이 다운로드되며, 따라서 호환성은 빠르고 최신의 기기에 제한됩니다.
이 기능은 다듬고 세밀하게 조정할 부분이 많으므로, 사용 후기를 알려주시면 좋겠습니다.
전사(Transcription)
라이브 자막을 활성화한 사용자는 이제 이를 기반으로 전사를 시작할 수 있으며, 이는 새로운 토픽 초안으로 백업됩니다. 녹음을 마치면, 채팅 인용문이 사용하는 것과 동일한 형식의 저장된 모든 전사가 포함된 컴포저가 제공됩니다.












