국비교육(22-23)

86일차(1)/Android App(51) : mp3 파일 재생 예제 / MediaPlayer(서버)

서리/Seori 2023. 2. 10. 23:58

86일차(1)/Android App(51) : mp3 파일 재생 예제 / MediaPlayer(서버)

 

- Service 음악파일 재생 예제 코드 복습

 

 

1) Activity

2) BroadcastReceiver

3) Service

4) ContentProvider

 

- 1~3은 intent 객체로 활성화시킨다.

- 4 는 ContentResolver로 활성화시킨다.

- 사용하려면 모두 AndroidManifest.xml에 등록되어 있어야 한다.

 

 

- 액티비티/서비스 이름 앞에 . 이라고 써 있는것은 이 패키지를 말한다.

- 이 패키지 안에 있는 MusicService라는 뜻

 

@Override
protected void onStart() {
    super.onStart();
    // MusicService 에 연결할 인텐트 객체
    Intent intent=new Intent(this, MusicService.class);
    //액티비티의 bindService() 메소드를 이용해서 연결한다.
    // 만일 서비스가 시작이 되지 않았으면 서비스 객체를 생성해서
    // 시작할 준비만 된 서비스에 바인딩이 된다.
    bindService(intent, sConn, Context.BIND_AUTO_CREATE);

}

- onStart() 안에 bind를 넣어두면 서비스가 실행중이면 바인딩한다는 뜻이다.

 

- 안드로이드 플랫폼 안에 있는 MusicService. 이것은 현재 실행중일 수도, 아닐 수도 있다.

 

- 어떤 기능,동작이 실행되려면 어떤 프로세스를 할당받아야 한다.

 이 프로세스상에서 뭔가가 실행된다.

- 이 프로세스에서 Service가 실행중이어도 MainActivity는 실행중이 아닐 수도 있다.

 (실행된다면 동일한 프로세스 상에 들어간다)

 

- 이 MainActivity에서 뭔가 MusicService랑 관련된 내용을 하려면 연결이 되어야 하는데,

 참조값을 쉽게 바로 얻어낼 수 없다.

- MusicService의 입장에서 MainActivity도 그렇다

 

- 액티비티가 활성화될 때 서비스를 바인딩한다.(서비스와 연결한다)

- 바인딩을 하기위해서는 intent 객체를 사용하고, 서비스 커넥션 객체를 사용한다.

 

 

- 바인딩 객체 사용하기!

- 비동기 처리로 이루어진다(약간 시간이 걸릴 수 있는 작업)

 

- 객체에 인자(sConn)를 전달하고, 바인딩이 성공하면  2번 메소드가 실행된다.

- 바인딩 연결객체가 있으면 바인딩하고 메소드를 실행해준다.

 

 

- 바인더에 있는 getService() 메소드를 사용해서

 백그라운드에서 돌던 서비스의 참조값을 액티비티의 필드에 저장해놓는 것이다.

 

- Binder는 IBinder를 구현한 것이므로

 LocalBinder 객체는 이렇게 여러 타입으로 받을수있다.

- IBinder타입으로 받아서 원래 타입으로 캐스팅해서 사용하면 된다.

 

- 여기서 리턴해주는 서비스의 참조값을 액티비티에서 가져간다.

 

 

- 일시중지 버튼 : 액티비티의 pause 메소드

 

- 재생 버튼 : start() 메소드

 

- 이 경우, 서비스는 계속 유지되므로 액티비티가 destroy되어도

 서비스는 프로세스를 할당받아서 계속 돌아가고 있다.

 


 

** 서버에 들어있는 음악파일 재생해보기!

 

- 이전 예제의 음악파일은 앱 내부에 들어가 있다.

- 실제로는 이렇게 고정적으로 앱 안에 갖고있는 경우는 드물다

 

 

* 재생할 mp3 파일의 위치

1. Phone 내부의 어딘가

  - App의 내부 저장장치 혹은 외부 저장장치

  - ContentProvider App의 내부 저장장치 혹은 외부 저장장치

2. 인터넷상의 특정 url을 가지고 있는 서버

  - http://192.168.0.1:9000/boot07/resources/upload/xxx.mp3 (특정 서버)

 

- 미디어플레이어 객체는 이렇게 인터넷상에있는 특정 파일을 로딩하는 기능도 가지고 있다.

 

- Spring Boot 프로젝트 boot07 내부 폴더에 mp3 파일을 넣는다. 브라우저에서 재생해볼 것!

 


 

- 새 모듈 생성-empty activity

step23mp3player

 

androidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid"
        android:usesCleartextTraffic="true">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

- 인터넷 사용 표기, http 가능 설정 추가

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_media_play"
        android:tooltipText="재생버튼"
        android:id="@+id/playBtn"/>
    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_media_pause"
        android:tooltipText="일시정지"
        android:id="@+id/pauseBtn"/>
</LinearLayout>

- ImageButton 요소를 넣어준다. ic_media_play는 이렇게 ▶ 생긴 아이콘!

 

 

MainActivity

package com.example.step23mp3player;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements MediaPlayer.OnPreparedListener {
    MediaPlayer mp;
    //재생 준비가 되었는지 여부
    boolean isPrepared=false;
    ImageButton playBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //재생 버튼
        playBtn=findViewById(R.id.playBtn);
        //재생 버튼을 사용 불가 상태로 일단 설정
        playBtn.setEnabled(false);
        playBtn.setOnClickListener(v -> {
            //만일 준비되지 않았으면
            if(!isPrepared){
                return; //메소드를 여기서 종료
            }
            mp.start();
        });
        //일시중지 버튼
        ImageButton pauseBtn=findViewById(R.id.pauseBtn);
        pauseBtn.setOnClickListener(v -> {
            mp.pause();
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        //음악을 재생할 준비를 한다.
        try {
            mp=new MediaPlayer();
            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mp.setDataSource("http://192.168.0.34:9000/boot07/resources/upload/mp3piano.mp3");
            mp.setOnPreparedListener(this);
            //로딩하기
            mp.prepareAsync();
        }catch (Exception e){
            Log.e("MainActivity", e.getMessage());
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        mp.stop();
        mp.release();
    }

    //재생할 준비가 끝나면 호출되는 메소드
    @Override
    public void onPrepared(MediaPlayer mp) {
        Toast.makeText(this, "로딩 완료!", Toast.LENGTH_SHORT).show();
        isPrepared=true;
        playBtn.setEnabled(true);
    }
}

 

- 이 자원이 없을 수도 있기 때문에 exception이 발생한다.

- try문으로 해당 부분을 묶어주면 된다.

 

- 다른 메소드에서도 쓸 수 있도록 MediaPlayer mp; 를 필드로 선언하기!

 

- 이 prepare는 동기 작업이다. 네트워크 상황 등 속도가 느려지면 곤란할 수 있다.

- mp.pause() 로 일시정지 가능

 

mp.setOnPreparedListener();
mp.prepareAsync();

 

- 이것이 비동기 로딩 작업을 할 수 있는 메소드이다. 바꿔주기!

 

- 준비가 다 되었을 때 호출하는 리스너를 같이 붙여준다.

 

- 리스너를 this로 받기 위해 MainActivity에서 MediaPlayer.OnPreparedListener 를 구현해준다.

- onPrepared() 메소드 오버라이드

- 비동기로 로딩한 다음에, 로딩이 끝나면 onPrepared가 호출된다.(mp의 참조값도 같이 전달된다)

 

 

- 재생시 이렇게 토스트 메시지가 뜨고, 음악도 잘 재생된다.

- 로딩이 완료되었을 때 실행되는 메소드를 사용해서 비동기 처리로 인터넷상의 음악을 재생하기!!

 

- 버튼을 누르기전에 준비작업이 필요하다. 

- prepareAsync() 에서 플레이를 누르기 전에 준비작업을 해준다.

 

- try~catch 문을 onStart() 안으로 옮겨주기

 

- mp.play(); 역시 onPrepared 안쪽이 아니라 재생 버튼을 클릭하면 재생되도록 넣어준다.

 

 

- 음악이 준비가 되었는지 안되었는지 파악할 수 있는 상태값이 있으면 좋겠다!

 

- 상태값 관리를 위한 필드를 만들어준다.(boolean)

 

 

- 준비 여부에 따라 이렇게 if문으로 분기할 수 있다.

 

 

-만약 준비되지않았다면 버튼이 아예 눌러지지 않게 할 수 있을까?

 

- UI 설정에서 clickable의 default값을 false로 만들 수도 있는데,

 현재는 xml 설정으로는 안되고 코드로만 된다...

- MainActivity에서 버튼의 참조값에 .setEnabled(false) 로 작성하면 된다.

 

- play 설정의 참조값이 필요하므로 playBtn을 필드로 선언하고,

 

playBtn.setEnabled(true);

- 재생할 준비가 끝나면(onPrepared 메소드) 이 playBtn을 누를 수 있게 설정한다.

 

- 재생 준비가 되면 재생 버튼이 활성화되고, 재생 가능해진다.

 


 

- 초보 개발자 입장에서는 무언가를 개발하려고 하면 한번에 모든 코드가 떠오르지 않는다.

1) 간단한 기능, 떠오르는 기능을 하나씩 만들어 본다(단위기능, ...)

   만들어보면 문제점이 떠오른다.

2) 문제점확인, 버그확인, 에러확인

  ex) 음악재생: 웹브라우저 재생>안드로이드 재생 단계적으로 진행

3) 개선!

 

- 하나하나씩 구현해 나가면 된다.

  이렇게 무한반복하다 보면 개선이 되고 차근차근 원하던 앱이 개발이 된다.