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());
    }

}



 

 

Sunday, February 11, 2024

Openssl s_client to verify SSL

Syntax general: openssl s_client [-connect host:port] [option]

 

Get and read openssl s_client output 

$ echo "Get HTTP/1.0" | openssl s_client google.com:443
CONNECTED(00000003)
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1                        <- verification chain 2 ok
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1                        <- verification chain 1 ok
depth=0 CN = *.google.com
verify return:1                        <- verification chain 0 ok
---
Certificate chain
 0 s:CN = *.google.com
   i:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jan  9 06:25:08 2024 GMT; NotAfter: Apr  2 06:25:07 2024 GMT
 1 s:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
   i:C = US, O = Google Trust Services LLC, CN = GTS Root R1
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Aug 13 00:00:42 2020 GMT; NotAfter: Sep 30 00:00:42 2027 GMT
 2 s:C = US, O = Google Trust Services LLC, CN = GTS Root R1
   i:C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
   a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 19 00:00:42 2020 GMT; NotAfter: Jan 28 00:00:42 2028 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
... <TRUNCATED> ...
-----END CERTIFICATE-----
subject=CN = *.google.com
issuer=C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 6833 bytes and written 396 bytes
Verification: OK                    <- handshake verification ok
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Use openssl s_client to export certificate PEM into a file (output file: certfs.pem)

$ echo "Get HTTP/1.0" | openssl s_client -showcerts -connect google.com:443 </dev/null | sed -n -e '/-.BEGIN/,/-.END/ p' > certifs.pem
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.com
verify return:1
DONE

Get fingerprint SHA1 in byte

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin

Fingerprint SHA256 in byte

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout -in /dev/stdin

Fingerprint SHA256 in base64 encode

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 | \
    openssl x509 -pubkey -noout | \
    openssl rsa -pubin -outform der | \
    openssl dgst -sha256 -binary | \
    openssl enc -base64 

To get service sertificates

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com.com:443 -showcerts

For Let's Encrypt, here is official information about compatibility platforms (operating system, browser and java virtual machine,  <link>

Reference:

  • https://www.openssl.org/docs/man1.0.2/man1/openssl-s_client.html
  • https://www.baeldung.com/linux/ssl-certificates


Saturday, February 10, 2024

Privacy Policy for Radio Walkman (com.dedetok.RadioWalkman)

Privacy Statement

Your privacy is important to us. This privacy statement explains what Radio Walkman (com.dedetok.RadioWalkman) application does, regarding your personal data.

Personal data we collect

Radio Walkman (com.dedetok.RadioWalkman) application does not requesting any information about your personal data.

Network & Internet Connection:

Radio Walkman (com.dedetok.RadioWalkman) application required internet connection for

  1. Get list of radio from my blogger https://dedetoknotes.blogspot.com/.
  2. Get radio stream from Radio Station Streaming server.
  3. Use to serve Google AdMob.

We do not provide any streaming services. Those streaming services are Radio Station's own and their services may change without any notice that cause this application can not get any radio streaming.

Contact Us:

If You need to contact Us, here is My email address:

dedetoke2021@gmail.com

Wednesday, February 7, 2024

Android java: Tutorial MediaPlayer and Media3 ExoPlayer

Android Studio Hedgehog | 2023.1.2

Note: For personal reference using MediaPlayer and Media3 ExoPlayer in java

build.gradle.kts (Module: app)

dependencies {
    ....
    implementation("androidx.media3:media3-exoplayer:1.2.1") // ExoPlayer
    implementation("androidx.media3:media3-common:1.2.1") // setAudioAttributes; MediaItem
}

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">
    <uses-permission android:name="android.permission.INTERNET" />
...

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">
    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MediaPlayer"
        android:id="@+id/b_mediaplayer"
        />
    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ExoPlayer"
        android:id="@+id/b_exoplayer"
        />
</androidx.appcompat.widget.LinearLayoutCompat>

MyExoPlayer.java

package com.dedetok.tutorialexoplayer;

import android.content.Context;
import android.util.Log;
import androidx.media3.common.MediaItem;
import androidx.media3.exoplayer.ExoPlaypackage com.dedetok.tutorialexoplayer;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    MyMediaPlayer myMediaPlayer;
    MyExoPlayer myExoPlayer;

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

        myMediaPlayer = new MyMediaPlayer();
        myExoPlayer = new MyExoPlayer(this);

        String myRadioUrl = "https://stream-node1.rri.co.id/streaming/25/9025/rrijakartapro1.mp3";
        //String myRadioUrl = "http://cast1.my-control-panel.com/proxy/radioso1/stream";

        AppCompatButton bMediaPlayer = findViewById(R.id.b_mediaplayer);
        bMediaPlayer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myExoPlayer.stop();
                myMediaPlayer.play(myRadioUrl);
            }
        });

        AppCompatButton bExoPlayer = findViewById(R.id.b_exoplayer);
        bExoPlayer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myMediaPlayer.stop();
                myExoPlayer.play(myRadioUrl);
            }
        });
    }
}er;

public class MyExoPlayer {
    // API level 23 / Android N / Android 6.0
    ExoPlayer myPlayer;

    public MyExoPlayer(Context appContext) {
        myPlayer = new ExoPlayer.Builder(appContext).build();
    }

    public void play(String myRadioUrl) {
        if (myPlayer.isPlaying()) {
            myPlayer.stop();
            Log.e("dedetok", "ExoPlaayer playing Stop"); // debug
        } else {
            try {
                Log.e("dedetok", "ExoPlaayer playing " + myRadioUrl); // debug
                MediaItem myRadio = MediaItem.fromUri(myRadioUrl);
                myPlayer.setMediaItem(myRadio);
                myPlayer.prepare();
                myPlayer.play();
            } catch (IllegalArgumentException e) {
                Log.e("dedetok", "ExoPlayer IllegalArgumentException " + e.getMessage()); // debug
            }
        }
    }

    public void stop() {
        myPlayer.stop();
    }

    public void release() {
        myPlayer.release();
        myPlayer=null;
    }
}

MyMediaPlayer.java

package com.dedetok.tutorialexoplayer;

import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.util.Log;
import java.io.IOException;

public class MyMediaPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {
    MediaPlayer myPlayer;

    boolean isPrepared= false;

    public MyMediaPlayer() {
        myPlayer = new MediaPlayer();
    }

    public void play(String myRadioUrl) {
        if (myPlayer.isPlaying()) {
            myPlayer.stop();
            myPlayer.reset();
            Log.e("dedetok", "MediaPlayer Stop"); // debug
        } else {
            Log.e("dedetok", "MediaPlayer playing " + myRadioUrl); // debug
            myPlayer.setOnPreparedListener(this);
            myPlayer.setAudioAttributes(
                    new AudioAttributes.Builder()
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .setUsage(AudioAttributes.USAGE_MEDIA)
                            .setLegacyStreamType(AudioAttributes.USAGE_MEDIA)
                            .build()
            );
            myPlayer.setOnErrorListener(this);
            try {
                myPlayer.setDataSource(myRadioUrl);
                isPrepared=true;
                myPlayer.prepareAsync();
            } catch (IOException e) {
                Log.e("dedetok", "IOException " + e.getMessage()); // debug
            } catch (IllegalArgumentException e) {
                Log.e("dedetok", "IllegalArgumentException " + e.getMessage()); // debug
            }
        }
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        /*
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (mediaPlayer.getDrmInfo() != null) {
                myPlayer.prepareDrm();
                //myPlayer.getKeyRequest();
                //myPlayer.provideKeyResponse();
            }
        }

         */
        myPlayer.start();
        isPrepared=false;
    }

    public void stop() {
        if (!isPrepared) {
            myPlayer.stop();
            myPlayer.reset();
            isPrepared = false;
        } else {
            myPlayer.reset();
            isPrepared = false;
        }
    }

    @Override
    public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
        String tmp="";
        if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN)
            tmp = "MediaPlayer.MEDIA_ERROR_UNKNOWN";
        if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED)
            tmp = "MediaPlayer.MEDIA_ERROR_SERVER_DIED";
        if (extra == MediaPlayer.MEDIA_ERROR_IO)
            tmp+=" MediaPlayer.MEDIA_ERROR_IO";
        if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED)
            tmp+=" MediaPlayer.MEDIA_ERROR_MALFORMED";
        if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED)
            tmp+=" MediaPlayer.MEDIA_ERROR_UNSUPPORTED";
        if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT)
            tmp+=" MediaPlayer.MEDIA_ERROR_TIMED_OUT";

        Log.e("dedetok", "OnErrorListener " + tmp); // debug
        myPlayer.reset();
        isPrepared=false;
        return true;
    }

    public void release() {
        myPlayer.release();
        myPlayer=null;
    }
}

MainActivity.java

