안드로이드 음악/비디오 플레이어 구현하기 🎵🎥
안녕, 친구들! 오늘은 정말 재미있는 주제로 이야기를 나눠볼 거야. 바로 안드로이드에서 음악과 비디오 플레이어를 만드는 방법에 대해서 말이야. 🚀 이 글을 다 읽고 나면, 너도 나만의 멋진 미디어 플레이어 앱을 만들 수 있을 거야!
우리가 매일 사용하는 스마트폰에서 음악을 듣고 동영상을 보는 건 이제 너무나 당연한 일이 됐지? 그런데 이런 앱들이 어떻게 만들어지는지 궁금해본 적 있어? 오늘 우리는 그 비밀을 파헤쳐볼 거야. Java를 사용해서 안드로이드 앱을 만드는 과정을 통해, 프로그래밍의 매력에 푹 빠져보자고!
참고로, 이런 재미있는 프로그래밍 지식을 더 많이 얻고 싶다면 재능넷(https://www.jaenung.net)을 방문해보는 것도 좋아. 거기서는 이런 프로그래밍 지식뿐만 아니라 다양한 분야의 재능을 공유하고 거래할 수 있거든. 자, 이제 본격적으로 시작해볼까?
🔑 핵심 포인트:
- 안드로이드 미디어 플레이어 기본 개념 이해하기
- 음악 플레이어 구현하기
- 비디오 플레이어 구현하기
- 사용자 인터페이스(UI) 디자인하기
- 고급 기능 추가하기
1. 안드로이드 미디어 플레이어의 기본 🎼
자, 먼저 안드로이드에서 미디어 플레이어를 만들기 위한 기본적인 개념부터 알아보자. 안드로이드는 미디어 재생을 위해 MediaPlayer 클래스를 제공해. 이 클래스 하나로 음악과 비디오를 모두 재생할 수 있어. 멋지지 않아? 😎
MediaPlayer는 상태 기반 시스템이야. 이게 무슨 말이냐고? 간단히 말해서, MediaPlayer는 여러 가지 상태를 가지고 있고, 각 상태에 따라 할 수 있는 동작이 정해져 있다는 거야. 예를 들면, 음악이 재생 중일 때는 일시정지나 정지를 할 수 있지만, 이미 정지된 상태에서는 일시정지를 할 수 없겠지?
위의 다이어그램을 보면 MediaPlayer의 상태 변화를 한눈에 볼 수 있어. Idle 상태에서 시작해서 Initialized, Prepared 상태를 거쳐 Started 상태가 되면 미디어가 재생되는 거지. 그리고 Paused나 Stopped 상태로 갈 수도 있고, 다시 Started 상태로 돌아올 수도 있어.
이제 MediaPlayer의 주요 메소드들을 살펴볼까?
setDataSource()
: 재생할 미디어 파일의 위치를 지정해.prepare()
또는prepareAsync()
: 미디어 재생을 위한 준비를 해.start()
: 미디어 재생을 시작해.pause()
: 재생을 일시 중지해.stop()
: 재생을 완전히 멈춰.seekTo()
: 특정 위치로 재생 위치를 이동해.release()
: MediaPlayer 객체를 해제하고 자원을 반환해.
이 메소드들을 잘 조합하면 멋진 미디어 플레이어를 만들 수 있어! 예를 들어, 음악을 재생하려면 이런 순서로 코드를 작성하면 돼:
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource("파일경로 또는 URL");
mediaPlayer.prepare();
mediaPlayer.start();
간단하지? 하지만 실제 앱에서는 이것보다 훨씬 더 복잡한 로직이 필요해. 에러 처리도 해야 하고, 사용자 인터페이스와 연동도 해야 하거든. 그래도 걱정하지 마! 우리가 차근차근 알아볼 거니까. 😉
💡 Tip: MediaPlayer를 사용할 때는 항상 release()
메소드를 호출해서 자원을 해제하는 것을 잊지 마! 그렇지 않으면 메모리 누수가 발생할 수 있어.
자, 이제 기본적인 개념은 알았으니 본격적으로 음악 플레이어를 만들어볼까? 다음 섹션에서 자세히 알아보자!
2. 음악 플레이어 구현하기 🎵
이제 본격적으로 음악 플레이어를 만들어볼 거야. 재능넷에서 배운 프로그래밍 실력을 발휘할 때야! 😎 먼저, 새로운 안드로이드 프로젝트를 만들고 시작해보자.
2.1 프로젝트 설정
안드로이드 스튜디오를 열고 새 프로젝트를 만들어. 프로젝트 이름은 "MyMusicPlayer"로 하고, 패키지 이름은 "com.example.mymusicplayer"로 설정해보자. 최소 SDK는 API 21 (Android 5.0 Lollipop)으로 설정하면 돼.
프로젝트가 생성되면, AndroidManifest.xml
파일을 열고 다음 권한을 추가해:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
이 권한은 기기의 저장소에서 음악 파일을 읽어오기 위해 필요해.
2.2 사용자 인터페이스 만들기
이제 음악 플레이어의 UI를 만들어볼 거야. activity_main.xml
파일을 열고 다음과 같이 수정해:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/songTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No song selected"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/prevButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prev" />
<Button
android:id="@+id/playButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" />
<Button
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next" />
</LinearLayout>
<ListView
android:id="@+id/songListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="16dp" />
</LinearLayout>
이 레이아웃은 상단에 현재 재생 중인 곡 제목을 표시하는 TextView, 재생 진행률을 보여주는 SeekBar, 재생 제어 버튼들, 그리고 곡 목록을 보여주는 ListView로 구성되어 있어.
위 그림은 우리가 만든 UI의 대략적인 모습이야. 실제로 앱을 실행하면 이것보다 더 멋진 모습이 될 거야! 😉
2.3 음악 파일 불러오기
이제 기기에 저장된 음악 파일들을 불러와야 해. 이를 위해 MainActivity.java
파일에 다음 메소드를 추가해:
private List<String> getSongList() {
List<String> songList = new ArrayList<>();
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);
if (cursor != null && cursor.moveToFirst()) {
int titleColumn = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
do {
String title = cursor.getString(titleColumn);
songList.add(title);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songList;
}
이 메소드는 ContentResolver를 사용해 기기의 미디어 스토어에서 음악 파일들의 정보를 가져와. 그리고 그 정보를 List<String> 형태로 반환해.
2.4 MediaPlayer 설정
이제 MediaPlayer를 설정하고 음악을 재생할 수 있도록 해보자. MainActivity.java
에 다음 코드를 추가해:
private MediaPlayer mediaPlayer;
private List<String> songList;
private int currentSongIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
songList = getSongList();
mediaPlayer = new MediaPlayer();
Button playButton = findViewById(R.id.playButton);
Button prevButton = findViewById(R.id.prevButton);
Button nextButton = findViewById(R.id.nextButton);
SeekBar seekBar = findViewById(R.id.seekBar);
ListView songListView = findViewById(R.id.songListView);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, songList);
songListView.setAdapter(adapter);
playButton.setOnClickListener(v -> {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
playButton.setText("Play");
} else {
mediaPlayer.start();
playButton.setText("Pause");
}
});
prevButton.setOnClickListener(v -> playPreviousSong());
nextButton.setOnClickListener(v -> playNextSong());
songListView.setOnItemClickListener((parent, view, position, id) -> {
currentSongIndex = position;
playSong();
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mediaPlayer.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
private void playSong() {
try {
mediaPlayer.reset();
Uri songUri = getSongUri(songList.get(currentSongIndex));
mediaPlayer.setDataSource(getApplicationContext(), songUri);
mediaPlayer.prepare();
mediaPlayer.start();
updateUI();
} catch (IOException e) {
e.printStackTrace();
}
}
private void playPreviousSong() {
if (currentSongIndex > 0) {
currentSongIndex--;
} else {
currentSongIndex = songList.size() - 1;
}
playSong();
}
private void playNextSong() {
if (currentSongIndex < songList.size() - 1) {
currentSongIndex++;
} else {
currentSongIndex = 0;
}
playSong();
}
private void updateUI() {
TextView songTitleTextView = findViewById(R.id.songTitleTextView);
songTitleTextView.setText(songList.get(currentSongIndex));
Button playButton = findViewById(R.id.playButton);
playButton.setText("Pause");
SeekBar seekBar = findViewById(R.id.seekBar);
seekBar.setMax(mediaPlayer.getDuration());
seekBar.setProgress(0);
new Thread(() -> {
while (mediaPlayer != null) {
try {
if (mediaPlayer.isPlaying()) {
int currentPosition = mediaPlayer.getCurrentPosition();
seekBar.setProgress(currentPosition);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private Uri getSongUri(String songTitle) {
ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0 AND " + MediaStore.Audio.Media.TITLE + " = ?";
String[] selectionArgs = new String[]{songTitle};
Cursor cursor = contentResolver.query(uri, null, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int idColumn = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
long id = cursor.getLong(idColumn);
cursor.close();
return ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
}
return null;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
우와, 코드가 꽤 길지? 하나씩 설명해줄게:
onCreate()
메소드에서는 UI 요소들을 초기화하고 클릭 리스너를 설정해.playSong()
메소드는 선택된 곡을 재생해.playPreviousSong()
과playNextSong()
메소드는 이전 곡과 다음 곡을 재생해.updateUI()
메소드는 UI를 업데이트하고, SeekBar를 현재 재생 위치에 맞게 업데이트해.getSongUri()
메소드는 곡 제목을 이용해 해당 곡의 URI를 가져와.onDestroy()
메소드에서는 MediaPlayer 자원을 해제해.
이렇게 하면 기본적인 음악 플레이어 기능이 완성돼! 곡 목록을 보여주고, 곡을 선택해 재생하고, 일시정지하고, 이전/다음 곡으로 넘어갈 수 있어. 또한 SeekBar를 통해 재생 위치를 조절할 수도 있지.
2.5 추가 기능 구현하기
기본 기능은 완성됐지만, 좀 더 멋진 음악 플레이어를 만들기 위해 몇 가지 기능을 더 추가해보자!
2.5.1 재생 모드 설정
반복 재생이나 랜덤 재생 같은 재생 모드를 추가해보자. MainActivity.java
에 다음 코드를 추가해:
private enum PlayMode {
NORMAL, REPEAT_ONE, REPEAT_ALL, SHUFFLE
}
private PlayMode currentPlayMode = PlayMode.NORMAL;
private Random random = new Random();
private void togglePlayMode() {
switch (currentPlayMode) {
case NORMAL:
currentPlayMode = PlayMode.REPEAT_ONE;
Toast.makeText(this, "Repeat One", Toast.LENGTH_SHORT).show();
break;
case REPEAT_ONE:
currentPlayMode = PlayMode.REPEAT_ALL;
Toast.makeText(this, "Repeat All", Toast.LENGTH_SHORT).show();
break;
case REPEAT_ALL:
currentPlayMode = PlayMode.SHUFFLE;
Toast.makeText(this, "Shuffle", Toast.LENGTH_SHORT).show();
break;
case SHUFFLE:
currentPlayMode = PlayMode.NORMAL;
Toast.makeText(this, "Normal", Toast.LENGTH_SHORT).show();
break;
}
}
private void playNextSong() {
switch (currentPlayMode) {
case NORMAL:
case REPEAT_ALL:
if (currentSongIndex < songList.size() - 1) {
currentSongIndex++;
} else {
currentSongIndex = 0;
}
break;
case REPEAT_ONE:
// Do nothing, it will replay the same song
break;
case SHUFFLE:
currentSongIndex = random.nextInt(songList.size());
break;
}
playSong();
}
그리고 UI에 재생 모드를 토글하는 버튼을 추가하고, 이 버튼에 togglePlayMode()
메소드를 연결해주면 돼.
2.5.2 앨범 아트 표시
음악 파일의 앨범 아트를 표시하면 더 멋진 UI를 만들 수 있어. 다음 메소드를 추가해봐:
private Bitmap getAlbumArt(long albumId) {
Bitmap bitmap = null;
try {
Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, albumId);
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
bitmap = BitmapFactory.decodeFileDescriptor(fd);
pfd.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
이 메소드를 사용해 앨범 아트를 가져오고, ImageView에 설정하면 돼.
2.5.3 이퀄라이저 추가
안드로이드는 기본적으로 이퀄라이저 기능을 제공해. 다음과 같이 이퀄라이저를 설정할 수 있어:
private Equalizer equalizer;
private void setupEqualizer() {
equalizer = new Equalizer(0, mediaPlayer.getAudioSessionId());
equalizer.setEnabled(true);
short bands = equalizer.getNumberOfBands();
for (short i = 0; i < bands; i++) {
short band = i;
int minEQLevel = equalizer.getBandLevelRange()[ 0];
int maxEQLevel = equalizer.getBandLevelRange()[1];
// 여기서 각 밴드에 대한 SeekBar를 생성하고 설정할 수 있어
SeekBar seekBar = new SeekBar(this);
seekBar.setMax(maxEQLevel - minEQLevel);
seekBar.setProgress(equalizer.getBandLevel(band));
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
equalizer.setBandLevel(band, (short) (progress + minEQLevel));
}
public void onStartTrackingTouch(SeekBar seekBar) {}
public void onStopTrackingTouch(SeekBar seekBar) {}
});
// SeekBar를 레이아웃에 추가
}
}
이렇게 하면 각 주파수 대역의 볼륨을 조절할 수 있는 이퀄라이저 기능이 추가돼. 멋지지 않아? 🎛️
2.6 마무리
자, 이제 우리만의 멋진 음악 플레이어가 완성됐어! 🎉 기본적인 재생 기능부터 재생 모드 설정, 앨범 아트 표시, 이퀄라이저까지 다양한 기능을 갖춘 플레이어를 만들었지. 이 코드를 바탕으로 더 많은 기능을 추가하거나 UI를 개선해볼 수 있을 거야.
예를 들어, 다음과 같은 기능들을 추가로 구현해볼 수 있어:
- 재생 목록 기능 (플레이리스트 생성, 편집, 저장)
- 음악 검색 기능
- 백그라운드 재생 및 알림 컨트롤
- 오디오 포커스 관리 (전화가 왔을 때 음악 일시정지 등)
- 음악 정보 편집 기능 (ID3 태그 편집)
이런 프로젝트를 통해 안드로이드 개발 실력을 크게 향상시킬 수 있어. MediaPlayer API, ContentResolver, 스레드 관리, UI 디자인 등 다양한 영역의 지식을 활용해볼 수 있거든. 계속해서 새로운 기능을 추가하고 개선해나가면서 더 멋진 앱을 만들어보자!
💡 Pro Tip: 실제 앱 개발에서는 아키텍처 패턴(예: MVVM)을 적용하고, 종속성 주입(Dependency Injection)을 사용하며, 단위 테스트를 작성하는 것이 좋아. 이런 방식으로 코드를 구조화하면 유지보수가 쉽고 확장성 있는 앱을 만들 수 있어.
3. 비디오 플레이어 구현하기 🎥
음악 플레이어를 만들어봤으니, 이제 비디오 플레이어를 만들어볼 차례야! 비디오 플레이어도 MediaPlayer를 사용하지만, 영상을 표시하기 위해 추가적인 컴포넌트가 필요해. 자, 시작해볼까?
3.1 레이아웃 만들기
먼저 비디오 플레이어를 위한 새로운 액티비티를 만들자. activity_video_player.xml
파일을 생성하고 다음과 같이 작성해:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:background="#80000000">
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<ImageButton
android:id="@+id/prevButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_media_previous"
android:background="?android:attr/selectableItemBackground" />
<ImageButton
android:id="@+id/playPauseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_media_play"
android:background="?android:attr/selectableItemBackground"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" />
<ImageButton
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_media_next"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
이 레이아웃은 화면 중앙에 VideoView를 배치하고, 하단에 컨트롤 버튼들을 배치해. 반투명한 배경을 사용해서 비디오 위에 컨트롤이 보이도록 했어.
3.2 VideoPlayerActivity 구현하기
이제 VideoPlayerActivity.java
파일을 만들고 다음과 같이 작성해:
public class VideoPlayerActivity extends AppCompatActivity {
private VideoView videoView;
private SeekBar seekBar;
private ImageButton playPauseButton;
private ImageButton prevButton;
private ImageButton nextButton;
private List<String> videoList;
private int currentVideoIndex = 0;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
videoView = findViewById(R.id.videoView);
seekBar = findViewById(R.id.seekBar);
playPauseButton = findViewById(R.id.playPauseButton);
prevButton = findViewById(R.id.prevButton);
nextButton = findViewById(R.id.nextButton);
videoList = getVideoList(); // 비디오 목록을 가져오는 메소드 (구현 필요)
videoView.setOnPreparedListener(mp -> {
seekBar.setMax(videoView.getDuration());
playVideo();
});
videoView.setOnCompletionListener(mp -> {
playNextVideo();
});
playPauseButton.setOnClickListener(v -> {
if (videoView.isPlaying()) {
pauseVideo();
} else {
playVideo();
}
});
prevButton.setOnClickListener(v -> playPreviousVideo());
nextButton.setOnClickListener(v -> playNextVideo());
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
playVideo();
}
private void playVideo() {
String videoPath = videoList.get(currentVideoIndex);
videoView.setVideoPath(videoPath);
videoView.start();
playPauseButton.setImageResource(android.R.drawable.ic_media_pause);
startProgressUpdate();
}
private void pauseVideo() {
videoView.pause();
playPauseButton.setImageResource(android.R.drawable.ic_media_play);
handler.removeCallbacks(updateProgressTask);
}
private void playNextVideo() {
if (currentVideoIndex < videoList.size() - 1) {
currentVideoIndex++;
} else {
currentVideoIndex = 0;
}
playVideo();
}
private void playPreviousVideo() {
if (currentVideoIndex > 0) {
currentVideoIndex--;
} else {
currentVideoIndex = videoList.size() - 1;
}
playVideo();
}
private void startProgressUpdate() {
handler.postDelayed(updateProgressTask, 100);
}
private Runnable updateProgressTask = new Runnable() {
@Override
public void run() {
seekBar.setProgress(videoView.getCurrentPosition());
handler.postDelayed(this, 100);
}
};
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(updateProgressTask);
}
// 비디오 목록을 가져오는 메소드 (구현 필요)
private List<String> getVideoList() {
// ContentResolver를 사용하여 기기의 비디오 파일 목록을 가져오는 로직 구현
// 음악 플레이어에서 했던 것과 유사한 방식으로 구현할 수 있어
return new ArrayList<>();
}
}
이 코드는 비디오 재생, 일시정지, 이전/다음 비디오 재생, 그리고 SeekBar를 통한 재생 위치 조절 등의 기능을 구현하고 있어. getVideoList()
메소드는 음악 플레이어에서 했던 것과 유사한 방식으로 구현하면 돼.
3.3 추가 기능 구현하기
기본적인 비디오 플레이어 기능은 구현했지만, 몇 가지 기능을 더 추가해서 더 멋진 플레이어를 만들어보자!
3.3.1 전체 화면 모드
비디오를 전체 화면으로 볼 수 있게 하자. 다음 메소드를 VideoPlayerActivity
에 추가해:
private void toggleFullscreen() {
if (getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_FULLSCREEN) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_VISIBLE
);
} else {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
이 메소드를 전체 화면 토글 버튼에 연결하면 돼.
3.3.2 화면 회전 처리
화면이 회전될 때 비디오 재생이 중단되지 않도록 처리하자. AndroidManifest.xml
의 VideoPlayerActivity
선언에 다음 속성을 추가해:
android:configChanges="orientation|screenSize|keyboardHidden"
그리고 VideoPlayerActivity
에 다음 메소드를 오버라이드해:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 화면 방향에 따라 VideoView의 크기를 조정
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
videoView.setLayoutParams(new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
videoView.setLayoutParams(new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
3.3.3 자막 지원
비디오에 자막을 추가할 수 있게 하자. 안드로이드는 SubtitleView
를 제공해. 먼저 레이아웃에 SubtitleView
를 추가해:
<com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
그리고 자막을 로드하고 표시하는 코드를 추가해:
private SubtitleView subtitleView;
private CaptioningManager captioningManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
subtitleView = findViewById(R.id.subtitleView);
captioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
// ...
}
private void loadSubtitles(String subtitlePath) {
try {
FileInputStream fis = new FileInputStream(subtitlePath);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
StringBuilder subtitleBuilder = new StringBuilder();
while ((line = br.readLine()) != null) {
subtitleBuilder.append(line).append('\n');
}
String subtitleContent = subtitleBuilder.toString();
// 여기서 subtitleContent를 파싱하고 자막을 표시하는 로직을 구현
// 예를 들어, SRT 형식의 자막을 파싱하고 타이밍에 맞춰 subtitleView에 표시
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
이 코드는 기본적인 자막 로딩 로직만 구현했어. 실제로는 자막 형식(SRT, WebVTT 등)에 맞는 파서를 구현하고, 비디오 재생 시간에 맞춰 자막을 표시하는 로직을 추가해야 해.
3.4 마무리
자, 이렇게 해서 기본적인 비디오 플레이어가 완성됐어! 🎉 음악 플레이어에 비해 화면 처리나 자막 같은 추가적인 요소들이 필요하지만, 기본 구조는 비슷하지? 이제 이 코드를 바탕으로 더 많은 기능을 추가하거나 UI를 개선해볼 수 있을 거야.
예를 들어, 다음과 같은 기능들을 추가로 구현해볼 수 있어:
- 화질 선택 기능 (가능한 경우)
- 재생 속도 조절 기능
- PIP(Picture-in-Picture) 모드 지원
- 비디오 정보 표시 (해상도, 코덱 등)
- 썸네일 생성 및 표시
이런 프로젝트를 통해 안드로이드의 멀티미디어 처리 능력을 크게 향상시킬 수 있어. VideoView, MediaController, 화면 방향 처리, 자막 처리 등 다양한 영역의 지식을 활용해볼 수 있거든. 계속해서 새로운 기능을 추가하고 개선해나가면서 더 멋진 앱을 만들어보자!
💡 Pro Tip: 실제 앱에서는 ExoPlayer 같은 고급 미디어 플레이어 라이브러리를 사용하는 것이 좋아. ExoPlayer는 더 많은 비디오 포맷을 지원하고, 스트리밍, DRM 등 고급 기능을 제공하거든. 게다가 커스터마이징도 더 쉽지!