1.

This music player is developed based on Android. It was originally a small project done by me and two other friends when WE were in school. Recently, we had time to sort it out. In my previous article, I have introduced the function realization of the playback interface (Android music player development), but the introduction is rather rough, and I will do more detailed sorting next. Content has been synchronized to Gitee repository, GitHub repository.

The original code was written haphazardly to achieve functionality. There is now a preference for code readability and simplicity, so minor changes will be made to the original program. You may not be able to do it all at once and plan to revise it slowly to improve your understanding.

The server side uses the more traditional servlet and JDBC to transfer data. After finishing sorting, the new version will be changed to the SSM framework, which is more concise and efficient. Android uses basic tools, such as the MediaPlayer class, and currently has no plans to change it.

Server: Android Music player development – server

Login: Android Music player development – Login

Register: Android Music Player development – Register

Change password: Android Music player development – change password

Play interface: Android music player development – play interface

(Suitable for those who make a small lesson in daily life)

2. Interface design

First, design a playing interface for the player

The functions of the playback interface include:

In addition to the function button described above, a button to close the service needs to be added in the subsequent debugging, and it will be placed on this interface for the time being

Use an XML file for interface design, named Activity_main.xml


      
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:padding="16dp"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:background="@drawable/background"
    >
    <Button
        android:id="@+id/quit_btn"
        android:layout_gravity="left"
        android:background="@drawable/kaiguan"
        android:layout_width="25dp"
        android:layout_height="25dp"/>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4"
        >

        <com.loopj.android.image.SmartImageView
            android:layout_width="260dp"
            android:layout_height="260dp"
            android:id="@+id/siv_icon"
            android:src="@drawable/default_record_album"
            android:layout_centerInParent="true"/>

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/text_view_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_centerHorizontal="true"
            android:layout_marginTop="8dp"

            android:textSize="26dp"
            android:textColor="#FFFFFF"

            android:text="Title"
            />


        <TextView
            android:id="@+id/text_view_artist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_centerHorizontal="true"
            android:layout_below="@id/text_view_name"
            android:text="Singer"
            android:textColor="#FFFFFF"

            android:textSize="20dp" />

    </RelativeLayout>

    <LinearLayout
        android:id="@+id/layout_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="40dp"

        android:gravity="center_vertical"
        >
        <SeekBar
            android:layout_width="match_parent"
            android:id="@+id/seek_bar"
            android:max="100"
            style="@style/Widget.AppCompat.SeekBar"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="8dp"
        android:showDividers="middle"
        android:gravity="center">

        <Button
            android:id="@+id/play_way_btn"
            android:layout_width="36dp"
            android:background="@drawable/xunhuanbofang"
            android:layout_marginRight="16dp"
            android:layout_height="36dp" />
        <Button
            android:id="@+id/play_last_btn"
            android:layout_width="40dp"
            android:layout_marginRight="16dp"
            android:background="@drawable/last"
            android:layout_height="40dp" />
        <Button
            android:id="@+id/play_or_pause_btn"
            android:layout_width="55dp"
            android:gravity="center"

            android:background="@drawable/bofang"
            android:layout_height="55dp" />
        <Button
            android:id="@+id/play_next_btn"
            android:layout_width="40dp"
            android:layout_marginLeft="16dp"
            android:background="@drawable/next"
            android:layout_height="40dp" />
        <Button
            android:id="@+id/play_menu_btn"
            android:layout_width="40dp"
            android:layout_marginLeft="16dp"
            android:background="@drawable/menu"
            android:layout_height="40dp" />

    </LinearLayout>
</LinearLayout>
Copy the code

All ICONS come from Alibaba icon Vector library

The layout file is relatively simple, so I will not introduce it too much here. The general interface and layout are shown as follows:

3. Realization of player functions

3.1 Two Interfaces

First of all, let’s sort out the overall idea. Because there are many functions here, including switching of background logic and interface, as well as updating of progress bar, two interfaces are set up here to separate the logic layer and the presentation layer. The logical layer methods are playLast(), playOrPause(), playNext(), stopPlay(), set the playback progress seekTo(); Presentation layer methods include onPlayerStateChange() for notification of playback status and onSeekChange() for change of playback progress, which are used to update UI.

