Skip to content

feat(android): floating lyrics overlay (SYSTEM_ALERT_WINDOW) + karaoke fill - #13

Merged
countnine merged 5 commits into
betafrom
feat/android/overlay
Jul 18, 2026
Merged

feat(android): floating lyrics overlay (SYSTEM_ALERT_WINDOW) + karaoke fill#13
countnine merged 5 commits into
betafrom
feat/android/overlay

Conversation

@countnine

Copy link
Copy Markdown
Owner

요약

다른 앱(음악 앱)·홈 화면 위에 떠 있는 오버레이로 실시간 가사를 표시한다. 글자 단위 카라오케 채움 포함. 기존 앱 내 가사 표시는 대체 확인용으로 유지.

오버레이 서비스 (Services/OverlayService.cs)

  • 포그라운드 Service — Android 8+ 필수 알림 채널 + "Musebase 가사 표시 중" + 정지 액션. specialUse 포그라운드 타입.
  • WindowManagerTYPE_APPLICATION_OVERLAY 뷰(화면 하단 중앙, 반투명 둥근 카드): 현재 줄(크게) + 번역(아래).
  • 터치 완전 통과: FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE.
  • 재생 중 + 표시할 라인이 있을 때만 표시. 일시정지·무곡 시 숨김.

글자 단위 카라오케 (Views/KaraokeTextView.cs)

  • Windows 오버레이 개념 포팅(코드 복사 아님): 흰색 베이스 위에 채움색 노랑 #FFEB3B(Windows 기본과 통일)을 진행 글자 위치까지 Path 클립으로 덧그린다.
  • 타임태그(InlineTimeTags.CharIndexAt) 있으면 글자 위치까지, 없으면 라인 표시 구간 대비 비율 폴백.
  • 엔진 LineProgressChanged는 100ms 간격 → 앵커+실시간으로 프레임마다 보간(60fps, postInvalidateOnAnimation).
  • StaticLayout으로 멀티라인/가운데정렬 대응(윗줄 전체 + 현재 줄 좌측~채움 x 클립, 소수 글자 보간).

권한/진입 (MainActivity.cs, AndroidManifest.xml)

  • Settings.CanDrawOverlays 확인 → 미허용 시 ACTION_MANAGE_OVERLAY_PERMISSION 인텐트. "오버레이 권한 허용" 버튼 + 상태 문구(알림 접근 안내와 나란히).
  • "가사 오버레이 켜기/끄기" 토글(서비스 시작/중지). 알림 "정지"로도 종료.
  • 매니페스트: SYSTEM_ALERT_WINDOW + FOREGROUND_SERVICE(+FOREGROUND_SERVICE_SPECIAL_USE, Android 14) + POST_NOTIFICATIONS.

엔진 재사용 (골든룰)

오버레이는 MusebaseApp이 1회 조립한 LyricsCoordinatorCurrentLineChanged/LineProgressChangedAndroidNowPlayingSource.IsPlayingChanged구독만 한다 — 엔진 재조립 없음. 초기 표시는 RefreshCurrentLine()로 현재 라인 재발행. 코어/엔진/contracts 무수정.

검증

  • APK 빌드 성공(경고 0 / 오류 0): src/Musebase.Android/bin/Debug/net8.0-android/com.countnine.musebase-Signed.apk
  • dotnet build Musebase.sln -c Release 성공(Android은 sln 밖 — 코어 미변경 확인).
  • Core 테스트 90개 전부 통과.

폰 테스트 시나리오

  1. "오버레이 권한 허용" → 시스템 "다른 앱 위에 표시"에서 Musebase ON → "허용됨 ✓".
  2. "가사 오버레이 켜기" → 상태바 알림 표시.
  3. 음악 재생 후 홈/음악 앱으로 나가도 하단에 가사 카드가 뜬다.
  4. 재생 위치에 따라 글자가 왼쪽부터 노랑으로 채워지면 카라오케 성공.
  5. 일시정지 시 자연스럽게 숨김/재개, 곡 넘김 시 즉시 갱신, 카드 터치는 아래 앱으로 통과.
  6. 알림 "정지" 또는 "가사 오버레이 끄기"로 종료.

🤖 Generated with Claude Code

Jay and others added 5 commits July 17, 2026 23:52
…e fill

다른 앱/홈 화면 위에 떠서 실시간 가사를 보여주는 오버레이를 추가한다.

