Skip to content

feat(multi): 설정 [정보] 탭 · 잠금화면 가사 · 중복 조회/번역 제거 · 광고 구간 제외 - #30

Merged
countnine merged 4 commits into
masterfrom
feat/multi/about-tab-and-dedupe
Aug 1, 2026
Merged

feat(multi): 설정 [정보] 탭 · 잠금화면 가사 · 중복 조회/번역 제거 · 광고 구간 제외#30
countnine merged 4 commits into
masterfrom
feat/multi/about-tab-and-dedupe

Conversation

@countnine

Copy link
Copy Markdown
Owner

커밋 4개. 앞의 둘은 Android UX, 뒤의 둘은 서버 로그를 실측해서 찾은 낭비를 걷어낸다.

1. 설정 [정보] 탭 · 같은 곡 중복 조회 · 버블 두 줄 표기 (f6645f9)

  • [정보] 탭(Android) — Windows [정보] 탭과 같은 내용에 안드로이드 전용 줄(패키지·OS·기기)을 더하고, 탭하면 버전까지 붙여 클립보드로 복사한다. 버전은 PackageManager에서 읽으므로 릴리스마다 손댈 곳이 없다. 탭 5개가 한 줄에 들어가도록 이름을 줄였다(소스/번역/오버레이/광고/정보).
  • 같은 곡 중복 조회 제거(코어) — 서버 로그에 같은 곡을 1~8초 간격으로 두 번 묻는 사례가 있었다. TrackInfo가 record라 길이·앨범이 뒤늦게 채워지면 값이 달라져 TrackChanged가 한 번 더 발화하고, 코디네이터가 그때마다 파이프라인을 다시 돌린 것. 마지막으로 검색한 제목|아티스트를 기억해 같으면 건너뛴다. Windows(SMTC)도 같은 재발화가 있어 함께 이득.
  • 버블 두 줄 표기 = "켜 둠" — 실제 표시 여부를 따르고 있어 가사 없는 구간·일시정지에 한 줄로 돌아가 꺼진 것처럼 보였다. 사용자가 켜 둔 상태를 따르게 하고, 그 값을 흔들던 곳(peek → _peeking 분리, 이동 모드)을 모두 걷어냈다.

2. 잠금화면 가사 (7838bd9)

플로팅 오버레이는 잠금화면 위에 뜰 수 없다(Android 8부터 TYPE_APPLICATION_OVERLAY가 키가드보다 아래 레이어로 고정). AOD는 더 막혀 있다 — 삼성이 API를 열지 않아 스토어의 "AOD 앱"들은 검은 화면 액티비티를 켜 두는 흉내다.

대신 이미 Visibility.Public인 포그라운드 알림에 현재 줄을 싣는다 — 접힌 알림은 원문, 펼친 알림은 원문 + 번역. 설정 [오버레이] 탭에서 끌 수 있다(기본 켬 — 끄면 듣는 내용이 잠금화면에 남지 않는다). 갱신은 최소 400ms 간격으로 묶는다(시스템의 알림 갱신 빈도 제한 — 간주 표시 줄처럼 짧은 줄이 연달아 나오면 갱신이 통째로 버려진다).

3. 동시 재생의 중복 검색·번역 (c2c0708)

Spotify Connect로 PC 재생 + 폰 조작을 하면 두 기기가 같은 곡을 동시에 처리해 각자 검색하고 각자 유료 번역을 부른다. 서버 로그 실측:

