package com.linroid.sky31radio.ui.fragment; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.text.Html; import android.text.Spanned; import android.text.TextUtils; import android.text.method.ScrollingMovementMethod; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.ViewSwitcher; import com.linroid.sky31radio.IRadioService; import com.linroid.sky31radio.R; import com.linroid.sky31radio.data.ApiService; import com.linroid.sky31radio.model.Program; import com.linroid.sky31radio.service.RadioPlaybackService; import com.linroid.sky31radio.ui.HomeActivity; import com.linroid.sky31radio.ui.base.InjectableFragment; import com.linroid.sky31radio.utils.BlurTransformation; import com.linroid.sky31radio.utils.ColorUtils; import com.linroid.sky31radio.utils.RadioUtils; import com.linroid.sky31radio.view.EqualizerView; import com.linroid.sky31radio.view.PlayPauseButton; import com.linroid.sky31radio.view.PlayPauseProgressButton; import com.linroid.sky31radio.view.SlidingUpPanelLayout; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import org.adw.library.widgets.discreteseekbar.DiscreteSeekBar; import org.michaelevans.colorart.library.ColorArt; import java.text.SimpleDateFormat; import java.util.Date; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; import rx.Observer; import rx.android.app.AppObservable; import rx.android.schedulers.AndroidSchedulers; import timber.log.Timber; /** * Created by linroid on 1/15/15. */ public class PlayerFragment extends InjectableFragment implements ServiceConnection { @InjectView(R.id.container) ViewGroup playerRootView; @InjectView(R.id.player_thumbnail) ImageView playerThumbnailIV; @InjectView(R.id.player_info) ViewGroup playerInfoView; @InjectView(R.id.player_program_title) TextView playerProgramNameTV; @InjectView(R.id.player_author) TextView playerAuthorTV; @InjectView(R.id.playPauseProgressButton) PlayPauseProgressButton playPauseProgressButton; @InjectView(R.id.action_button_play) PlayPauseButton actionPlayPauseButton; @InjectView(R.id.circularProgressBar) ProgressBar circularProgressBar; @InjectView(R.id.action_share) ImageButton shareBtn; @InjectView(R.id.btn_play_pause) PlayPauseButton playPauseButton; @InjectView(R.id.center_thumbnail) ImageView centerThumbnailIV; @InjectView(R.id.progress_seekbar) DiscreteSeekBar seekBar; @InjectView(R.id.duration_time) TextView durationTimeTV; @InjectView(R.id.position_time) TextView positionTimeTV; @InjectView(R.id.program_played_count) TextView playedCountTV; @InjectView(R.id.article_equalizer_switcher) ViewSwitcher viewSwitcher; @InjectView(R.id.visualizer) EqualizerView equalizerView; @InjectView(R.id.article) TextView articleTV; @InjectView(R.id.anchor_nickname) TextView centerAnchorNicknameTV; @InjectView(R.id.anchor_avatar) ImageView anchorAvatarIV; SlidingUpPanelLayout slidingUpPanelLayout; @Inject Picasso picasso; @Inject ApiService apiService; Program program; private int statusColor; private int homeStatusColor; SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss"); RadioReceiver receiver; IRadioService service; public static final int MSG_UPDATE = 0x1; private static final int MSG_SEEK = 0x2; Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case MSG_UPDATE: updatePlayingStatus(); try { if(!service.isPlaying()){ Timber.i("player not playing, stop update"); return; } } catch (RemoteException e) { e.printStackTrace(); } nextUpdate(); break; case MSG_SEEK: Timber.i("msg_seek, position:%d", msg.arg1); RadioUtils.seekToPosition(seekBar.getContext(), msg.arg1); handler.removeMessages(MSG_UPDATE); break; } } }; private void updatePlayingStatus() { try { if(service==null){ return; } boolean isPlaying = service.isPlaying(); if(isPlaying){ long position = service.getPosition(); long duration = service.getDuration(); if(duration == 0){ return; } int percent = (int) (position*100 / duration); circularProgressBar.setProgress(percent); seekBar.setMax((int) duration); seekBar.setMin(0); seekBar.setProgress((int) position); durationTimeTV.setText(dateFormat.format(new Date(duration))); positionTimeTV.setText(dateFormat.format(new Date(position))); } showPlayingStatus(isPlaying); } catch (RemoteException e) { e.printStackTrace(); } } private void updatePlayingProgram(final Program playingProgram){ this.program = playingProgram; if(playingProgram==null){ disablePlayerBar(); return; } playPauseButton.setEnabled(true); slidingUpPanelLayout.setSlidingEnabled(true); viewSwitcher.setDisplayedChild(0); String playedCountText = getResources().getString(R.string.tpl_played_count, program.getTotalPlay()); playedCountTV.setText(playedCountText); playerAuthorTV.setText(program.getAuthor()); playerProgramNameTV.setText(program.getTitle()); articleTV.setText(R.string.loading_article); picasso.load(program.getThumbnail()) .error(R.drawable.ic_launcher_square) .into(colorfulTarget); picasso.load(program.getCover()) .transform(new BlurTransformation(getActivity(), program.getCover())) .into(coverTarget); AppObservable.bindFragment(this, apiService.programDetail(program.getId())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Program>() { @Override public void onCompleted() { } @Override public void onError(Throwable throwable) { } @Override public void onNext(Program program) { PlayerFragment.this.program = program; if (program.getAnchor() != null) { picasso.load(program.getAnchor().getAvatar()).into(anchorAvatarIV); centerAnchorNicknameTV.setText(program.getAnchor().getNickname()); } if (TextUtils.isEmpty(program.getArticle())) { articleTV.setText(R.string.empty_article); } else { String newPlayedCountText = getResources().getString(R.string.tpl_played_count, program.getTotalPlay()); playedCountTV.setText(newPlayedCountText); Spanned article = Html.fromHtml(program.getArticle()); articleTV.setText(article); } } }); } private void disablePlayerBar() { playPauseButton.setPlaying(false); playerThumbnailIV.setImageResource(R.drawable.ic_launcher_square); playerProgramNameTV.setText(R.string.app_name); if(slidingUpPanelLayout.collapsePanel()){ slidingUpPanelLayout.setSlidingEnabled(false); } } @OnClick({R.id.article_equalizer_switcher, R.id.article}) public void onSwitcherClick(){ if(viewSwitcher.getDisplayedChild() == 0) { viewSwitcher.showNext(); }else{ viewSwitcher.showPrevious(); } } @OnClick(R.id.btn_skip_previous) public void onSkipPreviousButtonClick(View view){ RadioUtils.next(getActivity()); } @OnClick(R.id.btn_skip_next) public void onSkipNextButtonClick(View view){ RadioUtils.previous(getActivity()); } @OnClick(R.id.action_share) public void onShareProgramButtonClick(View view){ ShareFragment.shareProgram(program) .show(getFragmentManager(), "share"); } @OnClick(R.id.anchor_avatar) public void onAnchorAvatarClick(){ Timber.d("onAnchorAvatarClick"); if(program.getAnchor()!=null){ slidingUpPanelLayout.collapsePanel(); HomeActivity activity = (HomeActivity) getActivity(); activity.onAnchorSelected(program.getAnchor()); } } private void nextUpdate() { try { if(service==null){ playPauseButton.setPlaying(false); return; } handler.removeMessages(MSG_UPDATE); long position = service.getPosition(); long delay = 1000 - position%1000; handler.sendEmptyMessageDelayed(MSG_UPDATE, delay); } catch (RemoteException e) { e.printStackTrace(); } catch (Exception e){ Timber.e(e, "nextUpdate"); } } public PlayerFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); receiver = new RadioReceiver(); TypedValue tv = new TypedValue(); getActivity().getTheme().resolveAttribute(R.attr.colorPrimaryDark, tv, true); homeStatusColor = tv.data; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); slidingUpPanelLayout = (SlidingUpPanelLayout) getActivity().findViewById(R.id.sliding_layout); slidingUpPanelLayout.setPanelSlideListener(mSlidingListener); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_player, container, false); ButterKnife.inject(this, view); // initVisualizer(); articleTV.setMovementMethod(ScrollingMovementMethod.getInstance()); playPauseButton.setOnStateChangedListener(playPauseButtonListener); actionPlayPauseButton.setOnStateChangedListener(playPauseButtonListener); seekBar.setNumericTransformer(new DiscreteSeekBar.NumericTransformer() { @Override public int transform(int value) { return value; } @Override public String transformToString(int value) { return dateFormat.format(value); } @Override public boolean useStringTransform() { return true; } }); seekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnProgressChangeListener() { @Override public void onProgressChanged(final DiscreteSeekBar seekBar, final int value, boolean fromUser) { if (fromUser) { Timber.d("onProgressChanged: %d", value); handler.removeMessages(MSG_SEEK); Message msg = handler.obtainMessage(MSG_SEEK, value, 0); handler.sendMessageDelayed(msg, 300); } } }); return view; } @Override public void onDetach() { super.onDetach(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); picasso.cancelRequest(colorfulTarget); picasso.cancelRequest(coverTarget); } @Override public void onResume() { super.onResume(); Intent serviceIntent = new Intent(getActivity(), RadioPlaybackService.class); getActivity().bindService(serviceIntent, this, 0); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(RadioPlaybackService.ACTION_PROGRAM_CHANGED); intentFilter.addAction(RadioPlaybackService.ACTION_PLAYING_STATUS_CHANGED); getActivity().registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(receiver); getActivity().unbindService(this); equalizerView.release(); } @Override public void onDestroyView() { super.onDestroyView(); ButterKnife.reset(this); handler.removeMessages(MSG_UPDATE); } @Override public void onServiceConnected(ComponentName name, IBinder binder) { this.service = IRadioService.Stub.asInterface(binder); Timber.w("onServiceConnected"); try { equalizerView.linkPlayer(service.getPlayerSessionId()); equalizerView.setEnabled(slidingUpPanelLayout.isPanelExpanded()); updatePlayingProgram(service.getPlayingProgram()); } catch (RemoteException e) { e.printStackTrace(); } nextUpdate(); } @Override public void onServiceDisconnected(ComponentName name) { Timber.w("onServiceDisconnected"); this.service = null; handler.removeMessages(MSG_UPDATE); equalizerView.release(); disablePlayerBar(); playPauseButton.setEnabled(false); } private void showPlayingStatus(boolean playing){ actionPlayPauseButton.setPlaying(playing); playPauseButton.setPlaying(playing); } public class RadioReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Timber.i("receive intent: %s", intent.toString()); switch (intent.getAction()){ case RadioPlaybackService.ACTION_PROGRAM_CHANGED:{ Program newProgram = intent.getParcelableExtra(RadioPlaybackService.KEY_PROGRAM); updatePlayingProgram(newProgram); Timber.i("ACTION_PROGRAM_CHANGED"); nextUpdate(); break; } case RadioPlaybackService.ACTION_PLAYING_STATUS_CHANGED:{ Timber.i("ACTION_PLAYING_STATUS_CHANGED"); boolean isPlaying = intent.getBooleanExtra(RadioPlaybackService.KEY_IS_PLAYING, true); showPlayingStatus(isPlaying); nextUpdate(); break; } default: Timber.w("unknown intent: %s", intent.toString()); } } } SlidingUpPanelLayout.PanelSlideListener mSlidingListener = new SlidingUpPanelLayout.SimplePanelSlideListener() { @Override public void onPanelSlide(View panel, float slideOffset) { playerThumbnailIV.setTranslationX(-playerThumbnailIV.getWidth()*slideOffset); playerThumbnailIV.setScaleX(1f - slideOffset); playerThumbnailIV.setScaleY(1f-slideOffset); playerInfoView.setTranslationX(playerThumbnailIV.getTranslationX()); if(slideOffset <= 0.5f){ playPauseProgressButton.setVisibility(View.VISIBLE); shareBtn.setVisibility(View.INVISIBLE); playPauseProgressButton.setScaleX(1f-slideOffset*2f); playPauseProgressButton.setScaleY(1f-slideOffset*2f); }else{ playPauseProgressButton.setVisibility(View.INVISIBLE); shareBtn.setVisibility(View.VISIBLE); shareBtn.setScaleX(slideOffset*2f-1f); shareBtn.setScaleY(slideOffset*2f-1f); } } @Override public void onPanelCollapsed(View panel) { playPauseProgressButton.setVisibility(View.VISIBLE); shareBtn.setVisibility(View.INVISIBLE); setStatusColor(homeStatusColor); equalizerView.setEnabled(false); } @Override public void onPanelExpanded(View panel) { playPauseProgressButton.setVisibility(View.INVISIBLE); shareBtn.setVisibility(View.VISIBLE); setStatusColor(statusColor); equalizerView.setEnabled(true); } }; PlayPauseButton.OnStateChangedListener playPauseButtonListener = new PlayPauseButton.OnStateChangedListener(){ @Override public void onPlay() { RadioUtils.play(getActivity()); nextUpdate(); } @Override public void onPause() { RadioUtils.pause(getActivity()); } }; Target coverTarget = new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { Timber.d("onBitmapLoaded: %s (%s)", from.name(), bitmap.toString()); playerRootView.setBackgroundDrawable(new BitmapDrawable(getResources(), bitmap)); centerThumbnailIV.setImageBitmap(bitmap); } @Override public void onBitmapFailed(Drawable errorDrawable) { Timber.e("onBitmapFailed"); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { Timber.e("onPrepareLoad"); } }; Target colorfulTarget = new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { playerThumbnailIV.setImageBitmap(bitmap); ColorArt colorArt = new ColorArt(bitmap); int backgroundColor = colorArt.getBackgroundColor(); statusColor = ColorUtils.transformIfTooWhite(backgroundColor); Timber.d("BackgroundColor: %s, statusColor:%s", Integer.toHexString(backgroundColor), Integer.toHexString(statusColor)); if (slidingUpPanelLayout.isPanelExpanded()) { setStatusColor(statusColor); } int primaryColor = colorArt.getPrimaryColor(); seekBar.setThumbColor(primaryColor, colorArt.getSecondaryColor()); seekBar.setScrubberColor(primaryColor); seekBar.refreshDrawableState(); } @Override public void onBitmapFailed(Drawable errorDrawable) { Timber.e("onBitmapFailed"); playerThumbnailIV.setImageDrawable(errorDrawable); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { Timber.e("onPrepareLoad"); } }; }