이 글은 Today I Learn 시리즈의 106번째 기록입니다. (총 125개)

소리를 내는 앱은 환경이 바뀌면 물러날 줄 알아야 한다

이어폰을 꽂고 메트로놈을 켜 두었다가 이어폰을 뽑으면, 소리가 그대로 스피커로 넘어가 갑자기 크게 울린다. 조용한 곳이면 민망한 사고다. 전화가 오거나 다른 앱이 음악을 켤 때도 메트로놈은 그 위에 겹쳐서 계속 울렸다.

두 가지를 붙여서 해결했다.

  • 이어폰이 빠지거나 블루투스가 끊겨서 소리가 스피커로 넘어가기 직전에, 안드로이드가 앱들에게 미리 알려 주는 신호가 있다. 그 신호를 받으면 멈춘다.
  • 오디오 포커스라는 규칙이 있다. 지금 소리의 주인이 누구인지 시스템이 조율해 주는 장치다. 재생을 시작할 때 주인 자리를 요청하면 다른 앱이 물러나고, 반대로 다른 앱이 그 자리를 가져가면 우리에게 알림이 온다.

정리하면 소리를 내는 앱은 두 가지를 다 신경 써야 한다. 소리가 어디로 나가는지가 바뀌는 것과, 지금 소리를 낼 자격이 누구에게 있는지가 바뀌는 것이다.


중단에는 종류가 있다

포커스를 잃었다는 알림은 한 가지가 아니었다. 여기서 사용자 경험이 갈린다.

  • 다른 음악 앱이 켜져서 자리를 완전히 넘긴 경우. 사용자가 음악을 들으려고 옮겨 간 것이니 메트로놈은 그냥 멈추면 된다.
  • 전화가 와서 잠깐 뺏긴 경우. 통화가 끝나면 하던 연습으로 돌아가고 싶을 테니 다시 울려 주는 게 맞다.
  • 알림음처럼 아주 짧게 끼어드는 경우. 시스템은 이때 볼륨을 잠깐 낮추라고 권한다.

같은 “중단”이지만 복귀 정책이 셋 다 다르다. 이걸 구분 안 하고 전부 정지로 처리했다면, 전화 한 통 받고 나서 매번 다시 켜야 하는 앱이 됐을 것이다.

같은 멈춤이라도 누가 멈췄느냐에 따라 뒷정리가 다르다

여기서 걸린 게 하나 있었다. 전화가 끝난 뒤 자동으로 다시 울리려면, 잠깐 멈춰 있는 동안에도 우리가 주인 자리를 완전히 놓아 버리면 안 된다. 자리를 반납해 버리면 다시 돌려준다는 알림 자체가 오지 않는다.

그래서 멈추는 길을 둘로 나눴다. 사용자가 직접 누른 일시정지는 자리를 깨끗이 반납하고 멈춘다. 전화 때문에 잠깐 멈추는 것은 자리를 쥔 채로 소리만 멈추고, 돌려받으면 이어서 울린다.

겉으로는 똑같이 “멈춤”인데 뒷정리가 반대다. 이건 오디오만의 얘기가 아니다. 사용자가 스스로 끝낸 것인지, 바깥 사정으로 끊긴 것인지에 따라 정리할 것과 남겨 둘 것이 달라진다. 중단 처리를 짤 때는 중단의 주체를 먼저 묻는 게 맞다.


플랫폼이 권하는 동작도 앱 용도에 비춰 다시 판단한다

짧은 알림음이 끼어들 때 시스템은 볼륨을 잠깐 낮추라고 권한다. 이걸 덕킹이라고 부른다. 음악 재생기라면 그게 자연스럽다.

메트로놈에는 안 맞았다. 소리가 작아지면 그 구간의 박을 놓친다. 메트로놈은 감상용이 아니라 박을 세는 도구라, 들리지 않으면 기능이 없는 것과 같다. 그래서 이 권고는 따르지 않고 원래 크기로 계속 울리게 뒀다.

플랫폼 표준을 그대로 따르는 게 대개 맞지만, 그 표준이 어떤 앱을 상정하고 만들어졌는지는 따져 봐야 한다. 우리 앱이 그 상정과 다르면 표준을 안 따르는 것이 옳은 판단이 된다.


상시 감시 대신 필요한 구간에만 켠다

이어폰 빠짐을 감지하는 장치는 재생 중일 때만 의미가 있다. 메트로놈이 안 울리는 동안에는 감시할 이유가 없다.

그래서 감시를 재생의 수명에 묶었다. 재생을 시작할 때 켜고, 멈추거나 끝날 때 끈다. 끄는 길이 여러 갈래(사용자 정지, 자동 정지, 앱 종료)라 그 갈래마다 빠짐없이 꺼 주는 게 중요했다. 하나라도 빠뜨리면 안 쓰는 감시가 계속 남는다.

기능을 켜고 끄는 스위치를 만들 때는, 켜는 자리 하나보다 끄는 자리 전부를 찾는 쪽이 어렵다. 켜는 건 한 곳이지만 끝나는 길은 대개 여러 개다.