- Services/OverlayService.cs: 포그라운드 서비스. WindowManager의
  TYPE_APPLICATION_OVERLAY 뷰(하단 중앙 반투명 둥근 카드)를 붙이고,
  터치는 FLAG_NOT_FOCUSABLE|NOT_TOUCHABLE로 완전 통과. Android 8+ 알림
  채널 + "정지" 액션. specialUse 포그라운드 타입.
- Views/KaraokeTextView.cs: 글자 단위 카라오케 커스텀 뷰. 흰색 베이스 위에
  노랑(#FFEB3B)을 진행 글자까지 Path 클립으로 덧그린다. 타임태그
  (InlineTimeTags.CharIndexAt) 있으면 글자 위치, 없으면 라인 비율 폴백.
  100ms 갱신 사이를 앵커+실시간으로 60fps 보간. StaticLayout 멀티라인/
  가운데정렬 대응.
- MainActivity.cs: 오버레이 권한 안내/요청(CanDrawOverlays →
  ACTION_MANAGE_OVERLAY_PERMISSION) + 켜기/끄기 토글. 앱 내 가사 표시 유지.
- AndroidManifest.xml: SYSTEM_ALERT_WINDOW / FOREGROUND_SERVICE(+_SPECIAL_USE)
  / POST_NOTIFICATIONS 권한.

엔진 재사용(골든룰): 오버레이는 MusebaseApp이 1회 조립한 LyricsCoordinator의
CurrentLineChanged/LineProgressChanged와 소스의 IsPlayingChanged를 구독만 한다
(엔진 재조립 없음). 코어/엔진/contracts 무수정.

빌드: APK 산출 성공(경고 0/오류 0). sln Release 빌드 + Core 테스트 90개 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n API key)

Same root cause as the Windows fix — the Android head hardcoded libretranslate,
whose public instance silently fails without a key, so no translation appeared.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ics source

- Engine: LyricsCoordinator.CurrentTranslationStatus + TranslationStatusChanged.
  Computed after each translate run from run stats + routed translator failures
  (Quota/RateLimit -> quota). Factory now builds the coordinator first, then wires
  BuildTranslation with a callback that feeds failures into the coordinator (for
  status) and on to the app (logging/hint).
- Windows: appends " · 번역: 정상 번역 / 캐시 이용 / 한도 초과" to the lyrics source
  status in tray tooltip + mini window; re-renders on TranslationStatusChanged. i18n.
- Android: same suffix on the in-app lyrics status line.

Answers "is missing translation expected?" — no; it was the LibreTranslate breakage
(now MyMemory) plus no visibility. This surfaces quota vs live vs cache on both heads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation live

Extracts the translation wiring (failure routing to coordinator + app, target
language, refresh) so platform heads can rebuild the engine on settings change.
Used by Create; now reusable by Android settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MyMemory 하드코딩을 제거하고 사용자가 번역 엔진/키/대상 언어를 고르게 한다.

- Services/AndroidSettings.cs(신규): ISharedPreferences(앱 private) 래퍼.
  TranslationEngine/DeeplApiKey/TargetLanguage. 앱 private 저장(디스크 암호화는
  아님 — Windows DPAPI와 다름)임을 주석/README에 명시. EffectiveTranslationEngine
  헬퍼(Windows와 동일 규칙: 명시값 우선, 없으면 키 유무로 결정).
- SettingsActivity.cs(신규): 코드 UI, Exported=false. 엔진 스피너(MyMemory/DeepL/끄기)
  + DeepL 키(DeepL 선택 시만 표시, 눈 토글) + 대상 언어(비면 로케일 기본) + 저장.
  저장 시 AndroidSettings 반영 → MusebaseApp.ApplyTranslationSettings() → 토스트/종료.
- MusebaseApp.cs: EngineConfig를 설정값으로 구성(BuildConfig). translationCache/dbPath를
  필드로 보관. 신규 ApplyTranslationSettings()가 LyricsEngineFactory.ApplyTranslation으로
  재시작 없이 번역 재구성(새 엔진은 다음 곡/재검색부터 — Windows와 동일).
- MainActivity.cs: "번역 설정" 버튼 → StartActivity(SettingsActivity).
- README: 번역 설정 사용법/저장 한계 문서화.

코어/엔진/contracts 미변경(beta에 이미 있는 ApplyTranslation API만 사용).
APK 빌드 성공(경고 0/오류 0), sln Release + Core 테스트 90개 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@countnine
countnine merged commit 5cc4506 into beta Jul 18, 2026
1 check passed
@countnine
countnine deleted the feat/android/overlay branch July 18, 2026 02:40
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