BERITA CREATIVE, DESAIN & MULTIMEDIA Tutorial Membuat Aplikasi Pemutar Musik dengan Voice Command

Tutorial Membuat Aplikasi Pemutar Musik dengan Voice Command

Oleh Fedli Bagus Kurniawan | Kamis, 18 Maret 2021

Tutorial Membuat Aplikasi Pemutar Musik dengan Voice Command

Mendengarkan musik saat ini sudah menjadi kebiasaan untuk beberapa orang sebagai penghibur dan teman untuk bekerja. Pemutar musik yang memiliki fitur lebih baik akan digemari oleh pengguna. Menambahkan perintah voice akan menjadi alternatif fitur baru

Yuk, ikuti program inovatif MAGANG ONLINE untuk berbagai bidang seperti animasi, coding, 3D, illustrasi, musik dan bisnis hanya di GAMELAB.ID!

Ciptakan lingkungan belajar yang lebih MENYENANGKAN dengan GAME-BASED LEARNING!

Daftar Isi Artikel

Perkembangan zaman yang semakin maju menghasilkan berbagai produk berupa software yang dapat digunakan untuk mempermudah dan meringankan suatu pekerja pengguna. Dan perangkat yang paling laris saat ini adalah perangkat mobile yaitu android.  Banyak aplikasi android baru yang diciptakan setiap harinya. Aplikasi yang paling banyak diminati oleh pengguna smartphone adalah aplikasi yang menyediakan fasilitas multimedia, seperti aplikasi pengambil dan pengubah gambar, serta aplikasi yang berhubungan dengan audio dan video. Kebanyakan aplikasi audio yang telah ada sebelumnya, merupakan aplikasi pemutar audio atau musik yang telah tersimpan didalam penyimpanan smartphone.

Penggunaan pemutar musik sering ditemui di kalangan pendengar musik baik berupa aplikasi yang berjalan dengan jaringan internet maupun yang offline. Terdapat banyak aplikasi pemutar musik yang disediakan oleh google play store sehingga pengguna dapat menentukan pilihannya.

Semakin berkembang zaman pemutar musik yang biasa digunakan untuk menghibur pengguna sekarang mulai berkembangan untuk mempermudah penggunaannya. Pembuatan aplikasi pemutar musik otomatis menjadi salah satu perkembangannya yang kemudian membuat pengguna memilih aplikasi yang simple dalam kinerja aplikasinya. Pemutar dengan tambahan fitur perintah suara dapat menarik minat dari pengguna karena terdapat perintah dalam bahasa inggris untuk menjalankan aplikasi ini. Penggunaan fitur ini berjalan ketika menggunakan headset. Sehingga pengguna hanya perlu mengaktifkan perintah voice dan aplikasi akan menjalankan perintahnya.

Baca Juga : Rahasia JavaScript Terungkap! Kehebatannya dan Tips Optimalisasi Super Praktis

Langkah-langkah Membuat Pemutar Musik Dengan Perintah Voice

Untuk membuat aplikasi ini terdapat beberapa tahap sehingga tidak bingung nantinya.  Ada beberapa hal yang perlu disiapkan seperti aplikasi pengembangan android.  Saya menggunakan android studio namun masih ada pilihan lain seperti Eclipse , NetBeans dan Unity.

Membuat Usecase

Terlebih dahulu kita akan membuat alur berjalannya aplikasi yang akan kita dalam bentuk usecase  sehingga mudah dipahami berikut adalah usecase yang telah saya buat:

 

Untuk membuat usecase kalian dapat menggunakan aplikasi Star UML untuk membuat usecase. Dari usecase diatas kita dapat menyimpulkan bahwa dalam membuat pemutar musik kita butuh permission  untuk membaca data penyimpanan dan audio record . Jadi untuk perintah voice kita akan merekam perintah kemudian baru menjalankannya.

Mengumpulkan Gambar

Untuk tampilan kita butuh gambar untuk mempercantik tampilan pemutar musik yang akan dibuat  seperti  untuk bagian tampilan splash, icon aplikasi, background pada daftar musik, background ketika musik dijalankan dan tombol pembantu untuk memutar musik.

Membuat Aplikasi Pemutar Musik


Sebelum memulai pembuatan kita buka android  studio  dan buat project baru. Masukan semua gambar tadi kedalam drawable. Kalian dapat drag & drop kedalam drawable. Kemudian membuat layout baru dengan cara masuk pada bagian reslayout dilanjutkan klik kanan dan pilih newlayout resource file kemudian di beri nama "activity_splash.xml".

Kita baru saja menambahkan sebuah tampilan baru sebagai tampilan splash  berikut souce code kalian masukan:

activity_splash.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:background="@drawable/backsplash"
    tools:context=".SplashActivity">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:background="@drawable/splash" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_marginTop="450dp"
        android:layout_height="50dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Kefbiv Music"
            android:textSize="40dp"
            android:fontFamily="@font/kauhan"
            android:textAlignment="center"/>

    </LinearLayout>

</RelativeLayout>

Tampilan splash saya membuatnya menggunakan ImageView dan TextView. Untuk memperbesar ukuran gambar kalian dapat merubah bagian android:layout_width dan android:layout_height.

Selanjutnya kita membuat tampilan daftar musik. Tampilan daftar musik kita akan buat pada activity_main.xml untuk tampilan daftar musik saya menggunakan TextView  sebagai judul dan ListView  sebagai daftar seluruh lagu.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:background="@drawable/backmain"

    tools:context=".MainActivity">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="Daftar Musik"
        android:textStyle="bold"
        android:textAllCaps="true"
        android:textSize="30dp"
        android:textAlignment="center"
        android:textColor="#FFFFFF"
        android:background="@color/blackPanther"/>

    <ListView
        android:id="@+id/songList"
        android:layout_width="402dp"
        android:layout_height="693dp"
        android:padding="10dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="29dp">

    </ListView>
</RelativeLayout>

Tampilan terakhir yang kita buat adalah tampilan home atau tampilan pemutar musik . Fungsi yang dimasukan hampir sama dengan sebelumnya saya menggunakan ImageView, TextView dan button.

activity_home.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:id="@+id/parentRelativeLayout"
    android:background="@drawable/back"
    tools:context=".Home">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Kefbiv Music"
        android:fontFamily="@font/kauhan"
        android:textAlignment="center"
        android:textSize="40dp"
        android:layout_marginTop="525dp"
        android:textColor="@color/blackPanther"
        />
        <RelativeLayout
            android:id="@+id/upper"
            android:layout_width="match_parent"
            android:layout_height="480dp">
            <ImageView
                android:id="@+id/logo"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:layout_marginTop="180dp"
                android:layout_marginLeft="100dp"/>
            <TextView
                android:id="@+id/songName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/logo"
                android:textSize="25dp"
                android:textStyle="italic"
                android:textAlignment="center"
                android:textAllCaps="false"
                android:textColor="@android:color/primary_text_dark"
                android:layout_margin="7dp"
                android:singleLine="true"
                android:marqueeRepeatLimit="marquee_forever"
                android:ellipsize="marquee"
                />
        </RelativeLayout>
        <RelativeLayout
            android:id="@+id/lower"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/upper"
            android:visibility="gone"
            android:gravity="center">


            <ImageView
                android:id="@+id/previous_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/previous"
                android:layout_marginRight="30dp" />
            <ImageView
                android:id="@+id/play_pause_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/pause"
                android:layout_toEndOf="@+id/previous_btn"
                android:layout_marginRight="30dp"

                android:layout_toRightOf="@+id/previous_btn" />
            <ImageView
                android:id="@+id/next_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/next"
                android:layout_toEndOf="@+id/play_pause_btn"
                android:layout_toRightOf="@+id/play_pause_btn" />
        </RelativeLayout>

    <Button
        android:id="@+id/voice_enable"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="600dp"
        android:background="@color/blackPanther"
        android:text="Voice Enable MODE - ON"
        android:textAllCaps="false"
        android:textColor="@android:color/white"
        android:textSize="16dp"
        android:layout_centerHorizontal="true"/>


</RelativeLayout>

Setelah tampilan selesai kita masuk pada bagian java  untuk deklarasi dan inisialisasi. Buka java dan buat home.java dan splashActivity.java.   

Untuk splashActivity .java kita akan membuat durasi munculnya tampilan splash dan perpindahan antara tampilan splash ke tampilan daftar musik. Untuk durasi tampilan splash saya membuat 3 detik. 

splashAcrivity.java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;

public class SplashActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        Thread thread = new Thread(){
            public void run(){
                try {
                    sleep(3000);

                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    startActivity(new Intent(SplashActivity.this, MainActivity.class));
                    finish();
                }

            }
        };
        thread.start();
    }
}

Perpindahan tampilan daftar musik akan diikuti dengan permission untuk membaca penyimpanan dan permission audio record. Setelah permission  diperoleh maka aplikasi akan menampilkan data audio dalam daftar musik berdasarkan jenis yang  telah kita tentukan seperti .mp3 , .aac dan .wav. Musik ini akan dimasukan kedalam array dan ditampilkan kedalam ListView.

activity_main.java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.PermissionListener;

import java.io.File;
import java.util.ArrayList;

import static android.os.Environment.getExternalStorageDirectory;

public class MainActivity extends AppCompatActivity {
    private String[] itemsAll;
    private ListView mSongsList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSongsList =findViewById(R.id.songList);
        appExternalStorageStoragePermisision();
    }

    public void appExternalStorageStoragePermisision() {
        Dexter.withActivity(this)
                .withPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse response) {
                        display();

                    }

                    @Override
                    public void onPermissionDenied(PermissionDeniedResponse response) {

                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();
    }

    public ArrayList<File> readData(File file){
        ArrayList<File> arrayList = new ArrayList<>();
        File[] allFiles  =file.listFiles();
        for (File individualFile : allFiles)
        {
            if(individualFile.isDirectory() && !individualFile.isHidden())
            {
                arrayList.addAll(readData(individualFile));
            }
            else
            {
                if (individualFile.getName().endsWith(".aac") || individualFile.getName().endsWith(".wav")|| individualFile.getName().endsWith(".mp3"))
                {
                    arrayList.add(individualFile);
                }
            }

        }
        return arrayList;
    }
    private void display()
    {
        final ArrayList<File> audio = readData(getExternalStorageDirectory());

        itemsAll = new String[audio.size()];
        for (int songCounter=0;songCounter<audio.size();songCounter++){
            itemsAll[songCounter]=audio.get(songCounter).getName();

        }
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this, R.layout.row, itemsAll);
        mSongsList.setAdapter(arrayAdapter);
        mSongsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int i, long l)
            {
                String songName = mSongsList.getItemAtPosition(i).toString();
                Intent intent = new Intent(MainActivity.this, Home.class);
                intent.putExtra("song", audio );
                intent.putExtra("name", songName);
                intent.putExtra("posisi", i);
                startActivity(intent);
            }
        });
    }
}

Selanjutnya kita kan membuat bagian home.java yang berisi fungsi button  play, next, previous  dan tombol perintah voice.  Ketika tombol  perintah voice  dijalankan button lain akan dihilangankan.

home.java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.util.ArrayList;
import java.util.Locale;

public class Home extends AppCompatActivity {
    private RelativeLayout pareRelativeLayout;
    private SpeechRecognizer speechRecognizer;
    private Intent speechRecognizerIntent;
    private String keeper = "";
    private ImageView pausePlayBtn, nextBtn, preBtn;
    private TextView songNameTxt;
    private ImageView imageView;
    private RelativeLayout lowerRelativeLayout;
    private Button voiceEnableBtn;
    private String mode = "ON";
    private int posisi;
    private ArrayList<File> musik;
    private String mSongname;
    private MediaPlayer mediaPlayer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);


        checkVoiceCommandPermission();

        pausePlayBtn = findViewById(R.id.play_pause_btn);
        nextBtn = findViewById(R.id.next_btn);
        preBtn = findViewById(R.id.previous_btn);
        imageView = findViewById(R.id.logo);
        lowerRelativeLayout = findViewById(R.id.lower);
        voiceEnableBtn = findViewById(R.id.voice_enable);
        songNameTxt = findViewById(R.id.songName);
        speechRecognizer = SpeechRecognizer.createSpeechRecognizer(Home.this);
        speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
        pareRelativeLayout = findViewById(R.id.parentRelativeLayout);
        imageView.setBackgroundResource(R.drawable.logo);
        validasi();
        voiceEnableBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mode.equals("ON")) {
                    mode = "OFF";
                    voiceEnableBtn.setText("Voice Enable MODE - OFF");
                    lowerRelativeLayout.setVisibility(View.VISIBLE);
                } else {
                    mode = "ON";
                    voiceEnableBtn.setText("Voice Enable MODE - ON");
                    lowerRelativeLayout.setVisibility(View.GONE);
                }
            }
        });
        speechRecognizer.setRecognitionListener(new RecognitionListener() {
            @Override
            public void onReadyForSpeech(Bundle params) {

            }

            @Override
            public void onBeginningOfSpeech() {

            }

            @Override
            public void onRmsChanged(float rmsdB) {

            }

            @Override
            public void onBufferReceived(byte[] buffer) {

            }

            @Override
            public void onEndOfSpeech() {

            }

            @Override
            public void onError(int error) {

            }

            @Override
            public void onResults(Bundle bundle) {
                ArrayList<String> matchesFound = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                if (matchesFound != null) {
                        if (mode.equals("ON")) {
                            keeper = matchesFound.get(0);
                            Toast.makeText(Home.this, "Commad: " + keeper, Toast.LENGTH_LONG).show();
                            if (keeper.equals("pause the song")) {

                                playpuasemusik();
                            } else if (keeper.equals("Play the song")) {

                                playpuasemusik();

                            } else if (keeper.equals("play next song")) {
                                playNext();
                            } else if (keeper.equals("play previous song")) {
                                playprevoius();
                            }
                        }

                }
            }

            @Override
            public void onPartialResults(Bundle partialResults) {

            }

            @Override
            public void onEvent(int eventType, Bundle params) {

            }
        });
        pareRelativeLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent motionEvent) {
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        speechRecognizer.startListening(speechRecognizerIntent);
                        keeper = "";
                        break;
                    case MotionEvent.ACTION_UP:
                        speechRecognizer.stopListening();
                        break;
                }
                return false;
            }
        });

        pausePlayBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                playpuasemusik();
            }
        });

        preBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mediaPlayer.getCurrentPosition()>0)
                {
                    playprevoius();
                }
            }
        });

        nextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mediaPlayer.getCurrentPosition()>0)
                {
                    playNext();
                }
            }
        });

    }
    private void validasi ()
    {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();
        musik = (ArrayList) bundle.getParcelableArrayList("song");
        mSongname = musik.get(posisi).getName();
        String songName = intent.getStringExtra("name");
        songNameTxt.setText(songName);
        songNameTxt.setSelected(true);
        posisi = bundle.getInt("posisi", 0);
        Uri uri = Uri.parse(musik.get(posisi).toString());
        mediaPlayer = MediaPlayer.create(Home.this, uri);
        mediaPlayer.start();

    }
    private void checkVoiceCommandPermission () {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!(ContextCompat.checkSelfPermission(Home.this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED)) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
                startActivity(intent);
                finish();
            }
        }
    }
    private void playpuasemusik()
    {
        imageView.setBackgroundResource(R.drawable.four);

        if(mediaPlayer.isPlaying())
        {

            pausePlayBtn.setImageResource(R.drawable.play);
            mediaPlayer.pause();
        }
        else
        {

            pausePlayBtn.setImageResource(R.drawable.pause);
            mediaPlayer.start();
            imageView.setImageResource(R.drawable.five);
        }
    }


    private void playNext()
    {
        mediaPlayer.pause();
        mediaPlayer.stop();
        mediaPlayer.release();
        posisi=((posisi+1)&musik.size());
        Uri uri = Uri.parse(musik.get(posisi).toString());
        mediaPlayer = MediaPlayer.create(Home.this, uri);
        mSongname=musik.get(posisi).toString();
        songNameTxt.setText(mSongname);
        mediaPlayer.start();

        imageView.setBackgroundResource(R.drawable.three);
        if(mediaPlayer.isPlaying())
        {

            pausePlayBtn.setImageResource(R.drawable.pause);
        }
        else
        {
            pausePlayBtn.setImageResource(R.drawable.play);
            imageView.setImageResource(R.drawable.five);
        }
    }
    public void playprevoius()
    {
        mediaPlayer.pause();
        mediaPlayer.stop();
        mediaPlayer.release();

        posisi = ((posisi-1)<0 ? (musik.size()-1) : (posisi-1));
        Uri uri = Uri.parse(musik.get(posisi).toString());
        mediaPlayer = MediaPlayer.create(Home.this, uri);
        mSongname=musik.get(posisi).toString();
        songNameTxt.setText(mSongname);
        mediaPlayer.start();

        imageView.setBackgroundResource(R.drawable.two);
        if(mediaPlayer.isPlaying())
        {

            pausePlayBtn.setImageResource(R.drawable.pause);
        }
        else
        {
            pausePlayBtn.setImageResource(R.drawable.play);
            imageView.setImageResource(R.drawable.five);
        }
    }
}

Untuk yang terakhir kita perlu menambahkan permission pada bagian manifest agar dapat mengakses keluar aplikasi dan merubah bagian activity yang dijalankan pertama kali.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Home" />
        <activity android:name=".MainActivity"></activity>
    </application>

</manifest>

Aplikasi telah selesai dibuat dan  siap untuk dicoba. Namun jika kalian ingin memasangnya di smartphone kalian harus merubahnya kedalam bentuk .apk terlebih dulu dan memasangkan ke android kalian masing-masing. selamat mencoba....!

Sumber Referensi dan Gambar :

https://docplayer.info/

https://repository.amikom.ac.id

https://pngtree.com/

 


Fedli Bagus Kurniawan

Fedli Bagus Kurniawan

Kamis, 18 Maret 2021

ARTIKEL TERKAIT

Magang lebih mudah dan bisa dilakukan dari mana saja dengan Program Magang Online Gamelab. Magang Bersertifikat, plus Pelatihan!

DAFTAR MAGANG

ARTIKEL POPULER

KATEGORI