PlayerControl.java

public interface PlayerControl {
    /* * Play */
    void playOrPause(a);

    /* Play the previous song */
    void play_last(a);

    /* Play next track */
    void play_next(a);

    /* Stop playing */
    void stopPlay(a);

    /* Set the playback schedule */
    void seekTo(int seek);
}
Copy the code

PlayerViewControl.java

public interface PlayerViewControl {
    /*
        播放状态的通知
         */
    void onPlayerStateChange(int state);

    /* Change the playback schedule */
    void onSeekChange(int seek);
}
Copy the code

The PlayerControl interface is implemented by PlayerPresenter.

With two interfaces, don’t worry about implementing them. Now you can write something that initializes the playback screen.

3.2 Initializing User Information

Before initializing the play interface, you need to initialize the user information, because the initialization of the interface depends on the song ID and the play mode of the user information. The user information comes from the information obtained during login.

private String account;    / / account
private int musicId;   Id / / song
public int playPattern;  // Play mode

// Initialize user information
private void initUserData(a){
    Intent intent = getIntent();

    String userStr = intent.getStringExtra("result");
    JSONObject userData = RequestServlet.getJSON(userStr);
    account = userData.optString("account");
    musicId = userData.optInt("music_id");
    playPattern = userData.optInt("pattern");
}
Copy the code

3.3 Initializing the Playback interface

private SeekBar mSeekBar;  / / the progress bar
private Button mPlayOrPause;
private Button mPlayPattern;
private Button mPlayLast;
private Button mPlayNext;
private Button mPlayMenu;
private Button mQuit;
private TextView mMusicName;
private TextView mMusicArtist;
private SmartImageView mMusicPic;

public final int PLAY_IN_ORDER = 0;   // Play in sequence
public final int PLAY_RANDOM = 1;    // Random play
public final int PLAY_SINGLE = 2;    // Single loop

// Initialize the interface
private void initView(a){
    mSeekBar = (SeekBar) this.findViewById(R.id.seek_bar);
    mPlayOrPause = (Button) this.findViewById(R.id.play_or_pause_btn);
    mPlayPattern = (Button) this.findViewById(R.id.play_way_btn);
    mPlayLast= (Button) this.findViewById(R.id.play_last_btn);
    mPlayNext = (Button) this.findViewById(R.id.play_next_btn);
    mPlayMenu = (Button) this.findViewById(R.id.play_menu_btn);
    mQuit=(Button) this.findViewById(R.id.quit_btn);
    mMusicName = (TextView) this.findViewById(R.id.text_view_name);
    mMusicArtist = (TextView) this.findViewById(R.id.text_view_artist);
    mMusicPic = (SmartImageView) this.findViewById(R.id.siv_icon);

    // Mode conversion
    if (playPattern==PLAY_IN_ORDER) {
        mPlayPattern.setBackgroundResource(R.drawable.xunhuanbofang);
    }else if(playPattern==PLAY_RANDOM){
        mPlayPattern.setBackgroundResource(R.drawable.suijibofang);
    } else if (playPattern==PLAY_SINGLE) {
        mPlayPattern.setBackgroundResource(R.drawable.danquxunhuan);
    }

    // Get the music list
    getMusicListThread();
}
Copy the code

There are three aspects to initialize the playback interface:

  • Bind all controls in the XML file
  • The icon of the play mode button in the interface is changed according to the user’s play mode resolved in the database. The song information cannot be loaded here, because the song resource has not been obtained
  • Get the music list from the database and initialize the song information into the interface based on the musicId resolved in the user information, using a child thread getMusicListThread

getMusicListThread

To get the playlist, you need to get data from the server and start a child thread. This calls the getMusicList method in the RequestServlet class (you can create this method in the RequestServlet class and implement it later).

public static JSONArray sMusicList;   // List of songs
public int songNum = 0;  // Total number of songs

// Get the music list
private void getMusicListThread(a){
    new Thread(){
        @Override
        public void run(a) {
            try{
                JSONArray result = RequestServlet.getMusicList();
                Message msg = new Message();
                msg.what = 2;
                msg.obj = result;
                handler2.sendMessage(msg);
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
    }.start();
}

private Handler handler2 = new Handler(){
    public void handleMessage(android.os.Message msg) {
        try {
            if (msg.what == 2) {
                sMusicList = (JSONArray) msg.obj;
                songNum = sMusicList.length();

                // Initializes the song interface based on user data and song listsetMusicView(IsPlay.notPlay); }}catch(Exception e) { e.printStackTrace(); }}};Copy the code

The child thread gets the music list on the server side and passes it to the main thread, which calls the **setMusicView()** method to initialize the song-related interface

setMusicView

Before we initialize the song information, we have the user information and the song list, now we can get the individual song information from the song list based on the musicId that is resolved in the user information, and then initialize that song information into the interface.

Before I formally introduce the setMusicView() method, you can see that a parameter is passed before the method is called, using an enumeration type to distinguish whether it needs to be played. When we initialize the interface, we don’t need to play the song, but when we change the song information, we also need to play the song.

public enum IsPlay{
    play, notPlay
}
Copy the code
public String playAddress;  // Address of the music file
public static final String IMG = "http://10.0.2.2:8080/musicplayer/image/";    // The generic address of the music image

// Set up the interface about the song
public void setMusicView(IsPlay playState){
    try {
        JSONObject musicInfo = (JSONObject) sMusicList.get(musicId);
        String name = musicInfo.optString("name");
        String author = musicInfo.optString("author");
        String img = musicInfo.optString("img");
        playAddress=musicInfo.optString("address");
        mMusicPic.setImageUrl(IMG+img,R.mipmap.ic_launcher,R.mipmap.ic_launcher);   // Set the song cover on the interface
        mMusicName.setText(name);   // Set the name of the song on the interface
        mMusicArtist.setText(author);   // Set the singer on the interface
    } catch (Exception e) {
        e.printStackTrace();
    }
    if(playState == IsPlay.play){
        if( mPlayerControl ! =null) { mPlayerControl.stopPlay(); } mPlayerControl.playOrPause(playState); }}Copy the code

Call playOrPause(); / / playOrPause(); / / playOrPause(); / / playOrPause(); / / playOrPause(); / / playOrPause(); / / playOrPause(); / / playOrPause(); Why stop playing in the first place? Because of a feature of MediaPlayer, if you need to switch songs, you must first release the MediaPlayer resource and then instantiate an object to load the new resource.

3.4 Initialization Events

Click events involving several buttons

private PlayerControl playerControl = new PlayerPresenter(this);

// Initialize the event
private void initEvent(a){

    // Play/pause buttons
    mPlayOrPause.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(mPlayerControl! =null){ mPlayerControl.playOrPause(IsPlay.notPlay); }}});// Play the previous song
    mPlayLast.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(mPlayerControl! =null){ mPlayerControl.playLast(); }}});// Play the next track
    mPlayNext.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(mPlayerControl! =null){ mPlayerControl.playNext(); }}});// Play mode
    mPlayPattern.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            playPattern = (playPattern+1) %3;

            if (playPattern==PLAY_IN_ORDER) {
                mPlayPattern.setBackgroundResource(R.drawable.xunhuanbofang);
            }else if(playPattern==PLAY_RANDOM){
                mPlayPattern.setBackgroundResource(R.drawable.suijibofang);
            } else if(playPattern==PLAY_SINGLE) { mPlayPattern.setBackgroundResource(R.drawable.danquxunhuan); }}});// Music list
    mPlayMenu.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(MainActivity.this,MusicListActivity.class); startActivity(intent); }});// Exit button
    mQuit.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(MainActivity.this."Saving information...", Toast.LENGTH_SHORT).show(); saveDataToDB(); }});/ / the progress bar
    mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            // The progress bar has changed
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            // The hand is already touched
            isUserTouchProgressBar=true;
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {

            int touchProgress=seekBar.getProgress();
            // Stop dragging
            if( mPlayerControl ! =null) {
                mPlayerControl.seekTo(touchProgress);
            }
            isUserTouchProgressBar=false; }}); }Copy the code

PlayerControl is the interface of the logical layer, and PlayerPresenter is the implementation class of the interface function. Here, mainActivity is passed as a parameter, which is convenient to call some parameters and methods of MainActivity.

The click of play/pause, play previous, play next button is handled directly by the PlayerControl interface.

The play mode button is clicked, and the play mode is switched according to the sequence of play, random play and single cycle. A complementary operation is made here, so that the value of playPattern is always kept between 0, 1 and 2. Change the play mode button icon according to the playPattern value.

public final int PLAY_IN_ORDER = 0;   // Play in sequence
public final int PLAY_RANDOM = 1;    // Random play
public final int PLAY_SINGLE = 2;    // Single loop


mPlayPattern.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        playPattern = (playPattern+1) %3;

        if (playPattern==PLAY_IN_ORDER) {
            mPlayPattern.setBackgroundResource(R.drawable.xunhuanbofang);
        }else if(playPattern==PLAY_RANDOM){
            mPlayPattern.setBackgroundResource(R.drawable.suijibofang);
        } else if(playPattern==PLAY_SINGLE) { mPlayPattern.setBackgroundResource(R.drawable.danquxunhuan); }}});Copy the code

When the exit button is clicked, the current user information (play mode, played song ID) needs to be saved to the database, so the child thread implementation is enabled.

mQuit.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Toast.makeText(MainActivity.this."Saving information...", Toast.LENGTH_SHORT).show(); saveDataToDB(); }});Copy the code

Drag the progress bar event listeners need to implement SeekBar. OnSeekBarChangeListener interface, call the SeekBar setOnSeekBarChangeListener the object passed in the event monitoring event listeners. There are three important methods in the interface:

  1. OnProgressChanged, used when the progress bar changes;
  2. OnStartTrackingTouch, used when the progress bar starts dragging;
  3. OnStopTrackingTouch, used when the progress bar stops being dragged.

The third method is used here, which calls the seekTo method of the playerControl interface when you stop dragging the progress bar.

private boolean isUserTouchProgressBar = false;   // Check whether the hand touches the progress bar

mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // The progress bar has changed
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        // The hand is already touched
        isUserTouchProgressBar=true;
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

        int touchProgress=seekBar.getProgress();
        // Stop dragging
        if( playerControl ! =null) {
            playerControl.seekTo(touchProgress);
        }
        isUserTouchProgressBar=false; }});Copy the code

saveDataToDB

Dig a lot of holes in front, a lot of procedures are shelved first, there are two specific interface implementation, access to song information, save user information and so on.

SaveDataToDB is the method used to save user information to the database when the exit button is clicked.

