Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

การใช้ MediaPlayer สร้าง Audio Player เบื้องต้นใน Android

ภาพรวม

ตัวอย่างนี้สาธิตการสร้าง Audio Player ใช้ MediaPlayer พร้อมควบคุม Play, Pause, Forward, Backward และ SeekBar แสดงความคืบหน้า

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

Android Studio → File → New Project → Empty Activity

ขั้นตอนที่ 2: เตรียมไฟล์เสียง

วางไฟล์เสียง bohemian_rhapsody.mp3 ในโฟลเดอร์ res/raw/ (สร้างโฟลเดอร์ raw หากยังไม่มี)

ขั้นตอนที่ 3: จัดรูปแบบหน้าจอ (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Now Playing: "
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/txtSongName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/textView"
        android:layout_toEndOf="@id/textView"
        android:textStyle="bold" />

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/controlPanel"
        android:layout_marginBottom="16dp" />

    <LinearLayout
        android:id="@+id/controlPanel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="44dp"
        android:orientation="horizontal"
        android:gravity="center">

        <ImageButton
            android:id="@+id/btnBackward"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/ic_skip_previous"
            android:contentDescription="Backward 5s"
            android:background="?attr/selectableItemBackgroundBorderless" />

        <ImageButton
            android:id="@+id/btnPlay"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginHorizontal="8dp"
            android:src="@drawable/ic_play_arrow"
            android:contentDescription="Play"
            android:background="?attr/selectableItemBackgroundBorderless" />

        <ImageButton
            android:id="@+id/btnPause"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/ic_pause"
            android:contentDescription="Pause"
            android:background="?attr/selectableItemBackgroundBorderless" />

        <ImageButton
            android:id="@+id/btnForward"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_marginStart="8dp"
            android:src="@drawable/ic_skip_next"
            android:contentDescription="Forward 5s"
            android:background="?attr/selectableItemBackgroundBorderless" />

    </LinearLayout>

    <TextView
        android:id="@+id/txtStartTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/seekBar"
        android:layout_alignStart="@id/seekBar"
        android:text="0:00" />

    <TextView
        android:id="@+id/txtEndTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/seekBar"
        android:layout_alignEnd="@id/seekBar"
        android:text="0:00" />

</RelativeLayout>

ขั้นตอนที่ 4: เขียนโค้ด MainActivity.java

import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    private ImageButton btnPlay, btnPause, btnForward, btnBackward;
    private SeekBar seekBar;
    private TextView txtSongName, txtStartTime, txtEndTime;
    
    private MediaPlayer mediaPlayer;
    private Handler handler = new Handler();
    private boolean isPrepared = false;

    // Константы
    private static final int SEEK_INTERVAL = 5000; // 5 วินาที
    private static final int UPDATE_INTERVAL = 100; // อัปเดต SeekBar ทุก 100ms

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

        initViews();
        setupMediaPlayer();
        setupListeners();
    }

    private void initViews() {
        btnPlay = findViewById(R.id.btnPlay);
        btnPause = findViewById(R.id.btnPause);
        btnForward = findViewById(R.id.btnForward);
        btnBackward = findViewById(R.id.btnBackward);
        seekBar = findViewById(R.id.seekBar);
        txtSongName = findViewById(R.id.txtSongName);
        txtStartTime = findViewById(R.id.txtStartTime);
        txtEndTime = findViewById(R.id.txtEndTime);

        txtSongName.setText("Bohemian Rhapsody");
        seekBar.setEnabled(false); // ปิดการคลิก SeekBar จนกว่าจะ Prepared
    }

    private void setupMediaPlayer() {
        mediaPlayer = MediaPlayer.create(this, R.raw.bohemian_rhapsody);
        
        mediaPlayer.setOnPreparedListener(mp -> {
            isPrepared = true;
            seekBar.setMax(mediaPlayer.getDuration());
            seekBar.setEnabled(true);
            updateTimeDisplay();
        });

        mediaPlayer.setOnCompletionListener(mp -> {
            // เมื่อเพลงจบ
            resetPlayer();
            Toast.makeText(this, "Playback completed", Toast.LENGTH_SHORT).show();
        });

        mediaPlayer.setOnErrorListener((mp, what, extra) -> {
            Toast.makeText(this, "Playback error: " + what, Toast.LENGTH_SHORT).show();
            resetPlayer();
            return true;
        });
    }

    private void setupListeners() {
        // Play
        btnPlay.setOnClickListener(v -> {
            if (!isPrepared) return;
            
            mediaPlayer.start();
            updatePlayPauseButtons(true);
            startProgressUpdater();
            Toast.makeText(this, "Playing", Toast.LENGTH_SHORT).show();
        });

        // Pause
        btnPause.setOnClickListener(v -> {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
                updatePlayPauseButtons(false);
                handler.removeCallbacks(progressUpdater);
                Toast.makeText(this, "Paused", Toast.LENGTH_SHORT).show();
            }
        });

        // Forward 5s
        btnForward.setOnClickListener(v -> seek(SEEK_INTERVAL));

        // Backward 5s
        btnBackward.setOnClickListener(v -> seek(-SEEK_INTERVAL));

        // SeekBar Touch Listener
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser && isPrepared) {
                    mediaPlayer.seekTo(progress);
                    updateTimeDisplay();
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                handler.removeCallbacks(progressUpdater);
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                if (mediaPlayer.isPlaying()) {
                    startProgressUpdater();
                }
            }
        });
    }

    private void seek(int offset) {
        if (!isPrepared) return;
        
        int newPosition = mediaPlayer.getCurrentPosition() + offset;
        newPosition = Math.max(0, Math.min(newPosition, mediaPlayer.getDuration()));
        
        mediaPlayer.seekTo(newPosition);
        seekBar.setProgress(newPosition);
        updateTimeDisplay();
    }

    private void updatePlayPauseButtons(boolean isPlaying) {
        btnPlay.setEnabled(!isPlaying);
        btnPause.setEnabled(isPlaying);
    }

    private void startProgressUpdater() {
        handler.removeCallbacks(progressUpdater);
        handler.postDelayed(progressUpdater, UPDATE_INTERVAL);
    }

    private final Runnable progressUpdater = new Runnable() {
        @Override
        public void run() {
            if (mediaPlayer != null && isPrepared) {
                int currentPos = mediaPlayer.getCurrentPosition();
                seekBar.setProgress(currentPos);
                updateTimeDisplay();
                
                if (mediaPlayer.isPlaying()) {
                    handler.postDelayed(this, UPDATE_INTERVAL);
                }
            }
        }
    };

    private void updateTimeDisplay() {
        if (!isPrepared) return;
        
        int current = mediaPlayer.getCurrentPosition();
        int total = mediaPlayer.getDuration();
        
        txtStartTime.setText(formatTime(current));
        txtEndTime.setText(formatTime(total));
    }

    private String formatTime(int millis) {
        return String.format(Locale.getDefault(), "%d:%02d",
            TimeUnit.MILLISECONDS.toMinutes(millis),
            TimeUnit.MILLISECONDS.toSeconds(millis) % 60);
    }

    private void resetPlayer() {
        isPrepared = false;
        updatePlayPauseButtons(false);
        handler.removeCallbacks(progressUpdater);
        seekBar.setProgress(0);
        seekBar.setEnabled(false);
        txtStartTime.setText("0:00");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(progressUpdater);
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}

ขั้นตอนที่ 5: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <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">

        <activity android:name=".MainActivity">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

    </application>
</manifest>

การทดสอบ

รันแอป → กด Play → เพลงเล่น SeekBar ก้าวหน้า → กด Pause/Forward/Backward ทดสอบการควบคุม

การใช้ MediaPlayer สร้าง Audio Player เบื้องต้นใน Android การใช้ MediaPlayer สร้าง Audio Player เบื้องต้นใน Android การใช้ MediaPlayer สร้าง Audio Player เบื้องต้นใน Android การใช้ MediaPlayer สร้าง Audio Player เบื้องต้นใน Android

💡 แนะนำ: ExoPlayer (Media3) - ทันสมัยกว่า MediaPlayer

Google แนะนำให้ใช้ ExoPlayer (Media3) แทน MediaPlayer เพราะ:

  • รองรับ Format มากกว่า (DASH, HLS, SmoothStreaming)
  • Adaptive Bitrate Streaming
  • Custom Renderers, DRM Support
  • Memory Management ดีกว่า
  • Active Development และ Maintenance
// build.gradle (Media3)
implementation "androidx.media3:media3-exoplayer:1.1.1"
implementation "androidx.media3:media3-ui:1.1.1"

// ใช้งานง่ายมาก
ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaItem(MediaItem.fromUri("https://example.com/audio.mp3"));
player.prepare();
player.playWhenReady = true;

// UI Component สำเร็จรูป
PlayerView playerView = findViewById(R.id.playerView);
playerView.setPlayer(player);

Life Cycle ของ MediaPlayer

เข้าใจ State Machine สำคัญมาก:

Idle → (setDataSource) → Initialized → (prepare/prepareAsync) → Prepared
    ↑                                                                 |
    |                                                                 ↓
    ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
    (reset/release)                                          Started → Paused → PlaybackCompleted
                                                                    ↑         ↓
                                                                    └─────────┘ (seekTo, start)