안드로이드 자바

[JAVA][안드로이드] TelephonyManager를 이용한 mp3 플레이어 제어 빠르게 구현 하기

teamnova 2021. 3. 25. 12:00
728x90

안녕하세요.

TelephonyManager를 이용한 mp3 플레이어 제어하는 방법을 공유하겠습니다!

 

기기의 전화 상태(수신, 통화연결 중, 통화 끝)를 확인하여 mp3 플레이어를 제어 하는 방법입니다.

 

예외 처리 시 자주 사용하는 코드입니다.

스틱 코드에 등록된 코드를 불러와서 빠르게 구현하는 방법으로 진행하겠습니다.

 


사전 준비 사항

 

1. 스틱 코드 회원가입

(https://stickode.com/signup.html)

 

2. 안드로이드 스튜디오에 스틱 코드 설치 및 로그인

(https://stickode.com/howto.html#jetbrains_installation)


 

1. 스틱 코드에 파일 업로드 하기

스틱코드 코드 업로드 방법

업로드할 코드를 드래그한 후 오른쪽 마우스 - 코드 업로드(새창에서) 클릭

언어, 코드이름, 태그를 입력한 후 업로드 버튼 클릭

 

스틱코드 - 내 스틱코드

스틱 코드 로그인 후 좌측 사이드바 내 스틱코드 클릭

 

코드 리스트에 보면 업로드한 코드를 확인할 수 있습니다.

TelephonyManager mp3 control

 

 

2. 코드 불러오기 (onCreate메서드 위에)

 

 TelephonyManager mp3 control java코드 불러오기

TelephonyManager mp3 control java 파일에서 java코드 불러오기

 

 

3. MusicPlayerActivity onCreate() CallCheckHandler 추가

  • CallCheckHandler.postDelayed(CallCheckRunnable,0);

 

4. 결과 Logcat 캡처

 

5. 전체 코드

 

MusicPlayerActivity java 파일

package com.project.musicplayer;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Service;
import android.content.Context;
import android.database.Cursor;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;

import java.util.ArrayList;

public class MusicPlayerActivity extends AppCompatActivity implements View.OnClickListener  {

    public String log_name = "musicplayer";
    public ListView listView;
    private String songNames[];
    private ArrayList<Uri> songs;
    private String title = "";

    static private TextView titleTv;
    static public Button btnPlay;
    private Button btnBack, btnFor;
    private ImageView forwardIv, backwardIv, repeatIv;

    static public SeekBar seekBar;
    static public MediaPlayer mediaPlayer;
    static private int pos = -1;       //노래 포지션

    static public boolean bearPhoneOneCheck = false;    //bearPhoneOneCheck 이어폰이 한번 뽑히면 true, 그렇지 않으면 false
    static public int CheckearPhone = 0;   //이어폰을 빼면 0, 연결되어 있으면 1이상 값
    static public boolean bpause = false;    //일시 정지 버튼을 눌렀다면, true
    static public int repeatPlayMode = 0;  //반복재생 모드 0: 전곡 반복 재생 / 1: 한곡 반복 재생

    private Runnable runnable;

    private Handler handler;

    private Uri uri;

    public static boolean bCallYN = false;  //전화를 받았을 때 음악이 재생되어 있다면, 중지 통화 종료후 재시작
    private boolean bCallendYN = false;     //전화를 받고 끊었다면 true

    //전화 받는 것을 체크하는 Handler
    Handler CallCheckHandler = new Handler();
    private Runnable CallCheckRunnable = new Runnable() {
        @Override
        public void run() {

            Log.d("Call CallCheckRunnable", "CallCheckRunnable");

            if (bCallYN && mediaPlayer != null && mediaPlayer.isPlaying()) {
                Log.d("Call CallCheckRunnable", "mediaPlayer.pause()");
                mediaPlayer.pause();
                bpause = true;
                btnPlay.setText(">");
            } else if (bCallendYN) {
                Log.d("Call CallCheckRunnable", "mediaPlayer.start()");
                bCallYN = false;
                bCallendYN = false;
                mediaPlayer.start();
                btnPlay.setText("||");
                bpause = false;
                bearPhoneOneCheck = true;
            }

            TelephonyManager tm = (TelephonyManager) getApplicationContext().getSystemService(Service.TELEPHONY_SERVICE);

            switch (tm.getCallState()) {
                case TelephonyManager.CALL_STATE_RINGING:
                    //수신
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    // 통화 연결됨
                    Log.d("통화 연결", "통화 연결");
                    if (!bCallYN && mediaPlayer != null && mediaPlayer.isPlaying()) {
                        bCallYN = true;
                    }
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    // 통화 종료
                    if (bCallYN) {
                        Log.d("통화 종료", "통화 종료");
                        bCallendYN = true;
                    }
                    break;
            }

            CallCheckHandler.postDelayed(CallCheckRunnable, 1000);

        }
    };

    @Override
    protected void onResume() {
        super.onResume();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_music_player);

        titleTv = findViewById(R.id.titleTv);
        listView = findViewById(R.id.listView);

        btnPlay = findViewById(R.id.btnPlay);
        btnBack = findViewById(R.id.btnBack);
        btnFor = findViewById(R.id.btnFor);
        seekBar = findViewById(R.id.seekbar);
        forwardIv = findViewById(R.id.forwardIv);
        backwardIv = findViewById(R.id.backwardIv);
        repeatIv = findViewById(R.id.repeatIv);

        handler = new Handler();

        btnFor.setOnClickListener(this);
        btnBack.setOnClickListener(this);
        btnPlay.setOnClickListener(this);
        forwardIv.setOnClickListener(this);
        backwardIv.setOnClickListener(this);
        repeatIv.setOnClickListener(this);

        //음악이 재생중이였다면 seekBar, 노래제목 표시하기
        if(mediaPlayer != null)
        {
            if(mediaPlayer.isPlaying())
            {
                if(!title.equals("")){
                    titleTv.setSelected(true);
                    titleTv.setText(title);
                }

                seekBar.setMax(mediaPlayer.getDuration());
                seekBar.setProgress(mediaPlayer.getCurrentPosition());
                btnPlay.setText("||");
            }
            else if(!mediaPlayer.isPlaying())
            {
                if(!title.equals("")){
                    titleTv.setSelected(true);
                    titleTv.setText(title);
                }

                seekBar.setMax(mediaPlayer.getDuration());
                seekBar.setProgress(mediaPlayer.getCurrentPosition());
                btnPlay.setText(">");
            }

            if(repeatPlayMode == 0)
            {
                repeatIv.setImageResource(R.drawable.repeat_32px);
            }
            else if(repeatPlayMode == 1)
            {
                repeatIv.setImageResource(R.drawable.repeat_one_32px);
            }
        }

        // MP3 경로를 가질 문자열 배열.
        String[] resultPath = null;

        String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
        // 찾고자하는 파일 확장자명.
        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp3");

        String[] selectionArgsMp3 = new String[]{ mimeType };

        Cursor c = null;

        c = getContentResolver().query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Audio.Media._ID,MediaStore.Audio.Media.DISPLAY_NAME}, selectionMimeType, selectionArgsMp3, null);


        if (c.getCount() != 0){

            ArrayList<Uri> arrayList = new ArrayList<Uri>();
            songNames = new String[c.getCount()];

            //커서로 조회한 mp3 상대경로 와 mp3 제목을 저장한다.
            while (c.moveToNext()) {

                //음악파일이 저장되어 있는 상대 경로를 uri로 저장.
                Uri contentUri = Uri.withAppendedPath(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
                );

                //음악이 저장되어 있는 상대 경로 저장
                arrayList.add(contentUri);

                //음악 제목 저장
                songNames[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)).replace(".mp3", "");

                Log.d(log_name, contentUri.toString());
                Log.d(log_name, songNames[c.getPosition()]);
            }

            Log.d(log_name, String.valueOf(c.getCount()));
            Log.d(log_name, String.valueOf(arrayList.size()));
            songs = arrayList;

        }

        //음악 리스트를 위한 어댑터 구현
        //listview => song_layout textView 위젯에 음악 개수 만큼 음악 제목을 표시한다.
        ArrayAdapter<String> adaper = new ArrayAdapter<String>(getApplicationContext(), R.layout.song_layout
                , R.id.textView, songNames);

        listView.setAdapter(adaper);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                pos = position;

                if (mediaPlayer != null) {
                    mediaPlayer.stop();
                    mediaPlayer.release();
                }

                titleTv.setSelected(true);
                titleTv.setText(songNames[pos]);

                title = (String)titleTv.getText();

                mediaPlayer = MediaPlayer.create(MusicPlayerActivity.this, songs.get(pos));

                mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        //곡을 클릭하면 곡의 전체시간을 seekBar에 Max값으로 넣는다.
                        seekBar.setMax(mp.getDuration());
                        mediaPlayer.start();
                        bearPhoneOneCheck = true;
                        changeSeekbar();
                    }
                });

            }
        });




        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) {
                    //seekBar를 누르면 눌린 시간으로 mp3 시간을 이동
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });


        //전화를 받았는지 체크하여 받았다면, 음악과 타이머를 중지시키는 핸들러
        CallCheckHandler.postDelayed(CallCheckRunnable,0);


    }

    private void changeSeekbar(){

        //현재 mp3 재싱시간을 seekBar에 넣는다.
        seekBar.setProgress(mediaPlayer.getCurrentPosition());

        if(mediaPlayer.isPlaying())
        {

            AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            AudioDeviceInfo[] audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
            for (AudioDeviceInfo deviceInfo : audioDevices) {
                if (deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
                        || deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {

                    //이어폰이 연결되어 있다면 +1씩 증가
                    CheckearPhone += 1;
                }
            }

            //이어폰을 빼면 음악 일시정지
            if(CheckearPhone == 0){

                //bearPhoneOneCheck 이어폰이 한번 뽑히면 true, 그렇지 않으면 false
                if(!bearPhoneOneCheck)
                {
                    mediaPlayer.pause();
                    btnPlay.setText(">");
                    bearPhoneOneCheck = true;
                }
                else
                {
                    Log.d(log_name,"이어폰 분리~~~~~~~~~~~~");

                    btnPlay.setText("||");
                    runnable = new Runnable() {
                        @Override
                        public void run() {
                            changeSeekbar();
                        }
                    };

                    handler.postDelayed(runnable, 400);
                }

            }
            else
            {
                CheckearPhone = 0;

                Log.d(log_name,"이어폰 착용!!!!!!!!!!!!");

                btnPlay.setText("||");
                runnable = new Runnable() {
                    @Override
                    public void run() {

                        //이어폰을 다시 연결하면 bearPhoneOneCheck = false 로 변경
                        //bearPhoneOneCheck 는 이어폰을 연결하여 갑자기 뺏을경우 노래가 멈추게 하기위해 만듬
                        bearPhoneOneCheck = false;
                        changeSeekbar();
                    }
                };

                handler.postDelayed(runnable, 100);
            }

        }
        else
        {


            //한곡이 끝나면 자동 재생하려고 했으나, seekBar와 mediaPlayer 시간 값이 정확하게 일지하지 않아
            // 200 ms에 오차 범위를 넣었다.
            Log.d(log_name,"seekbar 끝남 다음곡 재생 : "+ seekBar.getProgress() + " / " + mediaPlayer.getDuration());
            if (!bpause && seekBar.getProgress() >= (mediaPlayer.getDuration() - 1500)) {
                Log.d(log_name, "seekbar 끝남 다음곡 재생 : " + seekBar.getProgress() + " / " + mediaPlayer.getDuration());

                btnPlay.setText(">");

                if (repeatPlayMode == 0) //전곡 반복 재생
                {
                    if (pos != -1 && songNames.length - 1 > pos) {
                        pos += 1;
                        playMusic(pos);

                    } else if (songNames.length - 1 == pos) {
                        pos = 0;
                        playMusic(pos);

                    }
                } else if (repeatPlayMode == 1)    //한곡 반복 재생
                {
                    playMusic(pos);
                }
            }

        }
    }

    @Override
    public void onClick(View v) {
        if(mediaPlayer != null) {

            switch (v.getId()) {
                case R.id.btnPlay:
                    if (mediaPlayer.isPlaying()) {
                        mediaPlayer.pause();
                        bpause = true;
                        btnPlay.setText(">");
                    } else {
                        mediaPlayer.start();
                        btnPlay.setText("||");
                        bpause = false;
                        bearPhoneOneCheck = true;
                        changeSeekbar();
                    }
                    break;

                case R.id.btnFor:
                    mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + 5000);
                    break;

                case R.id.btnBack:
                    mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 5000);
                    break;

                case R.id.forwardIv:
                    if(pos != -1 && songNames.length - 1 > pos)
                    {
                        pos += 1;
                        playMusic(pos);
                    }
                    else if(songNames.length - 1 == pos)
                    {
                        pos = 0;
                        playMusic(pos);
                    }
                    break;

                case R.id.backwardIv:
                    if(pos > 0)
                    {
                        pos -= 1;
                        playMusic(pos);
                    }
                    else if(pos == 0)
                    {
                        pos = songNames.length - 1;
                        playMusic(pos);
                    }
                    break;

                case R.id.repeatIv:
                    if(repeatPlayMode == 0)
                    {
                        repeatPlayMode = 1;
                        repeatIv.setImageResource(R.drawable.repeat_one_32px);
                    }
                    else if(repeatPlayMode == 1)
                    {
                        repeatPlayMode = 0;
                        repeatIv.setImageResource(R.drawable.repeat_32px);
                    }
                    break;
            }
        }
    }

    private void playMusic(int position)
    {

        Log.d(log_name,"playMusic position: " + position);

        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }

        Log.d(log_name,"playMusic uri: " + songs.get(position).toString());

        titleTv.setSelected(true);
        titleTv.setText(songNames[position]);

        title = (String)titleTv.getText();

        mediaPlayer = MediaPlayer.create(MusicPlayerActivity.this, songs.get(position));

        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                //곡을 클릭하면 곡의 전체시간을 seekBar에 Max값으로 넣는다.
                seekBar.setMax(mp.getDuration());
                mediaPlayer.start();
                changeSeekbar();

            }
        });
    }

}

 