항목
전체 조회 304
두 기기 동시 조회(30초 이내) 26
그중 양쪽 다 miss 22 (= 곡 11개를 두 번씩 번역)
  • 번역 양보 — 서버가 미스 응답에 pending(최근 30초 안에 다른 기기도 같은 제목을 미스함)을 실어 주면, 받은 앱은 제공자 검색은 평소대로 하고 번역만 미룬 뒤 서버를 재조회해(1~5초 간격, 상한 8초) 저쪽이 올린 번역본을 받아 쓴다. 원문 표시는 늦어지지 않는다 — 번역이 몇 초 뒤 붙는 건 원래 동작이라 사용자가 손해를 느끼지 않는다. 이 점이 "두 번째 기기를 통째로 대기시키는" 안보다 나은 이유다. 받아 쓴 것은 되올리지 않는다(같은 내용으로 revision만 오른다).
  • 판정 근거는 기존 lookups새 테이블이 없고, 조회 기록을 끄면 양보도 함께 꺼진다(청취 이력을 안 남기겠다는 선택이 우선). MUSEBASE_YIELD_WINDOW_SECONDS(0이면 끔).
  • 아티스트 꼬리표에 추가 — Spotify Android가 Phoenix • 스마트셔플 추천처럼 붙여 같은 곡이 기기별로 다른 키가 되고 있었다(조회 29건, 실제 중복 행 uptown girl·Go! 확인). SearchTermCleaner.CleanArtist(제공자 검색)와 LyricsStore.StripAlbumSuffix(서버 키) 양쪽에 반영.
  • IRemoteLyricsCache.GetAsyncRemoteLyricsResult(가사 + Pending/RetryAfterMs/Langs)를 돌려준다. 본문 없는 404(구버전 서버)는 평범한 미스로 강등되므로 서버만 먼저 올려도, 앱만 먼저 올려도 안전하다.

4. 광고 구간에는 가사를 찾지 않는다 (406d987)

Spotify 광고 중 SMTC/MediaSession이 광고를 "곡"으로 보고하면 가사를 검색하고 서버에 miss를 기록했다(실제로 Spotify / 광고 1/2 행이 저장돼 있었다). 이제 광고 구간에는 트랙을 만들지 않는다.

  • 그러려면 판정 규칙이 두 플랫폼 공통이어야 해서 AdSignalsMusebase.AndroidMusebase.Engine으로 옮겼다. ADR-0006이 "나중에 코어로 옮겨 테스트를 붙이겠다"고 예고한 이동이며, 옮기면서 실제로 붙였다(AdSignalsTests 5건 — 오탐 쪽을 특히 본다).
  • Windows는 SMTC에 광고 플래그·mediaId가 없어 실측 검증된 신호 ③(아티스트=Spotify/Sponsored Message + 앨범 비어 있음)만 본다. 앨범 조건이 오탐 안전장치다. 디바운스는 두지 않는다 — ③은 아티스트가 비면 false라 곡 전환 순간의 빈 메타데이터를 광고로 보지 않고, 한 틱 틀려도 가사가 잠깐 늦을 뿐이다.
  • Android는 IsAdvertisement 속성이 아니라 같은 메타데이터에서 직접 판정한다 — 그 속성은 RefreshTrack 뒤에 갱신돼 아직 이전 곡 기준이다.
  • 뮤트 상태 기계(AdDecision)는 볼륨 안전장치라 Android에 남는다. Windows에서 광고를 뮤트하고 MP3를 채우는 일은 별도 앱 Mutefy가 하며, 소스를 확인한 결과 충돌 지점이 없다(Spotify 프로세스의 오디오 세션만 음소거, 필러는 WasapiOut 직접 출력이라 SMTC 세션을 만들지 않는다, 자동시작 Run 키 값 이름·설정 경로·단일 인스턴스 뮤텍스 모두 다름).

검증

  • dotnet build Musebase.sln -c Release 경고 0 / Android Release 빌드 경고 0
  • dotnet test 174개 통과(신규 10건)
  • 로컬 서버 실측: 같은 기기 재조회엔 힌트 없음, 다른 기기엔 pending, 불릿 표기로 올린 번역본이 기존 행에 합쳐짐(곡 수 1 유지, revision 2, 아티스트는 먼저 저장된 표기 유지)
  • 실서버 배포 완료(이 브랜치 코드) — 힌트 3케이스 확인, 경고·오류 로그 없음. 데이터 정리: 광고 행 2건 삭제, Uptown Girl 잉여 행 삭제, Take on Me를 깨끗한 키로 이전 → 이제 양쪽 표기가 같은 행에 도달. 447곡/번역 420.

🤖 Generated with Claude Code

countnine and others added 4 commits August 1, 2026 16:11
세 가지를 함께 다룬다.

1) 설정 [정보] 탭(Android) — Windows [정보] 탭과 같은 내용(앱 이름·버전·구
   LyricsX·소개·MPL-2.0·LyricsKit 출처·링크)에 안드로이드 전용 줄을 더한다:
   패키지명·OS·기기를 보여 주고 탭하면 버전까지 붙여 클립보드로 복사한다.
   버전은 PackageManager에서 읽으므로 릴리스마다 손댈 곳이 없다.

2) 같은 곡 중복 조회 제거(코어) — 가사 서버 로그에 같은 곡을 1~8초 간격으로
   두 번 묻는 사례가 있었다. TrackInfo는 record라 길이·앨범이 뒤늦게 채워지면
   값이 달라져 TrackChanged가 한 번 더 발화하고, 코디네이터가 그때마다 검색
   파이프라인을 다시 돌렸다. 마지막으로 검색한 "제목|아티스트"를 기억해 같으면
   건너뛰고 표시 상태만 갱신한다. Windows(SMTC)도 같은 재발화가 있어 함께 이득.

3) 버블 두 줄 표기 = "켜 둠"(Android) — 두 줄 표기가 실제 표시 여부를 따르고
   있어, 가사가 없는 구간·일시정지에 한 줄로 돌아가 꺼진 것처럼 보였다. 이제
   사용자가 켜 둔 상태(_bandExpanded)를 따른다. 그러려면 그 값을 아무도 흔들지
   않아야 해서 peek을 별도 플래그(_peeking)로 분리하고, 이동 모드는 손대지
   않게 했다(UpdateVisibility가 _moveMode만 봐도 밴드를 띄운다). 실제 표시
   여부(_bandVisible)는 접근성 문구에만 쓴다.

테스트 2건 추가(중복 조회 억제 / 곡이 실제로 바뀌면 다시 조회) — 164개 통과.
솔루션·Android 빌드 경고 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
플로팅 오버레이는 잠금화면 위에 뜰 수 없다 — Android 8부터
TYPE_APPLICATION_OVERLAY가 키가드보다 아래 레이어로 고정됐고 그 위로 올라가던
TYPE_SYSTEM_OVERLAY는 일반 앱에서 제거됐다(오버레이 권한 예외 없음).
잠금화면 전용 액티비티로 덮는 방법도 있지만 시계·알림을 통째로 가리고 백그라운드
액티비티 실행 정책에 걸릴 위험이 있어, 확실히 동작하는 알림 쪽을 쓴다.

포그라운드 알림은 이미 Visibility.Public이라 잠금화면에 내용이 그대로 나온다.
여기에 현재 줄을 싣는다 — 접힌 알림은 원문, 펼친 알림(BigTextStyle)은 원문+번역.

- 설정 [오버레이] 탭에 "알림에 현재 가사 표시" 토글(기본 켬). 끄면 곡명·상태만
  보여 듣는 내용이 잠금화면에 남지 않는다. 저장 즉시 반영된다.
- 갱신은 최소 400ms 간격으로 묶는다. 시스템이 알림 갱신 빈도를 제한해 너무
  잦으면 갱신이 통째로 버려지는데, 간주 표시 줄처럼 짧은 줄이 연달아 나올 때
  그렇게 된다. 간격 안에 또 바뀌면 예약해 두고 마지막 값 하나만 뒤늦게 그린다.

Release 빌드 경고 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Spotify Connect로 PC에서 재생하고 폰에서 조작하면 두 기기가 같은 곡을 같은
시각에 처리한다 — 둘 다 서버 미스, 각자 제공자 검색, 각자 유료 번역.
서버 로그 실측: 조회 304건 중 동시 조회 26건, 그중 22건이 양쪽 다 miss
(= 곡 11개를 두 번씩 번역).

1) 번역 양보 — 서버가 미스 응답 본문에 pending(최근 30초 안에 다른 기기도 같은
   제목을 미스함)을 실어 준다. 받은 앱은 제공자 검색은 평소대로 하되 번역만
   미루고 서버를 재조회해(1~5초 간격, 상한 8초) 저쪽이 올린 번역본을 받아 쓴다.
   원문 표시는 늦어지지 않는다 — 번역이 몇 초 뒤 붙는 건 원래 동작이라 손해가
   없다. 이 점이 두 번째 기기를 통째로 대기시키는 안보다 나은 이유다.
   받아 쓴 것은 되올리지 않는다(같은 내용으로 revision만 오른다).
   판정 근거는 기존 lookups라 새 테이블이 없고, 조회 기록을 끄면 양보도 함께
   꺼진다(MUSEBASE_YIELD_WINDOW_SECONDS=0으로도 끔).

