728x90

Audio Streaming 서버 코드를 구현한 걸 오픈한다.


<?php
$search = isset($_POST['audioPath'])? str_replace("/mp3files","",$_POST['audioPath']) : '';
$dir ='./mp3files'.$search;

echo getCurrentFileList($dir);
function getCurrentFileList($dir){
    $web_url = "http://localhost/_test";
    $valid_formats = array("mp3","wav");
    $handle = opendir($dir); // 디렉토리의 핸들을 얻어옴
    // 지정한 디렉토리에 있는 디렉토리와 파일들의 이름을 배열로 읽어들임
    $R = array(); // 결과 담을 변수 생성
    $cnt = 0;
    while ($entry = readdir($handle)) {
        if($entry == '.' || $entry == '..') continue;
        $getExt = pathinfo($entry, PATHINFO_EXTENSION); // 파일 확장자 구하기
        $curdir = substr($dir,1,strlen($dir)-1); // 현재 폴더 정보
        if(empty($getExt)){
            $subdir = $dir.'/'.$entry;
            $subdir = substr($subdir,1,strlen($subdir)-1);
            array_push($R,array("isFile"=>1,"fileName"=>$entry,"filePath"=>$subdir,"curPath"=>$curdir));
        } else {
            if(in_array($getExt, $valid_formats)){
                $filepath = $dir.'/'.$entry;
                $filepath = substr($filepath,1,strlen($filepath)-1);
                array_push($R,array("isFile"=>2,"fileName"=>$entry,"filePath"=>$filepath,"curPath"=>""));
            }
        }

        // 상위폴더
        $check = str_replace("/mp3files","",$curdir);
        if(strlen($check)>0 && $cnt < 1){ // 상위폴더는 1회만 추가되도록 처리
            $filePath = substr($curdir,0,strripos($curdir, "/")); // 상위폴더
            $updir = substr($filePath,0,strripos($filePath, "/")); // 차상위폴더
            array_push($R,array("isFile"=>0,"fileName"=>"상위폴더","filePath"=>$filePath,"curPath"=>$updir));
            $cnt++;
        }

    }
    closedir($handle);
    sort($R); // 안드로이드에서 가나다순으로 정렬하기 위해
    return json_encode(array("result"=>$R));
}

?>
 


블로그 이미지

Link2Me

,
728x90

Audio Streaming 이 잘 동작하는 걸 확인하고 좀 더 살을 붙여서 제대로 동작되도록 1단계 작업을 했다.


서버에 지정될 폴더에 있는 음악 파일을 모두 가져와서 ListView 로 보여준다.

코드 구현하면서 상위 폴더로의 이동을 어떻게 할까 고민하느라고 시간이 좀 걸렸다.


- 음악 어플을 구동하면 화면이 꺼지지 않도록 했다. 파일 재생이 완료되면 화면 꺼짐 기능이 동작된다.

- ListView 구현은 RecyclerView 를 이용했다.

- 서버(PHP Web)에서 데이터를 가져올 때 음악파일인지, 폴더인지 구분하여 가져오고

  구분 정보를 기준으로 ImageView 아이콘이 다르게 표시되도록 하였다.

- 서버와의 통신은 HttpURLConnection 방식으로 접속한다.

- 상위 폴더로 이동할 수 있도록 하였다.

- 음악을 선택하면 해당 곡부터 아래 곡을 연속재생하도록 구현했다.

- 미구현 사항 : 음악 재생, 멈춤 하는 팝업화면을 아직 구현하지 않았다.


앱 build.gradle


AndroidManifest.xml


activity_main.xml


file_item.xml


폴더 아이콘은 구글 이미지에서 구해서 파일 사이즈를 줄였다.


FileList_Item.java


MainActivity.java


서버와의 통신 HttpURLConnection


서버에서 데이터를 가져오지 못하거나 접속 연결이 잘못되는 경우를 고려하여 GET Response Code 를 표시하도록 했다. 데이터를 못가져오면 서버 코드 파일이 잘못일 수도 있으니 코드를 잘 구현해야 한다.

 



곡의 총 재생시간과 현 재생시간을 표시하고 앞으로 5초, 뒤로 5초를 선택할 수 있게 추가를 해봤다.

http://link2me.tistory.com/1350 게시글을 참조하면 도움된다.

블로그 이미지

Link2Me

,
728x90

이전 게시글(http://link2me.tistory.com/1391)에서 서버 파일 1개만 재생하는 걸 Spinner 기능을 이용하여 몇개 선택해서 듣어볼 수 있도록 수정해봤다.

스트리밍 기능이 동작되는 걸 확인할 수 있다.


activity_main.xml



파일을 테스트해보니 mp3, wav 파일은 스트리밍 재생이 되는데 flac, ape 파일은 재생을 못한다.


MainActivity.java

 import android.app.ProgressDialog;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private Button btn;
    private boolean playPause;
    private MediaPlayer mediaPlayer;
    private ProgressDialog progressDialog;
    private boolean initalStage = true;

    private String[] getMp3files(){
        return new String[]{
                "/kim_01.wav",
                "/ImYours.mp3",
                "/kim_02.mp3",
                "/Rua_Vincent.wav",
                "/kimbumsu_01.mp3"
        };
    }

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

        getMp3fileOptions();

    }

    private void getMp3fileOptions(){
        final Spinner mp3fileOptions = (Spinner) findViewById(R.id.mp3fileOptions);
        String[] audiofile = getMp3files();
        ArrayAdapter<String> arrayAdapter =  new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, audiofile);
        mp3fileOptions.setAdapter(arrayAdapter);
        mp3fileOptions.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                initStreaming(Value.IPADDRESS + mp3fileOptions.getItemAtPosition(i).toString());
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    public void initStreaming(final String uri){
        if(mediaPlayer != null && mediaPlayer.isPlaying()){
            initalStage = true;
            playPause = false;
            btn.setText("Launch Streaming");
            mediaPlayer.stop();
            mediaPlayer.reset();
        }

        mediaPlayer = new MediaPlayer();

        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        progressDialog = new ProgressDialog(this);
        btn = (Button) findViewById(R.id.audioStreamBtn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!playPause){
                    btn.setText("Pause Streaming");
                    if(initalStage){
                        new Player().execute(uri);
                    } else {
                        if(!mediaPlayer.isPlaying()){
                            mediaPlayer.start();
                        }
                    }
                    playPause = true;
                } else {
                    btn.setText("Launch Streaming");
                    if(mediaPlayer.isPlaying()){
                        mediaPlayer.pause();
                    }
                    playPause = false;
                }
            }
        });
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mediaPlayer != null){
            mediaPlayer.reset();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
   
    class Player extends AsyncTask<String, Void, Boolean>{

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog.setMessage("Buffering...");
            progressDialog.show();
        }

        @Override
        protected Boolean doInBackground(String... strings) {
            Boolean prepared = false;

            try {
                mediaPlayer.setDataSource(strings[0]);
                mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mediaPlayer) {
                        initalStage = true;
                        playPause = false;
                        btn.setText("Launch Streaming");
                        mediaPlayer.stop();
                        mediaPlayer.reset();
                    }
                });

                mediaPlayer.prepare();
                prepared = true;
            } catch (IOException e) {
                Log.e("MyAudioStreamingApp",e.getMessage());
                prepared = false;
                e.printStackTrace();
            }
            return prepared;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if(progressDialog.isShowing()){
                progressDialog.cancel();
            }

            mediaPlayer.start();
            initalStage = false;
        }
    }
}


제어없이 바로 재생되는 것은  아래 메소드를 이용해도 된다.


     public void StartMusic(String url) {
        // HTTP스트리밍을 통한 원격의 URL에서 재생
        try {
            if(mediaPlayer!=null && mediaPlayer.isPlaying()){
                mediaPlayer.stop();
                mediaPlayer.reset();
            }

            mediaPlayer = new MediaPlayer();
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.setDataSource(url);
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });

        } catch (Exception e) {
            Log.e("MusicPlayer", e.getMessage());
        }
    }



스트리밍이 잘 되는 걸 확인했으니, 서버 파일을 자동으로 인식해서 처리하는 걸 해봐야겠다.

블로그 이미지

Link2Me

,
728x90

구글링해서 유투브 동영상에서 설명하는 코드를 보고 타이핑하여 기능이 제대로 동작되는지 확인하고 적어둔다.

가장 간단하게 서버에 있는 음악파일을 받아서 스트리밍으로 재생하는 것만 되는 코드라고 보면 된다.


테스트 환경

- Android Studio 3.3


앱 build.gradle

 apply plugin: 'com.android.application'

android {
    compileSdkVersion 26



    defaultConfig {
        applicationId "com.tistory.link2me.audiostreamingplayer"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}


AndroidManifest.xml 파일에는

<uses-permission android:name="android.permission.INTERNET" />
만 추가했다.


MainActivity.java

import android.app.ProgressDialog;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private Button btn;
    private boolean playPause;
    private MediaPlayer mediaPlayer;
    private ProgressDialog progressDialog;
    private boolean initalStage = true;
    String url = Value.IPADDRESS + "/kim_01.wav"; // 서버 음악파일 경로

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

        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        progressDialog = new ProgressDialog(this);
        btn = (Button) findViewById(R.id.audioStreamBtn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!playPause){
                    btn.setText("Pause Streaming");
                    if(initalStage){
                        new Player().execute(url);
                    } else {
                        if(!mediaPlayer.isPlaying()){
                            mediaPlayer.start();
                        }
                    }
                    playPause = true;
                } else {
                    btn.setText("Launch Streaming");
                    if(mediaPlayer.isPlaying()){
                        mediaPlayer.pause();
                    }
                    playPause = false;
                }
            }
        });

    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mediaPlayer != null){
            mediaPlayer.reset();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    class Player extends AsyncTask<String, Void, Boolean>{

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog.setMessage("Buffering...");
            progressDialog.show();
        }

        @Override
        protected Boolean doInBackground(String... strings) {
            Boolean prepared = false;

            try {
                mediaPlayer.setDataSource(strings[0]);
                mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mediaPlayer) {
                        initalStage = true;
                        playPause = false;
                        btn.setText("Launch Streaming");
                        mediaPlayer.stop();
                        mediaPlayer.reset();
                    }
                });

                mediaPlayer.prepare();
                prepared = true;
            } catch (IOException e) {
                Log.e("MyAudioStreamingApp",e.getMessage());
                prepared = false;
                e.printStackTrace();
            }
            return prepared;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if(progressDialog.isShowing()){
                progressDialog.cancel();
            }

            mediaPlayer.start();
            initalStage = false;
        }
    }
}


이 기본적인 걸 가지고 코드에 살이 붙이는 작업을 진행할 예정이다.

블로그 이미지

Link2Me

,
728x90

오디오 플레이어를 테스트 하다보니 화면 자동꺼짐 설정시간에 따라 음악 재생중에 화면이 꺼지면서 재생이 되지 않는 현상을 발견했다.


그래서 음악 재생중에는 화면 꺼짐이 발생하지 않게 처리하기 위한 코드를 추가했다.


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_player);
    mContext = this.getBaseContext();

    // 화면 꺼짐 방지
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    // 재생할 목록 배열에 저장하기
    Intent intent = getIntent();
    list =(ArrayList<Song_Item>) intent.getSerializableExtra("playList");

    mediaPlayer = new MediaPlayer();

} 


위와 같이 한줄을 추가해주면 된다.


선택한 노래가 5곡인데 5곡이 모두 재생되고 나면 화면 꺼짐 방지 설정 때문에 아무런 조치를 안하면 화면이 계속 껴진 상태로 남아있더라.

그러므로 화면 해제 설정을 해주어야 한다.

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


화면 설정 해제 명령어를 넣어줬음에도 불구하고 화면(SCREEN)이 계속 ON 상태가 되는 현상이 있어서 Log를 찍어보니까 songProgressBar 핸들러 처리가 잘못되어 문제가 생겼다.

그래서 해당 코드를 수정하고 http://link2me.tistory.com/1350 내용을 업데이트 했다.


음악 재생이 완료되지 마자 자동꺼짐 설정 모드로 변경되지는 않는다.

만약 그렇게 하고 싶다면

@SuppressLint("Wakelock")
private void acquireCPUWakeLock(Context context) {
    // 잠든 화면 깨우기
    if (wakeLock != null) {
        return;
    }

    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
            | PowerManager.ON_AFTER_RELEASE, "hi");
    wakeLock.acquire();
}

private void releaseCpuLock() {
    Log.e("PushWakeLock", "Releasing cpu WakeLock = " + wakeLock);

    if (wakeLock != null) {
        wakeLock.release();
        wakeLock = null;
    }
}


위 코드를 적절히 활용하면 될 것으로 본다.

mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        if(isRepeat){ // 반복재생
            playMusic(list.get(position));
        } else if(isShuffle){ // 랜덤재생
            Random rand = new Random();
            position = rand.nextInt((list.size() - 1) - 0 + 1) + 0;
            playMusic(list.get(position));
        } else {
            if(position + 1 < list.size()) {
                position++;
                playMusic(list.get(position));
            } else {
                Log.e("Music_Off","음악종료");
                seekHandler.removeCallbacks(mUpdateTimeTask);
                position = 0;
                acquireCPUWakeLock(mContext);
                releaseCpuLock();
                //getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            }
        }

    }
});
 


블로그 이미지

Link2Me

,
728x90

ArrayList 로 선택한 노래만 팝업 메뉴로 만드는 방법을 구현하고 적어둔다.


팝업은 API 레벨 11 이상에서만 사용할 수 있다.
PopupMenu(Context context, View anchor)
- 두번째 인수는 팝업 메뉴를 표시할 앵커 뷰를 지정


popupMenu.getMenu().add(group_id, item_id, order, "노래타이틀");

popupMenu.getMenu().add(Menu.NONE, i, Menu.NONE, list.get(i).getTitle());

Menu.NONE 대신에 0 을 넣어도 된다.



//import android.support.v7.widget.PopupMenu; // ==> 이줄 추가하면 에러 발생함
import android.widget.PopupMenu;  // ==> 에러 발생 안함
import android.view.Menu;
import android.view.MenuItem;


 //import android.support.v7.widget.PopupMenu;
import android.widget.PopupMenu;
import android.view.Menu;
import android.view.MenuItem;

public class Player extends AppCompatActivity implements View.OnClickListener {
    private ImageButton btnPlaylist;

    private ArrayList<Song_Item> list;
    private MediaPlayer mediaPlayer;
    private int position = 0; // 현재 재생곡 위치

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

        Intent intent = getIntent();
        list =(ArrayList<Song_Item>) intent.getSerializableExtra("playList");
        mediaPlayer = new MediaPlayer();

        // All player buttons
        btnPlaylist = (ImageButton) findViewById(R.id.btnPlaylist);

        btnPlaylist.setOnClickListener(this); // 곡 선택창

        System.out.println("선택된 노래곡수 = " + list.size());
        playMusic(list.get(position)); // 노래 곡
        seekBar_Progressing();
    }

    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnPlaylist:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(this, v);
                Menu menu = popupMenu.getMenu();
                for(int i=0; i < list.size();i++){

                    //Popup Menu에 들어갈 MenuItem 추가

                    menu.add(0,i,0,list.get(i).getTitle());
                }

                //메뉴가 클릭했을때 처리하는 부분

                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        int i = item.getItemId();
                        Toast.makeText(Player.this, item.getTitle() + " 선택했습니다", Toast.LENGTH_SHORT).show();
                        playMusic(list.get(i)); // 노래 곡
                        return true;
                    }
                });
                popupMenu.show();
                break;
        }
    }

    public void playMusic(Song_Item song_item) {
        try {
            seek_bar.setProgress(0);
            songTitleLabel.setText(song_item.getTitle());
            mediaPlayer.reset();
            mediaPlayer.setDataSource(song_item.getDataPath());
            mediaPlayer.prepare();
            mediaPlayer.start(); // 노래 재생 시작

            seek_bar.setMax(mediaPlayer.getDuration()); // 음악파일의 전체길이
            seek_bar.setProgress(mediaPlayer.getCurrentPosition());
            if(mediaPlayer.isPlaying()){
                btnPlay.setVisibility(View.GONE);
                btnPause.setVisibility(View.VISIBLE);
            } else {
                btnPlay.setVisibility(View.VISIBLE);
                btnPause.setVisibility(View.GONE);
            }

            /* Album Art Bitmap을 얻는다. */
            final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
            ImageView mImgAlbumArt = (ImageView) findViewById(R.id.albumart);
            Uri albumArtUri = ContentUris.withAppendedId(artworkUri, song_item.getAlbumId());
            Picasso.with(Player.this)
                    .load(albumArtUri)
                    .resize(800,800)
                    .into(mImgAlbumArt);

        } catch (IOException e) {
            e.getMessage();
            Toast.makeText(Player.this, "Error!!", Toast.LENGTH_SHORT).show();
        }
    }
}



미리 정해진 xml 을 메뉴로 만드는 방법

https://www.tutlane.com/tutorial/android/android-popup-menu-with-examples


https://www.javatpoint.com/android-popup-menu-example 를 참조하면 도움된다.


블로그 이미지

Link2Me

,
728x90

구글에서 Android Audio Player code 를 입력하여 검색하면 가장 먼저 검색되는 것이 https://www.androidhive.info/2012/03/android-building-audio-player-tutorial/ 게시글이다.

구글 계정을 허용하고 파일을 다운로드하면 Eclipse 기반 소스코드가 다운로드된다.

이 코드를 실행하면 바로 에러가 발생하면서 어플이 죽어버린다.


오디오 플레이 코드 구현을 통해서 기능을 익히고자 검색하고 몇가지 어플 코드를 참조하여 만들어보고 있는 중이다.


현재까지 구현한 APK 파일

linkaudioplayer-release.apk


설계 관점

1. 스마트폰에 있는 모든 음악파일을 검색해서 Custom ListView 에 보여준다.

   RecyclerView 가 ListView 의 단점을 개선한 ListView 인데 이 글 마지막 부분에 링크된 걸 참조하라.

2. Custom ListView 에 곡 정보를 클릭하면 AudioPlayer가 바로 재생된다.

    곡을 길게 클릭하면 전체선택, 선택한 리스트만 AudioPlayer ArrayList 에 담아서

    순차재생, 특정곡 반복재생, 랜덤재생을 할 수 있도록 한다.

3. 재생, 잠시멈춤, 5초 앞으로, 5초 뒤로, 이전곡, 다음곡 버튼을 제공한다.

    위 링크글에 구현되어 있으니 활용하면 된다.

4. 전화를 받으면 자동으로 재생되던 음악이 멈추고 통화가 끝나면 다시 음악이 재생된다.



1. 스마트폰에 있는 음악파일 리스트 가져오기

private void loadAudioList() {
     ContentResolver contentResolver = getContentResolver();
     // 음악 앱의 데이터베이스에 접근해서 mp3 정보들을 가져온다.

     Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
     String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0";
     String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
     Cursor cursor = contentResolver.query(uri, null, selection, null, sortOrder);
     cursor.moveToFirst();
     System.out.println("음악파일 개수 = " + cursor.getCount());
     if (cursor != null && cursor.getCount() > 0) {
         do {
             long track_id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));
             long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
             String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
             String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
             String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
             Integer mDuration = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
             String datapath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
             System.out.println("mId = " + track_id + " albumId = " + albumId + " title : " + title + " album : " + album + " artist: " + artist + " 총시간 : " + mDuration + " data : " + data);
             // Save to audioList
             listViewAdapter.addItem(track_id, albumId, title, artist, album, mDuration, datapath, false);
         } while (cursor.moveToNext());
     }
     cursor.close();
 }


ContentResolver 를 통해서 음악파일을 가져온다.

가져온 음악파일을 ArrayList 에 추가한다.

Song_Item 에 checkBoxState boolean 을 넣은 이유는 선택한 곡만 true, false 하기 위해서다.


 import java.io.Serializable;

public class Song_Item implements Serializable {
    private long mId; // 오디오 고유 ID
    private long AlbumId; // 오디오 앨범아트 ID
    private String Title; // 타이틀 정보
    private String Artist; // 아티스트 정보
    private String Album; // 앨범 정보
    private Integer Duration; // 재생시간
    private String DataPath; // 실제 데이터 위치
    boolean checkBoxState;

    public Song_Item() {
    }

    public Song_Item(long mId, long albumId, String title, String artist, String album, Integer duration, String dataPath, boolean checkBoxState) {
        this.mId = mId;
        AlbumId = albumId;
        Title = title;
        Artist = artist;
        Album = album;
        Duration = duration;
        DataPath = dataPath;
        this.checkBoxState = checkBoxState;
    }

    public long getmId() {
        return mId;
    }

    public void setmId(long mId) {
        this.mId = mId;
    }

    public long getAlbumId() {
        return AlbumId;
    }

    public void setAlbumId(long albumId) {
        AlbumId = albumId;
    }

    public String getTitle() {
        return Title;
    }

    public void setTitle(String title) {
        Title = title;
    }

    public String getArtist() {
        return Artist;
    }

    public void setArtist(String artist) {
        Artist = artist;
    }

    public String getAlbum() {
        return Album;
    }

    public void setAlbum(String album) {
        Album = album;
    }

    public Integer getDuration() {
        return Duration;
    }

    public void setDuration(Integer duration) {
        Duration = duration;
    }

    public String getDataPath() {
        return DataPath;
    }

    public void setDataPath(String dataPath) {
        DataPath = dataPath;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

    public void setCheckBoxState(boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
}



listViewAdapter.addItem(track_id, albumId, title, artist, album, mDuration, datapath, false);

  // 음악 데이터 추가를 위한 메소드
 public void addItem(long mId, long AlbumId, String Title, String Artist, String Album, Integer Duration, String DataPath, boolean checkItem_flag) {
     Song_Item item = new Song_Item();
     item.setmId(mId);
     item.setAlbumId(AlbumId);
     item.setTitle(Title);
     item.setArtist(Artist);
     item.setAlbum(Album);
     item.setDuration(Duration);
     item.setDataPath(DataPath);
     item.setCheckBoxState(checkItem_flag);
     songsList.add(item);
 }


private ListView audiolistView; // 리스트뷰
private ArrayList<Song_Item> songsList = null; // 음악 전체 데이터 리스트
private ArrayList<Song_Item> playList = new ArrayList<>(); // 선택한 음악 데이터 리스트
private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter


전체 선택, 선택 해제 버튼 구현 코드

// all checkbox
checkAll = (CheckBox) findViewById(R.id.lv_checkbox_all);
checkAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (checkAll.isChecked() == true) {
            isAll = true;
            listViewAdapter.setAllChecked(checkAll.isChecked());
            listViewAdapter.notifyDataSetChanged();
        } else {
            isAll = false;
            listViewAdapter.setAllChecked(checkAll.isChecked());
            listViewAdapter.notifyDataSetChanged();
        }
    }
});

final Button calcel = (Button) findViewById(R.id.btn_cancle);
calcel.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        isMSG = false;
        relative1.setVisibility(View.GONE);
        listViewAdapter.setAllChecked(false);
        checkAll.setChecked(false); // 전체 선택 체크박스 해제
        listViewAdapter.notifyDataSetChanged();
    }
});

 Button send = (Button) findViewById(R.id.btn_send); // 선택한 곡 Player.class 로 전송
send.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        playList.clear();
        for(int i = 0; i< songsList.size(); i++){
            if(songsList.get(i).isCheckBoxState() == true){
                PlayList(songsList.get(i).getmId(), songsList.get(i).getAlbumId(), songsList.get(i).getTitle(), songsList.get(i).getArtist(),
                        songsList.get(i).getAlbum(), songsList.get(i).getDuration(), songsList.get(i).getDataPath());
            }
        }

        if(playList.size() ==0){
            Toast.makeText(MainActivity.this, "선택한 리스트가 없습니다.", Toast.LENGTH_SHORT).show();
            return;
        } else {
            Intent intent = new Intent(MainActivity.this, Player.class);
            intent.putExtra("playList", playList); // 배열 데이터
            startActivity(intent);

            calcel.performClick(); // 버튼 강제로 자동 실행
        }
    }
});


// 음악 재생 데이터 추가를 위한 메소드
public void PlayList(long mId, long AlbumId, String Title, String Artist, String Album, Integer Duration, String DataPath) {
    Song_Item item = new Song_Item();
    item.setmId(mId);
    item.setAlbumId(AlbumId);
    item.setTitle(Title);
    item.setArtist(Artist);
    item.setAlbum(Album);
    item.setDuration(Duration);
    item.setDataPath(DataPath);
    playList.add(item);
}


Player.Class 주요 코드


// 배열로 넘긴 데이터를 배열로 받는 방법
private ArrayList<Song_Item> list;
Intent intent = getIntent();
list =(ArrayList<Song_Item>) intent.getSerializableExtra("playList");

private int position = 0; // 현재 재생곡 위치
playMusic(list.get(position)); // 노래 곡

public void playMusic(Song_Item song_item) {
    try {
        songTitleLabel.setText(song_item.getTitle());
        mediaPlayer.reset();
        mediaPlayer.setDataSource(song_item.getDataPath());
        mediaPlayer.prepare();
        mediaPlayer.start(); // 노래 재생 시작

        if(mediaPlayer.isPlaying()){
            btnPlay.setVisibility(View.GONE);
            btnPause.setVisibility(View.VISIBLE);
        } else {
            btnPlay.setVisibility(View.VISIBLE);
            btnPause.setVisibility(View.GONE);
        }

        /* Album Art Bitmap을 얻는다. */
        final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
        ImageView mImgAlbumArt = (ImageView) findViewById(R.id.albumart);
        Uri albumArtUri = ContentUris.withAppendedId(artworkUri, song_item.getAlbumId());
        Picasso.with(Player.this)
                .load(albumArtUri)
                .resize(800,800)
                .into(mImgAlbumArt);

        // set Progress bar values
        songProgressBar.setProgress(0);
        songProgressBar.setMax(100);

        // Updating progress bar
        updateProgressBar();

    } catch (IOException e) {
        e.getMessage();
        Toast.makeText(Player.this, "Error!!", Toast.LENGTH_SHORT).show();
    }
}


Albumart 이미지를 얻는 방법

1

 ImageView mImgAlbumArt = (ImageView) findViewById(R.id.albumart);
 Bitmap albumArt = getArtworkQuick(Player.this, (int) song_item.getAlbumId(), 800, 800);
 mImgAlbumArt.setImageBitmap(albumArt);

 2

 final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
 ImageView mImgAlbumArt = (ImageView) findViewById(R.id.albumart);
 Uri albumArtUri = ContentUris.withAppendedId(artworkUri, song_item.getAlbumId());
 Picasso.with(Player.this).load(albumArtUri).into(mImgAlbumArt);


검색을 하면 getArtworkQuick 메소드를 이용해서 하는 방법이 검색될 것이다.

이걸로 컴파일한 것이 폰이 낮은 버전에서 제대로 동작이 안되는 증상(?)이 있었다.


2번째 방법으로 하면 쉽게 해결된다.

Picasso(피카소)는 안드로이드를 위한 이미지 라이브러리다.
이것은 Square에 의해 만들어졌고, 이미지를 불러오거나 처리할 수 있도록 해준다.
피카소는 이미지를 디스플레이하는 과정을 외부 장소를 통해 단순화 시킨다.
이 유용한 라이브러리를 이용하면 고작 몇 줄로 이미지를 로딩할 수 있다.
build.gradle 파일 안에 dependency 코드 안에 compile 'com.squareup.picasso:picasso:2.5.2' 을 추가하면 자동 설치된다.


SeekBar 에 대한 것은 구글링을 해서 코드를 좀 찾아서 수정하거나
https://www.androidhive.info/2012/03/android-building-audio-player-tutorial/ 에 첨부파일 참조해서 구현하면 된다.



public void updateProgressBar() {
    seekHandler.postDelayed(mUpdateTimeTask, 100);
}

private Runnable mUpdateTimeTask = new Runnable() {
    public void run() {
        if(mediaPlayer != null){
            long totalDuration = mediaPlayer.getDuration();
            long currentDuration = mediaPlayer.getCurrentPosition();

            // 총 재생시간 화면 표시
            songTotalDurationLabel.setText("" + UtilitiesHelper.milliSecondsToTimer(totalDuration));
            // 현 재생시간 화면 표시
            songCurrentDurationLabel.setText("" + UtilitiesHelper.milliSecondsToTimer(currentDuration));

            // Updating progress bar
            int progress = (int)(UtilitiesHelper.getProgressPercentage(currentDuration, totalDuration));
            songProgressBar.setProgress(progress);

            // Running this thread after 100 milliseconds
            seekHandler.postDelayed(this, 100);
        }
    }
};

SeekBar.OnSeekBarChangeListener seekbarListener = new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        try {
            if(mediaPlayer.isPlaying() || mediaPlayer != null){
                if(fromUser)
                    mediaPlayer.seekTo(progress);
            } else if(mediaPlayer == null){
                Toast.makeText(getApplicationContext(), "Media is not running", Toast.LENGTH_SHORT).show();
                seekBar.setProgress(0);
            }
            if (seekBar.getMax()==progress) {
                songProgressBar.setProgress(0);
                long currentDuration = 0;
                songCurrentDurationLabel.setText("" + UtilitiesHelper.milliSecondsToTimer(currentDuration));
            }
        } catch (Exception e){
            Log.e("seek bar", "" + e);
            seekBar.setEnabled(false);
        }

    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        mediaPlayer.pause(); // 손으로 트랙바를 움직이는 동안 재생 중지
        seekHandler.removeCallbacks(mUpdateTimeTask);
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        seekHandler.removeCallbacks(mUpdateTimeTask);
        int totalDuration = mediaPlayer.getDuration();
        int currentPosition = UtilitiesHelper.progressToTimer(seekBar.getProgress(), totalDuration);

        // 손으로 움직인 지점, forward, backward 지점부터 재생
        mediaPlayer.seekTo(currentPosition);
        if(seekBar.getProgress()>0 ){
            mediaPlayer.start();
        }
        // update timer progress again
        updateProgressBar();
    }
};

 mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {

        seekHandler.removeCallbacks(mUpdateTimeTask);
        if(isRepeat){ // 반복재생
            playMusic(list.get(position));
        } else if(isShuffle){ // 랜덤재생
            Random rand = new Random();
            position = rand.nextInt((list.size() - 1) - 0 + 1) + 0;
            playMusic(list.get(position));
        } else {
            if(position + 1 < list.size()) {
                position++;
                playMusic(list.get(position));
            } else {
                Log.e("Music_Off","음악종료");               
                position = 0;
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

                acquireCPUWakeLock(mContext);

                releaseCpuLock();
            }
        }

    }

});


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.audioplayer">

    <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:noHistory="true">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>


ListView 방식을 RecyclerView 방식으로 변경해보면서 변경할 부분에 중점적으로 적어둔 것은 http://link2me.tistory.com/1374 에 있다.


앞으로 분석하면 도움이 될 사항이라 링크를 적어둔다.

https://github.com/googlesamples/android-UniversalMusicPlayer

블로그 이미지

Link2Me

,
728x90

Android Audio Player 관련 기능을 테스트 해보고 있는 중이다.

어떤 방식으로 하느냐에 따라 구현할 내용이 달라질 거 같은데 방향 잡기가 쉽지 않다.


먼저 AndroidManifest.xml file 에 권한을 추가한다.

<uses-permission android:name="android.permission.INTERNET" />
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


이미 안드로이드에서는 음악, 주소록같은 기본적인 어플리케이션이 존재한다.
이런 기본적인 앱안에는 정보들을 담은 DB가 구현되어 있다.
이런 앱들은 자신의 앱의 데이터베이스에 접근할 수 있도록 도와주는 컨텐트 프로바이더(Content Provider)란 것을 제공한다.
앱이 ContentProvider를 접근할 때에는 ContentResolver를 이용한다.
ContentResolver는 기본적으로 CRUD(Create Retreive Update Delete) 함수들을 제공하며 이를 통해 다른 어플리케이션에 있는 데이터베이스를 조작할 수 있다.


휴대폰에 있는 오디오 파일을 읽어오는 함수는 아래와 같다.

private void loadAudio() {
    ContentResolver contentResolver = getContentResolver();

    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0";
    String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
    Cursor cursor = contentResolver.query(uri, null, selection, null, sortOrder);
    cursor.moveToFirst();
    System.out.println("음악파일 개수 = " + cursor.getCount());
    if (cursor != null && cursor.getCount() > 0) {
        do {
            long track_id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));
            long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
            String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
            String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
            String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
            long mDuration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
            String datapath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
            System.out.println("mId = " + track_id + " albumId = " +albumId+ " title : "+title+" album : "+album+" artist: "+artist+" 총시간 : "+mDuration+" data : "+data);
            // Save to audioList
            listViewAdapter.addItem(track_id,albumId,title,artist,album,mDuration,datapath,false);
        } while (cursor.moveToNext());
    }
    cursor.close();
}



listViewAdapter.addItem 을 위한 정의서인 Song_Item 클래스를 만든다.

담을 내용을 정하는 것은 개발자 몫이다.

public class Song_Item {
    private long mId; // 오디오 고유 ID
    private long AlbumId; // 오디오 앨범아트 ID
    private String Title; // 타이틀 정보
    private String Artist; // 아티스트 정보
    private String Album; // 앨범 정보
    private long Duration; // 재생시간
    private String DataPath; // 실제 데이터 위치
    boolean checkBoxState;

    public Song_Item() {
    }

    public Song_Item(long mId, long albumId, String title, String artist, String album, long duration, String dataPath, boolean checkBoxState) {
        this.mId = mId;
        AlbumId = albumId;
        Title = title;
        Artist = artist;
        Album = album;
        Duration = duration;
        DataPath = dataPath;
        this.checkBoxState = checkBoxState;
    }

    public long getmId() {
        return mId;
    }

    public void setmId(long mId) {
        this.mId = mId;
    }

    public long getAlbumId() {
        return AlbumId;
    }

    public void setAlbumId(long albumId) {
        AlbumId = albumId;
    }

    public String getTitle() {
        return Title;
    }

    public void setTitle(String title) {
        Title = title;
    }

    public String getArtist() {
        return Artist;
    }

    public void setArtist(String artist) {
        Artist = artist;
    }

    public String getAlbum() {
        return Album;
    }

    public void setAlbum(String album) {
        Album = album;
    }

    public long getDuration() {
        return Duration;
    }

    public void setDuration(long duration) {
        Duration = duration;
    }

    public String getDataPath() {
        return DataPath;
    }

    public void setDataPath(String dataPath) {
        DataPath = dataPath;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

    public void setCheckBoxState(boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
}


MainActivity.java 에서 구현할 내용 일부를 발췌하여 적어둔다.

ListViewAdapter 구현 내용이라고 보면 된다.

ListView 상에 가져오는 코드를 작성했는데 Player.java 코드를 아직 제대로 구현하지 못한 상태다.

구글링해서 얻은 코드들을 발췌하고 내가 원하는 기능으로 구현해보는 중이다.

구현하려는 방법이 달라서 기존 코드를 그대로 사용하지는 않았다.

일부 코드는 그대로 활용해도 너무 좋은 코드가 있어서 그냥 사용한 부분도 있다.


ListViewAdapter 에 아무것도 담지 않은 방법으로 먼저 생성한 다음에 loadAudio() 을 읽어들이는 방식은 장점이 있다. loadAudio() 을 전체 오디오 파일을 가져오게 할 수도 있지만 원하는 것만 검색해서 가져오게 하는 방법도 가능하다. 즉 검색기능을 활용하여 데이터를 가져오는 방법이 가능하여 편리하여 이런 방법을 사용하고 있다.

이 부분은 개발자마다 코드 구현 방식이 다른 것을 구글링을 해보면 알 수 있다.


 public class MainActivity extends AppCompatActivity {
    private ListView audiolistView; // 리스트뷰
    private ArrayList<Song_Item> songsList = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

    Context context;

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

        context = this.getBaseContext();

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        songsList = new ArrayList<Song_Item>(); // ArrayList 생성
        audiolistView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        audiolistView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        audiolistView.setOnItemClickListener(songslistener);

        // 체크박스 보임/안보임 관련 코드는 생략했음...

        loadAudio();

    }

    class ViewHolder {
        public ImageView mImgAlbumArt;
        public TextView mTitle;
        public TextView mSubTitle;
        public TextView mDuration;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        Context context;

        public ListViewAdapter(Context context) {
            this.context = context;
        }

        // 음악 데이터 추가를 위한 메소드
        public void addItem(long mId, long AlbumId, String Title, String Artist, String Album,long Duration,String DataPath, boolean checkItem_flag) {
            Song_Item item = new Song_Item();
            item.setmId(mId);
            item.setAlbumId(AlbumId);
            item.setTitle(Title);
            item.setArtist(Artist);
            item.setAlbum(Album);
            item.setDuration(Duration);
            item.setDataPath(DataPath);
            item.setCheckBoxState(checkItem_flag);
            songsList.add(item);
        }

        // CheckBox를 모두 선택하는 메서드
        public void setAllChecked(final boolean ischeked) {
            final int tempSize = songsList.size();
            if(isAll == true){
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(true);
                }
            } else {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(false);
                }
            }
        }

        @Override
        public int getCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return songsList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final Integer index = Integer.valueOf(position);

            // 화면에 표시될 View
            if(convertView == null){
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.song_item,parent,false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.mImgAlbumArt = (ImageView) convertView.findViewById(R.id.album_Image);
                viewHolder.mTitle = (TextView) convertView.findViewById(R.id.txt_title);
                viewHolder.mSubTitle = (TextView) convertView.findViewById(R.id.txt_subtitle);
                viewHolder.mDuration = (TextView) convertView.findViewById(R.id.txt_duration);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final Song_Item songItem = songsList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            Bitmap albumArt = MainActivity.getArtworkQuick(context, (int)songItem.getAlbumId(), 100, 100);
            if(albumArt != null){
                viewHolder.mImgAlbumArt.setImageBitmap(albumArt);
            }
            viewHolder.mTitle.setText(songItem.getTitle());
            viewHolder.mSubTitle.setText(songItem.getArtist());

            int dur = (int) songItem.getDuration();
            int hrs = (dur / 3600000);
            int mns = (dur / 60000) % 60000;
            int scs = dur % 60000 / 1000;
            String songTime = String.format("%02d:%02d:%02d", hrs,  mns, scs);
            if(hrs == 0){
                songTime = String.format("%02d:%02d", mns, scs);
            }
            viewHolder.mDuration.setText(songTime);

            if (isMSG == false) {
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(), "노래 재생합니다" + songItem.getAlbumId(), Toast.LENGTH_SHORT).show();

                        Intent intent = new Intent(MainActivity.this, Player.class);
                        intent.putExtra("path", songItem.getDataPath());
                        intent.putExtra("mTitle", songItem.getTitle());
                        intent.putExtra("mDuration", songItem.getDuration());
                        startActivity(intent);
                    }
                });
            } else {
                if (isMSG == true) {
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.

                    viewHolder.checkbox.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if(songsList.get(position).isCheckBoxState() == true){
                                songsList.get(position).setCheckBoxState(false);
                            } else {
                                songsList.get(position).setCheckBoxState(true);
                            }
                        }
                    });

                    convertView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if(viewHolder.checkbox.isChecked() == false){
                                viewHolder.checkbox.setChecked(true);
                                songsList.get(position).setCheckBoxState(true);
                            } else {
                                viewHolder.checkbox.setChecked(false);
                                songsList.get(position).setCheckBoxState(false);
                            }
                        }
                    });

                }
            }

            // 재사용 문제 해결
            if(songsList.get(position).isCheckBoxState() == true){
                viewHolder.checkbox.setChecked(true);
            } else {
                viewHolder.checkbox.setChecked(false);
            }

            return convertView;
        }

    }

    /* Album ID로 부터 Bitmap 이미지를 생성해 리턴해 주는 메소드 */
    private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
    private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");

    // Get album art for specified album. This method will not try to
    // fall back to getting artwork directly from the file, nor will it attempt to repair the database.
    private static Bitmap getArtworkQuick(Context context, int album_id, int w, int h) {
        w -= 2;
        h -= 2;
        ContentResolver res = context.getContentResolver();
        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
        if (uri != null) {
            ParcelFileDescriptor fd = null;
            try {
                fd = res.openFileDescriptor(uri, "r");
                int sampleSize = 1;

                sBitmapOptionsCache.inJustDecodeBounds = true;
                BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, sBitmapOptionsCache);
                int nextWidth = sBitmapOptionsCache.outWidth >> 1;
                int nextHeight = sBitmapOptionsCache.outHeight >> 1;
                while (nextWidth>w && nextHeight>h) {
                    sampleSize <<= 1;
                    nextWidth >>= 1;
                    nextHeight >>= 1;
                }

                sBitmapOptionsCache.inSampleSize = sampleSize;
                sBitmapOptionsCache.inJustDecodeBounds = false;
                Bitmap b = BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, sBitmapOptionsCache);

                if (b != null) {
                    // finally rescale to exactly the size we need
                    if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) {
                        Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
                        b.recycle();
                        b = tmp;
                    }
                }

                return b;
            } catch (FileNotFoundException e) {
            } finally {
                try {
                    if (fd != null)
                        fd.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

}


song_item.xml 파일과 activity_main.xml 파일은 첨부파일로 올린다.

layout.zip


이런 형태로 데이터를 가져온 모습을 볼 수 있게 된다.


Player.java 파일 구현할 내용에 따라 넘겨줄 내용이 달라지므로 그 부분까지 완성되면 코드 전체를 첨부해볼 생각이다.

아직은 배워야 할 기능들이 많아서 완성된 코드까지는 많은 시간이 걸릴 것 같다.


두고 두고 읽어보고 싶은 블로그 글을 검색으로 알게되어 적어둔다. 나같은 초보에겐 주옥같은 글이다.

http://unikys.tistory.com/350


앞으로 참고할 사이트

https://www.sitepoint.com/a-step-by-step-guide-to-building-an-android-audio-player-app/


블로그 이미지

Link2Me

,