|  | @@ -5,12 +5,43 @@ import androidx.viewbinding.ViewBinding;
 | 
	
		
			
				|  |  |  import android.content.Context;
 | 
	
		
			
				|  |  |  import android.content.Intent;
 | 
	
		
			
				|  |  |  import android.graphics.Color;
 | 
	
		
			
				|  |  | +import android.media.AudioAttributes;
 | 
	
		
			
				|  |  | +import android.media.AudioManager;
 | 
	
		
			
				|  |  | +import android.media.SoundPool;
 | 
	
		
			
				|  |  |  import android.os.Bundle;
 | 
	
		
			
				|  |  | +import android.os.Handler;
 | 
	
		
			
				|  |  | +import android.util.Log;
 | 
	
		
			
				|  |  | +import android.view.View;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import com.cooleshow.base.ui.activity.BaseActivity;
 | 
	
		
			
				|  |  | +import com.cooleshow.metronome.constants.MetronomeConfig;
 | 
	
		
			
				|  |  | +import com.cooleshow.metronome.constants.MetronomeType;
 | 
	
		
			
				|  |  |  import com.cooleshow.metronome.databinding.ActivityMetronomeLayoutBinding;
 | 
	
		
			
				|  |  | +import com.cooleshow.metronome.widget.CircularSeekBar;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  public class MetronomeActivity extends BaseActivity<ActivityMetronomeLayoutBinding> {
 | 
	
		
			
				|  |  | +    public MetronomeType currentBeatType;
 | 
	
		
			
				|  |  | +    public int currentBeatRate = MetronomeConfig.DEFAULT_PLAY_RATE;
 | 
	
		
			
				|  |  | +    private SoundPool soundPool;
 | 
	
		
			
				|  |  | +    private int tickVoiceId;
 | 
	
		
			
				|  |  | +    private int tockVoiceId;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public int playPosition = 0;
 | 
	
		
			
				|  |  | +    private Handler mHandler = new Handler();
 | 
	
		
			
				|  |  | +    private Runnable mRunnable = new Runnable() {
 | 
	
		
			
				|  |  | +        @Override
 | 
	
		
			
				|  |  | +        public void run() {
 | 
	
		
			
				|  |  | +            long delayMillis = (long) (1 / (currentBeatRate / 60d) * 1000);
 | 
	
		
			
				|  |  | +            boolean tickOrTock = isTickOrTock();
 | 
	
		
			
				|  |  | +            if (tickOrTock) {
 | 
	
		
			
				|  |  | +                soundPool.play(tickVoiceId, 1F, 1F, 0, 0, 1F);
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                soundPool.play(tockVoiceId, 1F, 1F, 0, 0, 1F);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            playPosition++;
 | 
	
		
			
				|  |  | +            mHandler.postDelayed(mRunnable, delayMillis);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public static void start(Context context) {
 | 
	
		
			
				|  |  |          Intent intent = new Intent(context, MetronomeActivity.class);
 | 
	
	
		
			
				|  | @@ -25,10 +56,135 @@ public class MetronomeActivity extends BaseActivity<ActivityMetronomeLayoutBindi
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      protected void initView() {
 | 
	
		
			
				|  |  |          viewBinding.toolbarInclude.toolbar.setBackgroundColor(Color.TRANSPARENT);
 | 
	
		
			
				|  |  | +        currentBeatType = MetronomeType.METRONOME_44_TYPE;
 | 
	
		
			
				|  |  | +        viewBinding.tvCurrentBeat.setText(currentBeatType.getName());
 | 
	
		
			
				|  |  | +        viewBinding.tvSpeed.setText(String.valueOf(currentBeatRate));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      protected ActivityMetronomeLayoutBinding getLayoutView() {
 | 
	
		
			
				|  |  |          return ActivityMetronomeLayoutBinding.inflate(getLayoutInflater());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    protected void initData() {
 | 
	
		
			
				|  |  | +        super.initData();
 | 
	
		
			
				|  |  | +        initListener();
 | 
	
		
			
				|  |  | +        mteronomeSoundPool();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void play() {
 | 
	
		
			
				|  |  | +        mHandler.removeCallbacksAndMessages(null);
 | 
	
		
			
				|  |  | +        soundPool.stop(tickVoiceId);
 | 
	
		
			
				|  |  | +        soundPool.stop(tockVoiceId);
 | 
	
		
			
				|  |  | +        mHandler.post(mRunnable);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void mteronomeSoundPool() {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        SoundPool.Builder builder = new SoundPool.Builder();
 | 
	
		
			
				|  |  | +        //传入最多播放音频数量,
 | 
	
		
			
				|  |  | +        builder.setMaxStreams(2);
 | 
	
		
			
				|  |  | +        //AudioAttributes是一个封装音频各种属性的方法
 | 
	
		
			
				|  |  | +        AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
 | 
	
		
			
				|  |  | +        //设置音频流的合适的属性
 | 
	
		
			
				|  |  | +        attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);
 | 
	
		
			
				|  |  | +        //加载一个AudioAttributes
 | 
	
		
			
				|  |  | +        builder.setAudioAttributes(attrBuilder.build());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        soundPool = builder.build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        tickVoiceId = soundPool.load(this, com.daya.live_teaching.R.raw.tick, 0);
 | 
	
		
			
				|  |  | +        tockVoiceId = soundPool.load(this, com.daya.live_teaching.R.raw.tock, 0);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private int lastProgress = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void initListener() {
 | 
	
		
			
				|  |  | +        viewBinding.tvPlay.setOnClickListener(new View.OnClickListener() {
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public void onClick(View v) {
 | 
	
		
			
				|  |  | +                play();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        viewBinding.cirSeekbar.setSeekBarChangeListener(new CircularSeekBar.OnSeekChangeListener() {
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public void onProgressChange(CircularSeekBar view, int newProgress) {
 | 
	
		
			
				|  |  | +                Log.i("newProgress", "newProgress:" + newProgress);
 | 
	
		
			
				|  |  | +                Log.i("newProgress", "lastProgress:" + lastProgress);
 | 
	
		
			
				|  |  | +                int difference = newProgress - lastProgress;
 | 
	
		
			
				|  |  | +                if (Math.abs(difference) > 10) {
 | 
	
		
			
				|  |  | +                    lastProgress = newProgress;
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                int result = difference / MetronomeConfig.PLAY_RATE_UNIT;
 | 
	
		
			
				|  |  | +                if (difference < -1) {
 | 
	
		
			
				|  |  | +                    result = -1;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if (difference > 1) {
 | 
	
		
			
				|  |  | +                    result = 1;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if (result == 0) {
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                Log.i("newProgress", "result:" + result);
 | 
	
		
			
				|  |  | +                if (isCanChange(result)) {
 | 
	
		
			
				|  |  | +                    currentBeatRate += result;
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if (currentBeatRate < MetronomeConfig.MIN_PLAY_RATE) {
 | 
	
		
			
				|  |  | +                    currentBeatRate = MetronomeConfig.MIN_PLAY_RATE;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if (currentBeatRate > MetronomeConfig.MAX_PLAY_RATE) {
 | 
	
		
			
				|  |  | +                    currentBeatRate = MetronomeConfig.MAX_PLAY_RATE;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                lastProgress = newProgress;
 | 
	
		
			
				|  |  | +                viewBinding.tvSpeed.setText(String.valueOf(currentBeatRate));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public boolean isCanChange(int result) {
 | 
	
		
			
				|  |  | +        if (result < 0) {
 | 
	
		
			
				|  |  | +            return currentBeatRate != MetronomeConfig.MIN_PLAY_RATE;
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            return currentBeatRate != MetronomeConfig.MAX_PLAY_RATE;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private boolean isTickOrTock() {
 | 
	
		
			
				|  |  | +        if (currentBeatType.getValue() == MetronomeType.METRONOME_12_TYPE.getValue() && playPosition % 1 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_22_TYPE.getValue() && playPosition % 2 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_14_TYPE.getValue() && playPosition % 1 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_24_TYPE.getValue() && playPosition % 2 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_34_TYPE.getValue() && playPosition % 3 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_44_TYPE.getValue() && playPosition % 4 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_38_TYPE.getValue() && playPosition % 3 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else if (currentBeatType.getValue() == MetronomeType.METRONOME_68_TYPE.getValue() && playPosition % 6 == 0) {
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            return false;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    public void onDestroy() {
 | 
	
		
			
				|  |  | +        super.onDestroy();
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            soundPool.release();
 | 
	
		
			
				|  |  | +            soundPool = null;
 | 
	
		
			
				|  |  | +            mHandler.removeCallbacksAndMessages(null);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |