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

วิธีเล่นเพลงพื้นหลัง (Background Music) ในแอป Android

ภาพรวม

บทความนี้สาธิตวิธีเล่นเพลง/เสียงในพื้นหลัง (Background Music) ใน Android โดยใช้ Foreground Service พร้อม MediaPlayer ซึ่งเป็นวิธีมาตรฐานปัจจุบัน (Android 8.0+/API 26+) แทน Background Service แบบเก่าที่ถูกจำกัด

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

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

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

สร้างโฟลเดอร์ res/raw/ → คลิกขวา raw → New → Directory → raw → วางไฟล์เสียง (เช่น background_music.mp3)

ขั้นตอนที่ 3: Layout หลัก (res/layout/activity_main.xml)

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

    <Button
        android:id="@+id/btnPlay"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="▶ เริ่มเล่นเพลงพื้นหลัง" />

    <Button
        android:id="@+id/btnPause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="⏸ หยุดชั่วคราว" />

    <Button
        android:id="@+id/btnStop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="⏹ หยุดและปิด Service" />

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="สถานะ: ยังไม่เริ่ม"
        android:textSize="16sp"
        android:textStyle="bold" />

</LinearLayout>

ขั้นตอนที่ 4: Foreground Service (BackgroundSoundService.java)

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

public class BackgroundSoundService extends Service {

    private static final String CHANNEL_ID = "BackgroundMusicChannel";
    private static final int NOTIFICATION_ID = 1001;
    
    private MediaPlayer mediaPlayer;
    private NotificationManager notificationManager;

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
        initMediaPlayer();
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "Background Music Player",
                NotificationManager.IMPORTANCE_LOW
            );
            channel.setDescription("ควบคุมการเล่นเพลงพื้นหลัง");
            channel.setSound(null, null); // ไม่มีเสียงแจ้งเตือน
            
            notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }

    private void initMediaPlayer() {
        mediaPlayer = MediaPlayer.create(this, R.raw.background_music);
        if (mediaPlayer != null) {
            mediaPlayer.setLooping(true); // Loop ตลอด
            mediaPlayer.setVolume(0.8f, 0.8f); // 0.0 - 1.0
        }
    }

    private Notification buildNotification(String title, String text, int playPauseIcon) {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(
            this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
        );

        // Action Buttons ใน Notification
        Intent pauseIntent = new Intent(this, BackgroundSoundService.class)
            .setAction("ACTION_PAUSE");
        PendingIntent pausePendingIntent = PendingIntent.getService(
            this, 0, pauseIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
        );

        Intent stopIntent = new Intent(this, BackgroundSoundService.class)
            .setAction("ACTION_STOP");
        PendingIntent stopPendingIntent = PendingIntent.getService(
            this, 1, stopIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
        );

        return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(title)
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_music_note)
            .setLargeIcon(android.graphics.BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
            .setContentIntent(pendingIntent)
            .setOngoing(true) // ไม่ให้ Swipe ปิดได้
            .setCategory(NotificationCompat.CATEGORY_TRANSPORT)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
            .addAction(R.drawable.ic_stop, "Stop", stopPendingIntent)
            .setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
                .setShowActionsInCompactView(0, 1))
            .build();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent != null ? intent.getAction() : null;
        
        if ("ACTION_PAUSE".equals(action)) {
            togglePlayPause();
        } else if ("ACTION_STOP".equals(action)) {
            stopSelf();
        } else {
            // Start Playing
            if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
                mediaPlayer.start();
                showNotification("กำลังเล่น", "เพลงพื้นหลัง", R.drawable.ic_pause);
            }
        }
        return START_STICKY; // รีสตาร์ทถ้าถูก Kill
    }

    private void togglePlayPause() {
        if (mediaPlayer != null) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
                showNotification("หยุดชั่วคราว", "เพลงพื้นหลัง", R.drawable.ic_play);
            } else {
                mediaPlayer.start();
                showNotification("กำลังเล่น", "เพลงพื้นหลัง", R.drawable.ic_pause);
            }
        }
    }

    private void showNotification(String title, String text, int icon) {
        if (notificationManager != null) {
            Notification notification = buildNotification(title, text, icon);
            notificationManager.notify(NOTIFICATION_ID, notification);
            // เริ่ม Foreground Service (API 26+)
            startForeground(NOTIFICATION_ID, notification);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null; // ไม่รองรับ Binding
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        if (notificationManager != null) {
            notificationManager.cancel(NOTIFICATION_ID);
        }
        stopForeground(true);
    }
}

ขั้นตอนที่ 5: MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private Button btnPlay, btnPause, btnStop;
    private TextView tvStatus;
    private boolean isServiceRunning = false;

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

        btnPlay = findViewById(R.id.btnPlay);
        btnPause = findViewById(R.id.btnPause);
        btnStop = findViewById(R.id.btnStop);
        tvStatus = findViewById(R.id.tvStatus);

        btnPlay.setOnClickListener(v -> startMusicService());
        btnPause.setOnClickListener(v -> sendActionToService("ACTION_PAUSE"));
        btnStop.setOnClickListener(v -> stopMusicService());
    }

    private void startMusicService() {
        Intent serviceIntent = new Intent(this, BackgroundSoundService.class);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Android 8.0+ ต้องใช้ startForegroundService
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
        
        isServiceRunning = true;
        updateUI("กำลังเล่นเพลงพื้นหลัง...");
        Toast.makeText(this, "เริ่มเล่นเพลงในพื้นหลัง", Toast.LENGTH_SHORT).show();
    }

    private void sendActionToService(String action) {
        Intent serviceIntent = new Intent(this, BackgroundSoundService.class);
        serviceIntent.setAction(action);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }

    private void stopMusicService() {
        Intent serviceIntent = new Intent(this, BackgroundSoundService.class);
        stopService(serviceIntent);
        isServiceRunning = false;
        updateUI("หยุดเพลงแล้ว");
        Toast.makeText(this, "หยุดเพลงพื้นหลัง", Toast.LENGTH_SHORT).show();
    }

    private void updateUI(String status) {
        tvStatus.setText("สถานะ: " + status);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // ไม่หยุด Service ที่นี่ - ให้เล่นต่อในพื้นหลัง
    }
}

ขั้นตอนที่ 6: AndroidManifest.xml - สำคัญมาก!

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

    <!-- สิทธิ์สำหรับ Foreground Service (Android 9+) -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android 13+ -->

    <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/Theme.MaterialComponents.DayNight.NoActionBar">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- ประกาศ Service -->
        <service
            android:name=".BackgroundSoundService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="false" />

    </application>
</manifest>

ความแตกต่าง: Foreground Service vs Background Service

ด้านBackground Service (เก่า)Foreground Service (ใหม่)
Android 8.0+ (API 26+)ถูกจำกัด หยุดทำงานใน Backgroundทำงานได้ปกติ ต้องมี Notification
Notificationไม่จำเป็นบังคับ (Ongoing Notification)
User Awarenessผู้ใช้ไม่รู้ว่า Service รันอยู่ผู้ใช้เห็น Notification ตลอด
Battery Optimizationถูก Kill ง่ายPriority สูงกว่า
Media Playbackไม่แนะนำมาตรฐาน (foregroundServiceType="mediaPlayback")

ตัวเลือกอื่น: MediaSession + MediaController (Professional)

สำหรับ Media Player ระดับ Production ควรใช้ MediaSessionCompat + MediaControllerCompat ซึ่งรองรับ:

  • Lock Screen Controls
  • Bluetooth Headset Controls
  • Android Auto / Wear OS
  • Media Notification แบบมาตรฐาน
  • Voice Assistant Integration ("Hey Google, pause music")

ข้อควรระวัง

  • Permission: Android 13+ ต้องขอ POST_NOTIFICATIONS Runtime Permission
  • Foreground Service Type: ต้องระบุ mediaPlayback ใน Manifest (API 29+)
  • Notification Channel: สร้าง Channel แบบ LOW Importance เพื่อไม่รบกวน
  • MediaPlayer Lifecycle: ต้อง release() ใน onDestroy() กัน Memory Leak
  • Audio Focus: ควรขอ Audio Focus ก่อนเล่น (AudioManager.requestAudioFocus())