이번에는 안드로이드 앱으로 음악 재생하기!
첫번째로 url 을 통해 웹서버에 있는 음악을 재생하는 방법,
두번째로 프로젝트 내 리소스 폴더에 있는 음악을 재생하는 방법을 다뤄보도록 할 것이다.
첫번째 방법 - 웹서버에 있는 음악 재생하기
0. 인터넷이 필요하기 때문에 manifest 에 다음 권한을 추가한다.
<uses-permission android:name="android.permission.INTERNET"/>
1. xml 레이아웃 대충 만들기
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="27dp"
android:text="재생" />
<Button
android:id="@+id/pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="110dp"
android:text="일시정지" />
<Button
android:id="@+id/restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="190dp"
android:text="재시작" />
<Button
android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/restart"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="정지" />
</RelativeLayout>
2. 메인 액티비티 작성
따로 클래스 만들 필요도 없고 그냥 MediaPlayer 객체만 갖다 쓰면 된다. (안드로이드에 이미 다 있다는거에 감탄했다.)
amr 파일은 녹음파일의 확장자로, http://sites.google.com/site/ubiaccessmobile/sample_audio.amr 을 사용했다.
public class MainActivity extends AppCompatActivity {
public static String url = "http://sites.google.com/site/ubiaccessmobile/sample_audio.amr";
MediaPlayer player;
int position = 0; // 다시 시작 기능을 위한 현재 재생 위치 확인 변수
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.play).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
playAudio();
}
});
findViewById(R.id.pause).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
pauseAudio();
}
});
findViewById(R.id.restart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resumeAudio();
}
});
findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopAudio();
}
});
}
private void playAudio() {
try {
closePlayer();
player = new MediaPlayer();
player.setDataSource(url);
player.prepare();
player.start();
Toast.makeText(this, "재생 시작됨.", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
// 현재 일시정지가 되었는지 중지가 되었는지 헷갈릴 수 있기 때문에 스위치 변수를 선언해 구분할 필요가 있다. (구현은 안했다.)
private void pauseAudio() {
if (player != null) {
position = player.getCurrentPosition();
player.pause();
Toast.makeText(this, "일시정지됨.", Toast.LENGTH_SHORT).show();
}
}
private void resumeAudio() {
if (player != null && !player.isPlaying()) {
player.seekTo(position);
player.start();
Toast.makeText(this, "재시작됨.", Toast.LENGTH_SHORT).show();
}
}
private void stopAudio() {
if(player != null && player.isPlaying()){
player.stop();
Toast.makeText(this, "중지됨.", Toast.LENGTH_SHORT).show();
}
}
/* 녹음 시 마이크 리소스 제한. 누군가가 lock 걸어놓으면 다른 앱에서 사용할 수 없음.
* 따라서 꼭 리소스를 해제해주어야함. */
public void closePlayer() {
if (player != null) {
player.release();
player = null;
}
}
player.prepare 메서드는 오디오 파일의 일부 정보를 확인하는 작업으로, start 메서드를 호출하기 전에 반드시 호출해야한다.
prepare 메서드를 호출하지 않으면, 로그캣에 다음과 같은 오류가 뜨며 음악이 재생되지 않는다. (앱이 종료되진 않는다)
E/MediaPlayerNative: start called in state 2, mPlayer(0x7bdd2cd6c0)
E/MediaPlayerNative: error (-38, 0)
E/MediaPlayer: Error (-38,0)
두번째 방법 - 프로젝트 리소스 폴더 내 음악 재생하기
두번째 방법은 별 차이 없다. 위 예제와 다른것은 인터넷 권한이 필요 없다는 것과, playAudio 메서드만 조금 수정해주면 끝이다.
private void playAudio() {
closePlayer();
player = MediaPlayer.create(MainActivity.this, R.raw.calc);
player.start();
Toast.makeText(this, "재생 시작됨.", Toast.LENGTH_SHORT).show();
}
근데 조금 다른 것은, prepare 메서드를 호출할 필요가 없다는 것이다. 그래서 try - catch 도 필요없다. 오히려 prepare 메서드를 사용하면 다음과 같은 오류가 발생하며 앱이 종료된다.
java.lang.IllegalStateException at android.media.MediaPlayer._prepare(Native Method)
SD 카드에 있는 음악을 재생하는 방법도 간단하다. (직접 실행해보진 않았고, 부코에서 그냥 긁어왔다)
String filepath = "/sdcard/a.mp3";
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(filepath);
- 출처 : 부스트코스 강의
음악을 재생시켜놓고 홈버튼을 누르고, 화면 잠금을 해보는 시도를 해보았는데 음악은 백그라운드에서 여전히 재생되고 있는 것을 알 수 있었다.
그래서 MediaPlayer 객체의 수명주기에 대해 알아보기로 했다. (부코 강의에 링크가 걸려있었다.
https://developer.android.com/reference/android/media/MediaPlayer)
< 공식 사이트 영어 문서 번역기로 돌린 것 >
상태 다이어그램
오디오 / 비디오 파일 및 스트림의 재생 제어는 상태 머신으로 관리됩니다. 다음 다이어그램은 지원되는 재생 제어 작업으로 구동되는 MediaPlayer 객체의 수명주기 및 상태를 보여줍니다. 타원은 MediaPlayer 객체가 상주 할 수있는 상태를 나타냅니다. 호는 객체 상태 전이를 유도하는 재생 제어 작업을 나타냅니다. 호에는 두 가지 유형이 있습니다. 단일 화살표 머리가있는 호는 동기 메서드 호출을 나타내며 이중 화살표 머리는 비동기 메서드 호출을 나타냅니다.
이 상태 다이어그램에서 MediaPlayer 객체의 상태는 다음과 같습니다.
* MediaPlayer 객체가 new를 사용하여 방금 생성되거나 reset ()이 호출 된 후에는 Idle 상태에 있습니다. release ()가 호출 된 후에 End 상태가됩니다. 이 두 상태 사이에는 MediaPlayer 객체의 수명주기가 있습니다.
- reset ()이 호출 된 후 새로 생성 된 MediaPlayer 객체와 MediaPlayer 객체 사이에는 미묘하지만 중요한 차이가 있습니다. 두 경우 모두 Idle 상태에서 getCurrentPosition (), getDuration (), getVideoHeight (), getVideoWidth (), setAudioAttributes (AudioAttributes), setLooping (boolean), setVolume (float, float), pause (), start (), stop (), seekTo (long, int), prepare () 또는 prepareAsync ()와 같은 메소드를 호출하는 것은 프로그래밍 오류입니다. MediaPlayer 객체가 생성 된 직후에 이러한 메서드 중 하나가 호출되면 OnErrorListener.onError () 콜백 메서드는 내부 플레이어 엔진에서 호출하지 않으며 개체 상태는 변경되지 않습니다. 그러나 이 메소드가 reset () 직후에 호출되면 사용자가 제공 한 콜백 메소드 인 OnErrorListener.onError ()가 내부 플레이어 엔진에 의해 호출되고 객체가 Error 상태로 전송됩니다.
- MediaPlayer 객체가 더 이상 사용되지 않으면 즉시 release ()를 호출하여 MediaPlayer 객체와 연결된 내부 플레이어 엔진에서 사용하는 리소스를 즉시 해제 할 수 있도록하는 것이 좋습니다. 리소스에는 하드웨어 가속 구성 요소와 같은 싱글 톤 리소스가 포함될 수 있으며 release () 호출 실패로 인해 MediaPlayer 객체의 후속 인스턴스가 소프트웨어 구현으로 폴백되거나 모두 실패 할 수 있습니다. MediaPlayer 객체가 End 상태에 있으면 더 이상 사용할 수 없으며 다른 상태로 되돌릴 방법이 없습니다.
- 또한 new를 사용하여 생성 된 MediaPlayer 객체는 Idle 상태에 있으며, 오버로드 된 편리한 create 메소드 중 하나를 사용하여 만들어진 객체는 유휴 상태가 아닙니다. create 메소드를 사용한 작성이 성공하면 실제로 Prepared 상태가됩니다.
* 일반적으로, 지원되지 않는 오디오 / 비디오 포맷, 오디오 / 비디오의 인터리빙 불량, 해상도가 너무 높은 것, 스트리밍 타임 아웃 등과 같은 여러 가지 이유로 일부 재생 제어 동작이 실패 할 수 있습니다. 따라서 오류보고 및 복구는 이러한 상황에서 중요한 관심사입니다. 경우에 따라 프로그래밍 오류로 인해 잘못된 상태에서 재생 제어 작업을 호출 할 수도 있습니다. OnErrorListener가 setOnErrorListener (android.media.MediaPlayer.OnErrorListener)를 통해 미리 등록 된 경우 이러한 모든 오류 조건에서 내부 플레이어 엔진은 사용자가 제공 한 OnErrorListener.onError () 메서드를 호출합니다.
- 오류가 발생하면 MediaPlayer 객체는 오류 리스너가 응용 프로그램에 등록되지 않은 경우에도 Error 상태 (위에서 언급 한 내용 제외)로 들어갑니다.
- Error 상태에있는 MediaPlayer 객체를 재사용하고 오류를 복구하려면 reset ()을 호출하여 객체를 Idle 상태로 복원 할 수 있습니다.
- 응용 프로그램에서 내부 플레이어 엔진의 오류 알림을 알아보기 위해 OnErrorListener를 등록하도록하는 것이 좋은 프로그래밍 습관입니다.
- IllegalStateException - prepare (), prepareAsync (), 또는 오버로드 된 setDataSource 메서드 중 하나를 잘못된 상태로 호출하는 등의 프로그래밍 오류를 방지하기 위해 throw됩니다.
* setDataSource (FileDescriptor) 또는 setDataSource (String) 또는 setDataSource (Context, Uri) 또는 setDataSource (FileDescriptor, long, long) 또는 setDataSource (MediaDataSource)를 호출하면 유휴 상태에있는 MediaPlayer 객체가 초기화 상태로 전송됩니다. -> 내가 겪은 오류
- 다른 상태에서 setDataSource ()가 호출되면 IllegalStateException이 발생합니다.
- 오버로드 된 setDataSource 메서드에서 Throw 될 수있는 IllegalArgumentException 및 IOException을 항상주의 깊게 관찰하는 것이 좋습니다.
* MediaPlayer 객체는 재생을 시작하기 전에 먼저 Prepared 상태로 전환해야합니다.
- Prepared 상태에 도달 할 수있는 두 가지 방법 (동기식 대 비동기식)이 있습니다. 메서드 호출이 반환되면 Prepared 상태로 객체를 전송하는 prepare () (동기식) 또는 prepareAsync() (비동기식)은 호출이 돌아 오면 (준비가 거의 끝나면) 객체를 준비 상태로 먼저 전송하지만 내부 플레이어 엔진은 준비 작업이 완료 될 때까지 나머지 준비 작업을 계속합니다. 준비가 완료되거나 prepare () 호출이 반환 될 때 OnPreparedListener가 setOnPreparedListener (android.media.MediaPlayer.OnPreparedListener)를 통해 미리 등록 된 경우 내부 플레이어 엔진은 OnPreparedListener 인터페이스의 onPrepared ()와 같이 사용자가 제공하는 콜백 메서드를 호출합니다.
- 준비 상태는 일시적인 상태이므로 MediaPlayer 객체가 준비 중일 때 부작용이있는 메서드를 호출하는 동작은 정의되지 않습니다.
- prepare () 또는 prepareAsync ()가 다른 상태에서 호출되면 IllegalStateException이 발생합니다.
- Prepared 상태에있는 동안 오디오 / 사운드 볼륨, screenOnWhilePlaying, 루핑과 같은 속성은 해당하는 set 메서드를 호출하여 조정할 수 있습니다.
* 재생을 시작하려면 start ()를 호출해야합니다. start ()가 성공적으로 반환되면 MediaPlayer 객체가 Started 상태가됩니다. isPlaying ()을 호출하여 MediaPlayer 객체가 Started 상태인지 여부를 테스트 할 수 있습니다.
- OnBufferingUpdateListener가 setOnBufferingUpdateListener (OnBufferingUpdateListener)를 통해 미리 등록 된 경우 Started 상태에있는 동안 내부 플레이어 엔진이 OnBufferingUpdateListener.onBufferingUpdate () 콜백 메소드를 사용자에게 호출합니다. 이 콜백을 통해 응용 프로그램은 오디오 / 비디오를 스트리밍하는 동안 버퍼링 상태를 추적 할 수 있습니다.
- start () 호출은 이미 Started 상태에있는 MediaPlayer 객체에는 영향을주지 않습니다.
* stop ()을 호출하면 재생이 중지되고 Started, Paused, Prepared 또는 PlaybackCompleted 상태의 MediaPlayer가 Stopped 상태가됩니다.
- 일단 Stopped 상태가되면 prepare () 또는 prepareAsync ()가 호출되어 MediaPlayer 객체를 Prepared 상태로 다시 설정할 때까지 재생을 시작할 수 없습니다.
- stop () 호출은 이미 Stopped 상태에있는 MediaPlayer 객체에는 영향을 미치지 않습니다.
* 재생 위치는 seekTo (long, int)를 호출하여 조정할 수 있습니다.
- 비동기적인 seekTo (long, int) 호출이 즉시 반환 되더라도 실제 탐색 작업이 완료 될 때까지, 특히 오디오 / 비디오가 스트리밍되는 데에는 시간이 걸릴 수 있습니다. 실제 탐색 작업이 완료되면 OnSeekCompleteListener가 setOnSeekCompleteListener (OnSeekCompleteListener)를 통해 미리 등록 된 경우 내부 플레이어 엔진이 OnSeekComplete.onSeekComplete ()를 제공 한 사용자를 호출합니다.
- seekTo (long, int)는 Prepared, Paused 및 PlaybackCompleted 상태와 같은 다른 상태에서도 호출 될 수 있습니다. 이러한 상태로 seekTo (long, int)가 호출되면, 스트림에 비디오가있어 요구 된 위치가 유효한 경우, 1 개의 비디오 프레임이 표시됩니다.
- 또한 실제 현재 재생 위치는 getCurrentPosition ()을 호출하여 검색 할 수 있습니다. 이는 재생 진행 상황을 추적해야하는 음악 플레이어와 같은 응용 프로그램에 유용합니다.
* 재생이 스트림의 끝에 도달하면 재생이 완료됩니다.
- setLooping (boolean)을 사용하여 루핑 모드를 true로 설정 한 경우 MediaPlayer 객체는 Started 상태로 유지됩니다.
- 루프 모드가 false로 설정된 경우 setOnCompletionListener (OnCompletionListener)를 통해 OnCompletionListener가 미리 등록 된 경우 플레이어 엔진은 사용자가 제공 한 콜백 메서드 인 OnCompletion.onCompletion ()을 호출합니다. 콜백 호출은 객체가 현재 PlaybackCompleted 상태에 있음을 알립니다.
- PlaybackCompleted 상태에있는 동안 start ()를 호출하면 오디오 / 비디오 소스의 시작 부분에서 재생을 다시 시작할 수 있습니다.
'Android > Basic' 카테고리의 다른 글
[Android] 음성 녹음하기 (2) | 2018.08.29 |
---|---|
[Android] 동영상 재생하기 (0) | 2018.08.28 |
[Android] 카메라로 사진 촬영하기 (2) | 2018.08.27 |
[Android] 인터넷 연결상태 체크하기 (0) | 2018.07.31 |
[Android] SQLiteOpenHelper (0) | 2018.07.31 |