2) 아티스트 꼬리표에 • 추가 — Spotify Android가 "Phoenix • 스마트셔플 추천"처럼
   붙여 같은 곡이 기기별로 다른 키가 되고 있었다(조회 29건, 실제 중복 행
   uptown girl·Go! 확인). SearchTermCleaner.CleanArtist(제공자 검색)와
   LyricsStore.StripAlbumSuffix(서버 키) 양쪽에 반영.

3) 광고 트랙 차단 — 광고 구간에는 TrackChanged를 발화하지 않는다. IsAdvertisement
   속성이 아니라 같은 메타데이터에서 직접 판정한다(그 속성은 RefreshTrack 뒤에
   갱신돼 아직 이전 곡 기준이다). 서버에 "Spotify / 광고 • 1/2"가 실제로
   저장돼 있었다.

IRemoteLyricsCache.GetAsync가 RemoteLyricsResult(가사 + Pending/RetryAfterMs/
Langs)를 돌려준다. 본문 없는 404(구버전 서버)는 평범한 미스로 강등되므로
서버만 먼저 올려도, 앱만 먼저 올려도 안전하다.

로컬 서버로 실측 확인: 같은 기기 재조회엔 힌트 없음, 다른 기기엔 pending,
불릿 표기로 올린 번역본이 기존 행에 합쳐짐(곡 수 1 유지, revision 2, 아티스트는
먼저 저장된 표기 유지). 솔루션·Android 빌드 경고 0, 테스트 169개 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Spotify 광고 중 SMTC가 "Spotify / (광고)"를 보고하면 Musebase Windows가 그
"곡"의 가사를 검색하고 서버에 miss를 기록했다. 소리는 Mutefy가 죽여 주는데
오버레이만 헛돌고 서버 로그가 지저분해진다(실제로 광고 행이 저장돼 있었다).
Android에는 방금 넣었으니 Windows도 같게 한다.

- Windows: SMTC에 광고 플래그도 mediaId도 없어 실측 검증된 신호 ③만 본다
  (아티스트=Spotify/Sponsored Message + 앨범 비어 있음). 앨범 조건이 오탐
  안전장치다 — 그 이름의 실제 곡이 있어도 앨범이 채워져 있어 걸리지 않는다.
  디바운스는 두지 않는다: ③은 아티스트가 비면 false라 곡 전환 순간의 빈
  메타데이터를 광고로 보지 않고, 한 틱 틀려도 가사가 잠깐 늦을 뿐이다.
- 그러려면 판정 규칙이 두 플랫폼 공통이어야 해서 AdSignals를
  Musebase.Android → Musebase.Engine으로 옮겼다. ADR-0006이 "나중에 코어로
  옮겨 테스트를 붙이겠다"고 예고한 이동이며, 옮기면서 실제로 붙였다
  (AdSignalsTests 5건 — 오탐 쪽을 특히 본다).
- 뮤트 상태 기계(AdDecision/AdSignal)는 Android에 남는다. 볼륨을 다루는
  안전장치라 가사 쪽에는 필요 없다.

Windows에서 광고를 뮤트하고 MP3를 채우는 일은 별도 앱 Mutefy가 한다. 소스를
확인한 결과 충돌 지점이 없다 — Mutefy는 Spotify 프로세스의 오디오 세션만
음소거하고(SimpleAudioVolume, PID 대상), 필러는 WasapiOut 직접 출력이라 SMTC
세션을 만들지 않는다(필러가 "재생 중인 곡"으로 잡히지 않는다). 자동시작 Run 키
값 이름·설정 경로·단일 인스턴스 뮤텍스도 모두 다르고, SMTC는 둘 다 읽기 전용이라
동시 구독에 문제가 없다.

솔루션·Android 빌드 경고 0, 테스트 174개 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@countnine
countnine merged commit 0405e03 into master Aug 1, 2026
1 check passed
@countnine
countnine deleted the feat/multi/about-tab-and-dedupe branch August 1, 2026 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant