ภาพรวม
บทความนี้สาธิตวิธีเล่นเพลง/เสียงในพื้นหลัง (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_NOTIFICATIONSRuntime 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())