private void saveDataToDB(a){
    new Thread() {
        public void run (a) {
            try {
                JSONObject result = RequestServlet.savePlayerInformation(account, musicId, playPattern);
                Message msg = new Message();
                msg.what = 1;
                msg.obj = result;
                handler1.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }.start();
}
Copy the code

This method calls the savePlayerInformation method in the RequestServlet class to save the song ID and playback mode

Handler handler1 = new Handler(){
    public void handleMessage(android.os.Message msg) {
        try {
            if (msg.what == 1) {
                JSONObject result = (JSONObject) msg.obj;
                MainActivity.this.finish();
                Toast.makeText(MainActivity.this."Withdrawn", Toast.LENGTH_SHORT).show(); }}catch(Exception e) { e.printStackTrace(); }}};Copy the code

RequestServlet.savePlayerInformation()

The savePlayerInformation method is similar to the other methods in the class (since there are so many duplicate parts, you can write a separate method for the duplicate parts).

private static final String SAVE_USER_INFO ="http://192.168.43.xxx:8080/musicplayer/SaveMusic";

public static JSONObject savePlayerInformation(String account,int musicId,int playPattern){
    JSONObject result = null;

    String path = SAVE_USER_INFO+"? account="+account+"&musicId="+musicId+"&pattern="+playPattern;

    HttpURLConnection conn;

    try {
        conn = getConn(path);
        int code = conn.getResponseCode();    // HTTP status, 200 represents corresponding success
        if (code == 200){ InputStream stream = conn.getInputStream(); String str = streamToString(stream); result = getJSON(str); conn.disconnect(); }}catch (Exception e){
        e.printStackTrace();
    }

    return result;
}
Copy the code

RequestServlet.getMusicList()

You don’t need to pass parameter information to the server to get the playlist. You can simply call the corresponding servlet.

private static final String GET_MUSIC_LIST = "http://192.168.43.xxx:8080/musicplayer/GetMusicList";

// Get the list of songs
public static JSONArray getMusicList(a){
    JSONArray result = null;

    String path = GET_MUSIC_LIST;
    HttpURLConnection conn;

    try {
        conn = getConn(path);
        int code = conn.getResponseCode();
        if (code == 200){
            InputStream jsonArray = conn.getInputStream();
            String str = streamToString(jsonArray);
            result = getJsonArray(str);
            conn.disconnect();
        }else {
            return null; }}catch (Exception e){
        e.printStackTrace();
    }

    return result;
}
Copy the code

3.5 PlayerControl interface implementation

The PlayerControl interface is implemented by PlayerPresenter

The PlayerControl interface handles the logic layer, covering things like the player’s music controls.

Android has a lot of processing multimedia API, MediaPlayer is a very basic one, here with the help of MediaPlayer tool to achieve music playback function.

private MediaPlayer mMediaPlayer=null;
Copy the code
  1. Define global variables and constants
private MediaPlayer mMediaPlayer = null;

private static final String ADDRESS = "http://192.168.43.xxx:8080/musicplayer/music/";
private PlayerViewControl mViewController = null;    / / the presentation layer
private MainActivity mMainActivity = null; 

// Play status
public final int PLAY_STATE_PLAY=1;   / / in the sow
public final int PLAY_STATE_PAUSE=2;  / / pause
public final int PLAY_STATE_STOP=3;   / / not sow

public int mCurrentState = PLAY_STATE_STOP;   // The default state is to stop playback

private Timer mTimer;
private SeekTimeTask mTimeTask;

// Take the MainActivity
public PlayerPresenter(MainActivity activity){
    mMainActivity = activity;
}
Copy the code
  1. Play/Pause
@Override
public void playOrPause(MainActivity.IsPlay playState) {
    if(mViewController == null) {this.mViewController = mMainActivity.mPlayerViewControl;
    }

    if (mCurrentState == PLAY_STATE_STOP || playState == MainActivity.IsPlay.play) {
        try {
            mMediaPlayer = new MediaPlayer();
            // Specify the playback path
            mMediaPlayer.setDataSource(ADDRESS + mMainActivity.playAddress);
            // Get ready to play
            mMediaPlayer.prepareAsync();
            / / play
            mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) { mMediaPlayer.start(); }}); mCurrentState = PLAY_STATE_PLAY; startTimer(); }catch(IOException e) { e.printStackTrace(); }}else if (mCurrentState == PLAY_STATE_PLAY) {
        // If the current state is play, then pause
        if(mMediaPlayer ! =null) { mMediaPlayer.pause(); mCurrentState = PLAY_STATE_PAUSE; stopTimer(); }}else if (mCurrentState == PLAY_STATE_PAUSE) {
        // If the current state is paused, continue playing
        if(mMediaPlayer ! =null) {
            mMediaPlayer.start();
            mCurrentState = PLAY_STATE_PLAY;
            startTimer();
        }
    }

    mViewController.onPlayerStateChange(mCurrentState);
}
Copy the code

Play or pause involves interface changes, so you need to bind the presentation layer and the implementation of the presentation layer interface is written inside the MainActivity, which is called directly here.

if(mViewController == null) {this.mViewController = mMainActivity.mPlayerViewControl;
}
Copy the code

If the play status is Stop or the parameter passed is Play, the system starts to play.

  • Instantiate MediaPlayer, load the song resources, and prepare to play
  • Call **start()** to play
  • Example Change the playing state to Playing
  • Start the time
if (mCurrentState == PLAY_STATE_STOP || playState == MainActivity.IsPlay.play) {
    try {
        mMediaPlayer = new MediaPlayer();
        // Specify the playback path
        mMediaPlayer.setDataSource(ADDRESS + mMainActivity.playAddress);
        // Get ready to play
        mMediaPlayer.prepareAsync();
        / / play
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) { mMediaPlayer.start(); }}); mCurrentState = PLAY_STATE_PLAY; startTimer(); }catch(IOException e) { e.printStackTrace(); }}Copy the code

If the current playback state is playing, call pause() to pause the playback, change the playback state to pause, and stop the timing

else if (mCurrentState == PLAY_STATE_PLAY) {
    // If the current state is play, then pause
    if(mMediaPlayer ! =null) { mMediaPlayer.pause(); mCurrentState = PLAY_STATE_PAUSE; stopTimer(); }}Copy the code

If the current play state is play pause, this method is called to continue playing

else if (mCurrentState == PLAY_STATE_PAUSE) {
    // If the current state is paused, continue playing
    if(mMediaPlayer ! =null) { mMediaPlayer.start(); mCurrentState = PLAY_STATE_PLAY; startTimer(); }}Copy the code

Of course, each time this method is called, the interface needs to make a change based on the playback state (see 3.6).

mViewController.onPlayerStateChange(mCurrentState);
Copy the code
  1. timing

In the play/pause switch above, the timing function is used, which is mainly to update the progress bar according to the playback time

private void startTimer(a) {
    if (mTimer == null) {
        mTimer=new Timer();
    }
    if (mTimeTask == null) {
        mTimeTask = new SeekTimeTask();
    }
    mTimer.schedule(mTimeTask,0.500);
}
private void stopTimer(a) {
    if(mTimeTask ! =null) {
        mTimeTask.cancel();
        mTimeTask=null;
    }
    if(mTimer ! =null) {
        mTimer.cancel();
        mTimer=null; }}Copy the code

Timer is a normal class, while TimerTask is an abstract class. TimerTask has an abstract method run() that can be called every once in a while to implement interface changes.

The schedule method in the Timer class takes three parameters. The first parameter is the TimerTask object, the second parameter indicates how long it will take to execute, and the third parameter indicates the interval in milliseconds (ms), which I set to 500 milliseconds (slightly longer). This calls the run method every 500 milliseconds after the timer is started.

The run method, on the other hand, calculates a percentage based on the current playing time and the total song length, and submits it to the performance layer to update the progress bar. (See 3.6)

private class SeekTimeTask extends TimerTask {

    @Override
    public void run(a) {
        // Get the current playback progress
        if(mMediaPlayer ! =null&& mViewController! =null) {
            int currentPosition = mMediaPlayer.getCurrentPosition();
            // Record percentages
            int curPosition=(int)(currentPosition*1.0 f/mMediaPlayer.getDuration()*100);
            if(curPosition<=100) { mViewController.onSeekChange(curPosition); }}}}Copy the code
  1. Play the last song

To switch songs, you need to determine the user’s current playing mode and switch songs according to the different playing modes (sequence, random, single).

@Override
public void playLast(a) {
    // Play in sequence
    if (mMainActivity.playPattern == mMainActivity.PLAY_IN_ORDER) {
        if (mMainActivity.musicId == 0) {
            mMainActivity.musicId = mMainActivity.songNum-1;
            mMainActivity.setMusicView(MainActivity.IsPlay.play);
        } else {
            mMainActivity.musicId = mMainActivity.musicId - 1; mMainActivity.setMusicView(MainActivity.IsPlay.play); }}// Random play
    else if (mMainActivity.playPattern == mMainActivity.PLAY_RANDOM) {
        mMainActivity.musicId = ( mMainActivity.musicId+(int) (1+Math.random()*(20-1+1))) % mMainActivity.songNum ;
        mMainActivity.setMusicView(MainActivity.IsPlay.play);
    }
    // Single loop
    else if(mMainActivity.musicId==mMainActivity.PLAY_SINGLE){ mMainActivity.setMusicView(MainActivity.IsPlay.play); }}Copy the code

If the current ID is 0, then the previous ID should be the total number of songs -1 (id starts from 0 and the last song id is the total number of songs -1). If the current song ID is not 0, the current song ID is directly subtracted by 1. And then I’m gonna call setMusicView and pass it play

// Play in sequence
if (mMainActivity.playPattern == mMainActivity.PLAY_IN_ORDER) {
    if (mMainActivity.musicId == 0) {
        mMainActivity.musicId = mMainActivity.songNum-1;
        mMainActivity.setMusicView(MainActivity.IsPlay.play);
    } else {
        mMainActivity.musicId = mMainActivity.musicId - 1; mMainActivity.setMusicView(MainActivity.IsPlay.play); }}Copy the code

For random play, use the current song ID + a reasonable random integer as the switched song ID

// Random play
else if (mMainActivity.playPattern == mMainActivity.PLAY_RANDOM) {
    mMainActivity.musicId = ( mMainActivity.musicId+(int) (1+Math.random()*(20-1+1))) % mMainActivity.songNum ;
    mMainActivity.setMusicView(MainActivity.IsPlay.play);
}
Copy the code

If it is a single loop, the song will be replayed

// Single loop
else if(mMainActivity.musicId==mMainActivity.PLAY_SINGLE){
    mMainActivity.setMusicView(MainActivity.IsPlay.play);
}
Copy the code
  1. Play the next song

Similar to the logic of the previous one, I will not repeat it here

@Override
public void playNext(a) {
    // Play in sequence
    if (mMainActivity.playPattern == mMainActivity.PLAY_IN_ORDER) {

        mMainActivity.musicId = (mMainActivity.musicId + 1) % mMainActivity.songNum;
        mMainActivity.setMusicView(MainActivity.IsPlay.play);

    }
    // Random play
    else if (mMainActivity.playPattern == mMainActivity.PLAY_RANDOM) {
        mMainActivity.musicId = (mMainActivity.musicId+(int) (1+Math.random()*(20-1+1))) % mMainActivity.songNum ;
        mMainActivity.setMusicView(MainActivity.IsPlay.play);
    }
    // Single loop
    else if(mMainActivity.playPattern == mMainActivity.PLAY_SINGLE){ mMainActivity.setMusicView(MainActivity.IsPlay.play); }}Copy the code
  1. Stop playing

This method is called in the setMusicView method. Before switching songs, you need to release the MediaPlayer, otherwise the program will crash or play several songs at once

@Override
public void stopPlay(a) {
    if(mMediaPlayer ! =null ) {
        mMediaPlayer.stop();
        mCurrentState= PLAY_STATE_STOP;
        stopTimer();
        // Update the playback status
        if(mViewController ! =null) {
            mViewController.onPlayerStateChange(mCurrentState);
        }
        mMediaPlayer.release();// Release resources
        mMediaPlayer=null; }}Copy the code
  1. seekTo

This is called after dragging the progress bar. When you stop dragging the progress bar, you pass in a parameter that represents a percentage and jump directly to the calculated music duration using the seekTo method in the MediaPlayer class

@Override
public void seekTo(int seek) {
    / / between 0 ~ 100
    // A conversion is required to obtain seek as a percentage
    if(mMediaPlayer ! =null) {
        //getDuration() Gets the audio duration
        int tarSeek=(int)(seek*1f/100*mMediaPlayer.getDuration()); mMediaPlayer.seekTo(tarSeek); }}Copy the code

3.6 PlayerViewControl interface implementation

Considering the need to use mainActivity bound controls, so directly into the MainActivity to implement the PlayerViewControl interface

public PlayerViewControl mPlayerViewControl = new PlayerViewControl() {
    @Override
    public void onPlayerStateChange(int state) {
        // Modify the UI based on the playback state
        switch (state) {
            case PLAY_STATE_PLAY:
                // During playback, we need to modify the button display to pause
                mPlayOrPause.setBackgroundResource(R.drawable.bofangb);
                break;
            case PLAY_STATE_PAUSE:
            case PLAY_STATE_STOP:
                mPlayOrPause.setBackgroundResource(R.drawable.bofang);
                break; }}@Override
    public void onSeekChange(final int seek) {
        // To change the playback progress, there is one condition: when the user's hand touches the progress bar, it will not be updated.
        runOnUiThread(new Runnable() {
            @Override
            public void run(a) {
                if(! isUserTouchProgressBar) { mSeekBar.setProgress(seek);if(seek==100) { mPlayerControl.playNext(); }}}}); }};Copy the code
  1. It’s easy to understand how ICONS can be replaced based on the playback state
