Wednesday, February 21, 2024

Android java: Tutorial Media3 ExoPlayer & MediaSessionService

Note: For personal reference using Media3 ExoPlayer & MediaSessionService in java

project: com.dedetok.tutorialmediaplayerbackgroundservice

build.gradle.kts (Module: app) 

dependencies {
...
    // For exposing and controlling media sessions
    implementation("androidx.media3:media3-session:1.2.1")
    // For media playback using ExoPlayer
    implementation("androidx.media3:media3-exoplayer:1.2.1")
    // For HLS playback support with ExoPlayer
    implementation("androidx.media3:media3-exoplayer-hls:1.2.1")
...
}

package com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService.java

package com.dedetok.tutorialmediaplayerbackgroundservice.myservice;

import android.content.Intent;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.session.MediaSession;
import androidx.media3.session.MediaSessionService;

/*
 * MediaSession ok
 * ExoPlayer inside MediaSession
 */

public class DedetokMediaSessionService extends MediaSessionService {


    /*
     * MediaSession creates a default implementation of MediaSession.Callback that
     * automatically handles all commands a MediaController sends to your player
     */
    MediaSession myMediaSession = null;

    /*
     * Create your Player and MediaSession in the onCreate lifecycle event
     *
     */
    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        // Let system handle focus audio for incoming or outgoing call
        player.setAudioAttributes(AudioAttributes.DEFAULT, true);
        myMediaSession = new MediaSession.Builder(this, player).build();
        Log.e("dedetok","DedetokMediaSessionService onCreate"); // debug

    }

    /*
     * The user dismissed the app from the recent tasks
     */
    @Override
    public void onTaskRemoved(@Nullable Intent rootIntent) {
        Player player = myMediaSession.getPlayer();
        if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 ) {
            /*
             * stop player if not ready || no media playing
             * else play on background
             */
            player.stop();
            stopSelf();
            Log.e("dedetok", "onTaskRemoved stop and release resources"); // debug
        }
    }

    /*
     * Remember to release the player and media session in onDestroy
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        myMediaSession.getPlayer().release();
        myMediaSession.release();
        myMediaSession = null;
    }

    /*
     * extends MediaSessionService
     */
    @Nullable
    @Override
    public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
        return myMediaSession;
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- internet -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- foreground service -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <!-- target API 34 and above -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
...
<!-- omited -->
...
        <!-- android:foregroundServiceType="mediaPlayback" target API >=29 -->
        <!-- action android:name="android.media.browse.MediaBrowserService" backward compatible with client apps -->
        <!-- action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" Declare legacy support for voice actions -->
        <service
            android:name=".myservice.DedetokMediaSessionService"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback"
            android:icon="@mipmap/ic_launcher"
            >
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService" />
                <action android:name="android.media.browse.MediaBrowserService" />
                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
            </intent-filter>
        </service>
    </application>

</manifest>

package com.dedetok.tutorialmediaplayerbackgroundservice.RadioListItem.java

package com.dedetok.tutorialmediaplayerbackgroundservice;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class RadioListItem implements Parcelable {
    public String mCountry; //0
    public String mCity; //1
    public String mRadio; //2
    public String mIcon; //3
    public String mStreamURL; //4

    public RadioListItem() {
    }

    protected RadioListItem(Parcel in) {
        mCountry = in.readString();
        mCity = in.readString();
        mRadio = in.readString();
        mIcon = in.readString();
        mStreamURL = in.readString();
    }

    public static final Creator<RadioListItem> CREATOR = new Creator<RadioListItem>() {
        @Override
        public RadioListItem createFromParcel(Parcel in) {
            return new RadioListItem(in);
        }

        @Override
        public RadioListItem[] newArray(int size) {
            return new RadioListItem[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int i) {
        parcel.writeString(mCountry);
        parcel.writeString(mCity);
        parcel.writeString(mRadio);
        parcel.writeString(mIcon);
        parcel.writeString(mStreamURL);
    }
}

package com.dedetok.tutorialmediaplayerbackgroundservice.MainActivity.java

package com.dedetok.tutorialmediaplayerbackgroundservice;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;

/*
 * MediaController
 * UI
 */
public class MainActivity extends AppCompatActivity {
    MediaController myMediaController = null;
    AppCompatButton bPlay;

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

        bPlay = findViewById(R.id.b_play);
        bPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // https://stackoverflow.com/questions/74035158/android-media3-session-controller-playback-not-starting
                // https://stackoverflow.com/questions/77039284/how-to-modify-the-auto-generated-media3-notification-java
                if (myMediaController!=null) {
                    Log.e("dedetok", "MediaController set MediaItem"); // debug
                    RadioListItem myRadio = new RadioListItem();
                    myRadio.mStreamURL = "https://stream-node0.rri.co.id/streaming/25/9125/voi.mp3";
                    myRadio.mRadio = "RRI VOI";
                    myRadio.mIcon = "https://www.rri.co.id/assets/v2/img/pro6.png";
                    String mRadioTitle = "Playing "+myRadio.mRadio;
                    Log.e("dedetok", "MediaController prepare and play"); // debug
                    Uri myUri = Uri.parse(myRadio.mIcon);
                    MediaMetadata myMediaMetadata = new MediaMetadata.Builder().
                            setTitle(myRadio.mRadio).
                            setStation(myRadio.mRadio).
                            setDisplayTitle(mRadioTitle).
                            //setArtworkUri(myUri). // to set background icon may any size
                            build();
                    //MediaItem myMediaItem = MediaItem.fromUri(myRadio.mStreamURL); // original
                    MediaItem myMediaItem = new MediaItem.Builder().
                            setMediaMetadata(myMediaMetadata).
                            setUri(Uri.parse(myRadio.mStreamURL)).
                            build();
                    myMediaController.setMediaItem(myMediaItem);
                    myMediaController.prepare();
                    myMediaController.setPlayWhenReady(true);
                    Log.e("dedetok", "MediaController prepare and play"); // debug
                }
            }
        });

    }

    /*
     * ## activity life cycle 2 ##
     * Android N (7.0) create Player
     */
    @Override
    protected void onStart() {
        super.onStart();

        // start services and get MediaController
        createMediaController();

    }


    /*
     * ## activity life cycle 5 ##
     * Android N (7.0) release Player
     */
    @Override
    protected void onStop() {
        super.onStop();
        // THIS WILL STOP BACKGROUND PLAYER
        //myMediaController.stop();
        //Log.e("dedetok", "onStaop stoping player"); // debug
    }


    /*
     * ContextCompat.getMainExecutor() instead of MoreExecutors.directExecutor()
     * in case you run your service in another process than the UI.
     */
    private void createMediaController() {
        // https://developer.android.com/media/media3/session/connect-to-media-app#create-controller
        // step 1 create SessionToken
        SessionToken mySessionToken =
                new SessionToken(this,
                        new ComponentName(this, DedetokMediaSessionService.class));
        // step 2 build MediaController using SessionToken
        ListenableFuture<MediaController> myListenableFutureMediaController =
                new MediaController.Builder(this, mySessionToken).buildAsync();
        myListenableFutureMediaController.addListener(() -> {
            // MediaController is available here with myListenableFutureMediaController.get()
            try {
                myMediaController = myListenableFutureMediaController.get();
                Log.e("dedetok", "get MediaController"); // debug
                if (myMediaController==null) {
                    Log.e("dedetok", "MediaController null"); // debug
                }
            } catch(ExecutionException e) {
                Log.e("dedetok", e.getMessage()); // debug
                e.printStackTrace(); // debug
            } catch(InterruptedException e) {
                Log.e("dedetok", e.getMessage()); // debug
                e.printStackTrace(); // debug
            }
        }, MoreExecutors.directExecutor());
    }

}



 

 

No comments:

Post a Comment