본문 바로가기

[ANDROID(안드로이드) 앱 개발 기초] MEDIAPLAYER 음악 재생하기

반응형

* 미디어 재생하기

: 안드로이드 멀티미디어 프레임워크는 다양한 미디어 형태를 재생할 수 있도록 지원해준다. 따라서, 오디오, 비디오나 이미지를 앱에서 활용하는 것은 아주 쉽게 구현이 가능하다. 앱의 resource에 바로 저장을 한 순수한 resource를 재생할 수도 있고, 파일 시스템에 있는 파일들을 재생할수도 있으며, 네트워크 연결을 통해서 데이터 스트리밍으로도 활용할 수 있다. 이 모든 것은 MediaPlayer API를 통해서 구현이 가능하다.


: 이 글에서는 멀티미디어를 재생하는 앱을 개발하는 방법과 사용자와 시스템간 어떻게 상호작용을 해야 제대로된 퍼포먼스와 UX가 나오게 될지 다루게 된다. 


* 참고: 오디오 데이터는 기본 출력 장비를 통해서만 재생이 가능하다. 현재에는 장비 스피커나 블루투스 헤드셋 등의 장비이고, 통화 중에는 오디오 재생이 불가능하다.


* 기본 사항

: 아래의 클래스들은 음악과 비디오를 재생하는데 사용되어지는 프레임워크이다.


- MediaPlayer: 이 클래스는 오디오와 비디오를 재생하는데 있어서 가장 기본적인 클래스이다.

- AudioManager: 이 클래스는 오디오 소스와 출력 장비를 관리하는 클래스이다.


* Manifest 정의

: MediaPlayer를 사용하는 앱을 개발하기 전에 Manifest 파일을 통해서 아래의 권한들이 제대로 정의 되었는지 확인해보자.


- Internet Permission: MediaPlayer를 통해 네트워크 상에 있는 미디어를 스트리밍으로 연동하는 경우 네트워크 연동이 필요하다.

<uses-permission android:name="android.permission.INTERNET" />

- Wake Lock Permission: 만약 오디오 재생 앱이 화면이 자동으로 어두워지거나 잠금모드로 가는 것을 방지 해야 하거나, MediaPlayer.setScreenOnWhilePlaying()이나 MediaPlayer.setWakeMode()를 사용해야 한다면 아래의 권한을 요청해야 한다.

<uses-permission android:name="android.permission.WAKE_LOCK" />


* MediaPlayer 사용하기

: MediaPlayer 클래스는 안드로이드 미디어 프레임워크에서 가장 중요한 컴포넌트 중 하나이다. 이 객체는 오디오와 비디오를 가져오고 디코딩하고 재생하는 것을 최소한의 설정으로 가능하게 해준다. 몇 가지 서로 다른 미디어 제공 방식은 아래와 같다.


- 로컬 resource

- ContentResolver등을 활용할 내부 URI

- 외부 URL(스트리밍)


: 안드로이드에서 지원 가능한 포맷은 아래의 사이트를 참고하면 된다.


http://developer.android.com/guide/appendix/media-formats.html


: 아래는 미디어가 로컬의 resource에 순수하게 저장된 경우 재생하는 방법이다.

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1); mediaPlayer.start(); // prepare(); 나 create() 를 호출할 필요 없음

: 이 경우에는 "raw" 리소스는시스템에 저장되어있어서 별다른 처리 없이 바로 접근이 가능하다. 어쨌든, 이러한 파일도 순수한 오디오 파일이 아니라, 지원 가능한 한가지의 미디어 타입으로 인코딩이 되어있어야 할 것이다.


: 그리고 아래는 URI를 통해서 로컬 시스템에서 오디오를 재생하고자 할 때 재생하는 방법이다.

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

: HTTP스트리밍을 통한 원격의 URL에서 재생을 하는 경우는 아래와 같이 구현하면 된다.

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

* 참고: URL을 통해서 스트리밍을 하는 경우 서버에 있는 파일이 progressive download 가 가능해야 한다.

* 주의: setDataSource를 통해서 파일이 존재하지 않는다면, IllegalArgumentException이나 IOException을 받게 될 것이다.



* 비동기 사전준비

: MediaPlayer는 기본적으로 아주 직관적일 것이다. 하지만, 다른 안드로이드 앱에 통합할 때에는 몇 가지 주의할 것이 있다. 예를 들면, prepare() 함수를 호출하는 것은 시간이 다소 오래 걸릴수도 있다. 왜냐하면, 미디어 데이터를 가져오고 디코딩을 하는 과정이 포함될 수도 있기 때문이다. 따라서, 이 경우네는 절대 UI 쓰레드 상에서는 호출하지 않는 것이 좋다. 만약 그렇게 하게 된다면 UI는 일시정지에 걸리게 되고 ANR(Application Not Responsing)등과 같은 매우 좋지 않은 UX를 제공하게 될 것이다. 따라서 미디어가 빠르게 불러오게 되더라도, UI에서는 이것이 걸리는 시간이 1/10초이더라도 사용자는 앱이 느리다는 느낌을 받게 될 것이다.


: 이렇게 UI 쓰레드에서 일시 정지되는 현상을 없애기 위해서는, 다른 쓰레드에서 prepare를 한 뒤, 메인 쓰레드에 알려주는 것이 필요하다. 어쨋든, 쓰레드 로직을 직접 작성할수도 있지만, MediaPlayer에서는 prepareAsync() 함수를 통해서 이것을 쉽게 제공해주고 있다. 이 함수는 미디어를 백그라운드에서 준비시키기 시작해서 바로 리턴한다. 이후 미디어가 준비되면 setOnPreparedListener() 함수를 통해서 설정한 MediaPlayer.OnPreparedListener의 onPrepared() 함수가 호출된다.



* 상태 관리하기

: 다른 관점에서 살펴보면 MediaPlayer의 상태기반 동작들을 항상 주의해야 한다. MediaPlater는 항상 내부적인 상태를 가지고 있고, 특정 동작들은 player가 특정 상태에 있을 때에만 동작이 가능하다. 만약 동작이 불가능한 상태에서 특정 동작을 요청하게 되면 시스템은 exception이나 원하지 않은 동작을 하게 될 가능성이 있다.


: MediaPlayer가 가지는 상태의 다이어그램은 아래와 같다.



: 위의 다이어그램에서 보듯이 MediaPlayer상태 변화를 알 수 있다. 먼저 최초에 MediaPlayer를 생성하면 Idle 상태로 가고, setDataSource()를 호출하게 되면 Initialized 상태가 된다. 이후에 prepare() 또는 prepareAsync() 함수를 호출할 수 있고, MediaPlayer가 준비 되면 Prepared 상태가 된다. 이후에는 start() 함수를 호출하여 미디어가 재생되도록 할 수 있다. 이 때에는 Started와 Paused, PlaybackCompleted 사이를 왔다갔다할 수 있고, start(), pause(), seekTo() 함수를 사용할 수 있다. 이후 stop()을 호출하게 되면, prepared 상태가 되지 않으면 start()를 호출할 수 없음을 명심하자.


: 틀린 상태에서 틀린 함수를 호출하게 되면 버그가 발생할 수 있기 때문에, 항상 위의 상태 다이어그램을 염두에 두고 MediaPlayer 클래스를 사용하면 좋다.



* MediaPlayer 해제하기

: MediaPlayer는 시스템 자원을 소모한다. 따라서, 필요이상으로 MediaPlayer를 가지고 있는 것은 좋지 않다. 미디어 재생이 끝났다면 항상 release() 함수를 호출하여 시스템 자원을 해제하는 것이 바람직하다. 예를 들면, MediaPlayer를 사용하고 있을 때 onStop()을 받게 된다면 MediaPlayer를 해제하는 것이 Activity가 사용자와 상호작용하고 있지 않을 때 좋을 것이다. 그리고 이후 Activity가 다시 resume되거나 restart 될 때 다시 생성하여 준비시키는 것이 효율적일 것이다.


: 아래는 MediaPlayer를 해제하는 방법이다.

mediaPlayer.release();
mediaPlayer = null;

: 예를 하나 들자면, 만약 MediaPlayer를 해제하는 것을 잊어버렸다면, activity 가 멈출때마다 새로운 객체를 만들어낼 것이다. 그리고 사용자가 화면의 방향을 바꾸게 된다면, 시스템은 기본적으로 activity를 재시작하는 것으로 다루게 되기 때문에 사용자가 화면을 회전 시킬때마다 시스템 자원을 빠르게 소모하게 될 것이다.