package com.dedetok.tutorialexoplayer;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import android.os.Build;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    MyMediaPlayer myMediaPlayer;
    MyExoPlayer myExoPlayer;
    int buildVersion=0;
    /*
     * ## activity life cycle 1 ##
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buildVersion = Build.VERSION.SDK_INT; // get android version from device

        String myRadioUrl = "https://stream-node1.rri.co.id/streaming/25/9025/rrijakartapro1.mp3";
        //String myRadioUrl = "http://cast1.my-control-panel.com/proxy/radioso1/stream";

        AppCompatButton bMediaPlayer = findViewById(R.id.b_mediaplayer);
        bMediaPlayer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myExoPlayer.stop();
                myMediaPlayer.play(myRadioUrl);
            }
        });

        AppCompatButton bExoPlayer = findViewById(R.id.b_exoplayer);
        bExoPlayer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myMediaPlayer.stop();
                myExoPlayer.play(myRadioUrl);
            }
        });
    }

    /*
     * ## activity life cycle 2 ##
     */
    @Override
    protected void onStart() {
        super.onStart();
        // Android N Android 7
        if (buildVersion>= Build.VERSION_CODES.N) {
            myMediaPlayer = new MyMediaPlayer();
            myExoPlayer = new MyExoPlayer(this);
        }
    }

    /*
     * ## activity life cycle 3 ##
     */
    @Override
    protected void onResume() {
        super.onResume();
        // Android N Android 7
        if (buildVersion< Build.VERSION_CODES.N) {
            myMediaPlayer = new MyMediaPlayer();
            myExoPlayer = new MyExoPlayer(this);
        }
    }

    /*
     * ## activity life cycle 4 ##
     */
    @Override
    protected void onPause() {
        if (buildVersion<Build.VERSION_CODES.N) {
            myMediaPlayer.release();
            myMediaPlayer=null;
            myExoPlayer.release();
            myExoPlayer=null;
        }
        super.onPause();
    }

    /*
     * ## activity life cycle 5 ##
     */
    @Override
    protected void onStop() {
        if (buildVersion>=Build.VERSION_CODES.N) {
            myMediaPlayer.release();
            myMediaPlayer=null;
            myExoPlayer.release();
            myExoPlayer=null;
        }
        super.onStop();
    }

    /*
     * ## activity life cycle 6 ##
     */

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

Pron using ExoPlayer:

  1. ExoPlayer can handle https media streaming for self signing sertificate but MediaPlayer can not.
  2. Handing media streaming in ExoPlayer uses less code then MediaPlayer.

Cons using ExoPlayer

  • ExoPlayer required minimum API level 23 / Android N / Android 6.0


Monday, January 29, 2024

Jsoup extracting table element

Eclipse 2023-09

Note: For personal reference using Jsoup in java

  1. create a new project
  2. under project root create folder "lib"
  3. download Jsoup binary (jar file) from https://jsoup.org/download and put in folder "lib". Use the latest one, this example use jsoup-1.17.2.jar
  4. in project properties -> Java Build Path -> Libraries -> Modulepath -> Add external JARs and add your Jsoup binary, Apply and Close

Java code to extract element in table from my blog https://dedetoknotes.blogspot.com/2024/01/my-radio-list.html

package com.dedetok;

import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class Main {

    public static void main(String[] args) {
        try {
            Document doc = Jsoup.connect("https://dedetoknotes.blogspot.com/2024/01/my-radio-list.html").get();
            if (doc!=null) {
                System.out.println("Ok: "+doc.title()); //debug
                //Elements myTable = doc.getElementsByTag("table");
                Element myTable = doc.getElementById("com.dedetok.myradiolist");
                //System.out.println("size children: "+myTable.childrenSize()); //debug
                Elements myTr = myTable.select("tr");
                System.out.println("number of tr: "+myTr.size());
                //System.out.println(myTr.toString()); //debug

                // iterate row tr
                for (Element e : myTr) {
                    Elements myTd = e.select("td");
                    //System.out.println("number of td: "+myTd.size()); //debug

                    // iterate column td
                    for (Element eTd: myTd) {
                        //get icon url in image or text
                        Element myImg = eTd.select("img").first();
                        if (myImg!=null) {
                            String myIconUrl = myImg.absUrl("src");
                            System.out.print(myIconUrl+"|"+eTd.text()+" ");
                        } else {
                            System.out.print(eTd.text()+";");                           
                        }
                    }
                    System.out.println(); // add a new line
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

For Android Studio 2023.1.1 Patch 1

add build.gradle.kts (Module :app) 

dependencies {
    ...
    implementation("org.jsoup:jsoup:1.17.2") // jsoup
}