요약

  • 소리를 내는 앱은 소리가 어디로 나가는지와, 소리를 낼 자격이 누구에게 있는지를 둘 다 지켜봐야 한다.
  • 중단에는 종류가 있다. 완전히 넘긴 것과 잠깐 뺏긴 것을 구분해야 복귀 정책이 제대로 갈린다.
  • 같은 멈춤이라도 사용자가 끝낸 것인지 바깥 사정으로 끊긴 것인지에 따라 뒷정리가 달라진다. 자동 복귀는 자리를 지키고 있어야 가능하다.
  • 플랫폼이 권하는 동작도 그것이 상정한 앱과 우리 앱이 같은지 따져 본다. 도구형 앱은 들리는 것 자체가 기능이라 볼륨을 낮추라는 권고가 안 맞았다.
  • 스위치를 만들 때 어려운 쪽은 켜는 자리가 아니라 끄는 자리다. 끝나는 길은 대개 여러 개다.

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 insertOnConflictUpdate vs DoUpdate(target) 27 세션 필터 28 아코디언(Accordiaon) UI를 펼친상태로 만들기 29 input의 step 30 Word Cloud 31 Google Sheets를 데이터 버스로(with AppSheet) 32 Django 모델 텍스트 필드 자동 수집 패턴 33 localStorage로 섹션 토글 상태 유지 34 순차 ID 생성(`select_for_update()` + `max()` 조합) 35 역참조 검색과 distinct() 36 xlsx 다운로드와 로딩 오버레이 충돌 37 Android 파일 공유 MIME 타입 38 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 39 UTF-8 BOM과 PowerShell 파일 쓰기 40 소리꽃 KeyBloom TIL 1 41 소리꽃 KeyBloom TIL 2 42 소리꽃 KeyBloom TIL 3 43 메트로놈 Simple Metronome TIL 1 44 소리꽃 KeyBloom TIL 4 45 메트로놈 Simple Metronome TIL 2 46 소리꽃 KeyBloom TIL 5 47 메트로놈 Simple Metronome TIL 3 48 정적 블로그 SEO 정비와 Pagefind 검색 도입 49 메트로놈 Simple Metronome TIL 4 50 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 51 메트로놈 Simple Metronome TIL 5 52 모래게임 Sandrop TIL 1 53 메트로놈 Simple Metronome TIL 6 54 모래게임 Sandrop TIL 2 55 모래게임 Sandrop TIL 3 56 메트로놈 Simple Metronome TIL 7 57 모래게임 Sandrop TIL 4 58 모래게임 Sandrop TIL 5 59 모래게임 Sandrop TIL 6 60 소리꽃 KeyBloom TIL 6 61 모래게임 Sandrop TIL 7 62 모래게임 Sandrop TIL 8 63 소리꽃 KeyBloom TIL 7 64 모래게임 Sandrop TIL 9 65 소리꽃 KeyBloom TIL 8 66 모래게임 Sandrop TIL 10 67 소리꽃 KeyBloom TIL 9 68 소리꽃 KeyBloom TIL 10 69 소리꽃 KeyBloom TIL 11 70 소리꽃 KeyBloom TIL 12 71 소리꽃 KeyBloom TIL 13 72 온실 GreenHouse TIL 1 73 소리꽃 KeyBloom TIL 14 74 온실 GreenHouse TIL 2 75 소리꽃 KeyBloom TIL 15 76 소리꽃 KeyBloom TIL 16 77 소리꽃 KeyBloom TIL 17 78 소리꽃 KeyBloom TIL 18 79 소리꽃 KeyBloom TIL 19 80 소리꽃 KeyBloom TIL 20 81 로그스톤 상표 셀프 출원 TIL 1 82 소리꽃 KeyBloom TIL 21 83 소리꽃 KeyBloom TIL 22 84 소리꽃 KeyBloom TIL 23 85 소리꽃 KeyBloom TIL 24 86 MiniMacro TIL 1 87 Astro가 무엇인지, 왜 옮기는지 88 MiniMacro TIL 2 89 메트로놈 Simple Metronome TIL 8 90 메트로놈 Simple Metronome TIL 9 91 블로그 Astro 이관 TIL 92 오픈데이 Openday TIL 1 93 오픈데이 Openday TIL 2 94 Unreal Engine MCP TIL 1 95 콘티온 Conti On TIL 11 96 오픈데이 Openday TIL 3 97 오픈데이 Openday TIL 4 98 오픈데이 Openday TIL 5 99 ScorePlayer TIL 1 100 BuildingHub TIL 1 101 ScorePlayer TIL 2 102 BuildingHub TIL 2 103 ScorePlayer TIL 3 104 Unreal Engine MCP TIL 2 105 궁 미로 PalaceMaze TIL 1 106 메트로놈 Simple Metronome TIL 10 읽는 중 107 메트로놈 Simple Metronome TIL 11 108 오픈데이 Openday TIL 6 109 BuildingHub TIL 3 110 Obsidian vault에서 코드만 빼기 — directory junction 111 ScorePlayer TIL 4 112 오픈데이 Openday TIL 7 113 URL은 바뀔 수 있는 것에 묶지 않는다 114 도구는 기능이 아니라 내 일에 맞는지로 고른다 115 돌이키기 힘든 변경은 작게 먼저 확인한다 116 궁 미로 PalaceMaze TIL 3 117 궁 미로 PalaceMaze TIL 2 118 소리꽃 KeyBloom TIL 29 119 오픈데이 Openday TIL 8 120 BuildingHub TIL 4 121 ScorePlayer TIL 5 122 BuildingHub TIL 5 123 MiniMacro TIL 3 124 소리꽃 KeyBloom TIL 30 125 궁 미로 PalaceMaze TIL 4