: 이외에도 다른 음악 앱에서 activity가 종료된 이후에도 백그라운드로 미디어를 재생하는 방법에 대해서 궁금해 할 수도 있다. 이 때에는 MediaPlayer는 서비스를 통해서 제어되는 것이다.



* Service를 이용해서 MediaPlayer 사용하기

: 앱이 화면에 나와있지 않을 때에도 미디어를 백그라운드로 재생되기를 원한다면, 즉 사용자가 다른 앱을 사용하고 있는데도 사용하기를 원한다면, Service를 생성하여 MediaPlayer 객체로 미디어를 재생하면 된다. 이 때에는 서비스로 백그라운드에서 돌아갈 때에 다른 앱들이나 시스템과 상호작용으로 영향을 미칠 수 있기 때문에 조심히 개발해야 한다. 이러한 상호작용을 제대로 이해하지 못한다면 형편없는 UX를 사용자들에게 제공하게 될 것이다. 이번에는 그러한 백그라운드에서 재생을 할 때 유의해야 할 점들을 살펴본다.



* 비동기 실행

: 기본적으로 Service에서는 Activity와 동일하게 싱글쓰레드가 기본으로 되어있다. 만약 Activity 안에서 같은 앱의 Service를 실행한다면 동일한 메인 쓰레드를 기본적으로 사용하게 된다. 따라서, Service 들은 들어오는 Intent들을 빠르게 처리하고 응답을 해주고 길게 동기적인 처리를 해서는 안된다. 만약 계산량이 많은 작업을 수행하게 된다면 그것들은 비동기적으로 다른 쓰레드에서 실행을 할 수 있게 프레임워크를 사용하던지 자체적으로 개발해야 한다.


: 예를 들면, MediaPlayer를 메인 쓰레드에서 사용하게 될 때에는 prepare()를 호출하지 말고 prepareAsync()를 호출하는 것이 좋고, MediaPlayer.OnPreparedListener를 구현하여 준비가 다 되었을 때에 콜백을 실행하는 방식으로 재생을 시작하는 것이 좋다.

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mMediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mMediaPlayer = ... // initialize it here
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** MediaPlayer가 준비되면 호출된다 */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}



* 비동기 오류 다루기

: 동기적인 동작들을 수행하다가 에러가 난다면, Exception이 일어나거나 에러코드를 받게 된다. 하지만, 비동기적으로 미디어를 사용하게 되는 경우, 앱에서 에러가 나는 경우 정상적으로 알리고 있는지 확인해야 한다. 이 때에는 MediaPlayer에 MediaPlayer.OnErrorListener를 설정해야 한다.

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mMediaPlayer;

    public void initMediaPlayer() {
        // ...MediaPlayer를 초기화하는 함수...

        mMediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... 에러를 처리하기 ...
        // MediaPlayer를 error 상태로 돌입하였고, reset이 필요하다.
    }
}


: 중요한 것은 에러가 일어났을 때 MediaPlayer를 Error 상태로 들어가게 되므로, 다시 해당 객체를 사용하기 전에 reset을 해야 한다는 것이다.



* Wake lock 사용하기

: 미디어를 백그라운드에서 재생하는 앱을 디자인할 때 Service를 실행중일 때 장비가 휴면모드로 들어갈 수 있다. 안드로이드 시스템에서는 장비가 휴면모드로 들어가게 되면 배터리 소모를 최소화하기 위하여 불필요한 CPU나 와이파이를 포함한 모든 기능들은 정지시키려고 한다. 이 때에 Service가 음악이나 스트리밍 음악을 재생하고 있다면, 시스템에서 음악이 재생되는 것을 정지 시키지 못하게 방지해야 할 것이다.


: Service 가 지속적으로 실행이 되고 있는 것을 확실하게 보장하기 위해서 "wake locks"를 사용해야 한다. Wake lock는 앱에서 특정 기능들을 계속 활성화 시켜놓아야 한다는 것을 시스템에 알려주는 역할을 하는 것이다.


* 참고: 항상 wake locks는 정말로 필요할 때에망 자제해서 사용해야 사용자들의 장비의 배터리 수명이 줄어드는 것을 방지할 수 있을 것이다.


