본문 바로가기
안드로이드 자바

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

by teamnova 2021. 3. 25.

안녕하세요.

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>

 

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