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

,