Sử dụng thư viện Exoplayer để phát các video, audio trong Android

Trước khi bắt đầu, hãy chờ một lúc và nghĩ về một số ứng dụng có chứa Audio hoặc Video dưới mọi hình thức. Bạn đã có được tên của một ứng dụng chưa? Trên thực tế, hãy nghĩ đến những ứng dụng không có audio hoặc video dưới mọi hình thức. Bạn đã tìm thấy một ứng dụng nào chưa? Điều này cho thấy tầm quan trọng của Media đối với các ứng dụng.

Phát nhạc và video trong ứng dụng Android là một nhiệm vụ gần như bắt buộc phải có trong ứng dụng của bạn vì âm nhạc và video là cách giao tiếp hiệu quả nhất giữa người dùng và nhà cung cấp hoặc nhà phát triển ứng dụng. Media có thể được phát từ Internet hoặc từ thiết bị của bạn. Thậm chí, bạn có thể tải xuống và lưu trữ media trong thiết bị hoặc điện thoại. Vì vậy, mọi nhà phát triển đều muốn thêm một số loại media vào ứng dụng của họ.

Có nhiều cách khác nhau để thêm audio và video trong ứng dụng Android. Một số trong số này là API MediaPlayer và ExoPlayer. Vì vậy, trong blog này, chúng tôi sẽ tìm hiểu cách sử dụng ExoPlayer trong ứng dụng Android. Vì vậy, nội dung của blog này là:

  • Tóm tắt MediaPlayer.
  • ExoPlayer là gì?
  • MediaPlayer và ExoPlayer
  • Cách sử dụng ExoPlayer trong Activity/Fragment.
  • Ví dụ về ExoPlayer.
  • Tổng kết. Bắt đầu nào!

MediaPlayer

Để dễ dàng tích hợp âm thanh, video và hình ảnh vào ứng dụng của bạn, Android multimedia framework cung cấp cho bạn lớp MediaPlayer. Lớp MediaPlayer được sử dụng để phát media trong các thiết bị Android. Nó hỗ trợ tất cả các loại định dạng âm thanh và video trong Android. Sử dụng MediaPlayer rất dễ dàng. Nhưng nhược điểm chính của MediaPlayer là nó hỗ trợ rất ít tùy chỉnh, tức là nếu bạn muốn tùy chỉnh MediaPlayer theo nhu cầu của riêng mình, thì bạn sẽ khó thực hiện điều đó. Vì vậy, nếu bạn muốn tùy chỉnh trình phát đa phương tiện của mình thì bạn nên sử dụng ExoPlayer.

ExoPlayer

Ứng dụng Youtube và nhiều ứng dụng phát video trực tuyến khác của Google sử dụng ExoPlayer để truyền phát và phát video.

ExoPlayer là một thư viện cung cấp một cách để phát media trong ứng dụng Android của bạn. Nó có thể thay thế thư viện MediaPlayer của Android, được sử dụng để phát video và âm thanh. Nó hỗ trợ truyền phát thích ứng động qua HTTP (DASH). Nó bao gồm truyền phát mượt mà và mã hóa video đã phát. Nhưng lợi thế tốt nhất của việc sử dụng ExoPlayer là thuộc tính tùy biến của nó. Bạn có thể tùy chỉnh trình phát video của bạn theo nhu cầu của bạn.

ExoPlayer đáng tin cậy vì được xử lý trực tiếp bởi team Google Android. Vì vậy, bạn sẽ tìm thấy mọi hỗ trợ cho ExoPlayer trên trang web Developers của Google.

So sánh MediaPlayer và ExoPlayer

  • MediaPlayer được hỗ trợ từ API 1 đến API hiện tại của Android. Nhưng ExoPlayer có một điều kiện tiên quyết là API Android cấp 16, tức là Android Jelly Bean. Nhưng đây không phải là vấn đề đáng lo ngại. Gần như 100% (chính xác hơn là 99,2%) các thiết bị Android có trên Play Store có API 16 trở lên, vì vậy bạn có thể nói rằng hầu hết tất cả các thiết bị Android có trên Play Store, đều hỗ trợ ExoPlayer.
  • Video được phát trong MediaPlayer không hỗ trợ streaming mượt mà. Ngoài ra, không có hỗ trợ cho phát lại thích ứng như DASH và HSL.
  • Bạn không thể tùy chỉnh MediaPlayer. Nhưng ExoPlayer có thể tùy chỉnh, tức là thêm các tính năng theo nhu cầu của bạn bằng cách sử dụng ExoPlayer.

Để hiểu sâu về nơi MediaPlayer và ExoPlayer hoạt động, hãy xem hình ảnh dưới đây:

Tại đây, bạn có thể thấy rằng MediaPlayer được triển khai bên trong Hệ điều hành của Android và do đó, bạn sẽ gặp khó khăn khi tùy chỉnh MediaPlayer theo nhu cầu của mình. Nhưng trong trường hợp ExoPlayer, việc triển khai được thực hiện trong Ứng dụng của chúng ta. Vì vậy, bạn có thể dễ dàng tùy chỉnh ExoPlayer trong ứng dụng của mình.

Cách sử dụng ExoPlayer trong Activity/Fragment.

ExoPlayer cung cấp rất nhiều chức năng sẽ giúp chúng ta sử dụng ExoPlayer trong ứng dụng của mình. Nếu bạn muốn sử dụng ExoPlayer, bạn nên thêm hai phương thức. Một để khởi tạo trình phát và một để phát hành trình phát. Trong trường hợp của tôi, tôi đã sử dụng initizePlayer () và releaseplayer (). Chúng ta hãy hiểu tương tự như ví dụ sau:

  • initizePlayer (): Như tên cho thấy, chức năng này được sử dụng để khởi tạo ExoPlayer. Vì vậy, bằng cách sử dụng chức năng này, bạn có thể đặt trình phát của mình phát media khi trình phát của bạn sẽ sẵn sàng trong cửa sổ hiện tại. Nếu trình phát đã được tạo thì bạn có thể chuẩn bị media source từ bất kỳ URL nào.
private fun initializePlayer() {
    if (player == null) {
        player = ExoPlayerFactory.newSimpleInstance(
        DefaultRenderersFactory(context),
        DefaultTrackSelector(),
        DefaultLoadControl())
        playerView?.setPlayer(player)
        player.setPlayWhenReady(playWhenReady)
        player.seekTo(currentWindow, playbackPosition)
    }
    val mediaSource = buildMediaSource(Uri.parse(getString(R.string.media_url_mp4)))
    player.prepare(mediaSource, true, false)
}
  • releasePlayer(): Phương thức này được sử dụng để có được vị trí Phát lại, tìm trạng thái phát và tạm dừng được sử dụng trong ExoPlayer. Điều này cũng được sử dụng để giải phóng trình phát và đặt trình phát thành null.
private fun releasePlayer() {
    if (player != null) {
        playbackPosition = player.getCurrentPosition()
        currentWindow = player.getCurrentWindowIndex()
        playWhenReady = player.getPlayWhenReady()
        player.release()
        player = null
    }
}

Vì vậy, để sử dụng ExoPlayer trong Activity hoặc Fragment, hãy sử dụng các bước sau:

  • Sử dụng hàm initizePlayer () để khởi tạo trình phát của bạn trong ứng dụng. Bạn có thể sử dụng chức năng này tại onStart () và onResume (). Vì vậy, bất cứ khi nào bạn bắt đầu hoặc tiếp tục ứng dụng của mình, thì ExoPlayer sẽ được tìm thấy và media sẽ được phát.
  • Nếu bạn muốn hủy Activity hoặc Fragment của mình thì hãy sử dụng hàm releasePlayer () để giải phóng trình phát trước khi hủy Activity hoặc Fragment. Nếu bạn không thực hiện bước này, thì trình phát video của bạn sẽ có mặt trong background ứng dụng. Vì vậy, hãy sử dụng chức năng releasePlayer () này để giải phóng trình phát khỏi ứng dụng của bạn trước khi hủy hoạt động. Sử dụng releasePlayer () tại onPause () và onStop () để giải phóng trình phát.

Trạng thái phát lại

Trong bất kỳ trình phát nào được sử dụng để phát media, có 4 trạng thái hiện diện trong trình phát đó. Các trạng thái này là: Idle, Buffering, Ready và Ended. Một cái nhìn đơn giản về các trạng thái này là:

  1. Idle (Player.STATE_IDLE): Khi trình phát chưa có gì để chạy, trình phát sẽ ở trạng thái Idle.
  2. Buffering (Player.STATE.BUFFERING): khi trình phát tải dữ liệu hoặc media thì trình phát sẽ ở trạng thái Buffering.
  3. Ready (Player.STATE_READY): Khi trình phát đã sẵn sàng phát video thì trình phát sẽ ở trạng thái Ready.
  4. Ended (Player.STATE_END): Khi trình phát đã phát xong media thì trình phát sẽ ở trạng thái Ended. Trạng thái này phải được thêm vào nếu bạn muốn destroy một Activity hoặc một Fragment.

Trình phát có thể chuyển sang trạng thái Ended từ trạng thái Idle nếu bạn không muốn phát video hoặc bạn muốn hủy Activity. Nếu bạn không muốn trình phát đi đến trạng thái Ended, tức là bạn muốn phát media thì từ trạng thái Idle, trình phát sẽ chuẩn bị cho phát media bằng cách chuyển sang trạng thái Buffering. Từ trạng thái Buffering, trình phát có thể chuyển sang trạng thái Ready và phát video. Cuối cùng, trình phát sẽ đến trạng thái Ended sau khi Destroy Activity hoặc Fragment.

Bạn có thể đặt trạng thái của trình phát bằng cách làm như sau:

private val status:String
fun onPlayerStateChanged(playWhenReady:Boolean, playbackState:Int) {
    when (playbackState) {
        Player.STATE_BUFFERING -> status = PlaybackStatus.LOADING
        Player.STATE_ENDED -> status = PlaybackStatus.STOPPED
        Player.STATE_IDLE -> status = PlaybackStatus.IDLE
        Player.STATE_READY -> status = if (playWhenReady) PlaybackStatus.PLAYING else PlaybackStatus.PAUSED
        else -> status = PlaybackStatus.IDLE
    }
}

Ví dụ về ExoPlayer

  1. Bước 1: Thêm thư viện ExoPlayer vào project Thêm đoạn code sau vào file build.gradle của app

    implementation 'com.google.android.exoplayer:exoplayer-core:2.7.3'
    
  2. Bước 2: Thiết kế giao diện trong file activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <com.google.android.exoplayer2.ui.SimpleExoPlayerView
                android:id="@+id/simpleExoPlayerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
            </com.google.android.exoplayer2.ui.SimpleExoPlayerView>
    
            <ProgressBar
                android:id="@+id/progressBar"
                android:visibility="invisible"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
        </FrameLayout>
    
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:text="@string/lipsum"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="16dp"/>
    
        </ScrollView>
    
    </LinearLayout>
    
  3. Handle ExoPlayer trong MainActivity

class MainActivity : AppCompatActivity(), Player.EventListener {
    override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
    }

    override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {
    }

    override fun onPlayerError(error: ExoPlaybackException?) {
    }

    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        if (playbackState == Player.STATE_BUFFERING)
            progressBar.visibility = View.VISIBLE
        else if (playbackState == Player.STATE_READY)
            progressBar.visibility = View.INVISIBLE
    }

    override fun onLoadingChanged(isLoading: Boolean) {
    }

    override fun onPositionDiscontinuity() {
    }

    override fun onRepeatModeChanged(repeatMode: Int) {
    }

    override fun onTimelineChanged(timeline: Timeline?, manifest: Any?) {
    }

    private lateinit var simpleExoplayer: SimpleExoPlayer
    private var playbackPosition = 0L
    private val dashUrl = "http://rdmedia.bbc.co.uk/dash/ondemand/bbb/2/client_manifest-separate_init.mpd"
    private val bandwidthMeter by lazy {
        DefaultBandwidthMeter()
    }
    private val adaptiveTrackSelectionFactory by lazy {
        AdaptiveTrackSelection.Factory(bandwidthMeter)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        super.onStart()

        initializeExoplayer()
    }

    override fun onStop() {

        releaseExoplayer()
        super.onStop()
    }

    private fun initializeExoplayer() {
        simpleExoplayer = ExoPlayerFactory.newSimpleInstance(
                DefaultRenderersFactory(this),
                DefaultTrackSelector(adaptiveTrackSelectionFactory),
                DefaultLoadControl()
        )

        prepareExoplayer()
        simpleExoPlayerView.player = simpleExoplayer
        simpleExoplayer.seekTo(playbackPosition)
        simpleExoplayer.playWhenReady = true
        simpleExoplayer.addListener(this)
    }

    private fun releaseExoplayer() {
        playbackPosition = simpleExoplayer.currentPosition
        simpleExoplayer.release()
    }

    private fun buildMediaSource(uri: Uri): MediaSource {
        val dataSourceFactory = DefaultHttpDataSourceFactory("ua", bandwidthMeter)
        val dashChunkSourceFactory = DefaultDashChunkSource.Factory(dataSourceFactory)
        return DashMediaSource(uri, dataSourceFactory, dashChunkSourceFactory, null, null)
    }

    private fun prepareExoplayer() {
        val uri = Uri.parse(dashUrl)
        val mediaSource = buildMediaSource(uri)
        simpleExoplayer.prepare(mediaSource)
    }
}

Cuối cùng, bạn có thể chạy ứng dụng và xem thành quả rồi.

  1. Bước 4: Handle khi xoay màn hình

Có một hạn chế khi ta xoay màn hình. Đó là video sẽ chạy lại từ đầu mà không chạy tiếp. Để giải quyết vấn đề này, bạn thêm đoạn code sau vào file AndroiManifest.xml:

<activity android:name=".MainActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode">
</activity>

Tổng kết

Chúng ta đã đề cập rất nhiều về ExoPlayer trong bài này. Nhưng đây không phải là tất cả về ExoPlayer, có rất nhiều tính năng khác của ExoPlayer. Trên thực tế, nếu bạn có thể làm một cái gì đó với MediaPlayer, thì rất có khả năng bạn sẽ tìm thấy tính năng đó trong thư viện ExoPlayer. Nếu bạn đang tạo một ứng dụng nghe nhạc đơn giản thì hãy sử dụng MediaPlayer. Còn nếu muốn tùy chỉnh trình phát thì bạn nên sử dụng ExoPlayer.

Khám phá nhiều hơn về ExoPlayer ở đây nhé! Cảm ơn bạn đã đọc!