안녕하세요.
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 파일에서 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>
예외처리 시 유용하게 쓰이는 코드를 스틱 코드에 등록해 놓고 그때그때 사용하면 빠르게 기능 구현이 가능합니다.
'안드로이드 자바' 카테고리의 다른 글
[Java][Andoird] Picasso 라이브러리를 활용하여 이미지 URL을 ImageView에 쉽게 띄우기 (0) | 2021.03.27 |
---|---|
[Java][Android] Volley를 이용한 회원가입, 로그인(HTTP통신) (0) | 2021.03.26 |
[Java][안드로이드] 시간 입력 커스텀 다이얼로그 빠르게 만들기 (0) | 2021.03.24 |
[Java][Android] 안드로이드 스튜디오 - 폰트(글꼴) 일괄 적용하기 (0) | 2021.03.22 |
[Kotlin][Android] WebView를 사용해서 웹 페이지 띄우기 (0) | 2021.03.21 |