+1

Sử dụng MediaPlayer để phát các Audio File trong Android

Giới thiệu

Có nhiều cách để thêm tệp âm thanh trong ứng dụng Android. Ví dụ bạn có thể sử dụng ExoPlayer hoặc MediaPlayer để phát các tệp âm thanh trong ứng dụng Android.

Trong bài viết này, chúng ta sẽ tìm cách phát một file Audio trong Android bằng cách sử dụng MediaPlayer. Dưới đây là những gì chúng ta sẽ tìm hiểu trong bài viết này:

  • Giới thiệu về MediaPlayer
  • State Diagram của MediaPlayer
  • prepare() vs prepareAsync()
  • Release MediaPlayer sau khi dùng
  • Ví dụ về MediaPlayer
  • Các ví dụ khác

Vậy chúng ta hãy cùng bắt đầu nhé

Giới thiệu về MediaPlayer

Để phát file audio hoặc video trong Android, framework multimedia của Android bao gồm sự hỗ trợ của các API MediaPlayer. Vì vậy, bằng cách sử dụng các API MediaPlayer, bạn có thể phát file audio/video từ hệ thống tệp tin Android của mình hoặc phát các tệp từ tệp tài nguyên của ứng dụng của bạn, thậm chí bạn có thể phát trực tiếp các tệp âm thanh/video giống như Spotify.

Dưới đây là các hoạt động có thể thực hiện bằng cách sử dụng MediaPlayer:

  • Chuẩn bị file media: Để phát một file media, bạn cần chuẩn bị nó trước, tức là bạn cần tải file để phát lại. Các phương thức được sử dụng để thực hiện điều này là prepare(), prepareAsync(), và setDataSource().
  • Start/Pause playback: Sau khi tải file media, bạn có thể bắt đầu phát lại file media bằng cách sử dụng phương thức start(). Tương tự, bạn có thể tạm dừng phát lại file media đang phát bằng cách sử dụng phương thức pause().
  • Stop playback: Bạn có thể dừng playback bằng cách sử dụng phương thức reset(). Phương thức này được sử dụng khi chúng ta muốn dừng playback và sau đó chuẩn bị một playback khác để phát.
  • Kéo tới một vị trí: Bạn có thể trực tiếp chuyển đến một vị trí cụ thể (tính bằng ms) trong file media bằng cách sử dụng phương thức seekTo(position).
  • Độ dài của media: Bạn có thể tìm thấy độ dài của một bài hát cụ thể (tính bằng mili giây) bằng cách sử dụng phương thức getDuration().
  • Vị trí hiện tại của một bài hát: Bạn có thể lấy vị trí phát lại hiện tại bằng cách sử dụng phương thức getCurrentPosition().
  • Phân bổ tài nguyên: Bạn có thể phân bổ bộ nhớ và tài nguyên được trình phát media sử dụng bằng cách sử dụng phương thức Release().

Đây là một số hoạt động có thể thực hiện bằng cách sử dụng API MediaPlayer. Vậy nên chúng ta có nên bắt đầu viết mã cho MediaPlayer không? Không, một câu trả lời lớn là không. Trước khi bắt đầu viết code, hãy xem sơ đồ trạng thái của MediaPlayer vì nó rất quan trọng để hiểu rõ sơ đồ trạng thái, nếu không, ứng dụng của bạn có thể dẫn đến một số lỗi không mong muốn.

State Diagram của MediaPlayer