@Override
public void onPlayerStateChange(int state) {
    // Modify the UI based on the playback state
    switch (state) {
        case PLAY_STATE_PLAY:
            // During playback, we need to modify the button display to pause
            mPlayOrPause.setBackgroundResource(R.drawable.bofangb);
            break;
        case PLAY_STATE_PAUSE:
        case PLAY_STATE_STOP:
            mPlayOrPause.setBackgroundResource(R.drawable.bofang);
            break; }}Copy the code
  1. Progress bar Update

This method is called within the timing function and is now defined to update the progress bar every 500ms. A child thread is used here. In addition, there is a limitation that the progress bar is no longer automatically updated when the user presses it.

@Override
public void onSeekChange(final int seek) {
    // To change the playback progress, there is one condition: when the user's hand touches the progress bar, it will not be updated.
    runOnUiThread(new Runnable() {
        @Override
        public void run(a) {
            if(! isUserTouchProgressBar) { mSeekBar.setProgress(seek);if(seek==100) { mPlayerControl.playNext(); }}}}); }Copy the code

4. Test

Now the basic functions have been realized, and will continue to improve the function in the future.

Test environment: Android 10, LAN

Preparations: Enable Tomcat and USB debugging

4.1 Adding Database Data

Since there is no song data in the database, now add a few songs

INSERT INTO `music`(name, author, address, img, create_time)
VALUES ('Light years away'.G.E.M..'guangnian.mp3'.'guangnian.jpg', now()),
('goodbye'.G.E.M..'zaijian.mp3'.'zaijian.jpg', now()),
('A Song of Love'.'half Yang'.'yiqu.mp3'.'yiqu.jpg', now()),
('half'.'grain of Chen'.'xiaoban.mp3'.'xiaoban.jpg', now()),
('the fragrance'.'Jay Chou'.'daoxiang.mp3'.'daoxiang.jpg', now()),
('Take what you want.'.'Hu Yanbin'.'niyao.mp3'.'niyao.jpg', now()),
('Thief will do'.'Flower congee, Ma Yuyang'.'dao.mp3'.'dao.jpg', now()),
('Strongest'.'Alan worker'.'Strongest.mp3'.'Strongest.jpg', now());
Copy the code

4.2 Adding a Resource File

Create two new folders in the webApp directory on the server side, one folder for image files and the other folder for song files, so that the Android side can get these resources. (Make sure the name matches the information in the database)

4.3 the login

Now use the account “Cun” to log in. If you look at the database, you can see that the song ID is 1 and the play mode is 0.

For easy reference, the song information is shown below

Enter your account and password and click the login button. You can see from the following figure that login is successful! The initialized song information and playback mode are correct.

4.4 Play/Pause

Normal playback function

Pause function is normal

Test drag progress bar also works

4.5 Previous track/next track

Previous track/next track functions normally

The playback state is random, the function of the previous song/the next song is normal (but there is a high probability of repeating a song at present, and it will be improved later)

4.6 quit

Click the exit button, and the user information is saved to the server normally

5. Subsequent

To improve some features, the function of opening the music list on the far right of the play screen is not implemented yet

6. Improve

6.1 quit

In the original program, in the state of play, return to the main interface, and then open the software needs to log in again.

Now listen for the user’s return and treat the home screen as an Activity, exit and restart the software without restarting it

//MainActivity

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        Intent home = new Intent(Intent.ACTION_MAIN);
        home.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        home.addCategory(Intent.CATEGORY_HOME);
        startActivity(home);
        return true;
    }
    return super.onKeyDown(keyCode, event);
}
Copy the code

7.

Lately I’ve noticed that some people don’t read the beginning.

Procedures have been put into two warehouses, feel helpful to you, please give this text a thumbs-up! Give the warehouse a star! Thanks a million!

Gitee warehouse

Making the warehouse