/*
* Copyright 2016 Substance Mobile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.animbus.music.ui.custom.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.widget.AppCompatSeekBar;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import com.animbus.music.R;
import com.animbus.music.media.PlaybackRemote;
/**
* Created by Adrian on 9/24/2015.
*/
public class MusicControlsView extends FrameLayout implements PlaybackRemote.StateChangedListener {
private static final int SHOW_PROGRESS = 2;
private final Context mContext;
AudioManager mAudioManager;
private boolean mDragging;
private AppCompatSeekBar mProgress, mVolume;
private ImageView mPlayButton, mPauseButton, mNextButton, mPrevButton;
private ImageView mVolumeNoneIcon, mVolumeFullIcon;
private View mPlayButtonRoot, mPauseButtonRoot;
private TextView mEndTime, mCurrentTime;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_PROGRESS) {
if (!mDragging && PlaybackRemote.isPlaying()) {
setProgress();
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, 0);
}
}
}
};
private MediaControllerCompat mController;
SeekBar.OnSeekBarChangeListener mVolumeSeekListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mController.setVolumeTo(progress, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
private MediaControllerCompat.TransportControls mTransportControls;
private final View.OnClickListener mPauseListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mTransportControls.pause();
}
};
private final View.OnClickListener mPlayListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mTransportControls.play();
}
};
private final View.OnClickListener mNextListener = new OnClickListener() {
@Override
public void onClick(View v) {
mTransportControls.skipToNext();
}
};
private final View.OnClickListener mPrevListener = new OnClickListener() {
@Override
public void onClick(View v) {
mTransportControls.skipToPrevious();
}
};
/**
* There are two scenarios that can trigger the seekbar listener to trigger:
* <p/>
* The first is the user using the touchpad to adjust the posititon of the
* seekbar's thumb. In this case onStartTrackingTouch is called followed by
* a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
* We're setting the field "mDragging" to true for the duration of the dragging
* session to avoid jumps in the position in case of ongoing playback.
* <p/>
* The second scenario involves the user operating the scroll ball, in this
* case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
* we will simply apply the updated position without suspending regular updates.
*/
private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStartTrackingTouch(SeekBar bar) {
mDragging = true;
// By removing these pending progress messages we make sure
// that a) we won't update the progress while the user adjusts
// the seekbar and b) once the user is done dragging the thumb
// we will post one of these messages to the queue again and
// this ensures that there will be exactly one message queued up.
mHandler.removeMessages(SHOW_PROGRESS);
}
@Override
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
if (fromuser) {
mTransportControls.seekTo((long) progress);
}
updatePausePlay();
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime(progress));
}
@Override
public void onStopTrackingTouch(SeekBar bar) {
mDragging = false;
// Ensure that progress is properly updated in the future,
// the call to show() does not guarantee this because it is a
// no-op if we are already showing.
mHandler.sendEmptyMessage(SHOW_PROGRESS);
}
};
public MusicControlsView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
public MusicControlsView(Context context) {
super(context);
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
public void setController(MediaControllerCompat controller) {
mController = controller;
mTransportControls = controller.getTransportControls();
mVolume.setProgress(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
}
/**
* Create the view that holds the widgets that control playback.
* Derived classes can override this to create their own.
*
* @return The controller view.
*/
public View initView() {
LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View mRoot = inflate.inflate(R.layout.now_playing_controls_view, null);
addView(mRoot);
initControllerView(mRoot);
mHandler.sendEmptyMessage(SHOW_PROGRESS);
return mRoot;
}
private void initControllerView(View v) {
mPlayButtonRoot = v.findViewById(R.id.controls_play_button_root);
mPauseButtonRoot = v.findViewById(R.id.controls_pause_button_root);
mPlayButton = (ImageView) v.findViewById(R.id.controls_play_button);
mPlayButton.setOnClickListener(mPlayListener);
mPauseButton = (ImageView) v.findViewById(R.id.controls_pause_button);
mPauseButton.setOnClickListener(mPauseListener);
mNextButton = (ImageView) v.findViewById(R.id.controls_next_button);
mNextButton.setOnClickListener(mNextListener);
mPrevButton = (ImageView) v.findViewById(R.id.controls_prev_button);
mPrevButton.setOnClickListener(mPrevListener);
mProgress = (AppCompatSeekBar) v.findViewById(R.id.control_seek_bar);
mProgress.setOnSeekBarChangeListener(mSeekListener);
mProgress.setProgressDrawable(null);
mVolume = (AppCompatSeekBar) v.findViewById(R.id.controls_volume_seekbar);
mVolume.setMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
mVolume.setOnSeekBarChangeListener(mVolumeSeekListener);
mVolumeNoneIcon = (ImageView) v.findViewById(R.id.controls_volume_icon_empty);
mVolumeFullIcon = (ImageView) v.findViewById(R.id.controls_volume_icon_full);
mEndTime = (TextView) v.findViewById(R.id.controls_total_pos);
mCurrentTime = (TextView) v.findViewById(R.id.controls_current_pos);
setProgress();
PlaybackRemote.registerStateListener(this);
updatePausePlay();
}
public void setUIColors(int iconColor, int iconSecondaryColor, int volumeSeekerColor, int mainSeekbarColor) {
Drawable playIcon = mContext.getResources().getDrawable(R.drawable.ic_play_arrow_black_48dp);
Drawable pauseIcon = mContext.getResources().getDrawable(R.drawable.ic_pause_black_48dp);
Drawable nextIcon = mContext.getResources().getDrawable(R.drawable.ic_skip_next_black_48dp);
Drawable prevIcon = mContext.getResources().getDrawable(R.drawable.ic_skip_previous_black_48dp);
Drawable volumeNone = mContext.getResources().getDrawable(R.drawable.ic_volume_none);
Drawable volumeAll = mContext.getResources().getDrawable(R.drawable.ic_volume_up);
DrawableCompat.setTint(DrawableCompat.wrap(playIcon), iconColor);
DrawableCompat.setTint(DrawableCompat.wrap(pauseIcon), iconColor);
DrawableCompat.setTint(DrawableCompat.wrap(nextIcon), iconColor);
DrawableCompat.setTint(DrawableCompat.wrap(prevIcon), iconColor);
DrawableCompat.setTint(DrawableCompat.wrap(volumeNone), iconSecondaryColor);
DrawableCompat.setTint(DrawableCompat.wrap(volumeAll), iconSecondaryColor);
mPlayButton.setImageDrawable(playIcon);
mPauseButton.setImageDrawable(pauseIcon);
mNextButton.setImageDrawable(nextIcon);
mPrevButton.setImageDrawable(prevIcon);
mVolumeNoneIcon.setImageDrawable(volumeNone);
mVolumeFullIcon.setImageDrawable(volumeAll);
DrawableCompat.setTint(mVolume.getThumb(), volumeSeekerColor);
DrawableCompat.setTint(mVolume.getProgressDrawable(), volumeSeekerColor);
DrawableCompat.setTint(mProgress.getThumb(), mainSeekbarColor);
}
private int setProgress() {
if (mDragging) {
return 0;
}
int position = PlaybackRemote.getCurrentPosInSong();
int duration = 0;
if (PlaybackRemote.getCurrentSong() != null)
duration = (int) PlaybackRemote.getCurrentSong().getSongDuration();
if (mProgress != null) {
mProgress.setProgress(position);
mProgress.setMax(duration);
}
if (mEndTime != null)
mEndTime.setText(PlaybackRemote.getCurrentSong().getSongDurString());
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime(position));
return position;
}
private void updatePausePlay() {
if (PlaybackRemote.isPlaying()) {
mPlayButtonRoot.setVisibility(View.GONE);
mPauseButtonRoot.setVisibility(View.VISIBLE);
} else {
mPlayButtonRoot.setVisibility(View.VISIBLE);
mPauseButtonRoot.setVisibility(View.GONE);
}
}
private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
if (hours > 0) {
return String.format("%d:%02d:%02d", hours, minutes, seconds);
} else {
return String.format("%02d:%02d", minutes, seconds);
}
}
@Override
public void onStateChanged(PlaybackStateCompat newState) {
mHandler.sendEmptyMessage(SHOW_PROGRESS);
updatePausePlay();
}
public boolean onKeyEvent(KeyEvent event) {
int vol;
boolean handled = false;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
vol = mVolume.getProgress() + 1;
if (vol > mVolume.getMax()) vol = mVolume.getMax();
mVolume.setProgress(vol);
handled = true;
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
vol = mVolume.getProgress() - 1;
if (vol < 0) vol = 0;
mVolume.setProgress(vol);
handled = true;
break;
}
return handled;
}
}