Trong phần này, chúng ta sẽ tìm hiểu về các trạng thái khác nhau từ khi bắt đầu phát lại đến khi dừng playback. Các trạng thái này bao gồm:

  • Trạng thái Idle: Khi bạn tạo một đối tượng MediaPlayer bằng cách sử dụng từ khóa new, thì đối tượng đó hiện đang ở trạng thái idle. Tương tự, khi bạn gọi phương thức reset(), đối tượng sẽ chuyển sang trạng thái idle. Trong trạng thái idle, không có gì xảy ra với đối tượng MediaPlayer. Nó chỉ làm cho đối tượng sẵn sàng để được chuẩn bị để phát một số tệp âm thanh. Do đó, trong trạng thái idle, nếu bạn cố gắng lấy vị trí hiện tại của một bài hát bằng cách sử dụng phương thức getCurrentPosition() hoặc nếu bạn cố gắng sử dụng các phương thức pause(), start(), stop(), setVolume(), getDuration, vv, bạn sẽ gặp phải một số lỗi crash không mong muốn của ứng dụng.
  • Trạng thái Error: Nếu bạn sử dụng các phương thức được đề cập ở trạng thái idle ngay sau khi sử dụng phương thức reset(), thì OnErrorListener.onError() sẽ được gọi và đối tượng của bạn sẽ chuyển sang trạng thái lỗi. Nhưng nếu bạn sử dụng từ khóa "new" thì OnErrorListener.onError() sẽ không được gọi và trạng thái sẽ không thay đổi. Để tái sử dụng đối tượng ở trạng thái lỗi, bạn cần gọi phương thức reset().
  • Trạng thái End: Khi bạn gọi phương thức release(), tất cả các tài nguyên do đối tượng MediaPlayer sử dụng sẽ được giải phóng và đối tượng sẽ chuyển sang trạng thái end. Nếu một đối tượng ở trạng thái end, thì không có cách nào để đưa đối tượng trở lại các trạng thái khác.
  • Trạng thái Initialized: Toàn bộ vòng đời của một đối tượng MediaPlayer nằm giữa trạng thái idle và end. Sau trạng thái idle, bạn cần cung cấp nguồn dữ liệu bạn sẽ sử dụng, tức là nguồn phương tiện. Do đó, phương thức setDataSource() được sử dụng cho mục đích đó và sau đó, đối tượng sẽ được đặt vào trạng thái initialized. setDataSource() luôn nên được sử dụng khi ứng dụng ở trạng thái idle, nếu không, nó sẽ ném IllegalStateException.
  • Trạng thái Prepared: Trước khi bắt đầu phát nhạc, bạn cần đưa đối tượng MediaPlayer vào trạng thái prepared. Trong trạng thái prepared, bạn có thể điều chỉnh việc lặp video/audio hoặc bạn có thể đặt âm lượng. Vì vậy, để chuyển một đối tượng từ trạng thái initialized sang trạng thái prepared, bạn có thể sử dụng phương thức prepare() để chuyển trực tiếp đối tượng sang trạng thái prepared hoặc bạn có thể sử dụng một trạng thái trung gian gọi là preparing state. Để chuyển sang trạng thái preparing từ trạng thái initialized, bạn có thể gọi phương thức prepareAsync(), chuyển đối tượng sang trạng thái preparing và khi quá trình chuẩn bị hoàn tất, phương thức onPrepared() của giao diện OnPreparedListener sẽ được gọi và đối tượng sẽ chuyển sang trạng thái prepared. prepare() và prepareAsync() phải được gọi trong trạng thái này, nếu không sẽ ném IllegalStateException.
  • Trạng thái Started: Sau trạng thái prepared, nếu bạn sử dụng phương thức start() để phát bất kỳ tệp âm thanh/ video nào, thì đối tượng sẽ chuyển sang trạng thái started và ở đây bạn có thể áp dụng một số phương thức như seekTo() hoặc start(). Nếu bạn sử dụng start() trong trạng thái started, thì không có gì xảy ra.
  • Trạng thái Paused: Khi đang phát một tệp âm thanh hoặc video, nếu bạn tạm dừng phương tiện thì nó sẽ chuyển sang trạng thái paused. Bạn có thể sử dụng phương thức pause() để đưa phương tiện vào trạng thái pause. Để tiếp tục phát phương tiện, bạn có thể sử dụng phương thức start(). Vị trí phát lại được tiếp tục từ nơi nó đã được tạm dừng và sau khi sử dụng phương thức start(), việc phát lại sẽ được tiếp tục và đối tượng sẽ chuyển sang trạng thái started. Không có ảnh hưởng khi gọi phương thức pause() trong trạng thái paused.
  • Trạng thái Stopped: Khi bạn sử dụng phương thức stop() từ trạng thái prepared, started, paused hoặc playback completed, đối tượng MediaPlayer sẽ chuyển sang trạng thái stopped và để phát lại âm thanh/video, bạn cần đưa đối tượng về trạng thái prepared bằng cách gọi phương thức prepare() hoặc prepareAsync(). Không có ảnh hưởng khi gọi phương thức stop() trong trạng thái stopped.
  • Trạng thái PlaybackCompleted: Nếu việc lặp lại được đặt thành true, tức là bạn muốn phát lại tệp âm thanh/video liên tục, thì đối tượng sẽ tiếp tục ở trạng thái started. Nếu lặp lại được đặt thành false, thì OnCompletion.onCompletion() sẽ được gọi sau khi hoàn thành tệp âm thanh/video và đối tượng sẽ chuyển sang trạng thái PlaybackCompleted. Để bắt đầu phát lại, bạn có thể gọi phương thức start().

Hình dưới đây là một tóm tắt về những gì chúng ta đã thảo luận trong phần trước. Việc học về trạng thái của một MediaPlayer trước khi sử dụng là rất quan trọng, vì có một số phương thức phải được gọi ở một trạng thái cụ thể, nếu không sẽ gây ra một số ngoại lệ. Vì vậy, việc hiểu rõ về tất cả các trạng thái là lợi ích lớn.

prepare() vs prepareAsync()

Trong phần trước, chúng ta đã thấy để đưa đối tượng MediaPlayer từ trạng thái initialized sang trạng thái prepared, chúng ta có hai lựa chọn, tức là chúng ta có thể sử dụng phương thức prepare() và đối tượng sẽ trực tiếp chuyển sang trạng thái prepared, hoặc chúng ta có thể sử dụng một trạng thái trung gian, tức là trạng thái preparing để chuyển đối tượng sang trạng thái prepared. Để chuyển sang trạng thái preparing từ trạng thái initialized, chúng ta có thể sử dụng phương thức prepareAsync(). Vậy nên, sự khác biệt giữa các phương thức prepare() và prepareAsync() là gì?

Nếu bạn gọi phương thức prepare(), nó sẽ tìm kiếm và decode media của bạn và quá trình này có thể mất thời gian. Vì vậy, nếu bạn gọi phương thức prepare() này từ luồng UI của ứng dụng, điều này có thể dẫn đến sự trễ trong ứng dụng của bạn và bạn có thể mất đi người dùng của mình. Thay vào đó, thay vì chạy phương thức prepare() trên luồng UI, bạn có thể chạy nó trên một luồng khác và khi quá trình tìm kiếm và giải mã dữ liệu phương tiện của bạn hoàn tất, luồng đó sẽ thông báo cho luồng chính và luồng chính sẽ tiếp tục làm phần còn lại. Vì vậy, quá trình này được thực hiện nhờ vào phương thức prepareAsync().

Release MediaPlayer

Trong phần trước, chúng ta đã thấy tất cả các trạng thái của một đối tượng MediaPlayer. Trước khi tiến xa, tôi muốn đề cập đến một điểm quan trọng mà chúng ta nên tuân theo, đó là giải phóng đối tượng MediaPlayer sau khi sử dụng.

Khi chúng ta sử dụng MediaPlayer, có nhiều tài nguyên hệ thống được đối tượng MediaPlayer sử dụng. Vì vậy, khi bạn đã sử dụng xong đối tượng MediaPlayer, bạn nên giải phóng tất cả các tài nguyên đã được đối tượng MediaPlayer sử dụng bằng cách gọi phương thức release(). Ngoài ra, nếu bạn không chạy ứng dụng phương tiện của mình ở nền thì bạn nên giải phóng tất cả các tài nguyên khi ứng dụng đang ở trạng thái onPause(). Việc giải phóng tài nguyên là một bước quan trọng vì nếu bạn không thực hiện điều này, ứng dụng của bạn có thể dẫn đến trải nghiệm người dùng không tốt.

Ví dụ về MediaPlayer

Trong phần này của blog, tôi sẽ thảo luận về một ví dụ cơ bản về MediaPlayer, tức là cách phát một tệp phương tiện chỉ bằng cách mở một hoạt động. Ở đây, tôi sẽ không đề cập đến bất kỳ trạng thái nào của MediaPlayer hoặc sẽ không sử dụng bất kỳ phương thức nâng cao nào liên quan đến MediaPlayer như seekTo(), getCurrentPosition(), getDuration(), vv. Vì vậy, hãy xem ví dụ hai dòng của MediaPlayer.

Tạo một dự án mới trong Android Studio bằng cách sử dụng mẫu EmptyActivity. Ở đây, trong ví dụ này, chúng ta sẽ có một tệp âm thanh và chúng ta sẽ sử dụng tệp này trong ứng dụng của mình. Vì vậy, trong thư mục app/res, tạo một thư mục raw bằng cách nhấp chuột phải vào thư mục res và sau đó nhấp chuột phải vào mới và sau đó vào Thư mục Tài nguyên Android. Chọn Loại Tài nguyên là raw và sau đó nhấp vào OK. Trong thư mục raw bạn vừa tạo, đặt các tệp âm thanh của bạn và chúng ta sẽ sử dụng tệp âm thanh này sau đó.

Nếu bạn muốn truyền phát một tệp âm thanh, bạn phải cho phép quyền internet trong tệp AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET" />

Bây giờ, để phát một file audio từ thư mục tài nguyên của bạn (raw), chỉ cần thêm hai dòng mã dưới đây và bạn có thể sử dụng ứng dụng âm nhạc của mình chỉ với hai dòng code.

var mediaPlayer: MediaPlayer? = MediaPlayer.create(context, R.raw.sample_media)
mediaPlayer?.start() // no need to call prepare(); create() does that for you

Hãy thử chạy ứng dụng của bạn và thưởng thức âm nhạc 😃

Trong trường hợp phát nhạc từ hệ thống file, bạn có thể sử dụng phương thức getDataSource() và chuyển URI của bạn làm tham số cho nó.

val sampleUri: Uri = .... // your uri here
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
    setAudioStreamType(AudioManager.STREAM_MUSIC)
    setDataSource(applicationContext, sampleUri) //to set media source and send the object to the initialized state
    prepare() //to send the object to prepared state
    start() //to start the music and send the object to the started state
}

Ngoài ra, để phát một số tệp phương tiện từ một URL, bạn có thể làm như vậy bằng cách sử dụng quy trình URI ở trên và thay cho URI, bạn có thể đặt URL của tệp âm thanh.

val sampleUrl = "http://........" // your URL here
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
    setAudioStreamType(AudioManager.STREAM_MUSIC) //to send the object to the initialized state
    setDataSource(sampleUrl) //to set media source and send the object to the initialized state
    prepare() //to send the object to the prepared state, this may take time for fetching and decoding
    start() //to start the music and send the object to started state
}

Trong ví dụ trên, chúng ta đang thiếu một số bước được khuyến nghị, tức là chúng ta không giải phóng đối tượng sau khi sử dụng. Hơn nữa, chúng ta không chú ý đến tất cả các trạng thái của MediaPlayer. Vì vậy, hãy xem xét một số ví dụ nâng cao về MediaPlayer bằng cách sử dụng tất cả các chức năng.

Các ví dụ khác

Trong ví dụ này, chúng ta sẽ xây dựng một trình phát đa phương tiện và trong trình phát đó, chúng ta có thể phát, tạm dừng và dừng tệp phương tiện. Ngoài ra, chúng ta có thể di chuyển thanh tìm kiếm đến một vị trí cụ thể để phát nhạc từ vị trí đó. Trong ví dụ này, tôi sẽ tập trung vào phần logic. Vậy nên, hãy bắt đầu.

Trong folder app/res/raw, dán tệp âm thanh của bạn.

Bây giờ, hãy tạo ba nút Play, Pause và Stop. Đồng thời, bạn có thể thêm một thanh seekbar để hiển thị bộ đếm thời gian của tệp âm thanh (đây là bước tùy chọn).

Bây giờ, trong tệp MainActivity.kt, bạn cần tạo đối tượng của MediaPlayer.

private lateinit var mediaPlayer: MediaPlayer

Bây giờ, đối với button Play, có hai trường hợp:

  1. Âm thanh ở trạng thái paused: Khi audio ở trạng thái paused, bạn phải tìm vị trí hiện tại của âm thanh bằng cách sử dụng getCurrentPosition rồi khởi động media player.
  2. Âm thanh ở trạng thái đã prepared: Khi âm thanh ở trạng thái prepared, bạn có thể bắt đầu âm thanh bằng cách sử dụng phương thức start().

Sau đây là code cho button Play:

// Start the media player
playBtn.setOnClickListener{
    if(pause){ //initially, pause is set to false
        mediaPlayer.seekTo(mediaPlayer.currentPosition)
        mediaPlayer.start()
        pause = false
        //playing audio when in paused state
    }else{
        mediaPlayer = MediaPlayer.create(applicationContext,R.raw.demo)
        mediaPlayer.start()
        //playing audio when in prepared state
    }
}

Đối với nút Pause, chỉ có một trường hợp, tức là nếu âm thanh đang phát thì bạn chỉ cần tạm dừng âm thanh bằng cách sử dụng phương thức Pause(). Sau đây là code cho nó:

if(mediaPlayer.isPlaying){
    mediaPlayer.pause()
    pause = true
    //audio is paused here
}

Đối với nút Pause, bạn cần kiểm tra xem audio có đang phát hoặc ở trạng thái paused không, vì trước khi chuyển sang trạng thái paused, âm thanh có thể đang ở trạng thái đã bắt đầu hoặc đã tạm dừng. Nếu đúng như vậy, sau đó thực hiện các bước sau: gọi hàm stop(), sau đó reset() và cuối cùng là release() trên trình phát. Dưới đây là code cho việc này:

if(mediaPlayer.isPlaying || pause.equals(true)){
    pause = false
    mediaPlayer.stop()
    mediaPlayer.reset()
    mediaPlayer.release()
    //audio is stopped here
}

Kết luận

Cảm ơn bạn đã đọc tới đây, hi vọng qua bài viết này các bạn đã có thể nắm được về MediaPlayer và cách sử dụng nó.

Nguồn tham khảo: https://blog.mindorks.com/using-mediaplayer-to-play-an-audio-file-in-android/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí