This is source code for com.dedetok.turuntirta and com.dedetok.bramara. I keep using legacy MediaPlayer.
Put turuntirta.mp3 in folder [Workspace]/Wargasari/app/src/main/res/raw/
build.gradle.kts (Module: app)
plugins {
id("com.android.application")
}
android {
namespace = "com.dedetok.turuntirta"
compileSdk = 34
defaultConfig {
applicationId = "com.dedetok.turuntirta"
minSdk = 24
targetSdk = 34
versionCode = 202402
versionName = "2024.02"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("com.google.android.gms:play-services-ads-lite:23.0.0")
}
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">
<!-- For apps targeting Android 13 or higher & GMA SDK version 20.3.0 or lower -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- permission for media player -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- android:enableOnBackInvokedCallback="true" Back Pressed Target API level>33 -->
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/my_ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/my_ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TurunTirta"
tools:targetApi="31"
android:enableOnBackInvokedCallback="true"
>
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
<!-- AD App ID: ca-app-pub-0220748109202708~7061470683 : AndroidManifest.xml -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-0220748109202708~7061470683"
/>
</application>
</manifest>
res -> values -> strings.xml
<resources>
<string name="app_name" translatable="false">Kidung Turun Tirta</string>
<string-array name="kidung">
<item>Turun tirta saking luhur|\nMenyiratan pemangkune|\nMekalangan muncrat mumbul|\nMapan tirtha merta jati|\nPaican Bhatara sami|\nPanglukatan dasa mala|\nSami pada lebur|\nMalene ring gumi||</item>
<item>Meketis ping tiga sampun|\nPabahan Siwa dwarane|\nWasuhane raris kinum|\nPing tiga lantas mesugi|\nRing waktra megentos genti|\nToya amertha Hyang Widhine|\nSami sampun puput|\nMengalangin hati||</item>
</string-array>
<array name="my_font_size">
<item>18</item>
<item>24</item>
<item>32</item>
<item>42</item>
</array>
<string name="dialog_title">Exit Application</string>
<string name="dialog_message">Do you want to exit application?</string>
<string name="play_pause">Play/Pause</string>
<string name="zoom_in">Zoom In</string>
<string name="zoom_out">Zoom Out</string>
<string name="bait">Stanza</string>
<string name="play_repeat">Repeat play</string>
</resources>
res -> values -> ic_launcher_background.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
res -> Layout -> layout_stanza.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stanza_title"
android:textColor="#9C27B0"
/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stanza_content"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
res -> Layout -> 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.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_zoom_in"
android:text="@string/zoom_in"
/>
<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_zoom_out"
android:text="@string/zoom_out"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_weight="1"
/>
<androidx.appcompat.widget.AppCompatSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/my_seekbar"
/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center">
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/my_play"
android:src="@android:drawable/ic_media_play"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/play_pause"
/>
<androidx.appcompat.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_repeat"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/play_repeat"
android:text="@string/play_repeat"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- Sample AdMob unit ID: ca-app-pub-3940256099942544/6300978111 -->
<!-- AD Unit ID: ca-app-pub-0220748109202708/8538203883 : layout -->
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:adSize="BANNER"
android:layout_gravity="end|center"
app:adUnitId="ca-app-pub-0220748109202708/8538203883"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
java -> StanzaContainer.java
package package com.dedetok.wargasari;;
public class StanzaContainer {
public String stanzaTitle;
public String stanzaContent;
public StanzaContainer(String stanzaTitle, String stanzaContent) {
this.stanzaTitle = stanzaTitle;
this.stanzaContent = stanzaContent;
}
}
java -> MyAdapter.java
package com.dedetok.wargasari;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter {
ArrayList<StanzaContainer> myStanza;
int myUnitTextSize;
float myTextSize;
public MyAdapter(ArrayList<StanzaContainer> myStanza, int unit, float size) {
this.myStanza = myStanza;
this.myUnitTextSize = unit;
this.myTextSize = size;
}
@NonNull
@Override
public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.layout_stanza, parent, false);
return new MyAdapter.MyHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StanzaContainer myTmpStanza = myStanza.get(position);
((MyHolder) holder).stanzaTitle.setText(myTmpStanza.stanzaTitle);
((MyHolder) holder).stanzaTitle.setTextSize(myUnitTextSize, myTextSize);
((MyHolder) holder).stanzaContent.setText(myTmpStanza.stanzaContent);
((MyHolder) holder).stanzaContent.setTextSize(myUnitTextSize, myTextSize);
}
@Override
public int getItemCount() {
return myStanza.size();
}
public void changeFontSize(int unit, float size) {
this.myUnitTextSize = unit;
this.myTextSize = size;
notifyDataSetChanged();
}
private class MyHolder extends RecyclerView.ViewHolder {
public AppCompatTextView stanzaTitle, stanzaContent;
public MyHolder(@NonNull View itemView) {
super(itemView);
stanzaTitle = itemView.findViewById(R.id.stanza_title);
stanzaContent = itemView.findViewById(R.id.stanza_content);
}
}
}
java -> MainActivity.java
package com.dedetok.turuntirta;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatSeekBar;
import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
MediaPlayer myMediaPlayer = null;
AppCompatSeekBar mySeekBar;
int cTextSizeState = 0;
TypedArray availFontSize;
float defaultTextSize = 18;
boolean myPlayerStatus = false;
private Bundle myBundle;
private int myCurrentPos =0;
static final String STATE_MY_PLAYER_STATUS = "currentStatus";
static final String STATE_MY_PLAYER_POSITION = "currentPosition";
AppCompatImageButton myPlayButton; //20240302
// handler to update seekbar
final Handler myHandler = new Handler(Looper.getMainLooper()); // new Handler() deprecated 20240302
//20240423
PowerManager.WakeLock wakeLock;
/*
* ## activity life cycle 1 ##
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// add version number from gradle into activity title
try {
PackageInfo myPackage = getPackageManager().getPackageInfo(getPackageName(),0);
String myTitle = getString(R.string.app_name)+" "+myPackage.versionName;
setTitle(myTitle);
} catch (PackageManager.NameNotFoundException e) {
// Do Nothing, pass process
}
// ads:adUnitId ca-app-pub-0220748109202708/8538203883 : AdView in Layout
// app id ca-app-pub-0220748109202708~7061470683 : AndroidManifest.xml
AdView mAdView = findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
// Button Play 20240302
myPlayButton = findViewById(R.id.my_play);
myPlayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (myPlayerStatus) {
// pause
pauseMedia();
} else {
// play
playMedia();
}
}
});
// seekbar
mySeekBar = findViewById(R.id.my_seekbar);
mySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int myProgress, boolean isFromUser) {
if (isFromUser) {
if (myMediaPlayer!=null) {
myMediaPlayer.seekTo(myProgress);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// DO NOTHING
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// DO NOTHING
}
});
// recyclerview
availFontSize = getResources().obtainTypedArray(R.array.my_font_size);
RecyclerView myRecyclerView = findViewById(R.id.my_recycler_view);
LinearLayoutManager myLinearLayout = new LinearLayoutManager(getApplicationContext());
myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
myRecyclerView.setLayoutManager(myLinearLayout);
MyAdapter myAdapter = new MyAdapter(getTexts(), COMPLEX_UNIT_SP, availFontSize.getFloat(cTextSizeState, defaultTextSize));
myRecyclerView.setAdapter(myAdapter);
// button zoom text
AppCompatButton bZoomOut = findViewById(R.id.b_zoom_out);
bZoomOut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (cTextSizeState==0) {
//Log.i("Font Change", "Zoom out do nothing");
} else {
cTextSizeState--;
myAdapter.changeFontSize(COMPLEX_UNIT_SP,availFontSize.getFloat(cTextSizeState, defaultTextSize));
//Log.i("Font Change", Float.toString(availFontSize.getFloat(cTextSizeState,defaultTextSize)));
}
}
});
AppCompatButton bZoomIn = findViewById(R.id.b_zoom_in);
bZoomIn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (cTextSizeState+1==availFontSize.length()) {
//Log.i("Font Change", "Zoom in do nothing");
} else {
cTextSizeState++;
myAdapter.changeFontSize(COMPLEX_UNIT_SP,availFontSize.getFloat(cTextSizeState, defaultTextSize));
//Log.i("Font Change", Float.toString(availFontSize.getFloat(cTextSizeState,defaultTextSize)));
}
}
});
// to save state if user switch to / from other application
myBundle = savedInstanceState; // added multimedia
// onBackPressed() deprecated -> OnBackPressedCallback & getOnBackPressedDispatcher()
// dispatcher
OnBackPressedDispatcher myOnbackPressedDispatcher = getOnBackPressedDispatcher();
// callback on back key pressed
OnBackPressedCallback myBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
// Log.e("dedetok", "handleOnBackPressed()"); //debug
AlertDialog.Builder myAlertDialog = new AlertDialog.Builder(
MainActivity.this);
myAlertDialog.setTitle(R.string.dialog_title).
setMessage(R.string.dialog_message).
setIcon(android.R.drawable.ic_dialog_alert).
setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// finish() will execute onDestroy()
finish();
}
}).
setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// do nothing
}
}).
show();
}
};
// add callback to dispatcher
myOnbackPressedDispatcher.addCallback(this, myBackPressedCallback);
//20240422
SwitchCompat sRepeat = findViewById(R.id.switch_repeat);
sRepeat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean booleanRepeat) {
//myDataProvider.setRepeat(booleanRepeat);
if (myMediaPlayer!=null) {
myMediaPlayer.setLooping(booleanRepeat);
}
}
});
}
// ## activity life cycle 2 ##
// onStart() 20240302
@Override
protected void onStart() {
super.onStart();
// 20240423
// Android N (7.0) or latter MediaPlayer should create in OnStart
// Media Player
// preparing mediaplayer
// no need to call prepare(); create() does that for you
if (myMediaPlayer==null) {
myMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.turuntirta);
}
myMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
// seekbar at start and stop thread
myPlayerStatus = mediaPlayer.isPlaying();
myCurrentPos=0;
mySeekBar.setProgress(myCurrentPos);
if (!myMediaPlayer.isLooping()) {
pauseMedia();
}
}
});
}
/*
* ## activity life cycle 3 ## 20240302
* activity process 3
* onResume()
* @Override onRestoreInstanceState(Bundle savedInstanceState) ?
*/
@Override
public void onResume() {
super.onResume();
loadMyState();
// link mySeekbar to myPlayer
mySeekBar.setMax(myMediaPlayer.getDuration());
if (myPlayerStatus && !myMediaPlayer.isPlaying()) {
playMedia();
}
if (myMediaPlayer.isPlaying()) {
myMediaPlayer.seekTo(myCurrentPos);
}
}
/*
* ## activity life cycle 4 ## 20240302
* onPause()
* @Override onSaveInstanceState(Bundle outState) ?
*/
@Override
public void onPause() {
super.onPause();
}
// ## activity life cycle 5 ## 20240302
@Override
protected void onStop() {
saveMyState();
// 20240213
// Android N (7.0) or latter MediaPlayer should stop and release in OnStop
// we meed to keep play media even if screen does not on top
// move release resource to onDestroy()
super.onStop();
}
/*
* ## activity life cycle 5 ## 20240302
* clean up onDestroy()
*/
@Override
public void onDestroy() {
//Log.e("dedetok","onDestroy Activity"); //debug
// 20240423
// release all media resources
// release lock and executor include in funcion pauseMedia()
pauseMedia();
if (myMediaPlayer != null) {
myMediaPlayer.release();
myMediaPlayer = null;
}
super.onDestroy();
}
/* 20240302
* onConfigurationChanged Called:
* - after orientation change
*/
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// DO NOTHING
}
/* 20240302
* onSaveInstanceState Called:
* - after switch to other application
* - after onConfigurationChanged
*/
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//Log.e("dedetok", "onSaveInstanceState"); // debug
// TODO save something
}
/* 20240302
* onRestoreInstanceState Called:
* - after back to application
* - after onConfigurationChanged
*/
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//Log.e("dedetok", "onRestoreInstanceState"); // debug
// TODO save something
}
private ArrayList<StanzaContainer> getTexts() {
String[] kidung = getResources().getStringArray(R.array.kidung);
int numKidung = kidung.length;
ArrayList<StanzaContainer> myStanza = new ArrayList<StanzaContainer>();
for (int i=0;i<numKidung; i++) {
int j=i+1;
String stanzaTitle = getString(R.string.bait)+" "+j;
StanzaContainer myTmpStanza = new StanzaContainer(stanzaTitle, kidung[i]);
myStanza.add(myTmpStanza);
}
//Log.i("array",Integer.toString(kidunglist.length));
return myStanza;
}
/*
* to load state
*/
void loadMyState() {
if (myBundle != null) {
super.onRestoreInstanceState(myBundle);
// Save the user's current media player state
myCurrentPos = myBundle.getInt(STATE_MY_PLAYER_POSITION);
myPlayerStatus = myBundle.getBoolean(STATE_MY_PLAYER_STATUS);
}
}
/*
* to save state
*/
void saveMyState() {
if (myBundle != null) {
myBundle.putInt(STATE_MY_PLAYER_POSITION, myCurrentPos);
myBundle.putBoolean(STATE_MY_PLAYER_STATUS, myPlayerStatus);
super.onSaveInstanceState(myBundle);
}
}
// 20240302
void playMedia() {
//Log.e("dedetok", "playMedia create execcutor"); // debugg
executor = Executors.newSingleThreadExecutor();
//Log.e("dedetok", "playMedia wakelock and start"); // debugg
// 20240423
// try to get WAKE_LOCK
if (wakeLock==null) {
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MyApp::com.dedetok.turuntirta");
}
wakeLock.acquire();
myMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
myMediaPlayer.start();
// set icon to pause
myPlayButton.setImageResource(android.R.drawable.ic_media_pause);
myPlayerStatus = myMediaPlayer.isPlaying();
//Log.e("dedetok", "playMedia seekbar"); // debugg
// update seekbar TODO CRASH
executor.submit(mySeekbarUpdater);
}
// 20240302
void pauseMedia() {
//Log.e("dedetok", "pauseMedia pause"); // debugg
myMediaPlayer.pause();
// set icon to play
myPlayButton.setImageResource(android.R.drawable.ic_media_play);
myPlayerStatus = myMediaPlayer.isPlaying();
//Log.e("dedetok", "pauseMedia waklock release"); // debugg
// 20240423
// try to release WAKE_LOCK
if (wakeLock!=null) {
wakeLock.release();
}
//Log.e("dedetok", "pauseMedia executor release"); // debugg
if (executor!=null) {
executor.shutdown();
}
}
/* 20240302
* Thread to update seekbar during mediaplayer run
*/
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable mySeekbarUpdater = new Runnable() {
@Override
public void run() {
if (myMediaPlayer!=null) {
if (myMediaPlayer.isPlaying()) {
myCurrentPos = myMediaPlayer.getCurrentPosition();
mySeekBar.setProgress(myCurrentPos);
myHandler.postDelayed(this, 500); // 500 millisecond
}
}
}
};
}