: CPU가 MediaPlayer를 위해서 계속 실행되도록 하기 위해서는 MediaPlayer객체를 초기화할 때 setWakeMode() 함수를 실행해야 한다. 일단 MediaPlayer가 재생 중일 때에는 wake lock를 걸어놓고, 일시 정지하거나 멈추었을 대에는 lock을 해제하게 된다.

mMediaPlayer = new MediaPlayer();
// ... 다른 초기화 작업 수행 ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

: 위의 예에서는 CPU만 정지시키지 않도록 lock를 거는 것이다. 만약 네트워크를 통한 스트리밍 서비스를 제공한다면, 와이파이가 정지되는 것도 방지해야 할 것이다. 이 때에는 WifiLock을 수동적으로 lock을 걸었다가 해제해야 한다. 따라서, 원격의 URL을 사용하여 미디어를 재생하는 경우 MediaPlayer를 준비할 때에 WifiLock를 설정하면 될 것이다.

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

: 그리고 이후에 재생을 일시정지하거나 멈추었을 때에는 수동적으로 wifiLock을 해제해줘야 한다.

wifiLock.release();



* Foreground 서비스로 실행하기

: 서비스들은 이메일을 가져오거나, 데이터를 동기화하거나, 다운로드를 하거나 여러 가지 상황들에서 일반적으로 백그라운드에서 실행이 된다. 이러한 상황에서 사용자는 서비스의 실행을 주도적으로 조종하지 않고, 때로는 이러한 서비스들이 어떻게 돌아가는지 알지 못할지도 모른다.


: 하지만 이번에는 음악을 재생하는 경우를 생각해보면, 이러한 서비스는 사용자가 UX에 굉장히 민감하게 생각하면서 중간에 음악이 끊기지 않도록 생각할 것이다. 추가적으로, 사용자는 음악이 재생되고 있을 때에는 상호작용을 하면서 서비스를 사용하길 원할 것이다. 이러한 경우에는 "Foreground 서비스"로 실행을 하면 된다. Foreground 서비스는 시스템에서 더 높은 우선순위를 가지고 실행을 하게 되며, 현재 실행 중인 서비스가 사용자에게 중요한 서비스이므로 거의 중지 시키는 일이 없다. Foreground 서비스를 실행할 때에는 상태바에서 알림을 제공을 해야 사용자가 서비스가 현재 실행 중이라는 것을 알 수 있고, 액티비티를 쉽게 열어서 서비스와 상호작용을 쉽게할 수 있도록 할 수 있다.


: 서비스를 foreground 서비스로 제공하기 위해서는, 상태바에 알림을 나타내기 위해 Notification 과 Service에서 startForeground() 를 실행해야 한다. 예를 들면, 아래와 같다.

String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
                new Intent(getApplicationContext(), MainActivity.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
                "Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);


: 서비스가 foreground에서 실행이 되고 있을 때에는, notification의 설정 값을 visible로 설정하면 상단의 notification바에 나오게 된다. 만약 사용자가 notification을 선택하게 된다면, 시스템에서는 프로그램에서 설정한 PendingIntent을 호출하게 되고, 위의 예에서는 MainActivity를 열게 된다. Foreground 서비스는 사용자에게 특정 서비스를 제공하고 있을 때에만 실행이 되어야 하고, 서비스 제공을 하지 않게 된다면 반드시 stopForeground() 함수를 호출해야 한다.

stopForeground(true);



* 오디오 포커스 핸들링

: 한번에 하나의 액티비티티만 실행될 수 있지만, 안드로이드는 멀티태스킹 환경을 제공해 준다. 이러한 환경은 오디오를 사용할 때 서로 다른 미디어 서비스들이 오디오를 사용할 때 서로 경쟁을 하게 되어 이를 해결해야 할 문제가 될 것이다. 안드로이드 2.2 버전 이전에는 이를 해결할 수 있는 빌트인 메카니즘은 없어서 음악을 듣는 중에 다른 앱에서 오디오를 사용해서 음악이 끊기는 것을 경험하게 되면 안 좋은 UX를 제공하기도 하였다. 이에 안드로이드 2.2 버전부터는 플랫폼에서 앱끼리 서로 오디오 장치를 사용하는 메카니즘을 제공해주고 있으며, 이는 오디오 포커스라고 한다.


: 앱에서 음악이나 알림을 하기 위해 오디오 출력이 필요로 하게 될 때에는 항상 오디오 포커스를 요청해야 한다. 한번 오디오 포커스를 획득하게 되었다면, 자유롭게 음향 출력을 할 수 있게 되며, 이후 항상 포커스가 변화되는 것을 신경써야 한다. 만약 오디오 포커스를 잃게 되었을 때에는 오디오를 죽이거나 음향 레벨을 줄여야 한다.


: 오디오 포커스는 앱 간에 상호작용이 당연한 것이며, 앱에서 오디오 포커스의 가이드라인을 따를 것을 기본적으로 권하고 있지만 시스템에서 강요를 하고 있지는 않다. 만약 앱에서 오디오 포커스를 잃더라도 크게 음악을 재생하기 원한다면 시스템에서는 이를 방지하지 않는다. 하지만, 그렇게 구현하는 경우에는 사용자는 안 좋은 UX를 제공 받아 예상하는 것과 다른 앱의 행동들로 인하여 앱을 언인스톨하게 될지도 모른다.


: 오디오 포커스를 요청하기 위해서는 AudioManager의 requestAudioFocus()를 호출하면 된다.

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // 오디오 포커스를 획득할 수 없다.
}


: requestAudioFocus()의 첫 번째 파라미터는 AudioManager.OnAudioFocusChangeListener로 오디오 포커스가 변경되는 경우 onAudioFocusChange90 함수를 호출하게 되는 이벤트 핸들러이다. 따라서, 위의 예처럼 this를 넘겨주기 위해서는 service나 액티비티에서 해당 인터페이스를 구현해야 한다. 예를 들면 아래와 같다.

class MyService extends Service
                implements AudioManager.OnAudioFocusChangeListener {
    // ....
    public void onAudioFocusChange(int focusChange) {
        // 포커스가 변경된 것을 처리한다.
    }
}


: onAudioFocusChange() 함수의 파라미터 focusChange는 오디오가 어떻게 변경이 되었는지 알려주는 파라미터로 AudioManager에 상수로 선언되어 있다.


  • AUDIOFOCUS_GAIN: 오디오 포커스를 획득했다
  • AUDIOFOCUS_LOSS: 오디오 포커스를 장기적으로 잃게 되었다. 포커스가 다소 장기간 동안 획득하지 못하게 될 것이 예상되므로, 모든 오디오의 재생을 전부 중단해야 한다. 이 때에는 사용하고 있는 리소스를 해제하기에 좋은 때가 될 것이다.
  • AUDIOFOCUS_LOSS_TRANSIENT: 잠시 오디오 포커스를 잃게 되었지만, 짧은 시간 내에 다시 포커스를 획득하게 될 것이다. 이 때에도 모든 오디오의 재생을 중단해야 하지만, 포커스를 다시 금방 획득하게 될 것이기 떄문에 리소스는 보관하고 있어도 된다.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 잠시 오디오 포커스를 잃게 되었지만, 음악 재생을 중단하지 않고 볼륨을 낮춘 다음 재생을 유지해도 된다.

: 아래는 이를 구현한 예이다.

public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // 다시 재생
            if (mMediaPlayer == null) initMediaPlayer();
            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
            mMediaPlayer.setVolume(1.0f, 1.0f);
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // 포커스를 아주 장기적으로 잃었다. 음악 재생을 멈추고, 미디어 플레이어 자원도 해제
            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // 잠시 포커스를 잃었지만, 음악을 중단해야 한다. 하지만 포커스가 금방 돌아올 것으로 예상하기 떄문에 미디어 플레이어는 해제 하지 않는다
            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // 잠시 포커스를 잃었지만, 볼륨을 줄인 상태에서 음악 재생을 유지해도 된다.
            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
            break;
    }
}


: 주의할 것은 오디오 포커스 API는 안드로이드 2.2, API level 8 이후에만 있다. 이전 버전에서도 오디오 포커스를 적용하기 위해서는 하위 호환성을 제공해주어야 한다. 이 때에는 오디오 포커스와 관련된 기능들을 아래와 같이 별도의 클래스에 구현해두어야 한다.

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
    AudioManager mAudioManager;

    // other fields here, you'll probably hold a reference to an interface
    // that you can use to communicate the focus changes to your Service

    public AudioFocusHelper(Context ctx, /* other arguments here */) {
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        // ...
    }

    public boolean requestFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN);
    }

    public boolean abandonFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.abandonAudioFocus(this);
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        // let your service know about the focus change
    }
}


: 그리고 위의 AudioFocusHelper 클래스는 시스템에서 API 레벨을 체크한 뒤, 8 이상인 경우에만 아래와 같이 사용하면 된다. 

if (android.os.Build.VERSION.SDK_INT >= 8) {
    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
    mAudioFocusHelper = null;
}



* 미디어 플레이어 해제

: 이전에 언급했듯이, 미디어 플레이어는 시스템 자원을 많이 잡아먹게 되므로, 꼭 필요한 만큼만 사용하다가 사용을 완료하게 된다면 release()를 호출해야 한다. 이렇게 미디어와 관련된 자원을 사용할 때에는 시스템의 가비지 컬렉터에 의존하기 보다 가비지 컬렉터를 통하여 메모리가 해제되기 전에 해제를 시켜주는 것이 좋다. 또한 onDestroy() 함수에는 반드시 미디어 플레이어를 해제해 주어야 한다.

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}


: 이외에도 항상 미디어가 꺼질 때에는 미디어 플레이어를 해제할 수 있는 기회에는 해제를 해줘야 한다. 예를 들면, 오디오 포커스를 잃어서 장시간동안 미디어를 재생할 일이 없다고 한다면, 미디어 플레이어를 반드시 해제한 다음 나중에 다시 재생을 시작하게 될 때에 다시 생성해야 한다. 반면, 만약 짧은 시간동안만 재생이 안된다고 하면, 이를 해제하고 다시 생성하는 부담을 줄이기 위해 해제를 해주지 않아도 될 것이다.



* AUDIO_BECOMING_NOISY 인텐트 핸들링

: 오디오를 사용하는 잘 만들어진 앱들은 오디오 출력이 외부 출력으로 시끄러워지게 되면 자동으로 음악 재생을 멈추도록 개발하고 있다. 예를 들면, 사용자가 헤드폰으로 음악을 크게 듣다가 실수로 헤드폰이 빠지게 되면 외부 출력장치로 음악이 시끄럽게 재생될 것이다. 이러한 상황을 방지하기 위해서 시스템에서는 자동으로 해주는 것이 없고, 이 상황을 다루는 것을 구현하지 않으면 사용자가 원하지 않았을지도 모를 상황에서 외부 출력장치로 그대로 음악이 재생될 것이다.


: 이러한 상황은 ACTION_AUDIO_BECOMING_NOISY 인텐트를 등록하여 manifest 파일에서 어느 인텐트가 수신을 하여 처리하게 될 것인지 명시하면 된다.

<receiver android:name=".MusicIntentReceiver"> <intent-filter> <action android:name="android.media.AUDIO_BECOMING_NOISY" /> </intent-filter> </receiver>


: Manifest 파일에 위와 같이 등록하게 되면, MusicIntentReceiver 클래스가 브로드캐스트 receiver로서 인텐트를 수신하게 된다. 이 클래스는 아래아 같이 구현이 가능하다.

public class MusicIntentReceiver extends android.content.BroadcastReceiver {
   @Override
   public void onReceive(Context ctx, Intent intent) {
      if (intent.getAction().equals(
                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
          // 서비스에 intent 등으로 음악 재생을 중지하라고 전달
      }
   }
}



* 미디어를 Content Resolver로부터 수신하기

: 미디어 플레이어를 사용하는 앱에서 유용하게 구현이 가능한 방법은 바로 사용자가 장치에 있는 음악을 가져와서 재생하는 방법이다. 이 때에는 ContentResolver를 통해서 외부의 미디어를 요청하는 방법도 있다.

ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
    // 쿼리 실패, 에러 처리
} else if (!cursor.moveToFirst()) {
    // 미디어가 장치에 없음
} else {
    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
    do {
       long thisId = cursor.getLong(idColumn);
       String thisTitle = cursor.getString(titleColumn);
       // 목록을 처리
    } while (cursor.moveToNext());
}


: 미디어 플레이어에서는 아래와 같이 구현 하면 된다.

long id = /* 어디선가 아이디를 가져와서 설정 */;
Uri contentUri = ContentUris.withAppendedId(
        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

// 준비하고 앱 시작


 

반응형

댓글


Copyright ⓒ SmartWeb All rights reserved.