org.opensilk.music.ui3.nowplaying.NowPlayingScreenPresenter.java Source code

Java tutorial

Introduction

Here is the source code for org.opensilk.music.ui3.nowplaying.NowPlayingScreenPresenter.java

Source

/*
 * Copyright (c) 2015 OpenSilk Productions LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.opensilk.music.ui3.nowplaying;

import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.graphics.Palette;
import android.view.View;

import com.triggertrap.seekarc.SeekArc;

import org.opensilk.common.core.dagger2.ForApplication;
import org.opensilk.common.core.dagger2.ScreenScope;
import org.opensilk.common.ui.mortar.PauseAndResumeRegistrar;
import org.opensilk.common.ui.mortar.PausesAndResumes;
import org.opensilk.music.AppPreferences;
import org.opensilk.music.R;
import org.opensilk.music.artwork.PaletteObserver;
import org.opensilk.music.artwork.PaletteResponse;
import org.opensilk.music.model.ArtInfo;
import org.opensilk.music.artwork.requestor.ArtworkRequestManager;
import org.opensilk.music.artwork.ArtworkType;
import org.opensilk.music.playback.control.PlaybackController;
import org.opensilk.music.ui3.common.UtilsCommon;
import org.opensilk.music.ui3.main.MainPresenter;

import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import hugo.weaving.DebugLog;
import mortar.MortarScope;
import mortar.ViewPresenter;
import rx.Observable;
import rx.Scheduler;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;

import static android.support.v4.media.MediaMetadataCompat.*;
import static org.opensilk.common.core.rx.RxUtils.isSubscribed;
import static org.opensilk.music.playback.PlaybackConstants.META.TRACK_ARTWORK_URI;

/**
 * Created by drew on 4/20/15.
 */
@ScreenScope
public class NowPlayingScreenPresenter extends ViewPresenter<NowPlayingScreenView>
        implements PausesAndResumes, SeekArc.OnSeekArcChangeListener {

    final Context appContext;
    final PauseAndResumeRegistrar pauseAndResumeRegistrar;
    final PlaybackController playbackController;
    final ArtworkRequestManager requestor;
    //    final ActionBarOwner actionBarOwner;
    final AppPreferences settings;

    CompositeSubscription broadcastSubscription;

    long posOverride = -1;
    long lastSeekEventTime;
    boolean fromTouch = false;
    boolean isPlaying;
    int sessionId = AudioEffect.ERROR_BAD_VALUE;
    long lastPosition = -1;
    long lastDuration = -1;
    boolean lastPosSynced;
    ArtInfo lastArtInfo;
    long lastBlinkTime;

    @Inject
    public NowPlayingScreenPresenter(@ForApplication Context appContext,
            PauseAndResumeRegistrar pauseAndResumeRegistrar, PlaybackController playbackController,
            ArtworkRequestManager requestor,
            //             ActionBarOwner actionBarOwner,
            AppPreferences settings) {
        this.appContext = appContext;
        this.pauseAndResumeRegistrar = pauseAndResumeRegistrar;
        this.playbackController = playbackController;
        this.requestor = requestor;
        //        this.actionBarOwner = actionBarOwner;
        this.settings = settings;
    }

    @Override
    protected void onEnterScope(MortarScope scope) {
        super.onEnterScope(scope);
        pauseAndResumeRegistrar.register(scope, this);
    }

    @Override
    protected void onLoad(Bundle savedInstanceState) {
        super.onLoad(savedInstanceState);
        if (pauseAndResumeRegistrar.isRunning()) {
            Timber.v("missed onResume()");
            setup();
        }
        setupActionBar();
    }

    @Override
    protected void onSave(Bundle outState) {
        super.onSave(outState);
        if (pauseAndResumeRegistrar.isRunning()) {
            Timber.v("missed onPause()");
            teardown();
        }
    }

    @Override
    public void onResume() {
        if (hasView()) {
            Timber.v("missed onLoad()");
            setup();
        }
    }

    @Override
    public void onPause() {
        teardown();
    }

    @DebugLog
    void setup() {
        sessionId = playbackController.getAudioSessionId();
        if (hasView()) {
            getView().attachVisualizer(sessionId);
            getView().setEnabled(isPlaying);
        }
        subscribeBroadcasts();
    }

    @DebugLog
    void teardown() {
        if (hasView()) {
            getView().destroyVisualizer();
        }
        unsubscribeBroadcasts();
    }

    void subscribeBroadcasts() {
        if (isSubscribed(broadcastSubscription))
            return;
        Subscription s1 = playbackController.subscribePlayStateChanges(new Action1<PlaybackStateCompat>() {
            @Override
            public void call(PlaybackStateCompat playbackState) {
                boolean playing = MainPresenter.isPlaying(playbackState);
                if (hasView()) {
                    getView().play.setChecked(MainPresenter.isActive(playbackState));
                    getView().setVisualizerEnabled(playing);
                }
                isPlaying = playing;
                long position = playbackState.getPosition();
                long duration = playbackState.getBufferedPosition();
                updateProgress(position, duration);
                if (duration != lastDuration) {
                    setTotalTimeText(duration);
                }
                Timber.v("Position discrepancy = %d", lastPosition - position);
                lastPosition = position;
                lastDuration = duration;
                lastPosSynced = true;
                //TODO shuffle/repeat
            }
        });
        Subscription s2 = playbackController.subscribeMetaChanges(new Action1<MediaMetadataCompat>() {
            @Override
            public void call(MediaMetadataCompat mediaMetadata) {
                //TODO maybe should just set the large art uri in the metadata
                String track = mediaMetadata.getString(METADATA_KEY_TITLE);
                String artist = mediaMetadata.getString(METADATA_KEY_ARTIST);
                String albumArtist = mediaMetadata.getString(METADATA_KEY_ALBUM_ARTIST);
                String album = mediaMetadata.getString(METADATA_KEY_ALBUM);
                String uriString = mediaMetadata.getString(METADATA_KEY_ART_URI);
                Uri artworkUri = uriString != null ? Uri.parse(uriString) : null;
                Timber.d("%s, %s, %s, %s", artist, albumArtist, album, uriString);
                final ArtInfo artInfo = UtilsCommon.makeBestfitArtInfo(albumArtist, artist, album, artworkUri);
                Timber.d(artInfo.toString());
                if (!artInfo.equals(lastArtInfo)) {
                    lastArtInfo = artInfo;
                    if (hasView() && getView().artwork != null) {
                        requestor.newRequest(getView().artwork, getView().paletteObserver, artInfo,
                                ArtworkType.LARGE);
                    }
                }
                //Todo update display
            }
        });
        final long interval = 250;
        Subscription s3 = Observable.interval(interval, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Long>() {
                    @Override
                    public void call(Long aLong) {
                        long position = lastPosition;
                        if (lastPosSynced) {
                            lastPosSynced = false;
                        } else if (isPlaying) {
                            position += interval + 10;
                            lastPosition = position;
                        }
                        long duration = lastDuration;
                        updateProgress(position, duration);
                    }
                });
        broadcastSubscription = new CompositeSubscription(s1, s2, s3);
    }

    void unsubscribeBroadcasts() {
        if (isSubscribed(broadcastSubscription)) {
            broadcastSubscription.unsubscribe();
            broadcastSubscription = null;
        }
    }

    @Override
    public void onProgressChanged(final SeekArc bar, final int progress, final boolean fromuser) {
        if (!fromuser) {
            return;
        }
        final long now = SystemClock.elapsedRealtime();
        if (now > lastSeekEventTime + 30) {
            lastSeekEventTime = now;
            posOverride = (lastDuration * progress) / 1000;
            if (posOverride < 0 || !fromTouch) {
                posOverride = -1;
            } else {
                setCurrentTimeText(posOverride);
            }
        }
    }

    @Override
    public void onStartTrackingTouch(final SeekArc bar) {
        lastSeekEventTime = 0;
        posOverride = -1;
        fromTouch = true;
        setCurrentTimeVisibile();
    }

    @Override
    public void onStopTrackingTouch(final SeekArc bar) {
        fromTouch = false;
        if (posOverride != -1) {
            playbackController.seekTo(posOverride);
            updateProgress(posOverride, lastDuration);
        }
        posOverride = -1;
    }

    void updateProgress(long position, long duration) {
        if (position < 0 || duration <= 0) {
            setProgress(1000);
            setCurrentTimeText(position);
        } else {
            if (!fromTouch) {
                setCurrentTimeText(position);
                setProgress((int) (1000 * position / duration));
                if (isPlaying) {
                    setCurrentTimeVisibile();
                } else {
                    //blink the counter
                    long now = SystemClock.elapsedRealtime();
                    if (now >= lastBlinkTime + 500) {
                        lastBlinkTime = now;
                        toggleCurrentTimeVisiblility();
                    }
                }
            }
        }
    }

    void setProgress(int progress) {
        if (hasView()) {
            getView().seekBar.setProgress(progress);
        }
    }

    void setTotalTimeText(long duration) {
        if (hasView()) {
            if (duration > 0) {
                getView().totalTime.setText(UtilsCommon.makeTimeString(getView().getContext(), duration / 1000));
            } else {
                getView().totalTime.setText("--:--");
            }
        }
    }

    void setCurrentTimeText(long pos) {
        if (hasView()) {
            if (pos >= 0) {
                getView().currentTime.setText(UtilsCommon.makeTimeString(getView().getContext(), pos / 1000));
            } else {
                getView().currentTime.setText("--:--");
            }
        }
    }

    void setCurrentTimeVisibile() {
        if (hasView()) {
            getView().currentTime.setVisibility(View.VISIBLE);
        }
    }

    void toggleCurrentTimeVisiblility() {
        if (hasView()) {
            boolean visible = getView().currentTime.getVisibility() == View.VISIBLE;
            getView().currentTime.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
        }
    }

    void setupActionBar() {
        /*
        final Func1<Integer, Boolean> handler = new Func1<Integer, Boolean>() {
        @Override
        public Boolean call(Integer integer) {
            switch (integer) {
                case R.id.popup_menu_share:
                    Observable.zip(
                            musicService.getTrackName(),
                            musicService.getArtistName(),
                            musicService.getAlbumArtistName(),
                            musicService.getAlbumName(),
                            new Func4<String, String, String, String, String[]>() {
                                @Override
                                public String[] call(String s, String s2, String s3, String s4) {
                                    return new String[]{s, s2, s3, s4};
                                }
                            })
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe(new SimpleObserver<String[]>() {
                                @Override
                                public void onNext(String[] strings) {
                                    if (strings.length != 4
                                            || strings[0] == null
                                            || strings[1] == null
                                            || getView() == null) {
                                        notifyError();
                                    } else {
                                        Intent si = new Intent();
                                        String msg = getView().getContext().getString(
                                                R.string.now_listening_to, strings[0], strings[1]);
                                        si.setAction(Intent.ACTION_SEND);
                                        si.setType("text/plain");
                                        si.putExtra(Intent.EXTRA_TEXT, msg);
                                        String albumArtist = strings[1];
                                        if (strings[2] != null) {
                                            albumArtist = strings[2];
                                        }
                                        String album = strings[3];
                                        if (albumArtist != null && album != null) {
                                            si.putExtra(Intent.EXTRA_STREAM,
                                                    ArtworkProvider.createArtworkUri(albumArtist, album));
                                        }
                                        getView().getContext().startActivity(
                                                Intent.createChooser(si,
                                                        getView().getContext().getString(R.string.share_track_using))
                                        );
                                    }
                                }
            
                                @Override
                                public void onError(Throwable e) {
                                    super.onError(e);
                                    notifyError();
                                }
            
                                void notifyError() {
                                    eventBus.post(new MakeToast(R.string.err_generic));
                                }
                            });
                    return true;
                case R.id.popup_set_ringtone:
                    musicService.getTrackId()
                            .map(new Func1<Long, Long>() {
                                @Override
                                public Long call(Long id) {
                                    long realId = MusicProviderUtil.getRealId(appContext, id);
                                    if (realId < 0) {
                                        throw new IllegalArgumentException("Song not in MediaStore");
                                    }
                                    return realId;
                                }
                            })
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe(new SimpleObserver<Long>() {
                                @Override
                                public void onNext(Long id) {
                                    MakeToast mt = MusicUtils.setRingtone(appContext, id);
                                    if (mt != null) {
                                        eventBus.post(mt);
                                    }
                                }
            
                                @Override
                                public void onError(Throwable e) {
                                    super.onError(e);
                                    int msg = (e instanceof IllegalArgumentException)
                                            ? R.string.err_unsupported_for_library : R.string.err_generic;
                                    eventBus.post(new MakeToast(msg));
                                }
                            });
                    return true;
                case R.id.popup_delete:
                    Observable.zip(
                            musicService.getTrackName(),
                            musicService.getTrackId().map(new Func1<Long, Long>() {
                                @Override
                                public Long call(Long id) {
                                    long realId = MusicProviderUtil.getRealId(appContext, id);
                                    if (realId < 0) {
                                        throw new IllegalArgumentException("Song not in MediaStore");
                                    }
                                    return realId;
                                }
                            }), new Func2<String, Long, OpenDialog>() {
                                @Override
                                public OpenDialog call(String s, Long id) {
                                    return new OpenDialog(DeleteDialog.newInstance(s, new long[]{id}));
                                }
                            })
                            .subscribe(new SimpleObserver<OpenDialog>() {
                                @Override
                                public void onNext(OpenDialog openDialog) {
                                    eventBus.post(openDialog);
                                }
            
                                @Override
                                public void onError(Throwable e) {
                                    super.onError(e);
                                    int msg = (e instanceof IllegalArgumentException)
                                            ? R.string.err_unsupported_for_library : R.string.err_generic;
                                    eventBus.post(new MakeToast(msg));
                                }
                            });
                    return true;
                default:
                    return false;
            }
        }
        };
        actionBarOwner.setConfig(new ActionBarOwner.Config.Builder()
                    .setTitle(R.string.now_playing)
                    .setUpButtonEnabled(true)
                    .setMenuConfig(new ActionBarOwner.MenuConfig.Builder()
                                    .withMenus(
                                            R.menu.popup_share,
                                            R.menu.popup_set_ringtone,
                                            R.menu.popup_delete
                                    )
                                    .setActionHandler(handler).build()
                    ).build()
        );
        */
    }

}