MusicPlayerActivity xml 파일

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".MusicPlayerActivity">

    <TextView
        android:id="@+id/titleTv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10sp"
        android:layout_marginRight="10sp"
        android:gravity="center_vertical"
        android:textSize="20sp"
        android:text="제목"
        android:textColor="#000000"
        android:singleLine="true"
        android:marqueeRepeatLimit="marquee_forever"
        android:ellipsize="marquee"
        android:scrollHorizontally="true"
        app:layout_constraintBottom_toTopOf="@+id/seekbar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnBack"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="-5"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnPlay"
        app:layout_constraintStart_toEndOf="@+id/backwardIv"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/backwardIv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:scaleType="center"
        android:src="@drawable/backward_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnBack"
        app:layout_constraintHorizontal_bias="0.4"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <Button
        android:id="@+id/btnPlay"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text=">"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/btnFor"
        app:layout_constraintStart_toEndOf="@+id/btnBack"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/forwardIv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:scaleType="center"
        android:src="@drawable/forward_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btnFor"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <Button
        android:id="@+id/btnFor"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="+5"
        android:textSize="20dp"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toStartOf="@+id/forwardIv"
        app:layout_constraintStart_toEndOf="@+id/btnPlay"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />

    <ImageView
        android:id="@+id/repeatIv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:scaleType="center"
        android:src="@drawable/repeat_32px"
        app:layout_constraintBottom_toTopOf="@+id/listView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/forwardIv"
        app:layout_constraintTop_toBottomOf="@+id/seekbar" />



    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="40dp"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.169" />

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="350sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

예외처리 시 유용하게 쓰이는 코드를 스틱 코드에 등록해 놓고 그때그때 사용하면 빠르게 기능 구현이 가능합니다.